AWS ALBを使用し、EC2インスタンスへの通信をHTTPS化しようとした際にはまったこと

やりたかったこと

AWSALBを使用して、以下のようなHTTPS接続する構成を作ろうと考えていました。

f:id:Tk24:20200404222648p:plain

  • ALBとEC2インスタンス(Webサーバー)間の転送ルールにパスを使用
  • https://<ALBのDNS名>/webserviceのような形のみ転送を許可
  • その他の通信は「404 Not Found」を返すように設定
  • セキュリティグループは、EC2インスタンスで使用していたものをALBに適用

はまったこと

https://<ALBのDNS名>/webserviceにアクセスすると、「404 Not Found」が表示される。

解決

許可するパスを /webservice から /webservice* に変更することで解決しました。

よくよく考えれば、https://<ALBのDNS名>/webservice/login_pageとかアクセスするので、許可しておかないと繋がらないですよね。。

以上です。

Gradle初心者によるGradle事始め(ver6.5.1版)

初めに

本記事は、QiitaのGradle初心者によるGradle事始めの記事を、Gradleのバージョン6.5.1用に書き直したものになります。

主な変更点

  • コマンドの出力結果
  • taskの記載方法
  • compileライブラリの非推奨化

Gradleとは

Gadleとは、Groovyで記述するビルド自動化システムです。 JAVAで古くから使われていたAntやMavenXMLで記述するのに対し、 GradleはGroovyで記述できるため柔軟で協力なビルド自動化システムといえます。

「規約によるビルド」

MavenyたGradleは「規約によるビルド」システムと言われています。 一方、古くからあるmakeやAntは「手続き的なビルド」システムと言われています。

「手続き的なビルド」はソースファイルがあるディレクトリや生成ファイルの 出力先のディレクトリを逐次指定する必要がありましたが、「規約によるビルド」はシステム側に一定のルール(規約)があり、 ビルドシステムを利用する我々はそのルールに則ってソースファイルなど配置します。 ルールに則っていればスクリプトが簡潔だというのが、「規約によるビルド」を採用しているGradleの強みといえるでしょう。

Gradleのインストール

下記の手順でGradleのインストールと確認を行います。

必要な環境

現在のGradle最新版は6.5.1になります。 GradleはJVM言語なので、Javaの環境が必要で、6.5.1はJAVA 8〜14のJDKもしくはJREが必要です。
本手順を最後まで実行するためにはJDKをご準備ください。
手元のPCは「openjdk version "11.0.7"」なので、以下ではこの環境で試します。

ダウンロード

GradleのダウンロードサイトからComplete distributionもしくはBinary only distributionをダウンロードします。
Complete distributionにはドキュメント類も含まれているので、今回はComplete distributionをダウンロードします。

解凍と環境変数設定

ダウンロードしたZIPファイルを適当なディレクトリに解凍して、解凍したディレクトリまでをGRADLE_HOMEという環境変数に設定します。
併せてGRADLE_HOMEのbinディレクトリまでをPATH環境変数に追加します。

インストール出来たかテストする

コンソールを立ち上げてgradle -versionを実行し、Gradleがインストールできたかテストします。

Welcome to Gradle 6.5.1!

Here are the highlights of this release:
 - Experimental file-system watching
 - Improved version ordering
 - New samples

For more details see https://docs.gradle.org/6.5.1/release-notes.html


------------------------------------------------------------
Gradle 6.5.1
------------------------------------------------------------

Build time:   2020-06-30 06:32:47 UTC
Revision:     66bc713f7169626a7f0134bf452abde51550ea0a

Kotlin:       1.3.72
Groovy:       2.5.11
Ant:          Apache Ant(TM) version 1.10.7 compiled on September 1 2019
JVM:          1.8.0_221 (Oracle Corporation 25.221-b11)
OS:           Windows 10 10.0 x86

このようにバージョン情報が表示されればOKです。
Gradleを入れると、一緒にGroovyとAntが入る用ですね。

プロキシ設定

仕事場でGradleを使う場合、プロキシサーバを設定しないと後々困るのでここで設定します。
今回はユーザごとにGradleの設定を施す。
~/.gradle/gradle.propertiesファイルにプロキシ設定を記述します。

systemProp.http.proxyHost=xxx.xxx.xxx.xxx
systemProp.http.proxyPort=9999
systemProp.https.proxyHost=xxx.xxx.xxx.xxx
systemProp.https.proxyPort=9999

Gradleのキホン

設定が完了したので、本題に入ります。
以下では適当なところにGradleSampleというディレクトリを作成し、その中で各種のコマンドを実行します。

Gradleの入口

GradleはPATH環境変数に設定したディレクトリにあるgradleコマンドからすべての操作を実行します。
(すでにgradle -versionは実行しましたね)

手始めに引数無しのgradleコマンドを実行してみましょう。

Starting a Gradle Daemon (subsequent builds will be faster)

> Task :help

Welcome to Gradle 6.5.1.

To run a build, run gradle <task> ...

To see a list of available tasks, run gradle tasks

To see a list of command-line options, run gradle --help

To see more detail about a task, run gradle help --task <task>

For troubleshooting, visit https://help.gradle.org

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.5.1/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 7s
1 actionable task: 1 executed

引数無しのgradleコマンドの簡単なヘルプが出力されます。

ビルドをしたければgradle <task>を実行し、
利用可能なタスクを表示したければgradle tasksを実行し、
コマンドラインオプションを表示したければgradle --helpを実行し、
それぞれのタスクについて詳細が知りたければgradle help --task <task>を実行しろと言っています。

<task>の部分には具体的なタスク名を記述します。
この「タスク」がGradleのキモとなる部分です。 次にこのタスクについてみてみます。

タスク

いまいるGradleSampleディレクトリにbuild.gradleというファイル作って、テキストエディタで以下の内容を書き込みます。

task hello {
  doLast {
    println 'Hello world!'
  }
}

見て迷うことはないでしょうが、そのスクリプトにいま「hello」というタスクがあり、そのタスクはHello world!を出力します。
これをgradleコマンド実行します。
build.gradleはがデフォルトで読み込むビルドスクリプトですので、gradleコマンドにはスクリプトファイル名を指定する必要ありません。
また今回はログ出力を抑制して内容をスッキリさせるために-qオプションをつけます。
実行するコマンドはgradle -q helloです。

>gradle -q hello
Hello world!

意図した結果が出力されました。

タスクには、ある処理のまとまりを記述できます。 このタスクを使って、「コンパイルをする」や「テストを実行する」など処理を行います。

タスクの依存関係

Gradleに限らず、ビルドシステムには「タスクの依存関係」は必要不可欠な概念です。
例えばテストを実行するためにはコンパイルが完了していなければなりません。
これは「テスト実行する」というタスクは「コンパイルをする」というタスクに依存している状態です。
この依存関係を、Gradleでは以下のとうに記述します。

task runCompile {
  doLast {
    println 'Run compile.'
  }
}

task executeTest(dependsOn: runCompile) {
  doLast {
    println 'Execute test.'
  }
}

タスク名の後ろに(dependsOn: XXX)を記述し、XXXの部分に依存するタスク名を指定します。

このビルドスクリプトを実行します。 指定するタスクは「テストを実行する」ためgradle -q executeTestです。

>gradle -q executeTest
Run compile.
Execute test.

executeTestを実行する前に、依存しているrunCompileを実行しているのが分かると思います。

タスクの一覧

Gradleはタスクを指定して実行することはわかっていただけたでしょうが、そのタスクはビルドスクリプトを開かなくてもgradle -q tasksを実行すればわかります。

>gradle -q tasks

------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'GradleSample'.
components - Displays the components produced by root project 'GradleSample'. [incubating]
dependencies - Displays all dependencies declared in root project 'GradleSample'.
dependencyInsight - Displays the insight into a specific dependency in root project 'GradleSample'.
dependentComponents - Displays the dependent components of components in root project 'GradleSample'. [incubating]
help - Displays a help message.
model - Displays the configuration model of root project 'GradleSample'. [incubating]
outgoingVariants - Displays the outgoing variants of root project 'GradleSample'.
projects - Displays the sub-projects of root project 'GradleSample'.
properties - Displays the properties of root project 'GradleSample'.
tasks - Displays the tasks runnable from root project 'GradleSample'.

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

今回作成したビルドスクリプトの内容が表示されました。 おや?でも、肝心の確認したいタスクのexecuteTestrunCompileがありません。

今度はgradle -q tasks --allを実行し、すべての情報を出力します。

>gradle -q tasks --all

------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'GradleSample'.
components - Displays the components produced by root project 'GradleSample'. [incubating]
dependencies - Displays all dependencies declared in root project 'GradleSample'.
dependencyInsight - Displays the insight into a specific dependency in root project 'GradleSample'.
dependentComponents - Displays the dependent components of components in root project 'GradleSample'. [incubating]
help - Displays a help message.
model - Displays the configuration model of root project 'GradleSample'. [incubating]
outgoingVariants - Displays the outgoing variants of root project 'GradleSample'.
projects - Displays the sub-projects of root project 'GradleSample'.
properties - Displays the properties of root project 'GradleSample'.
tasks - Displays the tasks runnable from root project 'GradleSample'.

Other tasks
-----------
executeTest
prepareKotlinBuildScriptModel
runCompile

Other tasksのところにexecuteTestrunCompileが表示されました。
これが「タスクの依存関係」を表しています。
他の依存関係の表現として行の最後の[XXXX]があるのですが、これは後ほど説明しあmsう。

Gradleを使ったJavaのビルド

以下ではGradleを使ったJavaソースコードのビルド方法を記します。
先ほどまで使っていたbuild.gradleは不要なので削除しましょう。

サンプルのプロジェクトを作成

GradleはinitタスクでJava/Groovy/Scalaの雛形プロジェクトを作成することができます。 gradle help --task initを実行して、initの使い方を調べます。

>gradle help --task init

> Task :help
Detailed task information for init

Path
     :init

Type
     InitBuild (org.gradle.buildinit.tasks.InitBuild)

Options
     --dsl     Set the build script DSL to be used in generated scripts.
               Available values are:
                    groovy
                    kotlin

     --package     Set the package for source files.

     --project-name     Set the project name.

     --test-framework     Set the test framework to be used.
                          Available values are:
                               junit
                               junit-jupiter
                               kotlintest
                               scalatest
                               spock
                               testng

     --type     Set the type of project to generate.
                Available values are:
                     basic
                     cpp-application
                     cpp-library
                     groovy-application
                     groovy-gradle-plugin
                     groovy-library
                     java-application
                     java-gradle-plugin
                     java-library
                     kotlin-application
                     kotlin-gradle-plugin
                     kotlin-library
                     pom
                     scala-library
                     swift-application
                     swift-library

Description
     Initializes a new Gradle build.

Group
     Build Setup

BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

今回はJavaのプロジェクトを作成したので、gardle init --type java-libraryを実行します。

>gradle init --type java-library

Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2]

Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit 4) [1..4]

Project name (default: GradleSample):
Source package (default: GradleSample):

> Task :init
Get more help with your project: https://docs.gradle.org/6.5.1/userguide/java_library_plugin.html

BUILD SUCCESSFUL in 12s
2 actionable tasks: 2 executed

言語やフレームワーク、プロジェクト名、ソースパッケージを選択できますが、今回は全てdefaultで実行します。
initが完了すると、各種ファイルが生成されます。

│  .gitattributes
│  .gitignore
│  build.gradle
│  gradlew
│  gradlew.bat
│  settings.gradle
│
├─.gradle
│  ├─6.5.1
│  │  │  gc.properties
│  │  │
│  │  ├─executionHistory
│  │  │      executionHistory.bin
│  │  │      executionHistory.lock
│  │  │
│  │  ├─fileChanges
│  │  │      last-build.bin
│  │  │
│  │  ├─fileHashes
│  │  │      fileHashes.bin
│  │  │      fileHashes.lock
│  │  │
│  │  └─vcsMetadata-1
│  ├─buildOutputCleanup
│  │      buildOutputCleanup.lock
│  │      cache.properties
│  │      outputFiles.bin
│  │
│  ├─checksums
│  │      checksums.lock
│  │
│  └─vcs-1
│          gc.properties
│
├─gradle
│  └─wrapper
│          gradle-wrapper.jar
│          gradle-wrapper.properties
│
└─src
    ├─main
    │  ├─java
    │  │  └─GradleSample
    │  │          Library.java
    │  │
    │  └─resources
    └─test
        ├─java
        │  └─GradleSample
        │          LibraryTest.java
        │
        └─resources

色々なディレクトリやファイルが作成されますが、重要なのはbuild.gradleファイルと、srcディレクトリです。
(他のディレクトリとファイルは今回説明を省力します)

ビルドスクリプトの確認

作成されたbuild.gradleファイルを見てみます。

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java Library project to get you started.
 * For more details take a look at the Java Libraries chapter in the Gradle
 * User Manual available at https://docs.gradle.org/6.5.1/userguide/java_library_plugin.html
 */

plugins {
    // Apply the java-library plugin to add support for Java Library
    id 'java-library'
}

repositories {
    // Use jcenter for resolving dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

dependencies {
    // This dependency is exported to consumers, that is to say found on their compile classpath.
    api 'org.apache.commons:commons-math3:3.6.1'

    // This dependency is used internally, and not exposed to consumers on their own compile classpath.
    implementation 'com.google.guava:guava:29.0-jre'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.13'
}

コメントが多いので今回はコメントを削除します。

plugins {
        id 'java-library'
}

repositories {
    jcenter()
}

dependencies {
    api 'org.apache.commons:commons-math3:3.6.1'
    implementation 'com.google.guava:guava:29.0-jre'
    testImplementation 'junit:junit:4.13'
}

スッキリしました。

Javaプラグイン

Javaのビルドスクリプトで重要なのが、先頭の

plugins {
        id 'java-library'
}

で、これを記述するとJavaのビルドに必要な各種タスクが読み込まれます。

他にも、.projectのようなEclipse固有の設定ファイルを生成するプラグインや、Groovyや、 Scalaなどもありますが、今回は省略します。

Eclipse

plugins {
        id 'eclipse'
}

Groovy

plugins {
    id 'groovy'
}

Scala

plugins {
    id 'scala'
}

詳細は、Gradleの公式ユーザーガイドを参照してください。

リポジトリの指定

Javaのプログラムを書く上で、Sprong BootやJUnitやSLF4Jなどのライブラリを使用しないことはないでしょう。
Antの時代は各ライブラリの提供元のサイトから必要なJARファイルをダウンロードしてコンパイル時にローカルのパスを指定していましたが、Mavenの時代からそれはセントラルリポジトリと呼ばれるリモートに置かれ、必要に応じてビルドシステムが自動的にダウンロードするようになりました。
MavenであればMaven Central Repositoryからダウンロードします。

GradleはGadle固有のセントラルリポジトリがあるわけではなく、Mavenなど既存のセントラルリポジトリからライブラリをダウンロードします。
どのセントラルリポジトリを使用するかは、ビルドスクリプトの下記で指定します。

repositories {
    jcenter()
}

これは「BintrayのJCenterにある依存関係を探すための定義済みリポジトリ」を意味します。
今回は、Mavenセントラルリポジトリを参照したいので、これを以下のように変更します。

repositories {
    mavenCentral()
}

依存関係の定義

リポジトリからどのライブラリを使用するのかを定義するのが、ビルドスクリプトの下記の部分です。

dependencies {
    api 'org.apache.commons:commons-math3:3.6.1'
    implementation 'com.google.guava:guava:29.0-jre'
    testImplementation 'junit:junit:4.13'
}

ここで言う「依存関係」はライブラリの「依存関係」です。
タスクの依存関係ではありません。

apiは、特に意図せずとも他のプロジェクトに依存関係を伝番させるライブラリ(compileと同じ様な認識)です。
implementationは、明示しない限り他のプロジェクトに依存関係を伝番させないライブラリです。テスト時とコンパイル時に使用します。
testImplementationで指定するのはテスト時に使用するライブラリです。

詳しく知りたい方は、Gradle の compile, api, implementation とかについてを参照してください。

ライブラリの指定は、コロン区切りで'GroupId : ArtifactId : Version'の3つが必要です。
この3つはMavenセントラルリポジトリにあるものを指定します。

タスクの確認

この状態でタスクの一覧を確認します。

>gradle tasks --all
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :tasks

------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'GradleSample'.
components - Displays the components produced by root project 'GradleSample'. [incubating]
dependencies - Displays all dependencies declared in root project 'GradleSample'.
dependencyInsight - Displays the insight into a specific dependency in root project 'GradleSample'.
dependentComponents - Displays the dependent components of components in root project 'GradleSample'. [incubating]
help - Displays a help message.
model - Displays the configuration model of root project 'GradleSample'. [incubating]
outgoingVariants - Displays the outgoing variants of root project 'GradleSample'.
projects - Displays the sub-projects of root project 'GradleSample'.
properties - Displays the properties of root project 'GradleSample'.
tasks - Displays the tasks runnable from root project 'GradleSample'.

Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.

Other tasks
-----------
compileJava - Compiles main Java source.
compileTestJava - Compiles test Java source.
prepareKotlinBuildScriptModel
processResources - Processes main resources.
processTestResources - Processes test resources.

Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.
Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.

BUILD SUCCESSFUL in 8s
1 actionable task: 1 executed

Java用のプラグインを指定しているので、Build tasksなどが追加されています。
ビルドする場合、buildタスク指定します。

ここで先ほど保留した依存するタスクの話をします。
buildタスクの右にある[assemble, check]となっている部分が依存しているタスクです。
buildタスクの前にassembleタスク(プロジェクトのすべてのアーカイブを構築)とcheckタスク(プロジェクトのすべての検証タスクを実行)が実行されます。
assembleタスクの前にはjarタスク(当該プロジェクトのJARファイルを構築)が実行され、
jarタスクの前にはclassesタスク(製品クラスディレクトリを構築)が実行され、
classesタスクの前にはcompileJavaタスク(javacを使って製品javaソースファイルをコンパイル)とprocessResourcesタスク(製品リソースを製品クラスディレクトリにコピー)が実行されます。

規約

Gradleは「規約によるビルド」をするビルドシステムだと言いましたが、Javaプラグインを使用する場合は下記のようなディレクトリの規約があります。

ディレクト 意味
src/main/java 製品のJavaリソース
src/main/resources 製品のリソース
src/test/java テストのJavaリソース
src/test/java テストのリソース

ここでもう一度initタスクで作成したディレクトリを見てみます。

└─src
    ├─main
    │  ├─java
    │  │  └─GradleSample
    │  │          Library.java
    │  │
    │  └─resources
    └─test
        ├─java
        │  └─GradleSample
        │          LibraryTest.java
        │
        └─resources

initタスクで作成された「製品のJavaソース」ディレクトリと「テストのJavaソース」ディレクトリが、規約に則ったディレクトリだということが分かってもらえるかと思います。

ビルドの実行

処理を確認するために、ログレベルをinfoに変更するオプション-iをつけてgradle build -iで実行しましょう。

>gradle build -i

-- 略 --

> Configure project :
Evaluating root project 'GradleSample' using build file 'C:\Users\testuser\Desktop\test\GradleSample\build.gradle'.
All projects evaluated.
Selected primary task 'build' from project :
Tasks to be executed: [task ':compileJava', task ':processResources', task ':classes', task ':jar', task ':assemble', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test', task ':check', task ':build']
Tasks that were excluded: []
:compileJava (Thread[Execution worker for ':',5,main]) started.

> Task :compileJava
Caching disabled for task ':compileJava' because:
  Build cache is disabled
Task ':compileJava' is not up-to-date because:
  Output property 'destinationDirectory' file C:\Users\testuser\Desktop\test\GradleSample\build\classes\java\main has been removed.
  Output property 'destinationDirectory' file C:\Users\testuser\Desktop\test\GradleSample\build\classes\java\main\GradleSample has been removed.
  Output property 'destinationDirectory' file C:\Users\testuser\Desktop\test\GradleSample\build\classes\java\main\GradleSample\Library.class has been removed.
The input changes require a full rebuild for incremental task ':compileJava'.
Full recompilation is required because no incremental change information is available. This is usually caused by clean builds or changing compiler arguments.
Compiling with JDK Java compiler API.
Created classpath snapshot for incremental compilation in 0.001 secs.
:compileJava (Thread[Execution worker for ':',5,main]) completed. Took 0.138 secs.
:processResources (Thread[Execution worker for ':',5,main]) started.

> Task :processResources NO-SOURCE
Skipping task ':processResources' as it has no source files and no previous output files.
:processResources (Thread[Execution worker for ':',5,main]) completed. Took 0.001 secs.
:classes (Thread[Execution worker for ':',5,main]) started.

> Task :classes
Skipping task ':classes' as it has no actions.
:classes (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:jar (Thread[Execution worker for ':',5,main]) started.

> Task :jar
Caching disabled for task ':jar' because:
  Build cache is disabled
Task ':jar' is not up-to-date because:
  Output property 'archiveFile' file C:\Users\testuser\Desktop\test\GradleSample\build\libs\GradleSample.jar has been removed.
file or directory 'C:\Users\testuser\Desktop\test\GradleSample\build\resources\main', not found
:jar (Thread[Execution worker for ':',5,main]) completed. Took 0.015 secs.
:assemble (Thread[Execution worker for ':',5,main]) started.

> Task :assemble
Skipping task ':assemble' as it has no actions.
:assemble (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:compileTestJava (Thread[Execution worker for ':',5,main]) started.

> Task :compileTestJava
Caching disabled for task ':compileTestJava' because:
  Build cache is disabled
Task ':compileTestJava' is not up-to-date because:
  Output property 'destinationDirectory' file C:\Users\testuser\Desktop\test\GradleSample\build\classes\java\test has been removed.
  Output property 'destinationDirectory' file C:\Users\testuser\Desktop\test\GradleSample\build\classes\java\test\GradleSample has been removed.
  Output property 'destinationDirectory' file C:\Users\testuser\Desktop\test\GradleSample\build\classes\java\test\GradleSample\LibraryTest.class has been removed.
The input changes require a full rebuild for incremental task ':compileTestJava'.
Full recompilation is required because no incremental change information is available. This is usually caused by clean builds or changing compiler arguments.
Compiling with JDK Java compiler API.
Created classpath snapshot for incremental compilation in 0.002 secs.
:compileTestJava (Thread[Execution worker for ':',5,main]) completed. Took 0.153 secs.
:processTestResources (Thread[Execution worker for ':',5,main]) started.

> Task :processTestResources NO-SOURCE
Skipping task ':processTestResources' as it has no source files and no previous output files.
:processTestResources (Thread[Execution worker for ':',5,main]) completed. Took 0.001 secs.
:testClasses (Thread[Execution worker for ':',5,main]) started.

> Task :testClasses
Skipping task ':testClasses' as it has no actions.
:testClasses (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:test (Thread[Execution worker for ':',5,main]) started.
Gradle Test Executor 2 started executing tests.
Gradle Test Executor 2 finished executing tests.

> Task :test
Caching disabled for task ':test' because:
  Build cache is disabled
Task ':test' is not up-to-date because:
  Output property 'binaryResultsDirectory' file C:\Users\testuser\Desktop\test\GradleSample\build\test-results\test\binary has been removed.
  Output property 'binaryResultsDirectory' file C:\Users\testuser\Desktop\test\GradleSample\build\test-results\test\binary\output.bin has been removed.
  Output property 'binaryResultsDirectory' file C:\Users\testuser\Desktop\test\GradleSample\build\test-results\test\binary\output.bin.idx has been removed.
Starting process 'Gradle Test Executor 2'. Working directory: C:\Users\testuser\Desktop\test\GradleSample Command: C:\Program Files\AdoptOpenJDK\jdk-11.0.7.10-hotspot\bin\java.exe -Dorg.gradle.native=false @C:\Users\testuser\AppData\Local\Temp\gradle-worker-classpath3706733076989153461txt -Xmx512m -Dfile.encoding=windows-31j -Duser.country=JP -Duser.language=ja -Duser.variant -ea worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor 2'
Successfully started process 'Gradle Test Executor 2'
Finished generating test XML results (0.001 secs) into: C:\Users\testuser\Desktop\test\GradleSample\build\test-results\test
Generating HTML test report...
Finished generating test html results (0.008 secs) into: C:\Users\testuser\Desktop\test\GradleSample\build\reports\tests\test
:test (Thread[Execution worker for ':',5,main]) completed. Took 1.395 secs.
:check (Thread[Execution worker for ':',5,main]) started.

> Task :check
Skipping task ':check' as it has no actions.
:check (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:build (Thread[Execution worker for ':',5,main]) started.

> Task :build
Skipping task ':build' as it has no actions.
:build (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.

BUILD SUCCESSFUL in 3s
4 actionable tasks: 4 executed

実行したタスクが列挙されています。 compileJavaから、buildまで実行されました。

最初は依存ライブラリのダウンロードが発生するかもしれません。
もしプロキシを使用する環境で実行した場合、設定の不備があるとダウンロードに失敗するはずなので、設定を見直してください。

ビルド時に生成されるファイルはbuildディレクトリにあります。
今回のテンプレートソースのJARファイルはbuild/libs/GradleSample.jarです。

実行したタスクにtestがあるのでテストは実行されたはずですが、念のためにテストクラスでエラーを起こしてみます。
src/test/java/GradleSample/LibraryTest.javaを修正します。
修正前は下記のソースでした。

package GradleSample;

import org.junit.Test;
import static org.junit.Assert.*;

public class LibraryTest {
    @Test public void testSomeLibraryMethod() {
        Library classUnderTest = new Library();
        assertTrue("someLibraryMethod should return 'true'", classUnderTest.someLibraryMethod());
    }
}

これにエラーとなる処理を入れます。

package GradleSample;

import org.junit.Test;
import static org.junit.Assert.*;

public class LibraryTest {
    @Test public void testSomeLibraryMethod() {
        Library classUnderTest = new Library();
        assertTrue("someLibraryMethod should return 'true'", classUnderTest.someLibraryMethod());

        assertTrue(false);
    }
}

assertTrue(false);でテストは失敗するはず。
gradle build -iを実行します。

>gradle build

> Task :test FAILED

GradleSample.LibraryTest > testSomeLibraryMethod FAILED
    java.lang.AssertionError at LibraryTest.java:14

1 test completed, 1 failed

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///C:/Users/testuser/Desktop/test/GradleSample/build/reports/tests/test/index.html

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 3s
4 actionable tasks: 1 executed, 3 up-to-date

テストが失敗しました。

なのでここまで最初に言った「JavaソースコードコンパイルしてJARファイルにまとめてテストを実行するまで」が達成しました。

終わりに

私は、開発の知識がまるで無い状態で開発の自動化(CICD)の業務を担当することになりました。
Gradleの開発自動化といわれても、Gradleって何?という状態だった私はパニックでした。

Gradleについて何から勉強してよいかわからない状態だったのですが、そんな時に上記の記事を見かけて学習の取っ掛かりにできました。

以上です。

Jenkinsからserverspecを実行しようとしたら、ポート確認で失敗した話

目的

serverspecの手動実行時のチェック結果と、Jenkinsから実行したチェック結果に差異が生じました。
まったく同じサーバー、ユーザー、コマンドで実施したが結果が違ったため原因を調査する。

前提条件

バージョン
・rake:13.0.1
ruby:2.7.0p0
・Jenkins:2.220

AWS上にJenkisサーバ、webサーバーをそれぞれ準備
・公開鍵を登録し、特定のユーザーのみパスワード無しで、sshログインできる
・2サーバ間のセキュリティは全アクセスを許可済み

serverspecのチェック内容

require 'spec_helper'

describe package('nginx') do
  it { should be_installed }
end

describe service('nginx') do
  it { should be_enabled }
  it { should be_running }
end

describe port(80) do
  it { should be_listening }
end

describe file('/usr/share/nginx/html/index.html') do
  it { should be_file }
  it { should exist }
  its(:content) { should match /^Hello, development ansible!!$/ }
end

詳細事象

wedサーバー上でrake specを実行したところ、以下のように0 failuresで実行できました。

/home/ec2-user/.rbenv/versions/2.7.0/bin/ruby -I/home/ec2-user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-support-3.9.2/lib:/home/ec2-user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/lib /home/ec2-user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/exe/rspec --pattern spec/localhost/\*_spec.rb

Package "nginx"
  is expected to be installed

Service "nginx"
  is expected to be enabled
  is expected to be running

Port "80"
  is expected to be listening

File "/usr/share/nginx/html/index.html"
  is expected to be file
  is expected to exist
  content
    is expected to match /^Hello, development ansible!!$/

Finished in 0.09714 seconds (files took 0.55084 seconds to load)
7 examples, 0 failures

しかし、Jenkinsで同じユーザーでログインし、rake specを実行したところ以下の結果になりました。

[SSH] executing...
/home/ansible/.rbenv/versions/2.7.0/bin/ruby -I/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-support-3.9.2/lib:/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/lib /home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/exe/rspec --pattern spec/localhost/\*_spec.rb failed
/home/ansible/.rbenv/versions/2.7.0/bin/ruby -I/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-support-3.9.2/lib:/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/lib /home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/exe/rspec --pattern spec/localhost/\*_spec.rb

Package "nginx"
  is expected to be installed

Service "nginx"
  is expected to be enabled
  is expected to be running

Port "80"
  is expected to be listening (FAILED - 1)

File "/usr/share/nginx/html/index.html"
  is expected to be file
  is expected to exist
  content
    is expected to match /^Hello, production ansible!!$/

Failures:

  1) Port "80" is expected to be listening
     On host `localhost'
     Failure/Error: it { should be_listening }
       expected Port "80" to be listening
       /bin/sh -c ss\ -tunl\ \|\ grep\ -E\ --\ :80\\\ 
       
     # ./spec/localhost/web_spec.rb:13:in `block (2 levels) in <top (required)>'

Finished in 0.11224 seconds (files took 0.54982 seconds to load)
7 examples, 1 failure

Failed examples:

rspec ./spec/localhost/web_spec.rb:13 # Port "80" is expected to be listening


[SSH] completed
[SSH] exit-status: 1

まったく同じコマンドを実行したはずですが、1 failureになってしまいました。

どうも80番ポートのチェックで失敗しているように見えます。

切り分け

実施コマンドの確認

エラーメッセージを確認すると以下のメッセージが表示されていることから、 ssコマンドが実行されていることがわかりました。

/bin/sh -c ss\ -tunl\ \|\ grep\ -E\ --\ :80\\\ 

webサーバーの対象ユーザーからssコマンドを実行してみます。

$ ss -tunl
 Netid    State      Recv-Q     Send-Q                             Local Address:Port           Peer Address:Port     
 udp      UNCONN     0          0                                        0.0.0.0:725                 0.0.0.0:*        
 udp      UNCONN     0          0                                      127.0.0.1:323                 0.0.0.0:*        
 udp      UNCONN     0          0                                        0.0.0.0:68                  0.0.0.0:*        
 udp      UNCONN     0          0                                        0.0.0.0:111                 0.0.0.0:*        
 udp      UNCONN     0          0                                           [::]:725                    [::]:*        
 udp      UNCONN     0          0                                          [::1]:323                    [::]:*        
 udp      UNCONN     0          0                [fe80::4db:f6ff:fee3:8fec]%eth0:546                    [::]:*        
 udp      UNCONN     0          0                                           [::]:111                    [::]:*        
 tcp      LISTEN     0          128                                      0.0.0.0:80                  0.0.0.0:*        
 tcp      LISTEN     0          128                                      0.0.0.0:22                  0.0.0.0:*        
 tcp      LISTEN     0          100                                    127.0.0.1:25                  0.0.0.0:*        
 tcp      LISTEN     0          128                                      0.0.0.0:111                 0.0.0.0:*        
 tcp      LISTEN     0          128                                         [::]:80                     [::]:*        
 tcp      LISTEN     0          128                                         [::]:22                     [::]:*        
 tcp      LISTEN     0          128                                         [::]:111                    [::]:*        

当然実行できました。

次に、Jenkisサーバからsshコマンドにssコマンドを渡して実行してみます。

$ ssh <対象ユーザー>@<webサーバーIP> "ss -tunl" 
 bash: ss: command not found

失敗しました。。コマンドが存在しない???
コマンドの渡し方が間違っていたのか確認しましたが、あってそうです。

ssコマンドをフルパスで実行してみます。

$ which ss
/sbin/ss

$ ssh <対象ユーザー>@<webサーバーIP> "/sbin/ss -tunl"
 Netid    State      Recv-Q     Send-Q                             Local Address:Port           Peer Address:Port     
 udp      UNCONN     0          0                                        0.0.0.0:725                 0.0.0.0:*        
 udp      UNCONN     0          0                                      127.0.0.1:323                 0.0.0.0:*        
 udp      UNCONN     0          0                                        0.0.0.0:68                  0.0.0.0:*        
 udp      UNCONN     0          0                                        0.0.0.0:111                 0.0.0.0:*        
 udp      UNCONN     0          0                                           [::]:725                    [::]:*        
 udp      UNCONN     0          0                                          [::1]:323                    [::]:*        
 udp      UNCONN     0          0                [fe80::4db:f6ff:fee3:8fec]%eth0:546                    [::]:*        
 udp      UNCONN     0          0                                           [::]:111                    [::]:*        
 tcp      LISTEN     0          128                                      0.0.0.0:80                  0.0.0.0:*        
 tcp      LISTEN     0          128                                      0.0.0.0:22                  0.0.0.0:*        
 tcp      LISTEN     0          100                                    127.0.0.1:25                  0.0.0.0:*        
 tcp      LISTEN     0          128                                      0.0.0.0:111                 0.0.0.0:*        
 tcp      LISTEN     0          128                                         [::]:80                     [::]:*        
 tcp      LISTEN     0          128                                         [::]:22                     [::]:*        
 tcp      LISTEN     0          128                                         [::]:111                    [::]:*        

実行できました!
どうもパスが通っていないように感じますね。

普通にコマンド実行する場合とssh越しにコマンド実行する場合で、環境変数に差があるか確認してみます。

<通常実行時(webサーバ)>

$ env
 XDG_SESSION_ID=4
 HOSTNAME=
 SHELL=/bin/bash
 TERM=xterm
 HISTSIZE=1000
 USER=ansible
 LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
  SUDO_USER=ec2-user
  SUDO_UID=1000
  USERNAME=root
  PATH=/sbin:/bin:/usr/sbin:/usr/bin
  MAIL=/var/spool/mail/ec2-user
  PWD=/home/ec2-user
  LANG=en_US.UTF-8
  SHLVL=1
  SUDO_COMMAND=/bin/su ansible
  HOME=/home/ansible
  LOGNAME=ansible
  LESSOPEN=||/usr/bin/lesspipe.sh %s
  SUDO_GID=1000
  _=/bin/env

<ssh越しにenvコマンド実行時(Jenkinsサーバ)>

$ ssh <対象ユーザー>@<webサーバーIP> "env"
 XDG_SESSION_ID=44
 SHELL=/bin/bash
 SSH_CLIENT=
 USER=ansible
 MAIL=/var/mail/ansible
 PATH=/usr/local/bin:/usr/bin
 PWD=/home/ansible
 LANG=en_US.UTF-8
 SHLVL=1
 HOME=/home/ansible
 LOGNAME=ansible
 SSH_CONNECTION=
 LESSOPEN=||/usr/bin/lesspipe.sh %s
 XDG_RUNTIME_DIR=/run/user/1001
 _=/usr/bin/env

予想通り、PATHに差異があることがわかりました。

<通常実行時(webサーバ)>
PATH=/sbin:/bin:/usr/sbin:/usr/bin

<ssh越しにenvコマンド実行時(Jenkinsサーバ)>
PATH=/usr/local/bin:/usr/bin

解決

問題を解決すべく、ssh時の環境変数について調べたところ以下のブログが見つかりました。

ssh-env - ssh実行時に環境変数を設定/変更したい - spikelet days

上記ブログに記載してある以下の方法を試します。

  • sshdの設定変更 (PermitUserEnvironment=yes)、sshd再起動
  • ~/.ssh/environment に環境変数を書く (VAR=VAL 形式で一行一変数で)

まずは、webサーバの /etc/ssh/sshd_config を編集します。
109行目あたりにある PermitUserEnvironment noPermitUserEnvironment yes に変更します。

#変更前の設定内容を確認
$ sudo grep PermitUserEnvironment /etc/ssh/sshd_config
#PermitUserEnvironment no

#PermitUserEnvironmentをyesに変更
$ sudo vi /etc/ssh/sshd_config

#変更されていることを確認
$ sudo grep PermitUserEnvironment /etc/ssh/sshd_config
PermitUserEnvironment yes

次に、~/.ssh/environment に環境変数を書く (VAR=VAL 形式で一行一変数で) を実行します。

sshでログインしたいユーザーのホームディレクトリ配下にある ~/.ssh に移動し、
設定したい環境変数を記載した environment ファイルを作成します。

今回は、コマンドを実行するために PATH= の値を変更したいので、 PATH=/sbin:/bin:/usr/sbin:/usr/bin を記載します。

#ホームディレクトリは以下の.sshディレクトリに移動
$ cd ~/.ssh

#設定する環境変数(PATH=/sbin:/bin:/usr/sbin:/usr/bin)を記載
$ vi environment

#記載内容の確認
$ cat environment
PATH=/sbin:/bin:/usr/sbin:/usr/bin

これで、設定完了です。
試しに、Jenkinsサーバからsshコマンド越しにserverspecを実行してみます。

ssh <ユーザー名>@<IPアドレス> "ss -tunl"
NetidState  Recv-Q Send-Q                     Local Address:Port   Peer Address:Port
udp  UNCONN 0      0                                0.0.0.0:111         0.0.0.0:*
udp  UNCONN 0      0                                0.0.0.0:724         0.0.0.0:*
udp  UNCONN 0      0                              127.0.0.1:323         0.0.0.0:*
udp  UNCONN 0      0                                0.0.0.0:68          0.0.0.0:*
udp  UNCONN 0      0                                   [::]:111            [::]:*
udp  UNCONN 0      0                                   [::]:724            [::]:*
udp  UNCONN 0      0                                  [::1]:323            [::]:*
udp  UNCONN 0      0        [fe80::4db:f6ff:fee3:8fec]%eth0:546            [::]:*
tcp  LISTEN 0      128                              0.0.0.0:111         0.0.0.0:*
tcp  LISTEN 0      128                              0.0.0.0:80          0.0.0.0:*
tcp  LISTEN 0      128                              0.0.0.0:22          0.0.0.0:*
tcp  LISTEN 0      100                            127.0.0.1:25          0.0.0.0:*
tcp  LISTEN 0      128                                 [::]:111            [::]:*
tcp  LISTEN 0      128                                 [::]:80             [::]:*
tcp  LISTEN 0      128                                 [::]:22             [::]:*

実行できました!!

次は、いよいよJenkinsのGUIからjobを実行してみます。

Started by user root
Running as SYSTEM
Building in workspace /var/lib/jenkins/workspace/serverspec jobtest
[SSH] script:

cd /tmp/serverspec_sample
~/.rbenv/shims/rake spec

[SSH] executing...
/home/ansible/.rbenv/versions/2.7.0/bin/ruby -I/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-support-3.9.2/lib:/home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/lib /home/ansible/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/rspec-core-3.9.1/exe/rspec --pattern spec/localhost/\*_spec.rb

Port "80"
  is expected to be listening

Package "nginx"
  is expected to be installed

Service "nginx"
  is expected to be enabled
  is expected to be running

Port "80"
  is expected to be listening

File "/usr/share/nginx/html/index.html"
  is expected to be file
  is expected to exist
  content
    is expected to match /^Hello, development ansible!!$/

Finished in 0.08618 seconds (files took 1.01 seconds to load)
8 examples, 0 failures


[SSH] completed
[SSH] exit-status: 0

Started calculate disk usage of build
Finished Calculation of disk usage of build in 0 seconds
Started calculate disk usage of workspace
Finished Calculation of disk usage of workspace in 0 seconds
Finished: SUCCESS

f:id:Tk24:20200325012403p:plain

成功しました!!

余談

今回色々調べている中で、以下のようにパイプを使用して、ssh越しにコマンドを実行する方法があるのを知りました。

echo `ss -tunl`  | ssh <ユーザー名>@<IPアドレス>

このコマンドだと、上記の解決を実施しなくてもコマンド実行できました。

これも環境変数が違うのかと思い、envコマンドを実行してみました。

$ echo "env"  | ssh ansible@172.31.23.243
Pseudo-terminal will not be allocated because stdin is not a terminal.
XDG_SESSION_ID=24
HOSTNAME=ansible-server.tk.com
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=172.31.18.127 54540 22
USER=ansible
MAIL=/var/spool/mail/ansible
PATH=/home/ansible/.rbenv/shims:/home/ansible/.rbenv/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/home/ansible/.local/bin:/home/ansible/bin
PWD=/home/ansible
LANG=en_US.UTF-8
RBENV_SHELL=bash
HISTCONTROL=ignoredups
SHLVL=1
HOME=/home/ansible
LOGNAME=ansible
SSH_CONNECTION=172.31.18.127 54540 172.31.23.243 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/1001
_=/bin/env

今までのどれとも違う環境変数が出力されました。。

少し調べてみましたが、結論が出ませんでした。 時間のある時に調べて、原因がわかり次第追記しようと思います。

以上です。

AWS lambdaで英語の音声ファイルを日本語のテキストに翻訳する

やりたいこと

以下の画像のような流れで英語の音声ファイルを日本語のテキストに翻訳したい。
f:id:Tk24:20200311111732p:plain

前回書いた記事で作成した機能を組み合わせたような形になります。

①英語の音声ファイルを手動でS3のinputバケットへアップロード
②inputバケットにファイルがアップロードされたことを検知し、文字起こし用Lambdaが実行される
Amazon Transcribeが実行され、音声ファイルから文字起こしファイルを作成します
④文字起こし結果をoutput用バケットに格納する
⑤output用バケットにファイルが作成されたことを検知し、翻訳用Lambdaが実行される
⑥翻訳結果をtranslate用バケットに格納する

構築

必要なバケットを準備

input用とoutput用のバケットは、前回作成しているので今回はtranslate用バケットのみ作成します。
※作成方法自体は、全てのバケット共通です。

こいつですね。

f:id:Tk24:20200311112236p:plain

1.S3のコンソール画面に移動します

2.バケットの作成をクリックします
f:id:Tk24:20200311114244j:plain

3.バケットリージョンを設定する1
f:id:Tk24:20200311114413j:plain

4.画面左下の作成をクリックします

文字起こし用Lambdaの準備

文字起こし用のLambdaは、前回作成した以下のLambdaを使用します。

こいつですね。

f:id:Tk24:20200311112346p:plain

import json
import urllib.parse
import boto3
import datetime

s3 = boto3.client('s3')
transcribe = boto3.client('transcribe')

def lambda_handler(event, context):
  #バケット名を取得
  bucket = event['Records'][0]['s3']['bucket']['name']
  #オブジェクトのキーを取得
  key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
  try:
    transcribe.start_transcription_job(
      #文字起こしのオブジェクト名を作成
      TranscriptionJobName= datetime.datetime.now().strftime("%Y%m%d%H%M%S") + '_Transcription',
      LanguageCode='en-US',
      #文字起こし用の音声ファイルを取得
      Media={
        'MediaFileUri': 'https://s3.ap-northeast-1.amazonaws.com/' + bucket + '/' + key
      },
      #作成したファイルを保存するバケット
      OutputBucketName=<文字起こしファイルの保存用バケット>
    )
  except Exception as e:
    print(e)
    print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
    raise e

Amazon Transcribeを使用し、音声を文字起こしします。
ちなみに、現在Amazon Transcribeに対応している言語は以下になります。

https://docs.aws.amazon.com/transcribe/latest/dg/what-is-transcribe.html

S3のinput用バケットがトリガーになっていることを確認し、完了です。

f:id:Tk24:20200311115152j:plain

翻訳用lambdaの作成

今回のメインですね。

f:id:Tk24:20200311112444p:plain

1. Lambdaのコンソールに移動します

2. 関数を作成をクリックします
f:id:Tk24:20200220115728j:plain

3. 設計図の使用にチェックを入れ、フィルターにS3と入力します
f:id:Tk24:20200311115548j:plain

4. 表示されたs3-get-object-pythonをクリックし、最下行の設定をクリックします
f:id:Tk24:20200311115840j:plain

5. 関数名ロール名を入力し、ポリシーテンプレートからAmazon S3 オブジェクトの読み取り専用アクセス権限を選択します
f:id:Tk24:20200311120039j:plain

6. バケットで自分で作成したoutput用バケットを選択します
f:id:Tk24:20200311120213j:plain

7. 最下行の関数の作成をクリックします

8. 表示された関数コードを以下のコードに変更します
f:id:Tk24:20200311120446j:plain

import json
import urllib.parse
import boto3
import datetime

print('Loading function')

s3 = boto3.client('s3')

def lambda_handler(event, context):
    #バケット名を取得
    bucket = event['Records'][0]['s3']['bucket']['name']
    #オブジェクトのキーを取得
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

    try:
        translate = boto3.client('translate')
        #翻訳前のオブジェクトを取得
        input_text = s3.get_object(Bucket = bucket, Key = key)
        #jsonファイル内の翻訳対象が含まれるBodyのみを抜粋
        input_text = json.loads(input_text["Body"].read().decode('utf-8'))

        #さらに翻訳対象絞り込み、英語から日本語に変換
        response = translate.translate_text(
            Text=input_text["results"]["transcripts"][0]["transcript"],
            SourceLanguageCode="en",
            TargetLanguageCode="ja"
            )

    except Exception as e:
        print(e)
        print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
        raise e

    #作成したファイルを保存するオブジェクト名
    TranslateJobName= datetime.datetime.now().strftime("%Y%m%d%H%M%S") + '_Translate'
    #作成したファイルを保存するバケット名
    OutputBucketName=<翻訳ファイルの保存用バケット>
    #オブジェクトの内容
    output_text = response.get('TranslatedText')
    #翻訳した内容をtranslate用バケットに保存
    response = s3.put_object(Bucket=OutputBucketName, Key=TranslateJobName, Body=output_text.encode())
    
    return {
        'statusCode': 200,
        'body': json.dumps({
        'output_text': output_text
        })
    }

9. 実行ロールの設定を変更します
IAM コンソールで <設定したロール名>ロールを表示します。をクリックします
f:id:Tk24:20200311132236j:plain

ポリシーをアッタッチしますをクリックします
f:id:Tk24:20200311132332j:plain

以下のポリシーを検索し、左側にチェックを入れ、右下のポリシーをアタッチをクリックします
TranslateFullAccessAmazonS3FullAccess

f:id:Tk24:20200311132512j:plain

以上で設定は完了(のはず)です。

実践

では、実際に試してみます。 使用する音声ファイルは、前回と同じAmazon Pollyのサンプルを使用します。

https://d1.awsstatic.com/product-marketing/Polly/HelloEnglish-Joanna.0aa7a6dc7f1de9ac48769f366c6f447f9051db57.mp3

1. 音声ファイルをinput用のS3バケットにアップロードします。
f:id:Tk24:20200311133444j:plain

2. Amazon Transcribeの画面を確認し、ジョブが実行されていることを確認します。
f:id:Tk24:20200311132833j:plain

3. output用のS3バケットに文字起し結果のファイルが作成されていることを確認します
f:id:Tk24:20200311133457j:plain

4. 少し待ち、translate用バケットにファイルが新規作成されていることを確認します
f:id:Tk24:20200311133509j:plain

5. ファイルをダウンロードし、中身が
こんにちは。 外国語を話せますか。 一つの言語では十分ではありません。
となっていれば成功です!

もし、上手くいかない場合は、被疑箇所のLambdaのモニタリングCloudWatch Logs Insightsから実行結果ログを確認し、問題を特定してください。

以上です。


  1. リージョンはlambdaと同じにする必要があります

手を動かしながら学ぶサーバーレスハンズオンをやってみた

初めに

私が、AWSを学習したudemyの講座の講師をされていた
金澤さんのハンズオンだったので、凄く楽しみにしていたのですが、
ハンズオンに行けなかったため自己学習です。

本記事は、以下の勉強会で実施された内容です。

https://jawsug-bgnr.connpass.com/event/165065/

"サーバーレス"とは何か?何がうれしいのか?

開発者がやりたいこととやらなければいこと

  • 開発者やりたいこと

    • ユーザーに価値を届ける
  • やるべきこともたくさん

サーバーレスでは、サーバの管理が不要なため、
開発者はユーザーに価値を届けることに集中できる!

サーバーレスアーキテクチャの特徴

  • インフラのプロビジョニングや管理が不要
  • ビルドインの高可用性
  • 自動でスケール
  • 価値に対する支払い

AWS Lamdbaで 日→英 翻訳します

シナリオ#1 日→英翻訳をすぐさまやりたい

  • オリンピックのある今年は英語を使う機会も多い
  • 英語の聞き取りは大丈夫、でも喋るのが。。
  • でもいつその機会に遭遇するかはわからない
  • コストは少しでも押さえたい

AWSにおけるComputeサービス

  • EC2
    • 自由度:高い
    • 管理の手間:多い
  • Lambda
    • 自由度:低い
    • 管理の手間:少ない

AWS Lambdaの特徴

  • サーバーのプロビジョニング/管理なしで、プログラムを実行できるサービス
  • コードを実行するための準備、スケーリングなどは、Lambda側で実施
    開発者の方はコードを書くことに集中できる
  • 料金体系はリクエストベース
    • 実行回数 + 実行時間(単価は確保したメモリ量)
      ※無料枠あり

ハンズオン

AWS Lambdaを用い、下記の2つの作業を進めていく

  1. Lambda Functionを作成し、"Hello World"と表示します
  2. Lambda Functionを修正し、Amazon Translateと連携します

Lambda Functionを作成し、"Hello World"と表示します

  1. IAMユーザでログイン&初期設定

    • 右上:東京リージョンを選択
    • 画面左下のEnglish(US)日本語に変更 f:id:Tk24:20200220183744j:plain
  2. AWS Lambda画面へ

    • サービス検索窓にLambdaと入力し、下に出てくるLambdaをクリックします f:id:Tk24:20200220183831j:plain
  3. Lambda Functionの作成

    • 右上の橙色の関数の作成をクリックします f:id:Tk24:20200220183901j:plain

    • 一から作成のままでOK

    • 関数の情報を入力し、右下の関数の作成をクリックします

      • 関数名:translate-function
      • ランタイム:Python3.8 f:id:Tk24:20200220183917j:plain
    • 少し下にスクロールするとソースコードが見えてくる

    • 7行目の"Hello from Lambda!"の中身を何でも良いので修正します
    • 右上の保存ボタンをクリックします f:id:Tk24:20200220184020j:plain
  4. Lambda Functionをテスト実行します

    • 右上のテストをクリックします
    • イベント名にTestと入力します f:id:Tk24:20200220184154j:plain
    • 右下の作成ボタンをクリックします f:id:Tk24:20200220184205j:plain
    • 右上のテストボタンをクリックします
    • statusCode: 200,が表示されれば成功です f:id:Tk24:20200220184236j:plain

Lambda Functionを修正し、Amazon Translateと連携します

  1. Lambda Function のIAMロールを修正します
    • Lambda Function 画面の下の方にスクロールしてください
    • xxx ロール表示しますをクリックします f:id:Tk24:20200220184427j:plain

    • ポリシーをアタッチしますをクリックします f:id:Tk24:20200220184447j:plain

    • 検索窓にtranslateと入力し、
      TranslateFullAccessの方にチェックを入れます f:id:Tk24:20200220184511j:plain
    • その後、右下のポリシーのアタッチをクリックします
    • Lambda画面に戻ります
  2. Lambda Functionのソースコードを修正
    • 以下のようにソースコードを修正します 1 ・"AWS SDK Python"と検索するとリファレンスが出てくるので、それをみながら開発を進めます
import json
import boto3

def lambda_handler(event, context):

  translate = boto3.client('translate')
  input_text = '順調ですか?'
  response = translate.translate_text(
    Text=input_text,
    SourceLanguageCode='ja',
    TargetLanguageCode='en'
  )

  output_text = response.get('TranslatedText')

  return {
    'statusCode': 200,
    'body': json.dumps({
      'output_text': output_text
    })
  }
  • 保存 > テストをクリックします
    • 翻訳結果が出ていればOKです
      input_test の文字列を変更し、色々試してください

翻訳 Web APIを作る

シナリオ#2 日→英 翻訳をWeb API化したい

  • 翻訳したいときに常にマネージメントコンソールにログインできるとは限らない
  • スマホからでもサクッと接続できるようにWeb API 化したい
  • いつでも使えないと困るけど、冗長性のための設計はしている時間がない...

サーバーレスアーキテクチャでよく利用されるAWSサービス

Amazon API Gatewayの特徴

  • サーバーのプロビジョニングや管理無しで、APIを作成・管理できるマネージドサービス
  • 可用性の担保、スケーリングなどのAPI管理で必要なことをAPI Gatewayに任せる事で、開発者はビジネスの差別化につながる作業に集中できます
  • 料金体系はリクエストベース(※REST APIの場合)

ハンズオン

Amazon API GatewayAWS lambdaを組み合わせて、外部から叩けるWeb APIを作って行きます。

  1. Lambda Functionのソースを修正
    • input_text の部分をAPI Gatewayから渡される、クエリストリンクパラメータを受け取る形に修正し、保存します
import json
import boto3

def lambda_handler(event, context):
  translate = boto3.client('translate')

  input_text = event['queryStringParameters']['input_text']

  response = translate.translate_text(
    Text=input_text,
    SourceLanguageCode="ja",
    TargetLanguageCode="en"
  )
...
  1. API Gateway - APIの作成

    • "Amazon API Gateway"画面に遷移
      右上のサービス > 検索窓にapiと入力 > API Gatewayを選択
    • 右上のAPIを作成を押してください2
    • 下部に遷移し、REST API構築ボタンをクリックします3
      f:id:Tk24:20200220184730j:plain
    • プロトコルRESTのまま、そのしたで新しいAPIを選択します
    • API名をtranslate-apiとし、右下のAPIを作成をクリックします f:id:Tk24:20200220184850j:plain
  2. API Gateway - リソースの作成

    • アクション > リソースの作成をクリックします f:id:Tk24:20200220184936j:plain
    • "リソース名"をtranslateとし、リソースの作成をクリックします f:id:Tk24:20200220185002j:plain
  3. API Gateway - メソッドの作成

    • /translateが選ばれている状態で、アクション > メソッドの作成をクリックします
    • "/translate"の下のプルダウンから、GETを選択し、チェックマークをクリックします f:id:Tk24:20200220185031j:plain
    • Lambdaプロキシ統合の使用チェックしたのち、
      "Lambda 関数"にtranslate-functionを指定し、保存をクリックします f:id:Tk24:20200220185107j:plain
    • "Lambda 関数に権限を追加する"ダイアログで、OKをクリックします
  4. API Gateway - デプロイ

    • アクションからAPIのデプロイを実施 f:id:Tk24:20200220185136j:plain

    • 初めてのデプロイなので、ステージを作成します
      新しいステージを選択し、ステージ名にdevと入力し、デプロイをクリックします f:id:Tk24:20200220185157j:plain

    • 左側のメニューでステージが選ばれていることを確認します4

    • URLが生成されるので、コピーし、ブラウザに貼り付けます

    • 貼り付けたURLの後ろに、/translate?input_text=初めてのAPI呼び出しですを付けます

    • これで、翻訳API完成です
      好きな言葉を入れて試してみてください

文字起こし + 翻訳パイプラインを作る

シナリオ#3 英文の文字起こしをしたい

  • さっき聞き取りは問題ないといったけど、嘘でした...
  • 喋ってもらった内容をテキスト化できないと辞書も引けない
  • まず文字お越しするところだけでも自動化できないだろうか

Amazon Transcribeの特徴

  • 音声をテキストにする文字お越しサービス
    • 2019年11月に日本語対応
    • 保存した音声ファイルやリアルタイム変換が可能 5

Amazon Simple Storage Service(Amazon S3)の特徴

  • 高い耐久性を持つオブジェクトストレージサービス
    • 99.999999999%(イレブンナイン)
    • 標準で少なくとも3つのAZに格納されている
  • 容量無制限、安価なストレージ
  • 様々なAWSサービスと連携
    • 例:ファイルが格納されたことをトリガーに非同期にLambda関数を呼ぶ

ハンズオン

AWS LambdaとAmazon Transcribeを組み合わせて、音声データを文字お越しするパイプラインを作ります

1.S3のバケットを作成する

  • input用とoutput用に2つのS3バケットを作成します
  • サービス > S3と入力 > S3を選択
  • バケットを作成するをクリックします
  • バケット名をyyymmdd-transcribe-input-yournameとする6 リージョンは、lambdaと同じにしてください

f:id:Tk24:20200220185704j:plain

  • 左下の作成ボタンをクリックします
  • 2つ目のバケットも作成します
  • バケット名をyyymmdd-transcribe-output-yournameとする
  • 左下の作成ボタンをクリックします

2.Lambda Functionの作成

  • AWS Lambdaの画面へ:サービス > lambdaと入力 > Lambdaを選択します
  • 関数の作成をクリックします
  • 設計図の使用 > 検索窓にS3と入力し、Enter f:id:Tk24:20200220191737j:plain
  • s3-get-object-pythonを選択し、右下の設定をクリックします f:id:Tk24:20200220192107j:plain
  • Function名:transcribe-function,
    Role 名:transcribe-function-roleとし、下へ移動します
  • S3 トリガーの対象になるinput用のバケットを指定します
  • トリガーの有効化チェックをつけます f:id:Tk24:20200220192142j:plain
  • 下部の関数の作成をクリックします
  • IAM ロールを修正する
    Lambda Functionの下部からxxx ロールを表示をクリックします
  • ポリシーをアタッチしますをクリックします
  • 検索窓にS3と入力 > AmazonS3FullAccessにチェックします
  • 検索窓をクリアし、transcribeと入力 > AmazonTranscribeFullAccessにチェックします
  • ソースコードを以下のように修正し、保存する
    • OutputBucketNameにはOutputバケット名を入力してください
import json
import urllib.parse
import boto3
import datetime

s3 = boto3.client('s3')
transcribe = boto3.client('transcribe')

def lambda_handler(event, context):
  bucket = event['Records'][0]['s3']['bucket']['name']
  key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
  try:
    transcribe.start_transcription_job(
      TranscriptionJobName= datetime.datetime.now().strftime("%Y%m%d%H%M%S") + '_Transcription',
      LanguageCode='en-US',
      Media={
        'MediaFileUri': 'https://s3.ap-northeast-1.amazonaws.com/' + bucket + '/' + key
      },
      OutputBucketName='YYYYMMDD-transcribe-output-YourName'
    )
  except Exception as e:
    print(e)
    print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
    raise e

3.パイプラインのテストを実施する

  • 音声ファイルは下記URL(Amazon Pollyのサンプル)から、ダウンロードします

https://d1.awsstatic.com/product-marketing/Polly/HelloEnglish-Joanna.0aa7a6dc7f1de9ac48769f366c6f447f9051db57.mp3

  • S3にmp3ファイルをアップロードする
    input側のS3バケットアップロードをクリックします

  • ファイルを追加から先ほどDLしたmp3ファイルを指定し、アップロードします

  • サービス > transcribeと入力 > Amazon Trabscribeを選択します

  • 左上のハンバーガーメニューをクリックし > Transcription Jobsをクリックします

  • Job StatusがIn progressであることを確認し、Completeになるまで待ちます f:id:Tk24:20200220192248j:plain

  • Jobが完了したら、Output側のS3を確認します
    yyyymmddhhmmss_Transcription.jsonファイルがあれば成功です f:id:Tk24:20200220192322j:plain

  • Jsonファイルの中の resulte > transcripts > transcriptを確認します
    “transcript”: “Hello. Do you speak a foreign language? One language is never enough.”と文字起こしされていると思います。 {"jobName":"20200220074630_Transcription","accountId":"XXXXXXXXXXXX","results":{"transcripts":[{"transcript":"Hello. Do you speak a foreign language? One language is never enough."}],"items":[{"start_time":"0.04","end_time":"0.65","alternatives":[{"confidence":"0.9139","content":"Hello"}],"type":"pronunciation"},{"alternatives":[{"confidence":"0.0","content":"."}],"type":"punctuation"},{"start_time":"1.04","end_time":"1.14","alternatives":[{"confidence":"1.0","content":"Do"}],"type":"pronunciation"},{"start_time":"1.14","end_time":"1.27","alternatives":[{"confidence":"1.0","content":"you"}],"type":"pronunciation"},{"start_time":"1.27","end_time":"1.59","alternatives":[{"confidence":"1.0","content":"speak"}],"type":"pronunciation"},{"start_time":"1.59","end_time":"1.65","alternatives":[{"confidence":"0.9991","content":"a"}],"type":"pronunciation"},{"start_time":"1.65","end_time":"1.99","alternatives":[{"confidence":"1.0","content":"foreign"}],"type":"pronunciation"},{"start_time":"1.99","end_time":"2.59","alternatives":[{"confidence":"1.0","content":"language"}],"type":"pronunciation"},{"alternatives":[{"confidence":"0.0","content":"?"}],"type":"punctuation"},{"start_time":"2.88","end_time":"3.19","alternatives":[{"confidence":"0.9944","content":"One"}],"type":"pronunciation"},{"start_time":"3.19","end_time":"3.61","alternatives":[{"confidence":"0.991","content":"language"}],"type":"pronunciation"},{"start_time":"3.61","end_time":"3.75","alternatives":[{"confidence":"0.991","content":"is"}],"type":"pronunciation"},{"start_time":"3.75","end_time":"4.03","alternatives":[{"confidence":"1.0","content":"never"}],"type":"pronunciation"},{"start_time":"4.03","end_time":"4.48","alternatives":[{"confidence":"0.9079","content":"enough"}],"type":"pronunciation"},{"alternatives":[{"confidence":"0.0","content":"."}],"type":"punctuation"}]},"status":"COMPLETED"}

まとめ

ハンズオン資料がかなりしっかりしていたため、
特に困ることなく構築できました。 順序だてて丁寧に説明があるので、非常にわかりやすかったです。

こんな簡単に、システムを作れるとは面白いですね。 宿題 や +α があるので構築しようと思います。


  1. AWS SDK for Python(boto3)を利用します

  2. 既存のAPIがある場合のみ、ない場合は次の手順へ

  3. もし、"最初にAPIを作成する"ポップアップが出てきたらOKをクリックします

  4. 選ばれていなければステージを選択>devをクリック

  5. ただし、リアルタイム変換は日本語未対応

  6. バケット名は世界中でユニークにする必要があります

AWS ご利用開始時に最低限おさえておきたい10のこと

初めに

本記事は、AWS White Belt Online Seminar のアウトプットになります。

pages.awscloud.com

AWS Well-Architected Framework(W-A)

AWS Well-Architected Framework(W-A)とは?

システム設計・運用の大局的な考え方とベストプラクティス集
AWSのソリューションアーキテクトと利用者が数多くの経験から作り上げたもの
・毎年アップデートされる

AWS Well-Architected Frameworkホワイトペーパー

内容

・運用の優秀性
・セキュリティ
・信頼性
・パフォーマンス効率
・コストの最適化

設計原則

・必要なキャパシティを勘に頼らない
本番環境でのテストを行う
アーキテクチャ試行の回数を増やすために自動化を取り入れる
発展的なアーキテクチャを受け入れる
データ計測に基づいてアーキテクチャを決定する
・本番環境で想定されるトラブルをあらかじめテストし、対策する

質問と回答形式のベストプラクティス

・ベストプラクティスとしては、全項目網羅されている事
・リスクや改善点などを理解したうえで、明確な判断材料に基づいてあえてベストプラクティスを実施しないことは問題ない

設計構築時に”W-A”を実施

「ベストプラクティスの質問」を活用
・合計46個のベストプラクティスの質問に答えて、ワークロードとの差分(改善点・リスク)を把握する。
・ベストプラクティスを理解した上で、判断する 。
イメージは健康診断。改善、要改善項目を見つけ対応の要不要の判断をする。

※運用中のサービスのレビューも可能

AWS利用開始時に最低限おさえておきたいこと

セキュリティ(5/11項目抜粋)

・確認事項: ワークロードの認証情報と認証をどのように管理していますか。

ベストプラクティス:
rootアカウントには、必ずMFA(多要素認証)を設定し、最小限の利用に留める(極力利用しない)。
普段の作業には、IAMユーザーを使用する。

・確認事項: AWSサービスへの人為的なアクセスをどのように制御していますか。

ベストプラクティス:
ユーザー各個人に固有の認証情報(IAMユーザー)を払い出し、必要に応じた最小限の権限を付与する。 ユーザーグループを使うと、権限をまとめて管理できる。

・確認事項: AWSサービスへのプログラムによるアクセスを、どのように制御していますか。

ベストプラクティス:
認証情報をハードコーディングをせず、IAMロールを活用する。git-secretsを使い認証情報を管理することができる。

※git-secret‥AWS Labsの認証情報をgitリポジトリにコミットすることを防ぐツール。

・確認事項: ワークロードのセキュリティイベントをどのように検知して調査していますか。

ベストプラクティス:
セキュリティ関連のログを取得し、一元的に監視と分析をする。CloudTrailAPI、GUい操作ログ、コマンド操作ログなどを取得可能。
何かあった際にログを確認するだけでなく、普段から監視することも大事。Amazon GuardDutyを活用することで、通知の自動化も可能。

・確認事項: ネットワークをどのように保護していますか。

ベストプラクティス:
ネットワークACLSG(セキュリティグループ)を活用することで、各レイヤでのセキュリティ対策や必要最低限のアクセス設定に絞ることが可能。

AWS WAFAWS Shieldを活用することで、よりセキュアなシステムを構築できる。

信頼性(2/9項目抜粋)

・確認事項: どのようにデータをバックアップしていますか。

ベストプラクティス:
バックアップを取得し、定期的なリカバリテストでRTO・RPOを満たすことを確認している。 EC2 AMIEBS スナップショットRDSの自動バックアップ機能を活用する。
トラブル発生時を想定した、復旧テストを定期的に実施し、手順の確認をすることを推奨。

RTO‥インシデント発生から復旧までの「許容できるダウンタイムの最大時間」を指す。

RPO‥復旧すべきバックアップファイルの古さのこと。つまり、どの程度データの損失を許容できるのかを決める。

・確認事項: システムがコンポーネントのエラーに耐えられるようにどのように設計していますか。

ベストプラクティス:
マルチAZまたは、複数リージョンでシステムが実行されている疎結合アーキテクチャを採用している。
障害を監視し、自動的に回復する仕組みがある。
単一障害点の排除がキーポイント。

コストの最適化(3/9項目抜粋)

・確認事項: AWS使用量とコストをどのようにモニタリングしていますか。

ベストプラクティス:
請求ダッシュボードAWS Cost Explorerで積極的に使用量を把握し、分析している。
そのために、IAMユーザーの請求情報へのアクセス有効化コストエクスプローラーの有効化をルートアカウントで設定する。
日頃から定期的に確認することを推奨。

AWS Budgetsを活用することで、設定した予算から超過した場合に通知させることができる。

・確認事項: コスト目標を達成するためにインスタンスタイプとサイズをどのように選択していますか。

ベストプラクティス:
メトリクスに基づいて、サイジングする。
そのために、AWS CloudWatchでリソース状況を把握し、利用状況に応じた適切なインスタンスタイプなどを選択する。
リザーブドインスタンスAutoScalingなどの活用も検討する。
定期的に見直すことを推奨。
基本的には、新しいタイプ、ファミリーの方が安い。

・確認事項: コスト削減のために料金も出るはどのように選択していますか。

ベストプラクティス:
利用率を分析し、購入オプションを検討している。
時間課金系サービスを使い分けることを意識する。

リージョン毎の料金差も考慮している。
より安価なリージョンを選択できるような場合もある。

【CentOS7】Kernel panic について 【not syncing: No init found.】

内容

CentOSのISOファイルが問題なく使えるよねという確認がしたかっただけなので、
各種設定はデフォルトの推奨スペックのまま作成していました。
VMplayerにCentOS7.7をインストールしようとたしたら、ISOの読み込み中にカーネルパニックが起きた。

f:id:Tk24:20200214194041j:plain

検証

検索しても原因が、わからなかったためISOイメージの破損やVMplayerとCentOSの相性の問題かと考えました。

まず、同じイメージを使用して、VirtualBoxでサーバーを作成したところ問題なく作成できたので、
ISOイメージは破損していないことは確認できました。

VMplayerとの相性問題かと思い、CentOS 7.5、7.2、7.0 をインストールしてみました。

f:id:Tk24:20200214194054j:plain

  • CentOS 7.2
    言語選択の画面が表示されました!
    しかし、日本語を選択し「次へ」を押すとフリーズしました。。

f:id:Tk24:20200214194112j:plain

しばらく放置していたら先に進みましたが、ディスクが検出されず進みませんでした。

f:id:Tk24:20200214194126j:plain

  • CentOS 7.0 他のバージョンと同じように起動したところ、以下のメッセージが表示されました。
    どうやら、CentOS7.0をインストールするためには、メモリが512MB必要なようです!

f:id:Tk24:20200214194141j:plain

結論

CentOSに対して、割り当てるメモリが足りていませんでした。
VMplayerのデフォルトの推奨スペックが256MBだったので、そのまま作成していたことが原因でした。
VirtualBoxのデフォルトの推奨スペックは1024MBなので、その違いで作成できたんですね。。

通常は、ある程度スペックを決めてから作成するのでまず引っかかりませんよね。

それにしても、7.0だけちゃんとメモリを要求するメッセージを表示してくれるんですね!
後継バージョンのはずの7.Xでは出てこないのは不思議です。

CentOSの最小RAM

以下のサイトを参考にさせていただくと、CentOS7のインストーラーを
動作させるためには、最小限の406MB必要だそうです。

512MBはCentOS-7の最小メモリ要件だそうですが、
全ての機能を使用するためには、1GB必要だそうです。

Minimum RAM For CentOS7

ソースがリンク切れしていたので、公式情報までは辿れませんでしたが、
今回色々試した感覚だとあっているような気がします!