20190506のAWSに関する記事は15件です。

Lambda から Google API を呼ぶと権限ファイルの書き込みで `[Errno 30] Read-only file system` になる

背景

  • 以下の記事に沿って Gmail API を叩く Python スクリプトを作成
  • スクリプトを Lambda で動くようにして、実行したところ [Errno 30] Read-only file system が発生
    • ローカルで実行したときには起きなかった

原因

  • 権限ファイル (client_id.json) の書き込みでエラーになっている模様
  • エラーのスタックトレースを見ると、oauth2client ライブラリ内で発生しているようだった
"errorMessage": "[Errno 30] Read-only file system: 'client_id.json'",
  :
    [
      "/var/task/oauth2client/file.py",
      79,
      "locked_put",
      "f = open(self._filename, 'w')"
    ],
  :
  • self._filename は Storage クラスのコンストラクタ引数
  • Lambda が書き込みできるのは /tmp 配下のファイルのみなので、Read-only エラーとなった

対応

  • client_id.json を一旦 /tmp に退避させてから Storage クラスを作成する
before
    user_secret_file = 'client_id.json'
    store = Storage(user_secret_file)
    credentials = store.get()
after
import shutil
 :
    user_secret_file = 'client_id.json'
    user_secret_tmp_path = '/tmp/' + user_secret_file
    shutil.copy2(user_secret_file, user_secret_tmp_path)

    store = Storage(user_secret_tmp_path)
    credentials = store.get()

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsアプリをHerokuでデプロイする際の注意点

はじめに

初投稿です。本とネット教材で半年学んだ程度のヒヨッコです。
AWS cloud9でRuby on Railsプロジェクトを作成し、GitHub経由でHerokuでデプロイしました。
その際に発生したエラー・注意点を備忘録として投稿させていただきます。

Herokuの注意点

Herokuではsqlite3が使えないようなので、本番環境用のgemを別に設定する必要があります。
以下リンクを参考にPostgreSQLを入れました。
(参考)HerokuではSQLite3が使えない?! https://qiita.com/MosamosaPoodle/items/7149aa66f1c087472777

・Rails Gemfileを修正

Gemfile
gem 'sqlite3' 

↓変更する

Gemfile
gem 'sqlite3', groups: %w(test development), require: false
gem 'pg', groups: %w(production), require: false

・Rails Gemfile.lockを削除しbundle installし直す

Terminal
$ rm Gemfile.lock
$ bundle install

これでRails上の設定は完了です。

AWS cloud9からgit push時の注意点

安全性の観点から、GitHubを二段階認証で管理すべきなのですが、
そうするとcloud9からpushする際にGitHubアカウントパスワードを入力してもエラーになります。

Terminal
Username for 'https://github.com': hoge-user
Password for 'https://hoge-user@github.com':
remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/hoge-user/github-repository-name.git/'

対策として
・GitHubでPersonal Access tokensを発行し、tokenをPassword欄に入力する(https接続)
・SSH接続するよう設定を変更する
の2つがありますが、今回は前者を選びました。
(参考)GitHub「Personal access tokens」の設定方法 https://qiita.com/kz800/items/497ec70bff3e555dacd0

ちなみに個人的にハマった罠が2つありますのでそちらも書いておきます。
1. Terminal上のPassword入力時は、画面は反応しないが文字はちゃんと入力されている。心配せずに入力する!
2. コピーした文字をTerminal上にペーストするときはcommand+v (Ctrl+v)で入力しない。右クリック→Pasteでペーストする。

成果

半年間HTML, CSS, JavaScript, Ruby, Rails, AWS cloud9などと悪戦苦闘しましたが、素人でもHerokuでデプロイできました。
飲み会のお会計をシチュエーション別で傾斜計算する簡単なツールです。
Warikan - https://warikanappbygmnori.herokuapp.com/

デザインや計算方法の拙さは本人が一番実感しています。次はもっと良いものを作れるよう頑張ります。

参考リンク

HerokuではSQLite3が使えない?! https://qiita.com/MosamosaPoodle/items/7149aa66f1c087472777
GitHub「Personal access tokens」の設定方法 https://qiita.com/kz800/items/497ec70bff3e555dacd0

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初学者向け】RailsアプリをHerokuでデプロイする際の注意点

はじめに

初投稿です。本とネット教材で半年学んだ程度のヒヨッコです。
AWS cloud9でRuby on Railsプロジェクトを作成し、GitHub経由でHerokuでデプロイしました。
その際に発生したエラー・注意点を備忘録として投稿させていただきます。

Herokuの注意点

Herokuではsqlite3が使えないようなので、本番環境用のgemを別に設定する必要があります。
以下リンクを参考にPostgreSQLを入れました。
(参考)HerokuではSQLite3が使えない?! https://qiita.com/MosamosaPoodle/items/7149aa66f1c087472777

・Rails Gemfileを修正

Gemfile
gem 'sqlite3' 

↓変更する

Gemfile
gem 'sqlite3', groups: %w(test development), require: false
gem 'pg', groups: %w(production), require: false

・Rails Gemfile.lockを削除しbundle installし直す

Terminal
$ rm Gemfile.lock
$ bundle install

これでRails上の設定は完了です。

AWS cloud9からgit push時の注意点

安全性の観点から、GitHubを二段階認証で管理すべきなのですが、
そうするとcloud9からpushする際にGitHubアカウントパスワードを入力してもエラーになります。

Terminal
Username for 'https://github.com': hoge-user
Password for 'https://hoge-user@github.com':
remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/hoge-user/github-repository-name.git/'

対策として
・GitHubでPersonal Access tokensを発行し、tokenをPassword欄に入力する(https接続)
・SSH接続するよう設定を変更する
の2つがありますが、今回は前者を選びました。
(参考)GitHub「Personal access tokens」の設定方法 https://qiita.com/kz800/items/497ec70bff3e555dacd0

ちなみに個人的にハマった罠が2つありますのでそちらも書いておきます。
1. Terminal上のPassword入力時は、画面は反応しないが文字はちゃんと入力されている。心配せずに入力する!
2. コピーした文字をTerminal上にペーストするときはcommand+v (Ctrl+v)で入力しない。右クリック→Pasteでペーストする。

成果

半年間HTML, CSS, JavaScript, Ruby, Rails, AWS cloud9などと悪戦苦闘しましたが、素人でもHerokuでデプロイできました。
飲み会のお会計をシチュエーション別で傾斜計算する簡単なツールです。
Warikan - https://warikanappbygmnori.herokuapp.com/

デザインや計算方法の拙さは本人が一番実感しています。次はもっと良いものを作れるよう頑張ります。

参考リンク

HerokuではSQLite3が使えない?! https://qiita.com/MosamosaPoodle/items/7149aa66f1c087472777
GitHub「Personal access tokens」の設定方法 https://qiita.com/kz800/items/497ec70bff3e555dacd0

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon WorkSpacesで利用できるショートカットキー

WorkSpacesに接続した際、クライアントからアクセスした時とブラウザからアクセスした時で挙動が異なっていたため、まとめました。

試した環境

  • バンドル:Standard with Windows 10
  • Amazon WorkSpacesクライアント:Version 2.5.5.1172
  • Amazon WorkSpaces web client:version 2.4.1

挙動

一般的なWindows端末と同様の挙動をしたところは〇、そうでないところには挙動を記載しました。

ショートカットキー 目的 クライアント ブラウザ
Ctrl + C 選択した項目をコピーする。
Ctrl + V 選択した項目を貼り付ける。
Alt + Tab ウィンドウを切り替える。 作業端末でアプリの切り替えが行われる。
Windows ロゴ キー + L PC をロックする。 作業端末とクライアントの両方でロックがかかる。 作業端末のみロックがかかり、WorkSpace内ではスタートメニューが開く。
Windows ロゴ キー + A アクションセンターを開く。 アクションセンターが開くと同時にスタートメニューも開く。
Windows ロゴ キー + ↑ ウィンドウを最大化する。 ウィンドウの大きさは変わらず、スタートメニューが開く。
Windows ロゴ キー + P プレゼンテーション表示モードを選択する。 作業端末のみプレゼンテーション表示モードの選択画面が開き、WorkSpace内ではスタートメニューが開く。
F2 選択された項目の名前を変更する。
Ctrl + Alt + Tab 方向キーを使って、ウィンドウを切り替える。 作業端末でアプリの切り替えが行われる。
Ctrl + Alt + delete Windowsのセキュリティやタスクマネージャを呼び出す。 作業端末とクライアントの両方で呼び出される。 作業端末のみ呼び出される。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メモ CodeBuild docker on docker -> ECR pushなど

やること

※この記事には画像がなく痩せた大地のようなのでCodeBuildでツラまった時に見るとかがいい

  1. マネージドイメージ内でmvnビルド、生成されたjarをS3にアップロード AWS CodeBuildの使用開始
  2. カスタムイメージ(自作dockerイメージ)内でmvnビルド、生成されたjarをS3にアップロード
  3. マネージドイメージ内でdockerイメージをビルド、ビルドしたイメージをECRにpush
  4. カスタムイメージ内でdockerイメージをビルド、ビルドしたイメージをECRにpush

最終的にやりたかった事は自前のdockerイメージ内でビルドして、そのイメージをECRにpushすること
詰まった時の問題点を特定しやすくするために段階を分割してる
1でCodeBuildの設定などを把握、2でカスタムイメージ利用、3でdockerイメージビルドからpush、4で2と3を合わせる

CodeBuildからECRへのpull/push権限とdocker in docker周りが理解できれば詰まることは無くなると思う
CodeBuildはUI上からビルド開始出来る、GitHubからのwebhookなどは検証してないけどそれも出来る(サンプルにはある)
サンプルが多いのでとりあえず迷ったらサンプル集を見てみたら解決しそう、ドキュメントが揃っていてえらい
CodeBuildサンプル

環境

  • AWSマネジメントコンソールにログイン出来る (ECR/IAM/CodeBuild/S3周りの権限がある)
  • dockerイメージがローカルでビルド出来る
  • AWS CLIがローカルで使える

作業

マネージドイメージ内でmvnビルド、生成されたjarをS3にアップロード

まずはCodeBuildのイメージを掴むためにAWSが用意しているチュートリアルをやる
本家を見たほうが参考になると思う
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/getting-started.html

やること

  • s3 input/outputの定義
  • ビルド用プロジェクトの作成/buildspec.yml作成/s3アップロード
  • CodeBuild ビルドプロジェクト作成
  • ビルド実行
  • ビルドログ/成果物確認

ではやっていく

S3バケット作成 input/output用

このサンプルではソースコード取得/成果物出力をS3バケットに行うのでそれ用のバケットを作成する

今回の名前はこんな感じ、名前は好きに付けてOK

入力用バケット: codebuild-AWSアカウントID-yyyyMMdd-input-bucket
出力用バケット: codebuild-AWSアカウントID-yyyyMMdd-output-bucket

入力用バケットはCodeBuildのSource
出力用バケットはCodeBuildのArtifactsで指定する

※バケットが1つでも動作するが確認しやすいので入力出力を分けているらしい
※CodeBuildプロジェクトとS3バケットは同一リージョンに作成する必要がある

ビルド用mvnプロジェクト作成

ローカルの好きなところにmvnプロジェクト用ディレクトリを作成する
mvnプロジェクトの作成

mkdir codebuild_lesson
cd codebuild_lesson
mkdir -p mkdir -p ./src/main ./src/test
touch src/main/MessageUtil.java src/test/TestMessageUtil.java
touch pom.xml

それぞれのファイルに追記
実行しないので正直中身はビルドできれば何でもいい
サンプル通りに貼り付ける

MessageUtil.java
public class MessageUtil {
  private String message;

  public MessageUtil(String message) {
    this.message = message;
  }

  public String printMessage() {
    System.out.println(message);
    return message;
  }

  public String salutationMessage() {
    message = "Hi!" + message;
    System.out.println(message);
    return message;
  }
}
TestMessageUtil.java
import org.junit.Test;
import org.junit.Ignore;
import static org.junit.Assert.assertEquals;

public class TestMessageUtil {

  String message = "Robert";    
  MessageUtil messageUtil = new MessageUtil(message);

  @Test
  public void testPrintMessage() {      
    System.out.println("Inside testPrintMessage()");     
    assertEquals(message,messageUtil.printMessage());
  }

  @Test
  public void testSalutationMessage() {
    System.out.println("Inside testSalutationMessage()");
    message = "Hi!" + "Robert";
    assertEquals(message,messageUtil.salutationMessage());
  }
}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>messageUtil</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>
  <name>Message Utility Java Sample App</name>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>   
  </dependencies>  
</project>

ここまででmvnビルド用の用意は完了

次にCodeBuildビルド仕様ファイルの追加
アプリケーションルートにbuildspec.ymlというファイルを作成
この名前はCodeBuildが認識するためこの名前にする必要がある(プロジェクト側の設定でビルド仕様ファイル名を任意の名前に設定できるが特に問題がない場合そのままで良い)

touch buildspec.yml
buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - echo Nothing to do in the install phase...
  pre_build:
    commands:
      - echo Nothing to do in the pre_build phase...
  build:
    commands:
      - echo Build started on `date`
      - mvn install
  post_build:
    commands:
      - echo Build completed on `date`
artifacts:
  files:
    - target/messageUtil-1.0.jar

ビルドバージョンは最新の0.2を指定
phasesにそれぞれあるinstallpre_buildbuildpost_buildの4つがビルドフェーズとして存在する、名前はこれ以外に設定できないし新しいフェーズを追加することも出来ない
必要のないフェーズを省略することも出来るがechoで特にやることがない的なメッセージを出力しておいて必要になったら追記するような感じでやるとフェーズ名を調べる手間が省けそう
commandsは実行したいLinuxコマンド
artifactsは成果物を設定した出力場所にどのファイルを配置するかの設定、このサンプルでは生成されたjarをS3に出力するがS3という設定はCodeBuildプロジェクト側で設定する

mvnプロジェクトをzipしてinput用s3バケットにアップロード

アプリケーションルート以下のファイルをzipしてMessageUtil.zipというファイル名にする

zip -r MessageUtil.zip ./

生成されたファイルをcodebuild-AWSアカウントID-yyyyMMdd-input-bucket/MessageUtil.zipにアップロードする

今回の全サンプルのソースはS3から取得するためビルド仕様ファイルの仕様をあまり読んでないが困ったらこれを読めば良さそう
CodeBuild のビルド仕様に関するリファレンス

ビルドプロジェクトを作成する

入力/プロジェクト/出力が揃ったのでようやくCodeBuildプロジェクトを作成する
AWSコンソールからCodeBuildのプロジェクト作成ボタンを押す

以下設定の入力項目と値
特に書いていない箇所はデフォルト設定

Project name: codebuild-AWSアカウントID-yyyyMMdd-mvn-project
Source provider: Amazon S3
バケット: codebuild-AWSアカウントID-yyyyMMdd-input-bucket
S3 object key or S3 folder: MessageUtil.zip
Environment image: Managed image
Operating system: Ubuntu
Runtime(s): Standard
Image: aws/codebuild/standard:1.0
Artifacts Type: Amazon S3
Artifacts Bucket name: codebuild-AWSアカウントID-yyyyMMdd-output-bucket
Logs CloudWatch Logs: チェックする
Logs Group name: codebuild-AWSアカウントID-yyyyMMdd-mvn-project-log

サンプルと違う箇所はログ名を付けたくらい
この状態で作成する

ビルドする

ビルドプロジェクトを作成したので後はUI上でビルドを開始して結果を確認するだけ

AWSコンソールから先程作成したビルドプロジェクトに入ってStart build -> Start buildでビルド開始
ビルド中はタブでBuild logsPhase detailsなどあるので確認する

Phase detailsで全ての工程がSucceededになったらS3にjarが出力されているか確認する

出力用S3バケットには以下のように出力される
codebuild-AWSアカウントID-yyyyMMdd-output-bucket/codebuild-AWSアカウントID-yyyyMMdd-mvn-project/target/messageUtil-1.0.jar

ここまではほぼサンプル通りのビルドなので特に問題は無いと思う

カスタムイメージ(自作dockerイメージ)内でmvnビルド、生成されたjarをS3にアップロード

次はカスタムイメージをビルド環境にしてみる

対応するCodeBuildサンプルは以下
CodeBuildのAmazon ECRサンプル
サンプルはGoプロジェクトになっているが、前回のmvnプロジェクトを使用する

やること

  • ECRリポジトリの作成
  • ローカルでmvnがビルドできるdockerイメージの作成
  • dockerイメージをECRにpush
  • CodeBuildプロジェクトでpushしたdockerイメージを利用するように変更
  • ECRリポジトリにCodeBuild実行roleからのイメージ取得権限を設定

注意点としては、ECRリポジトリにポリシー設定を行わないと同じAWSアカウントからでもpullが出来ないのでビルド時にエラーが出る
それ以外は特に前回と変わらないので前回と同じプロジェクトを編集していく

mvnビルド出来るdockerイメージの作成

その前にECRにリポジトリを作成しておく
UIからでもCLIからでもいいので名前をmvn_for_ecrとしておく

アプリケーションルートにDockerfile用ディレクトリを作成する
個人的にdockerfilesというディレクトリにそれぞれのDockerfileを置いているのでそうする

mkdir -p dockerfiles/mvn_for_ecr
touch dockerfiles/mvn_for_ecr/Dockerfile

Dockerfileの中身は以下
DockerhubのmavenをpullしてECR push用にタグ付けすれば問題なく動きそうだけど自分で色々出来るように一応Dockerfile書く

dockerfiles/mvn_for_ecr/Dockerfile
FROM java:8-jdk-alpine

WORKDIR /tmp

RUN apk update && apk add openssl && apk add curl

RUN curl -OL https://archive.apache.org/dist/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz
RUN tar -xzvf apache-maven-3.6.1-bin.tar.gz
RUN mv apache-maven-3.6.1 /usr/local/bin/
ENV PATH $PATH:/usr/local/bin/apache-maven-3.6.1/bin

RUN echo $PATH
RUN which mvn

WORKDIR /app

CMD ["/bin/ash"]

イメージのビルド/タグ付け/push
※ECRにpush出来るようにaws ecr get-loginでdockerログインしておく

aws ecr get-login --region リージョン --no-include-email
> docker login -u AWS -p ~~~~~~ https://AWSアカウントID.dkr.ecr.リージョン.amazonaws.com

docker build -t mvn_for_ecr ./dockerfiles/mvn_for_ecr
docker tag mvn_for_ecr:latest AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/mvn_for_ecr:1.0.0
docker push AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/mvn_for_ecr:1.0.0

pushが完了したらAWSコンソール上でECRのリポジトリを確認する、1.0.0があればOK

CodeBuildプロジェクトでpushしたdockerイメージを利用するように変更

AWSコンソールでcodebuild-AWSアカウントID-yyyyMMdd-mvn-projectに入り編集 -> Environmentを選択してEdit Environment画面に入る

Override imageを押下

New environment image: Custom image
Environment type: Linux
Image registry: Amazon ECR
ECR account: My ECR account
Amazon ECR repository: mvn_for_ecr
Amazon ECR image: 1.0.0
Image pull credentials: Project service role
Service role: codebuild-codebuild-AWSアカウントID-yyyyMMdd-mvn-project-service-role ※多分ロールの名前はこんな感じ

ポイントはImage pull credentialsProject service roleに設定するところ
後でECRリポジトリにポリシーで、指定しているロールからのアクセスを許可することでイメージをpull出来るようにする

ここまでの設定で一旦ビルドしてみる
※ECRリポジトリのポリシーを設定していないのでエラーが出る、一回リポジトリのポリシーを有効にすると数時間間違った設定でもSucceededになるので注意
こんな感じ at Phase details

BUILD_CONTAINER_UNABLE_TO_PULL_IMAGE: Unable to pull customer's container image.
CannotPullContainerError: Error response from daemon: pull access denied for AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/mvn_for_ecr,
repository does not exist or may require 'docker login'

次にECRリポジトリmvn_for_ecrのポリシーを以下のように設定する
PermissionsというリンクをクリックするとポリシーJSONを編集と出るのでそこで行う

ECRリポジトリのポリシー
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "mvn_for_ecr_policy",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::AWSアカウントID:role/service-role/codebuild-codebuild-AWSアカウントID-yyyyMMdd-mvn-project-service-role"
      },
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer"
      ]
    }
  ]
}

特定のロールに対してECR取得系のアクションを許す
これを保存してもう一度CodeBuildでビルドしてみると全てSucceededになると思う
ならなかったらCodeBuildのenvironmentを同じ設定で保存し直すといけるかも、検証してない

マネージドイメージ内でdockerイメージをビルド、ビルドしたイメージをECRにpush

次はdockerイメージをビルドしてECRに対してpushする
CodeBuildサンプルは以下
CodeBuildのDockerサンプル

やること

  • ECRリポジトリの作成(push用)
  • 作成したECRリポジトリのポリシー設定
  • buildspec.ymlの変更
  • イメージ作成用のDockerfile作成
  • プロジェクトをS3へ再アップロード
  • CodeBuildのEnvironment設定

dockerイメージをCodeBuildからECRにpushするのでECRリポジトリのポリシーにpush用の権限を設定する必要がある
dockerコマンドで利用する環境変数をCodeBuildプロジェクトのEnvironmentsから設定する

ECRリポジトリの作成

push用リポジトリを作成する
名前はbuild_from_codebuild_echoalpineにする
今回はmvnコマンドを利用せずに単純にdocker builddocker pushがちゃんと行えるようになるかを確認していく

UIやCLIから作成する

build_from_codebuild_echoalpineのポリシー設定を行う

ビルドしたイメージをpushするリポジトリのポリシーJSON
pushを行うので更新系のアクションを許可するようにする
こんな感じ、ロール名は予測なのでサジェストに出てきた対応するやつを使う

build_from_codebuild_echoalpine
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "build_from_codebuild_echoalpine_policy",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::AWSアカウントID:role/service-role/codebuild-codebuild-AWSアカウントID-yyyyMMdd-mvn-project-service-role"
      },
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:CompleteLayerUpload",
        "ecr:GetAuthorizationToken",
        "ecr:InitiateLayerUpload",
        "ecr:PutImage",
        "ecr:UploadLayerPart"
      ]
    }
  ]
}
buildspec.ymlの変更

今までのbuildspec.ymlだとmvnコマンドを実行してからartifactsでS3に成果物を置くようにしていたが、今回はビルドしたdockerイメージをECRにpushするのでそれ用に書き直す

buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - echo Logging in to commands...
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - echo $DOCKER_VERSION
      - which docker
      - pwd
      - ls -l
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG

dockerイメージを成果物として捉えるならartifactsに書きたくなるが、docker pushはpost_buildフェーズにcommandsとして書く
pre_buildフェーズでaws ecr get-loginコマンドがあるが、environmentsでマネージドイメージを選択するので実行可能になっている、嬉しい
いくつも環境変数を利用している、CodeBuildプロジェクトのenvironmentsで環境変数設定で定義する
buildフェーズのecho $DOCKER_VERSION which docker pwd ls -lなどはなんか確認したくて書いてる、別に必要はない

イメージ作成用のDockerfile作成

buildspec.yml内でdocker buildコマンドを実行しているのでDockerfileを作成する

作成したdockerfilesディレクトリに置いたほうが恐らく良いが、怠惰男になったので一旦アプリケーションルートに置く、ゴメス

touch Dockerfile
touch echo.sh
Dockerfile
# 成果物を実行させるイメージを生成する
FROM alpine:3.9.3

ADD ./echo.sh /tmp/echo.sh

CMD /tmp/echo.sh
echo.sh
#!/bin/ash

count=0
while true
do
    echo "my alpine container build from CodeBuild!! 1.0.0"
    sleep 10
    count=$(expr $count + 1)
    if [ $count -ge 10 ]; then
      echo "echoalpineの死!!"
      exit
    fi
done
プロジェクトをS3へ再アップロード

CodeBuildプロジェクトに必要なファイルが揃ったので再びzipにしてinput用S3にアップロードする

zip -r MessageUtil.zip ./

S3にアップロードする
codebuild-AWSアカウントID-yyyyMMdd-input-bucket/MessageUtil.zip

CodeBuildのEnvironment設定

後はCodeBuildの設定を更新してビルドすれば問題ないはず、恐れずに行け。

AWSコンソールの作成しているプロジェクト -> 編集 -> Environment
Edit Environment画面で以下の設定にする

Override imageを押下して

New environment image: Managed image
Operating system: Ubuntu
Runtime(s): Standard
Image: aws/codebuild/standard:1.0
Image version: latest
Privileged: チェックする
Service role: arn:aws:iam::AWSアカウントID:role/service-role/codebuild-codebuild-AWSアカウントID-yyyyMMdd-mvn-project-service-role

追加設定を開いてEnvironment variablesを追加していく、以下key: valueで全てPlaintext
AWSアカウントIDは自分のに変更、リージョンも今まで指定してきたリージョンに変更

  • IMAGE_REPO_NAME: build_from_codebuild_echoalpine
  • IMAGE_TAG: latest
  • AWS_ACCOUNT_ID: AWSアカウントID
  • AWS_DEFAULT_REGION: ap-northeast-1

最も重要なのはPrivilegedにチェックを入れること、これをチェックしないとCannot connect to the Docker daemonとエラーが出てdockerが使えない
Runtimeにはdockerを指定しないといけないと思いきやStandardでOK

これでECR push用の設定は完了したのでビルド実行
恐らくSucceededになるはず(記憶を頼りに書いてる)
ECRのpush対象リポジトリに入ってイメージが追加されているか確認
動作確認もしたければローカル等にpullしてrunする、ECR pushが目的だったので動作はあまり気にしてないけど
エラーが出たときはリポジトリのポリシーとCodeBuild environments設定のPrivilegedチェックを確認してみる
…散々Privilegedチェックを書いているけど、検証時はカスタムイメージでdocker:dindを指定してたからもしかしたらマネージドイメージでは不要かもしれない…すみません

カスタムイメージ内でdockerイメージをビルド、ビルドしたイメージをECRにpush

さあ本命の登場だ
これまでのことを組み合わせると簡単に行けるだろうと思っていたがdocker on dockerなのでカスタムイメージ設定で苦戦した

ほぼ参考にしてないけどCodeBuild対応サンプル
CodeBuild のカスタム Docker イメージのサンプル

やること

  • カスタムイメージの作成
  • buildspec.ymlの変更
  • CodeBuildのEnvironmentsの変更

カスタムイメージではmvnを入れているけどdocker buildでは結局echoalpineをビルドしているという点は許してほしい、カスタムイメージ内でdockerが使えるようになればあとはどうにでもなる、何を作るかなど今はどうでも良いのだ…

カスタムイメージの作成

最も重要な点として、ベースイメージをdocker:dind系にしないといけないということ
※dindはdocker in dockerの略
前回のベースイメージはjava:8-jdk-alpineだったのでdindにするとjdkをインストールしなければいけない
それではカスタムイメージのDockerfile編集

dockerfiles/mvn_for_ecr/Dockerfile
FROM docker:18.09.5-dind

WORKDIR /tmp

RUN apk update && apk add openssl && apk add curl
RUN apk add openssh
RUN apk add bash

# javaのダウンロー
# ド
RUN apk add openjdk8

RUN curl -OL https://archive.apache.org/dist/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz
RUN tar -xzvf apache-maven-3.6.1-bin.tar.gz
RUN mv apache-maven-3.6.1 /usr/local/bin/
ENV PATH $PATH:/usr/local/bin/apache-maven-3.6.1/bin

RUN echo $PATH
RUN which mvn

WORKDIR /app

重要なのはdocker:18.09.5-dind これ以外のイメージをFROMに指定して出来るもんなのか、dockerインストールするとか以外で

Dockerfile内でCMD指定してないのはdocker:18.09.5-dindのCMDを利用するため、Dockerシェフは素材の味を活かす…
※そもそもCMD指定しなかったらFROMのCMDを利用するのか知らない、雰囲気でそうしてる、検証します…上記のDockerfileでCodeBuildでdockerコマンドが利用できるのはちゃんと確認した

カスタムイメージ用のDockerfileを編集したのでビルドしてECRにpushする
ここはECRにpushするサンプルとほぼ同じなのでパラメータとかを適当なものに
ここではタグを1.1.0にする

aws ecr get-login --region リージョン --no-include-email
> docker login -u AWS -p ~~~~~~ https://AWSアカウントID.dkr.ecr.リージョン.amazonaws.com

docker build -t mvn_for_ecr ./dockerfiles/mvn_for_ecr
docker tag mvn_for_ecr:latest AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/mvn_for_ecr:1.1.0
docker push AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/mvn_for_ecr:1.1.0
buildspec.ymlの変更

カスタムイメージを作成したので次はその中でどんなコマンドを実行するかを変更していく

buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - echo Logging in to commands...
      - nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2&
      - timeout -t 15 sh -c "until docker info; do echo .; sleep 1; done"
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - docker login -u AWS -p $ECR_LOGIN_PASS https://$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - echo $DOCKER_VERSION
      - which docker
      - pwd
      - ls -l
      - which mvn
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG

installフェーズに追加しているnohup...でホスト上のdockerをコンテナ上で利用できるようにしてる的な

AWS CLIのインストールをサボってpull出来るように直接docker loginをかましているけどもっと良い方法があるはず(絶対)
そもそもこの方法だとECRへのdocker login有効期限12時間が経過する度に$ECR_LOGIN_PASSを更新しないといけないのでダメ
mvnコマンドを利用しない代わりにとりあえずwhich mvnでコマンドがあるかだけ確認している

buildspec.ymlの編集が完了したらまたzipしてinput用S3バケットにアップロードする
zipコマンドだけ

zip -r MessageUtil.zip ./
CodeBuildのEnvironmentsの変更

CodeBuildのenvironmentsを編集する

New environment image: Custom image
Environment type: Linux
Image registry: Amazon ECR
ECR account: My ECR account
Amazon ECR repository: mvn_for_ecr
Amazon ECR image: 1.1.0
Image pull credentials: Project service role
Privileged: チェックする
Service role: codebuild-codebuild-AWSアカウントID-yyyyMMdd-mvn-project-service-role

環境変数に以下を追加
ローカルでaws ecr get-login --region リージョン --no-include-emailを実行して-pの文字列をコピーしてくる

  • ECR_LOGIN_PASS: コピーした文字列

これで更新

今回は絶対Privilegedチェック必要
ビルド実行してSucceededになってpush先のECRリポジトリが更新されていればOK
これまでの全てを組み合わせればエラーが出ても解決できるはずじゃ…(記憶とメモを頼りに書いてる)

まとめ

後半になるにつれて雑になってきたけど多分要所は抑えているのでエラーが出たらサンプルを頼りにがんばる、ツラくなったらFutureFunkやElectroSwingを聞いて踊れ、この記事で伝えたい全てはそれだ。
困ったらBuild logsを見よう、問題はそこに出ている
AWSサービスは概念掴めばドキュメント見てterraform化とか出来るのでそこまでは
マルチステージビルドとかソース取得元とGitHubにしたりとかまだやりたいことはあるけどとりあえずこれで

問題

定期的に権限の有効期限が切れているので解決する

参考

AWS CodeBuildの使用開始
CodeBuildサンプル

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メモ CodeBuild docker in docker -> ECR pushなど

やること

※この記事には画像がなく痩せた大地のようなのでCodeBuildでツラまった時に見るとかがいい

  1. マネージドイメージ内でmvnビルド、生成されたjarをS3にアップロード AWS CodeBuildの使用開始
  2. カスタムイメージ(自作dockerイメージ)内でmvnビルド、生成されたjarをS3にアップロード
  3. マネージドイメージ内でdockerイメージをビルド、ビルドしたイメージをECRにpush
  4. カスタムイメージ内でdockerイメージをビルド、ビルドしたイメージをECRにpush

最終的にやりたかった事は自前のdockerイメージ内でビルドして、そのイメージをECRにpushすること
詰まった時の問題点を特定しやすくするために段階を分割してる
1でCodeBuildの設定などを把握、2でカスタムイメージ利用、3でdockerイメージビルドからpush、4で2と3を合わせる

CodeBuildからECRへのpull/push権限とdocker in docker周りが理解できれば詰まることは無くなると思う
CodeBuildはUI上からビルド開始出来る、GitHubからのwebhookなどは検証してないけどそれも出来る(サンプルにはある)
サンプルが多いのでとりあえず迷ったらサンプル集を見てみたら解決しそう、ドキュメントが揃っていてえらい
CodeBuildサンプル

環境

  • AWSマネジメントコンソールにログイン出来る (ECR/IAM/CodeBuild/S3周りの権限がある)
  • dockerイメージがローカルでビルド出来る
  • AWS CLIがローカルで使える

作業

マネージドイメージ内でmvnビルド、生成されたjarをS3にアップロード

まずはCodeBuildのイメージを掴むためにAWSが用意しているチュートリアルをやる
本家を見たほうが参考になると思う
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/getting-started.html

やること

  • s3 input/outputの定義
  • ビルド用プロジェクトの作成/buildspec.yml作成/s3アップロード
  • CodeBuild ビルドプロジェクト作成
  • ビルド実行
  • ビルドログ/成果物確認

ではやっていく

S3バケット作成 input/output用

このサンプルではソースコード取得/成果物出力をS3バケットに行うのでそれ用のバケットを作成する

今回の名前はこんな感じ、名前は好きに付けてOK

入力用バケット: codebuild-AWSアカウントID-yyyyMMdd-input-bucket
出力用バケット: codebuild-AWSアカウントID-yyyyMMdd-output-bucket

入力用バケットはCodeBuildのSource
出力用バケットはCodeBuildのArtifactsで指定する

※バケットが1つでも動作するが確認しやすいので入力出力を分けているらしい
※CodeBuildプロジェクトとS3バケットは同一リージョンに作成する必要がある

ビルド用mvnプロジェクト作成

ローカルの好きなところにmvnプロジェクト用ディレクトリを作成する
mvnプロジェクトの作成

mkdir codebuild_lesson
cd codebuild_lesson
mkdir -p ./src/main ./src/test
touch src/main/MessageUtil.java src/test/TestMessageUtil.java
touch pom.xml

それぞれのファイルに追記
実行しないので正直中身はビルドできれば何でもいい
サンプル通りに貼り付ける

MessageUtil.java
public class MessageUtil {
  private String message;

  public MessageUtil(String message) {
    this.message = message;
  }

  public String printMessage() {
    System.out.println(message);
    return message;
  }

  public String salutationMessage() {
    message = "Hi!" + message;
    System.out.println(message);
    return message;
  }
}
TestMessageUtil.java
import org.junit.Test;
import org.junit.Ignore;
import static org.junit.Assert.assertEquals;

public class TestMessageUtil {

  String message = "Robert";    
  MessageUtil messageUtil = new MessageUtil(message);

  @Test
  public void testPrintMessage() {      
    System.out.println("Inside testPrintMessage()");     
    assertEquals(message,messageUtil.printMessage());
  }

  @Test
  public void testSalutationMessage() {
    System.out.println("Inside testSalutationMessage()");
    message = "Hi!" + "Robert";
    assertEquals(message,messageUtil.salutationMessage());
  }
}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>messageUtil</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>
  <name>Message Utility Java Sample App</name>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>   
  </dependencies>  
</project>

ここまででmvnビルド用の用意は完了

次にCodeBuildビルド仕様ファイルの追加
アプリケーションルートにbuildspec.ymlというファイルを作成
この名前はCodeBuildが認識するためこの名前にする必要がある(プロジェクト側の設定でビルド仕様ファイル名を任意の名前に設定できるが特に問題がない場合そのままで良い)

touch buildspec.yml
buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - echo Nothing to do in the install phase...
  pre_build:
    commands:
      - echo Nothing to do in the pre_build phase...
  build:
    commands:
      - echo Build started on `date`
      - mvn install
  post_build:
    commands:
      - echo Build completed on `date`
artifacts:
  files:
    - target/messageUtil-1.0.jar

ビルドバージョンは最新の0.2を指定
phasesにそれぞれあるinstallpre_buildbuildpost_buildの4つがビルドフェーズとして存在する、名前はこれ以外に設定できないし新しいフェーズを追加することも出来ない
必要のないフェーズを省略することも出来るがechoで特にやることがない的なメッセージを出力しておいて必要になったら追記するような感じでやるとフェーズ名を調べる手間が省けそう
commandsは実行したいLinuxコマンド
artifactsは成果物を設定した出力場所にどのファイルを配置するかの設定、このサンプルでは生成されたjarをS3に出力するがS3という設定はCodeBuildプロジェクト側で設定する

mvnプロジェクトをzipしてinput用s3バケットにアップロード

アプリケーションルート以下のファイルをzipしてMessageUtil.zipというファイル名にする

zip -r MessageUtil.zip ./

生成されたファイルをcodebuild-AWSアカウントID-yyyyMMdd-input-bucket/MessageUtil.zipにアップロードする

今回の全サンプルのソースはS3から取得するためビルド仕様ファイルの仕様をあまり読んでないが困ったらこれを読めば良さそう
CodeBuild のビルド仕様に関するリファレンス

ビルドプロジェクトを作成する

入力/プロジェクト/出力が揃ったのでようやくCodeBuildプロジェクトを作成する
AWSコンソールからCodeBuildのプロジェクト作成ボタンを押す

以下設定の入力項目と値
特に書いていない箇所はデフォルト設定

Project name: codebuild-AWSアカウントID-yyyyMMdd-mvn-project
Source provider: Amazon S3
バケット: codebuild-AWSアカウントID-yyyyMMdd-input-bucket
S3 object key or S3 folder: MessageUtil.zip
Environment image: Managed image
Operating system: Ubuntu
Runtime(s): Standard
Image: aws/codebuild/standard:1.0
Artifacts Type: Amazon S3
Artifacts Bucket name: codebuild-AWSアカウントID-yyyyMMdd-output-bucket
Logs CloudWatch Logs: チェックする
Logs Group name: codebuild-AWSアカウントID-yyyyMMdd-mvn-project-log

サンプルと違う箇所はログ名を付けたくらい
この状態で作成する

ビルドする

ビルドプロジェクトを作成したので後はUI上でビルドを開始して結果を確認するだけ

AWSコンソールから先程作成したビルドプロジェクトに入ってStart build -> Start buildでビルド開始
ビルド中はタブでBuild logsPhase detailsなどあるので確認する

Phase detailsで全ての工程がSucceededになったらS3にjarが出力されているか確認する

出力用S3バケットには以下のように出力される
codebuild-AWSアカウントID-yyyyMMdd-output-bucket/codebuild-AWSアカウントID-yyyyMMdd-mvn-project/target/messageUtil-1.0.jar

ここまではほぼサンプル通りのビルドなので特に問題は無いと思う

カスタムイメージ(自作dockerイメージ)内でmvnビルド、生成されたjarをS3にアップロード

次はカスタムイメージをビルド環境にしてみる

対応するCodeBuildサンプルは以下
CodeBuildのAmazon ECRサンプル
サンプルはGoプロジェクトになっているが、前回のmvnプロジェクトを使用する

やること

  • ECRリポジトリの作成
  • ローカルでmvnがビルドできるdockerイメージの作成
  • dockerイメージをECRにpush
  • CodeBuildプロジェクトでpushしたdockerイメージを利用するように変更
  • ECRリポジトリにCodeBuild実行roleからのイメージ取得権限を設定

注意点としては、ECRリポジトリにポリシー設定を行わないと同じAWSアカウントからでもpullが出来ないのでビルド時にエラーが出る
それ以外は特に前回と変わらないので前回と同じプロジェクトを編集していく

mvnビルド出来るdockerイメージの作成

その前にECRにリポジトリを作成しておく
UIからでもCLIからでもいいので名前をmvn_for_ecrとしておく

アプリケーションルートにDockerfile用ディレクトリを作成する
個人的にdockerfilesというディレクトリにそれぞれのDockerfileを置いているのでそうする

mkdir -p dockerfiles/mvn_for_ecr
touch dockerfiles/mvn_for_ecr/Dockerfile

Dockerfileの中身は以下
DockerhubのmavenをpullしてECR push用にタグ付けすれば問題なく動きそうだけど自分で色々出来るように一応Dockerfile書く

dockerfiles/mvn_for_ecr/Dockerfile
FROM java:8-jdk-alpine

WORKDIR /tmp

RUN apk update && apk add openssl && apk add curl

RUN curl -OL https://archive.apache.org/dist/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz
RUN tar -xzvf apache-maven-3.6.1-bin.tar.gz
RUN mv apache-maven-3.6.1 /usr/local/bin/
ENV PATH $PATH:/usr/local/bin/apache-maven-3.6.1/bin

RUN echo $PATH
RUN which mvn

WORKDIR /app

CMD ["/bin/ash"]

イメージのビルド/タグ付け/push
※ECRにpush出来るようにaws ecr get-loginでdockerログインしておく

aws ecr get-login --region リージョン --no-include-email
> docker login -u AWS -p ~~~~~~ https://AWSアカウントID.dkr.ecr.リージョン.amazonaws.com

docker build -t mvn_for_ecr ./dockerfiles/mvn_for_ecr
docker tag mvn_for_ecr:latest AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/mvn_for_ecr:1.0.0
docker push AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/mvn_for_ecr:1.0.0

pushが完了したらAWSコンソール上でECRのリポジトリを確認する、1.0.0があればOK

CodeBuildプロジェクトでpushしたdockerイメージを利用するように変更

AWSコンソールでcodebuild-AWSアカウントID-yyyyMMdd-mvn-projectに入り編集 -> Environmentを選択してEdit Environment画面に入る

Override imageを押下

New environment image: Custom image
Environment type: Linux
Image registry: Amazon ECR
ECR account: My ECR account
Amazon ECR repository: mvn_for_ecr
Amazon ECR image: 1.0.0
Image pull credentials: Project service role
Service role: codebuild-codebuild-AWSアカウントID-yyyyMMdd-mvn-project-service-role ※多分ロールの名前はこんな感じ

ポイントはImage pull credentialsProject service roleに設定するところ
後でECRリポジトリにポリシーで、指定しているロールからのアクセスを許可することでイメージをpull出来るようにする

ここまでの設定で一旦ビルドしてみる
※ECRリポジトリのポリシーを設定していないのでエラーが出る、一回リポジトリのポリシーを有効にすると数時間間違った設定でもSucceededになるので注意
こんな感じ at Phase details

BUILD_CONTAINER_UNABLE_TO_PULL_IMAGE: Unable to pull customer's container image.
CannotPullContainerError: Error response from daemon: pull access denied for AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/mvn_for_ecr,
repository does not exist or may require 'docker login'

次にECRリポジトリmvn_for_ecrのポリシーを以下のように設定する
PermissionsというリンクをクリックするとポリシーJSONを編集と出るのでそこで行う

ECRリポジトリのポリシー
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "mvn_for_ecr_policy",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::AWSアカウントID:role/service-role/codebuild-codebuild-AWSアカウントID-yyyyMMdd-mvn-project-service-role"
      },
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer"
      ]
    }
  ]
}

特定のロールに対してECR取得系のアクションを許す
これを保存してもう一度CodeBuildでビルドしてみると全てSucceededになると思う
ならなかったらCodeBuildのenvironmentを同じ設定で保存し直すといけるかも、検証してない

マネージドイメージ内でdockerイメージをビルド、ビルドしたイメージをECRにpush

次はdockerイメージをビルドしてECRに対してpushする
CodeBuildサンプルは以下
CodeBuildのDockerサンプル

やること

  • ECRリポジトリの作成(push用)
  • 作成したECRリポジトリのポリシー設定
  • buildspec.ymlの変更
  • イメージ作成用のDockerfile作成
  • プロジェクトをS3へ再アップロード
  • CodeBuildのEnvironment設定

dockerイメージをCodeBuildからECRにpushするのでECRリポジトリのポリシーにpush用の権限を設定する必要がある
dockerコマンドで利用する環境変数をCodeBuildプロジェクトのEnvironmentsから設定する

ECRリポジトリの作成

push用リポジトリを作成する
名前はbuild_from_codebuild_echoalpineにする
今回はmvnコマンドを利用せずに単純にdocker builddocker pushがちゃんと行えるようになるかを確認していく

UIやCLIから作成する

build_from_codebuild_echoalpineのポリシー設定を行う

ビルドしたイメージをpushするリポジトリのポリシーJSON
pushを行うので更新系のアクションを許可するようにする
こんな感じ、ロール名は予測なのでサジェストに出てきた対応するやつを使う

build_from_codebuild_echoalpine
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "build_from_codebuild_echoalpine_policy",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::AWSアカウントID:role/service-role/codebuild-codebuild-AWSアカウントID-yyyyMMdd-mvn-project-service-role"
      },
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:CompleteLayerUpload",
        "ecr:GetAuthorizationToken",
        "ecr:InitiateLayerUpload",
        "ecr:PutImage",
        "ecr:UploadLayerPart"
      ]
    }
  ]
}
buildspec.ymlの変更

今までのbuildspec.ymlだとmvnコマンドを実行してからartifactsでS3に成果物を置くようにしていたが、今回はビルドしたdockerイメージをECRにpushするのでそれ用に書き直す

buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - echo Logging in to commands...
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - echo $DOCKER_VERSION
      - which docker
      - pwd
      - ls -l
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG

dockerイメージを成果物として捉えるならartifactsに書きたくなるが、docker pushはpost_buildフェーズにcommandsとして書く
pre_buildフェーズでaws ecr get-loginコマンドがあるが、environmentsでマネージドイメージを選択するので実行可能になっている、嬉しい
いくつも環境変数を利用している、CodeBuildプロジェクトのenvironmentsで環境変数設定で定義する
buildフェーズのecho $DOCKER_VERSION which docker pwd ls -lなどはなんか確認したくて書いてる、別に必要はない

イメージ作成用のDockerfile作成

buildspec.yml内でdocker buildコマンドを実行しているのでDockerfileを作成する

作成したdockerfilesディレクトリに置いたほうが恐らく良いが、怠惰男になったので一旦アプリケーションルートに置く、ゴメス

touch Dockerfile
touch echo.sh
Dockerfile
# 成果物を実行させるイメージを生成する
FROM alpine:3.9.3

ADD ./echo.sh /tmp/echo.sh

CMD /tmp/echo.sh
echo.sh
#!/bin/ash

count=0
while true
do
    echo "my alpine container build from CodeBuild!! 1.0.0"
    sleep 10
    count=$(expr $count + 1)
    if [ $count -ge 10 ]; then
      echo "echoalpineの死!!"
      exit
    fi
done
プロジェクトをS3へ再アップロード

CodeBuildプロジェクトに必要なファイルが揃ったので再びzipにしてinput用S3にアップロードする

zip -r MessageUtil.zip ./

S3にアップロードする
codebuild-AWSアカウントID-yyyyMMdd-input-bucket/MessageUtil.zip

CodeBuildのEnvironment設定

後はCodeBuildの設定を更新してビルドすれば問題ないはず、恐れずに行け。

AWSコンソールの作成しているプロジェクト -> 編集 -> Environment
Edit Environment画面で以下の設定にする

Override imageを押下して

New environment image: Managed image
Operating system: Ubuntu
Runtime(s): Standard
Image: aws/codebuild/standard:1.0
Image version: latest
Privileged: チェックする
Service role: arn:aws:iam::AWSアカウントID:role/service-role/codebuild-codebuild-AWSアカウントID-yyyyMMdd-mvn-project-service-role

追加設定を開いてEnvironment variablesを追加していく、以下key: valueで全てPlaintext
AWSアカウントIDは自分のに変更、リージョンも今まで指定してきたリージョンに変更

  • IMAGE_REPO_NAME: build_from_codebuild_echoalpine
  • IMAGE_TAG: latest
  • AWS_ACCOUNT_ID: AWSアカウントID
  • AWS_DEFAULT_REGION: ap-northeast-1

最も重要なのはPrivilegedにチェックを入れること、これをチェックしないとCannot connect to the Docker daemonとエラーが出てdockerが使えない
Runtimeにはdockerを指定しないといけないと思いきやStandardでOK

これでECR push用の設定は完了したのでビルド実行
恐らくSucceededになるはず(記憶を頼りに書いてる)
ECRのpush対象リポジトリに入ってイメージが追加されているか確認
動作確認もしたければローカル等にpullしてrunする、ECR pushが目的だったので動作はあまり気にしてないけど
エラーが出たときはリポジトリのポリシーとCodeBuild environments設定のPrivilegedチェックを確認してみる
…散々Privilegedチェックを書いているけど、検証時はカスタムイメージでdocker:dindを指定してたからもしかしたらマネージドイメージでは不要かもしれない…すみません

カスタムイメージ内でdockerイメージをビルド、ビルドしたイメージをECRにpush

さあ本命の登場だ
これまでのことを組み合わせると簡単に行けるだろうと思っていたがdocker in dockerなのでカスタムイメージ設定で苦戦した

ほぼ参考にしてないけどCodeBuild対応サンプル
CodeBuild のカスタム Docker イメージのサンプル

やること

  • カスタムイメージの作成
  • buildspec.ymlの変更
  • CodeBuildのEnvironmentsの変更

カスタムイメージではmvnを入れているけどdocker buildでは結局echoalpineをビルドしているという点は許してほしい、カスタムイメージ内でdockerが使えるようになればあとはどうにでもなる、何を作るかなど今はどうでも良いのだ…

カスタムイメージの作成

最も重要な点として、ベースイメージをdocker:dind系にしないといけないということ
※dindはdocker in dockerの略
前回のベースイメージはjava:8-jdk-alpineだったのでdindにするとjdkをインストールしなければいけない
それではカスタムイメージのDockerfile編集

dockerfiles/mvn_for_ecr/Dockerfile
FROM docker:18.09.5-dind

WORKDIR /tmp

RUN apk update && apk add openssl && apk add curl
RUN apk add openssh
RUN apk add bash

# javaのダウンロー
# ド
RUN apk add openjdk8

RUN curl -OL https://archive.apache.org/dist/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz
RUN tar -xzvf apache-maven-3.6.1-bin.tar.gz
RUN mv apache-maven-3.6.1 /usr/local/bin/
ENV PATH $PATH:/usr/local/bin/apache-maven-3.6.1/bin

RUN echo $PATH
RUN which mvn

WORKDIR /app

重要なのはdocker:18.09.5-dind これ以外のイメージをFROMに指定して出来るもんなのか、dockerインストールするとか以外で

Dockerfile内でCMD指定してないのはdocker:18.09.5-dindのCMDを利用するため、Dockerシェフは素材の味を活かす…
※そもそもCMD指定しなかったらFROMのCMDを利用するのか知らない、雰囲気でそうしてる、検証します…上記のDockerfileでCodeBuildでdockerコマンドが利用できるのはちゃんと確認した

カスタムイメージ用のDockerfileを編集したのでビルドしてECRにpushする
ここはECRにpushするサンプルとほぼ同じなのでパラメータとかを適当なものに
ここではタグを1.1.0にする

aws ecr get-login --region リージョン --no-include-email
> docker login -u AWS -p ~~~~~~ https://AWSアカウントID.dkr.ecr.リージョン.amazonaws.com

docker build -t mvn_for_ecr ./dockerfiles/mvn_for_ecr
docker tag mvn_for_ecr:latest AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/mvn_for_ecr:1.1.0
docker push AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/mvn_for_ecr:1.1.0
buildspec.ymlの変更

カスタムイメージを作成したので次はその中でどんなコマンドを実行するかを変更していく

buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - echo Logging in to commands...
      - nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2&
      - timeout -t 15 sh -c "until docker info; do echo .; sleep 1; done"
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - docker login -u AWS -p $ECR_LOGIN_PASS https://$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - echo $DOCKER_VERSION
      - which docker
      - pwd
      - ls -l
      - which mvn
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG

installフェーズに追加しているnohup...でホスト上のdockerをコンテナ上で利用できるようにしてる的な

AWS CLIのインストールをサボってpull出来るように直接docker loginをかましているけどもっと良い方法があるはず(絶対)
そもそもこの方法だとECRへのdocker login有効期限12時間が経過する度に$ECR_LOGIN_PASSを更新しないといけないのでダメ
mvnコマンドを利用しない代わりにとりあえずwhich mvnでコマンドがあるかだけ確認している

buildspec.ymlの編集が完了したらまたzipしてinput用S3バケットにアップロードする
zipコマンドだけ

zip -r MessageUtil.zip ./
CodeBuildのEnvironmentsの変更

CodeBuildのenvironmentsを編集する

New environment image: Custom image
Environment type: Linux
Image registry: Amazon ECR
ECR account: My ECR account
Amazon ECR repository: mvn_for_ecr
Amazon ECR image: 1.1.0
Image pull credentials: Project service role
Privileged: チェックする
Service role: codebuild-codebuild-AWSアカウントID-yyyyMMdd-mvn-project-service-role

環境変数に以下を追加
ローカルでaws ecr get-login --region リージョン --no-include-emailを実行して-pの文字列をコピーしてくる

  • ECR_LOGIN_PASS: コピーした文字列

これで更新

今回は絶対Privilegedチェック必要
ビルド実行してSucceededになってpush先のECRリポジトリが更新されていればOK
これまでの全てを組み合わせればエラーが出ても解決できるはずじゃ…(記憶とメモを頼りに書いてる)

まとめ

後半になるにつれて雑になってきたけど多分要所は抑えているのでエラーが出たらサンプルを頼りにがんばる、ツラくなったらFutureFunkやElectroSwingを聞いて踊れ、この記事で伝えたい全てはそれだ。
困ったらBuild logsを見よう、問題はそこに出ている
AWSサービスは概念掴めばドキュメント見てterraform化とか出来るのでそこまでは
マルチステージビルドとかソース取得元とGitHubにしたりとかまだやりたいことはあるけどとりあえずこれで

問題

定期的に権限の有効期限が切れているので解決する

参考

AWS CodeBuildの使用開始
CodeBuildサンプル

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon Connectにおけるユーザーとオペレータ―との間の音声を取得する方法

はじめに

Amazon Connectで通話した音声を保存したいということで、Amazon Connectを介してユーザーとエージェントとの間の音声を取得する方法について確認してみました。

音声取得方法

現状では次の二つの方法で音声を取得することが可能でした。

1.通話録音機能
2.Kinesis Video Stream

1.通話録音機能

コンタクトフローに[通話記録動作の設定]ブロックを設定することで通話を録音することができます。

image.png

オプションとしては次の4つを指定することが可能です。

image.png

顧客とエージェントの通話が終了した後に、wav形式の音声データがS3に保存されます。
構成は次のような感じ。
image.png

数分程度の短い通話であればほぼ即時で作成されました。通話時間に比例して作成時間も長くなるようです。

通話記録機能のオプションで「エージェント AND 顧客」を選択した場合は1つのファイルとして保存されました。
採取した音声データを視聴したところ、ステレオ音声として保存されており、左チャネルに顧客側の音声が、右チャネルにエージェントの音声が保存されましたのでAWSドキュメントの通りでした。

image.png

片方が会話している際は基本的には(せっかちな方でなければ)もう片方の音声は保存されませんので音声のデータとしては長時間になると思われます。また、分析などを行う際はチャネルがどちらの会話を扱っているのか、会話の流れを正しく把握する部分に注意したほうがよさそうです。

2.Kinesis Video Stream

Amazon Kinesis Video Streams を使用すると、分析、機械学習 (ML)、再生、およびその他の処理のために、接続されたデバイスから AWS へ動画を簡単かつ安全にストリーミングできるようになります。

Amazon Connectでは、コンタクトフローに[メディアストリーミングの開始]ブロックを設定することで通話をリアルタイムでストリーミングできます。

image.png

ユーザーとオペレータ―間の通話ごとにストリームが作成されました。
image.png

AWSマネジメントコンソールでは視聴することができませんでした。AWSドキュメントに従い、コンシューマで音声データを取得します。
構成は次のような感じ。

image.png

コンシューマで取得した音声ファイルはRAW形式のファイルとなり、最小単位は1024バイトでした。ストリームされる音声データは、Single 16bit PCM 8000HzのRAWデータで、バイトオーダーはリトルエンディアンでした。
動作確認では通話開始からおおよそ20sec後にストリームが流れ始めたためリアルタイムに状況を分析してフィードバックするような処理には向いていないと考えられ、扱い方は検討が必要そうです。

まとめ

Amazon Connectにおけるユーザーとオペレータ―との間の音声の取得方法の確認結果は次の通りです。

通話録音機能
- 通話が完了した後にwavファイルが保存される
- 通話の時間に比例してwavファイルが保存される時間が長くなる(実測60secの通話でファイル作成まで10sec)
- 顧客とオペレータ両方の通話を保存する場合
- wavファイルは1つ作成される
- ステレオ音声の左チャネルに顧客側の音声が保存され、右チャネルにエージェントの音声が保存される

Kinesis Video Stream
- 音声データはコンシューマ(SDK開発が必要)を介してRAWファイルとして採取可能
- 通話が開始してからおおよそ20sec後に音声データが取得できる
- 取得できる音声データ
- Single 16bit PCM 8000HzのRAWデータ
- バイトオーダーはリトルエンディアン
- 最小単位は1024バイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaでAWS Signature V4を生成してAPIをリクエストする

はじめに

AWS APIは、リクエストにAWS Signature V4という署名をつけることでIAM認証を利用できます。普通はAWS SDKを利用することでSignature V4の仕様をさほど意識する必要はないのですが、諸事情で、自前によるSignature V4実装をする機会がありましたので、メモを残します。

Java 11で実装しており、HTTPクライアントはApache HttpComponents Clientを使ってます。

コードは切り貼り、加工しているので、正しく動作するか若干怪しいです。

署名しない時のリクエスト

 わかりやすくするため、署名しないときのリクエストサンプルを示します。 Elasticsearch APIで検索リクエストするサンプルです。このサンプルではPOSTメソッドを利用しているためクエリパラメータはHTTP bodyに書いています。

このリクエストにAWS Signature V4署名を施し、認証されたユーザからのみAPIを受けるように改修していきます。

import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;

void reqFunc(){
    // リクエストの生成
    URI uri                 = new URI(AWS_ELASTICSEARCH_URL + "/index/hoge/_search?");
    HttpPost post           = new HttpPost(uri);
    post.setHeader("Content-Type", "application/json");
    post.setEntity(new StringEntity( "クエリー文字列(省略)", "UTF-8"));

    // HTTPクライアントでリクエストの実行
    HttpClient client       = HttpClientBuilder.create().build();
    CloseableHttpResponse res = (CloseableHttpResponse) client.execute(post);

    // ...レスポンスに対する処理...
}

署名に関する情報

AWS Signature V4はAmazonのWebサイトで情報提供がされています。下記のURLからたどって情報を得ました。
https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-4.html

また、認証に必要となるアクセスキーIDとシークレットアクセスキーの取得方法は、下記のURLからたどって情報を得ています。
https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws-sec-cred-types.html

署名の実装

AWS Signature V4は、リクエスト情報そのものと、予め払い出されているアクセスキーID/シークレットアクセスキーを使いハッシュを生成し、リクエストヘッダに付与します。何度も繰り返しハッシュ化したり、ハッシュの対象対象がどこまでなのか分からず、試行錯誤しました。

HttpRequestInterceptorクラスの作成

最初に送信するリクエスト自身の情報を取得するため、HttpRequestInterceptorクラスと、それを挟み込んだHTTPクライアントを用意します。HTTPクライアントを生成する際にこのクラスを挟むことで、サーバへのリクエスト送信直前に処理を差し込むことができます。

  • HttpRequestInterceptor
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.protocol.HttpContext;

public class AmazonizeInterceptor implements HttpRequestInterceptor {

    @Override
    public void process(HttpRequest request, HttpContext context) throws Exception{
        AwsSigner4(request);  
    }

    private void AwsSigner4(HttpRequest request) throws Exception {
        /* この関数実装内でAWS Signature V4の実装をします。 */
    }
}
  • HTTPクライアント呼び出し部の修正
//...略...

void reqFunc(){
    //...略...

    // HTTPクライアントでリクエストの実行
    HttpClient client = HttpClientBuilder.create()
                        .addInterceptorLast(new AmazonizeInterceptor())  // ←これを追加する
                        .build();
    CloseableHttpResponse res = (CloseableHttpResponse) client.execute(post);

    //...レスポンスに対する処理...
}

これで、RequestInterceptorを挟み込んだHTTPクライアントでリクエストを行うと、サーバ送信前に先程定義したAwsSigner4関数が実行されるようになります。

AwsSigner4関数の実装

次に実際の署名処理を行うAwsSigner4関数の実装をしていきます。
処理内容は、リクエストヘッダーに「X-Amz-Date」と「Authorization」を追加するだけですが、Authorizationの値を求めるのに何段階か必要になります。

  1. 正規リクエスト文字列(canonicalRequest)の生成
  2. 署名文字列(StringToSign)の生成
  3. 署名キー(SigningKey)の生成
  4. Authorizationヘッダー文字列の生成

参考までに、canonicalRequestに含まれるヘッダー情報は、host,x-amz-dateが必須ですが、それ以外のヘッダー情報を追加しても良いみたいです。

import org.apache.http.util.EntityUtils;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.text.SimpleDateFormat;

private void AwsSigner4(HttpRequest request) throws Exception {

    /* X-Amz-Dateヘッダーの生成 */
    SimpleDateFormat xAmzDateFormatter  = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
    xAmzDateFormatter.setTimeZone(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); // Signerの有効期限はUTCで判定される
    String xAmzDate         = xAmzDateFormatter.format(new Date()).trim();
    request.setHeader("X-Amz-Date", xAmzDate);

    /* Authorizationヘッダー文字列の生成 */
    /* 1. 正規リクエスト文字列(canonicalRequest)の生成 */
    String path             = getPath(request);
    String xAmzContentSha   = getBodyHash(request);
    String canonicalRequest = "POST\n"
                        + path + "\n"
                        + "\n"
                        + "host:" + request.getFirstHeader("Host").getValue() + "\n"
                        + "x-amz-date:" + xAmzDate + "\n"
                        + "\n"
                        + "host;x-amz-date\n"
                        + xAmzContentSha;

    /* 2. 署名文字列(StringToSign)の生成 */
    String awsRegion = "ap-northeast-1" ;  // AWS APIリクエスト先のリージョン情報
    String awsNameSpace = "es" ;  // リクエストするAWSサービスの名前空間
    String StringToSign = "AWS4-HMAC-SHA256\n"
                        + xAmzDate + "\n"
                        + xAmzDate.substring(0, 8) + "/" + awsRegion 
                        + "/" + awsNameSpace +"/aws4_request\n"
                        + DigestUtils.sha256Hex(canonicalRequest);

    /* 3. 署名キー(SigningKey)の生成 */
    String awsSecretAccessKey = "AWSシークレットアクセスキー" ;
    // X-Amz-Date → リージョン → AWSサービス名前空間 → 固定文字(aws4_request) の順でハッシュ化していく
    String hashStr    = getHmacSha256ByStrKey("AWS4" + awsSecretAccessKey, xAmzDate.substring(0, 8));
    hashStr           = getHmacSha256ByHexKey(hashStr, awsRegion);
    hashStr           = getHmacSha256ByHexKey(hashStr, awsNameSpace);
    String SigningKey = getHmacSha256ByHexKey(hashStr, "aws4_request");

    /* 4. Authorizationヘッダー文字列の生成 */
    String awsAccessKeyId = "AWSアクセスキーID" ;
    String sig          = getHmacSha256ByHexKey(SigningKey, StringToSign);
    String authorization = "AWS4-HMAC-SHA256 Credential="
                        + awsAccessKeyId
                        + "/" + xAmzDate.substring(0, 8)
                        + "/" + awsRegion
                        + "/" + awsNameSpace
                        + "/aws4_request,"
                        + "SignedHeaders=host;x-amz-date,"
                        + "Signature=" + sig;

    request.setHeader("Authorization", authorization);
}

/***  リクエストパスの取得 */
private String getPath(HttpRequest req) throws Exception {
    String uri              = req.getRequestLine().getUri();

    // URLのクエリ文字列とのセパレータである「?」はpathに含めない
    if (uri.endsWith("?"))  uri = uri.substring(0, uri.length()-1);

    // URLエンコードも色々種類があるらしく、Amazonが指定したロジックでエンコードする
    return awsUriEncode(uri,true);
}

/*** リクスとボディのハッシュ取得 */
private String getBodyHash(HttpRequest req) throws Exception{
    HttpEntityEnclosingRequest ereq = (HttpEntityEnclosingRequest) req;
    String body             = EntityUtils.toString(ereq.getEntity());
    return DigestUtils.sha256Hex(body);
}

/***
    * AWS指定スペックのURLエンコーダ
    * @param input
    * @param encodeSlash
    * @return
    * @throws UnsupportedEncodingException
    */
private String awsUriEncode(CharSequence input, boolean encodeSlash) throws UnsupportedEncodingException {
    StringBuilder result = new StringBuilder();
    boolean queryIn = false;
    for (int i = 0; i < input.length(); i++) {
        char ch = input.charAt(i);
        if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || ch == '.') {
            result.append(ch);
        } else if (ch == '/') {
            if (queryIn) result.append(encodeSlash ? "%2F" : ch);
            else result.append(ch);
        } else {
            if(!queryIn && ch=='?') {
                queryIn = true;
                result.append(ch);
            }else {
                byte[] bytes = new String(new char[] {ch}).getBytes("UTF-8");
                result.append("%" + Hex.encodeHexString(bytes,false));
            }
        }
    }
    return result.toString();
}

private String getHmacSha256ByStrKey(String strkey, String target) throws Exception {
    return getHmacSha256(strkey.getBytes(), target);
}

private String getHmacSha256ByHexKey(String hexkey, String target) throws Exception {
    return getHmacSha256(Hex.decodeHex(hexkey), target);
}

/***
    * target文字列をKeyを使いHMAC-SHA-256にハッシュ化する
    * @param target ハッシュ対象
    * @param key キー
    * @return Hex形式のハッシュ値
    */
private String getHmacSha256(byte[] key, String target) throws Exception {
    final Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(key, "HmacSHA256"));
    return String.valueOf(Hex.encodeHex(mac.doFinal(target.getBytes()), true));
}

まとめ

最終的にまとめると下記のようになりました。
リクエスト毎にフォーマッターを生成したり、定数が埋め込みになっていたりでアレですが、そこら辺はよしなに直してください。

import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.binary.Hex;
import java.text.SimpleDateFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class AmazonizeInterceptor implements HttpRequestInterceptor {

    @Override
    public void process(HttpRequest request, HttpContext context) throws Exception{
        AwsSigner4(request);  
    }

    private void AwsSigner4(HttpRequest request) throws Exception {
        /* X-Amz-Dateヘッダーの生成 */
        SimpleDateFormat xAmzDateFormatter  = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        xAmzDateFormatter.setTimeZone(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); // Signerの有効期限はUTCで判定される
        String xAmzDate         = xAmzDateFormatter.format(new Date()).trim();
        request.setHeader("X-Amz-Date", xAmzDate);

        /* Authorizationヘッダー文字列の生成 */
        /* 1. 正規リクエスト文字列(canonicalRequest)の生成 */
        String path             = getPath(request);
        String xAmzContentSha   = getBodyHash(request);
        String canonicalRequest = "POST\n"
                            + path + "\n"
                            + "\n"
                            + "host:" + request.getFirstHeader("Host").getValue() + "\n"
                            + "x-amz-date:" + xAmzDate + "\n"
                            + "\n"
                            + "host;x-amz-date\n"
                            + xAmzContentSha;

        /* 2. 署名文字列(StringToSign)の生成 */
        String awsRegion = "ap-northeast-1" ;  // AWS APIリクエスト先のリージョン情報
        String awsNameSpace = "es" ;  // リクエストするAWSサービスの名前空間
        String StringToSign = "AWS4-HMAC-SHA256\n"
                            + xAmzDate + "\n"
                            + xAmzDate.substring(0, 8) + "/" + awsRegion 
                            + "/" + awsNameSpace +"/aws4_request\n"
                            + DigestUtils.sha256Hex(canonicalRequest);

        /* 3. 署名キー(SigningKey)の生成 */
        String awsSecretAccessKey = "AWSシークレットアクセスキー" ;
        // X-Amz-Date → リージョン → AWSサービス名前空間 → 固定文字(aws4_request) の順でハッシュ化していく
        String hashStr    = getHmacSha256ByStrKey("AWS4" + awsSecretAccessKey, xAmzDate.substring(0, 8));
        hashStr           = getHmacSha256ByHexKey(hashStr, awsRegion);
        hashStr           = getHmacSha256ByHexKey(hashStr, awsNameSpace);
        String SigningKey = getHmacSha256ByHexKey(hashStr, "aws4_request");

        /* 4. Authorizationヘッダー文字列の生成 */
        String awsAccessKeyId = "AWSアクセスキーID" ;
        String sig          = getHmacSha256ByHexKey(SigningKey, StringToSign);
        String authorization = "AWS4-HMAC-SHA256 Credential="
                            + awsAccessKeyId
                            + "/" + xAmzDate.substring(0, 8)
                            + "/" + awsRegion
                            + "/" + awsNameSpace
                            + "/aws4_request,"
                            + "SignedHeaders=host;x-amz-date,"
                            + "Signature=" + sig;

        request.setHeader("Authorization", authorization);
    }

    /***  リクエストパスの取得 */
    private String getPath(HttpRequest req) throws Exception {
        String uri              = req.getRequestLine().getUri();

        // URLのクエリ文字列とのセパレータである「?」はpathに含めない
        if (uri.endsWith("?"))  uri = uri.substring(0, uri.length()-1);

        // URLエンコードも色々種類があるらしく、Amazonが指定したロジックでエンコードする
        return awsUriEncode(uri,true);
    }

    /*** リクスとボディのハッシュ取得 */
    private String getBodyHash(HttpRequest req) throws Exception{
        HttpEntityEnclosingRequest ereq = (HttpEntityEnclosingRequest) req;
        String body             = EntityUtils.toString(ereq.getEntity());
        return DigestUtils.sha256Hex(body);
    }

    /***
        * AWS指定スペックのURLエンコーダ
        * @param input
        * @param encodeSlash
        * @return
        * @throws UnsupportedEncodingException
        */
    private String awsUriEncode(CharSequence input, boolean encodeSlash) throws UnsupportedEncodingException {
        StringBuilder result = new StringBuilder();
        boolean queryIn = false;
        for (int i = 0; i < input.length(); i++) {
            char ch = input.charAt(i);
            if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || ch == '.') {
                result.append(ch);
            } else if (ch == '/') {
                if (queryIn) result.append(encodeSlash ? "%2F" : ch);
                else result.append(ch);
            } else {
                if(!queryIn && ch=='?') {
                    queryIn = true;
                    result.append(ch);
                }else {
                    byte[] bytes = new String(new char[] {ch}).getBytes("UTF-8");
                    result.append("%" + Hex.encodeHexString(bytes,false));
                }
            }
        }
        return result.toString();
    }

    private String getHmacSha256ByStrKey(String strkey, String target) throws Exception {
        return getHmacSha256(strkey.getBytes(), target);
    }

    private String getHmacSha256ByHexKey(String hexkey, String target) throws Exception {
        return getHmacSha256(Hex.decodeHex(hexkey), target);
    }

    /***
        * target文字列をKeyを使いHMAC-SHA-256にハッシュ化する
        * @param target ハッシュ対象
        * @param key キー
        * @return Hex形式のハッシュ値
        */
    private String getHmacSha256(byte[] key, String target) throws Exception {
        final Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(key, "HmacSHA256"));
        return String.valueOf(Hex.encodeHex(mac.doFinal(target.getBytes()), true));
    }
}

あとがき

今回、私が実際に動作確認したのはElasticsearch APIのみですが、IAMの権限が正しく設定されていれば、他のAPIも同じ方法で利用できるかと思います。もし動作確認が取れたAWSサービスがあったら、コメントに書いていただけるとありがたいです。

お役に立ちましたら幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ラズパイとAWS IoT連携 (Pythonで温度情報をクラウド送信)

はじめに

例えばラズパイを使用して、センサーの情報を取得し、クラウドにデータをアップロードし活用したいですよね!

今回はその手始めとして、ラズパイとAWS IoTを連携してみたいと思います。
今後、DBへの保存や、グラフの表示などもまとめていきたいと思います!

使用機材

安価に以下を用意して試してみました。

  • Raspberry Pi Zero WH
  • ブレッドボード(165408010E)
  • デジタル温度センサADT7310 DIP化モジュール[MDK001]
  • ジャンパーワイヤ

ラズパイで温度情報を取得

Myブログで恐縮ですが、ラズパイで温度センサーから温度を取得する記事をまとめましたので参考にしてください。

ラズパイで温度を取得(デジタル温度センサADT7310)

AWS IoTの設定

AWSアカウントを取得後、AWS IoTの設定を行います。
結構AWS IoTの設定が多く、私は最初戸惑いました:sweat:
一度設定をしてみると、流れを把握できると思います。

AWS IoT

AWS IoTについては、AWS ドキュメントに記載があります。

AWS IoT とは

AWS IoT では、インターネットに接続されたデバイス (センサー、アクチュエーター、組み込みマイクロコントローラー、スマート家電など) と AWS クラウドとのセキュアな双方向通信が可能になります。これにより、複数のデバイスからテレメトリデータを収集し、保存して、分析できます。また、これらのデバイスをユーザーが各自の電話やタブレットから制御できるようにするアプリケーションを作成することもできます。

まずはAWS IoT コンソールを開きます。

リージョンは東京にしました。

デバイスの登録

最初の画面で「モノの登録」をクリックします。

「単一のモノを作成する」をクリックします。
2019-05-06_11h37_47.png

任意で名前を入力し、次へ進みます。
screencapture-ap-northeast-1-console-aws-amazon-iot-home-2019-05-06-11_42_01.png

「証明書の作成」をクリックします。
2019-05-06_11h46_24.png

パブリックキーとプライベートキー、証明書をダウンロードします。
また、「有効化」をクリックします。
このページを閉じると、パブリックキーとプライベートキーが取得できなくなるので注意が必要です。
その後、「ポリシーをアタッチ」をクリックします。
2019-05-06_11h48_56.png

ルート認証機関 (CA) は以下のURLからダウンロードします。
RSA 2048 ビットキー: Amazon ルート CA 1を選択しました。
AmazonRootCA1.pemという名前で保存します。

AWS IoTのルートCAダウンロード

「モノの登録」をクリックします。
2019-05-06_12h24_56.png

「安全性→ポリシー」をクリックし、「ポリシーの作成」をクリックします。
2019-05-06_12h26_40.png

ポリシーの「名前」を任意で入力します。
アクションに「iot:*」と入力し、リソース ARNに「*」と入力します。
「許可」にチェックを入れ、「作成」をクリックします。
(許可範囲が広いので、本番稼働時は適宜設定をしてください)
2019-05-06-12_43_19.png

管理をクリックし、先ほど作成したモノをクリックします。
「セキュリティ」をクリックし証明書をクリックします。
2019-05-06_12h47_09.png

「アクション→ポリシーのアタッチ」をクリックします。

作成したポリシーをチェックし、「アタッチ」をクリックします。
2019-05-06_12h49_44.png

以上で、AWS IoTの設定が完了しました。

ラズパイ側のコーディング

続いてラズパイ側の実装を行います。
以降、ラズパイ上での作業になります。

ラズパイで温度情報を取得

Pythonで温度を取得する処理を作成しました。
(詳しくはブログ記事を参照)

get_temp.py
import time
import spidev
import sys

spi_ch = 0

try:
    spi = spidev.SpiDev()
    spi.open(0,spi_ch)
    spi.mode = 0x03
    spi.max_speed_hz = 1000000
    time.sleep(0.01)
    spi.xfer2([0xff, 0xff, 0xff, 0xff])

    while True:

        spi.xfer2([0x54])
        time.sleep(0.5)
        adc = spi.xfer2([0xff,0xff]) 
        temp = (adc[0] << 8) | adc[1] 
        temp = temp >> 3  
        if(temp >= 4096):  
                temp = temp - 8192

        print(temp / 16.0)
        time.sleep(1)

except KeyboardInterrupt:
    spi.close()
    sys.exit(0)

AWS IoTに送信

この処理をもとに、温度をAWS IoTに送信したいと思います。
デバイスからは443ポートで、TLSクライアント認証によるMQTT通信をします。

PythonでMQTTの通信処理を書いてもよいのですが、今回はaws-iot-device-sdk-pythonを使用しました。
https://github.com/aws/aws-iot-device-sdk-python

ラズパイにインストールします。
自分のラズパイにpipもgitも入ってなかったので、今回はzipからインストールしました。

mkdir AWSIoTPythonSDK
cd AWSIoTPythonSDK
wget https://s3.amazonaws.com/aws-iot-device-sdk-python/aws-iot-device-sdk-python-latest.zip
unzip AWSIoTPythonSDK
sudo python3 setup.py install

ファイルの構成は以下です。
AWS IoTの設定時にダウンロードしたキーなども配置します。
(xxxは各自のファイル名で読み替えてください)

/
├ aws-test.py
└ cert/
   ├ AmazonRootCA1.pem
   ├ xxx-private.pem.key
   └ xxx-certificate.pem.crt

実装は以下になりました。
まずは単純にデータの送信を試します。

エンドポイントURLは、「AWS IoT→管理→モノ→操作」から確認できます。
configureCredentialsの引数は、設定時にダウンロードしたファイルのパスを指定します。

aws-test.py
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import json

# 初期化
myMQTTClient = AWSIoTMQTTClient("raspi-zero")

# MQTTクライアントの設定
myMQTTClient.configureEndpoint("エンドポイントURL", 443)
myMQTTClient.configureCredentials("/xxx/xxx/cert/AmazonRootCA1.pem", "/xxx/xxx/cert/xxx-private.pem.key", "/xxx/xxx/cert/xxx-certificate.pem.crt")
myMQTTClient.configureOfflinePublishQueueing(-1)
myMQTTClient.configureDrainingFrequency(2)
myMQTTClient.configureConnectDisconnectTimeout(10)
myMQTTClient.configureMQTTOperationTimeout(5)

# Connect to AWS IoT endpoint and publish a message
myMQTTClient.connect()
print ("Connected to AWS IoT")
myMQTTClient.publish("awsiot/test", json.dumps({"test": "test message!"}), 0)
myMQTTClient.disconnect()

動作確認

AWS IoTのコンソールから「テスト」をクリックし、トピックのサブスクリプションに「awsiot/test」と入力しサブスクライブします。
2019-05-06_14h05_40.png

作成したPythonを実行します。

python3 aws-test.py

JSON形式でデータが受信できることを確認します。
2019-05-06_14h08_18.png

温度情報を定期的に送信

温度取得処理とAWS IoT送信処理とくっつけて、1分毎にデータを送信する処理を作成しました。
(ちょっとやっつけ感ありますが:sweat_smile:)

aws-send-temp.py
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import json
import time
import spidev
import sys

# 初期化
myMQTTClient = AWSIoTMQTTClient("raspi-zero")

# MQTTクライアントの設定
myMQTTClient.configureEndpoint("エンドポイントURL", 443)
myMQTTClient.configureCredentials("/xxx/xxx/cert/AmazonRootCA1.pem", "/xxx/xxx/cert/xxx-private.pem.key", "/xxx/xxx/cert/xxx-certificate.pem.crt")
myMQTTClient.configureOfflinePublishQueueing(-1)
myMQTTClient.configureDrainingFrequency(2)
myMQTTClient.configureConnectDisconnectTimeout(10)
myMQTTClient.configureMQTTOperationTimeout(5)

# Connect to AWS IoT endpoint and publish a message
myMQTTClient.connect()
print ("Connected to AWS IoT")

# 温度情報取得・送信
spi_ch = 0

try:
    spi = spidev.SpiDev()
    spi.open(0,spi_ch)
    spi.mode = 0x03
    spi.max_speed_hz = 1000000
    time.sleep(0.01)
    spi.xfer2([0xff, 0xff, 0xff, 0xff])

    while True:

        spi.xfer2([0x54])
        time.sleep(0.5)
        adc = spi.xfer2([0xff,0xff]) 
        temp = (adc[0] << 8) | adc[1] 
        temp = temp >> 3  
        if(temp >= 4096):  
                temp = temp - 8192

        # 温度情報送信
        print(temp / 16.0)
        myMQTTClient.publish("awsiot/test", json.dumps({"temperature": (temp / 16.0)}), 0)
        # 一分待機
        time.sleep(60)

except KeyboardInterrupt:
    spi.close()
    myMQTTClient.disconnect()
    sys.exit(0)

実行してみると、1分毎くらいにデータが送信できました!
2019-05-06_14h15_35.png

今後について

まずはAWS IoTと連携できました。
次は例えばDBにデータを保存したり、グラフ化したりできますね!
またの機会にまとめたいです:grinning:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

aws cloud9を軽く触ってみた結果をまとめてみる

初めに

はじめまして。raqwelと言います。
普段は新卒として入った都内のITベンチャーでSREとして働いています。
業務中で気になった技術やらなんやらを休みの日に調べたり試したりすることが増えてきたので、
備忘録がてらまとめて見たいと思い、qiitaに投稿しようと思いました。

やったこと

今回はaws cloud9を軽く触ってみたので、自身が感じたメリデメ等をまとめようかと思います。
cloud9に関しては↓を見ていただけると良いかと思います。

簡潔に述べると、awsが提供しているクラウドIDEで、
awsアカウントとブラウザとネット環境があればどこでもコーディングできるものです。
最近だと東京リージョンに来たことがちょっとしたニュースになりました。

実際にはこちらの方が行ったことをなぞるような形で手を動かしました。
https://qiita.com/icck/items/7705ef6871e261d6535e
この場を借りてお礼申し上げます。

実際に触ってみた感想

実際に触ってみた感想を以下にメリデメで書いていきます。
あくまで個人の意見なので、その点配慮して見ていただけたら幸いです。

メリット

  • ブラウザがあればどこでもコーディングできる
  • ペアプロが容易なため、手戻りの発生を抑えられる
  • セットアップの容易さ
    • cloud9で実際に開発しようとしたとき、基本的には以下のステップを行うだけで簡単に用意できます。しかもcloud9自体は無料で使えます。
setup
    1. コンソールを開いて、
    2. cloud9を選択して、
    3. Create Environmentをクリックして、
    4. Name(作業環境名)とDescription(環境の説明)を入力して、
    5. そのまま進めて「Create environment」を押して、
    6. 少し待つ。
  • 対応言語が豊富
  • localstackとかのモック化したawsサービスを用意せずともそのまま連携できる。
    • ローカル開発環境でawsのサービスを用いた機能を開発しているとデバッグするのに大変です。
    • その際によく用いられるのは、fakes3やlocalstack等のawsのサービスをモックしたようなものです。
    • もしawsが新たなサービスを発表しそれを用いるとなった時や、なにかがあって配布が中止されてしまった時に、まだlocalstackが使えなくなってしまうケースが考えられます。
    • しかし、aws cloud9はaws上のサービスなので、他のサービスと連携した際のデバッグ、テストができます。これによって実際のawsのサービスを用いて開発ができます。
デメリット
  • 普段利用しているIDEからの移行コストが高そう
    • 特に中〜大規模のプロダクトに関して移行が厳しい印象。
      • 実際に全員が使える様になるまでの学習コスト
      • 複数人が同じenvironmentで触った時の動作が重くならないか・・・
      • 開発者全員分のawsアカウントを用意し無くてはならない。ということは組織的にアカウント管理が抜けもれなく土壌が備わっているか・・・ IAMユーザの間違いでした。(@tokusyuさんありがとうございます!)
  • gitのGUIがない
    • 自分はいつもIDE付属のGUIでgit操作を行っています。
      • その方が直感的に理解、操作がしやすいためです。
    • しかし、cloud9にはGUI機能が見当たりませんでした。(もしかして見逃してるかもしれません)
    • git操作を普段からコマンドで行っている方は問題ないと思いますが、GUIを利用する人の事を考えると、どちらも用意してある環境が欲しいな〜と思ってしまいます。

まとめ

今回はawsがリリースしたクラウドIDE「aws cloud9」を触ってみました。
実際触ってみて感じたのは、中〜大規模のプロジェクトがIDEからcloud9に移行するのは結構大変そうな印象を受けました。
しかし、セットアップの容易さ、対応言語の多さ等を考えると、個人や小規模のプロジェクトの開発にはむしろ使えるのではないかと感じました。
今後、どのようにアップデートしていくかが気になります。

というわけで以上です。
初記事なので至らぬ点が多いかと思いますが、温かい目で見ていただけたら幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Innovate Online Conference 「試験対策セッション1:回復性の高いアーキテクチャを設計する」をまとめてみた

参考文献

AWS Innovate Online Conference
AWS 認定 - 試験対策 「ソリューションアーキテクト - アソシエイト」
セッション1:回復性の高いアーキテクチャを設計する

を視聴して、まとめた内容を記載しています。

1.1 信頼性と回復性に優れたストレージを選択する

EC2インスタンスストア

  • エフェメラルボリューム
  • 特定のEC2インスタンスのみ
  • キャパシティ固定
  • ディスクタイプとキャパシティはEC2インスタンスタイプによって異なる
  • アプリケーションレベルの耐久性
  • 無料で利用できる
  • 一時的なストレージ
  • キャッシュや一時コンテンツなど、頻繁に更新される情報のストレージ

EBS

  • EC2で使用するためのボリュームタイプ
  • 暗号化
  • スナップショット
  • プロビジョンドキャパシティー
  • EC2インスタンスに依存しないライフサイクル(永続的なストレージ)
  • EC2の料金とは別に、料金が発生する
  • データにすばやくアクセスする場合や継続性が必要な場合に推奨
  • ランダムや読み取り書き込みを行う
  • ユースケースに応じた4つのボリュームタイプから選択(特徴とユースケースを理解すべし)

EFS

  • AWSクラウド上のファイルストレージ
  • 複数のEC2から同時にアクセスできる共有ストレージ
  • ギガバイトからペタバイトまで自動的にスケール
  • 高い耐久性と可用性を考慮
  • NFS v4.0およびNFS v4.1プロトコルをサポート

Amazon S3

  • インターネット経由で利用可能なオブジェクトストレージ
  • 信頼性が高く高速で安価なデータストレージ
  • 容量は事実上無制限
  • 99.999999999%の高い耐久性(9が11個)
  • リージョン内で複数の施設にまたがる複数のデバイスにデータを冗長的に格納→高い耐久性
  • S3上のデータ要件に応じてストレージクラスを使い分けることでコスト最適化
  • データの暗号化が可能
    • SSE-S3
    • SSE-KMS
    • SSE-C
  • サーバサイドで暗号化が可能
    • HTTPS
  • バージョニング
  • マルチパートアップロード機能(大規模データを分割してアップロード)

Amazon Glacier

  • データのバックアップおよびアーカイブ用のストレージ
  • 低コスト
  • S3と似ているが、こちらはアーカイブ用のストレージ
  • データを取り出すことにコストと時間がかかる
  • データサイズに対するコストがS3より安い
  • データを取り出すことに時間がかかるが、3つの取り出しオプションがある
    • 迅速
    • 標準
    • 大容量
  • 暗号化
  • S3のライフサイクルポリシー
  • リージョン内の可用性
  • 99.999999999%の高い耐久性(S3と一緒)

1.2 AWSのサービスを使用した疎結合化メカニズムの設計方法を決定する

1.3 多層アーキテクチャソリューションの設計方法を決定する

  • 疎結合化のメリット
    • 可用性を向上
    • スケーラビリティ

Webサーバからメールを送信する場合

  • 密結合の場合
    • Eメールサーバが利用できなくなりメール送信が失敗
    • Webサーバの処理に影響
  • 疎結合の場合
    • Webサーバからのメッセージをキューで受け止める
    • 非同期でメール送信する仕組み
    • Webサーバの処理に影響せず、システム全体で可用性を向上

Webサーバからdynamo DBにログを格納する場合

  • 密結合の場合
    • ログ記録サービスが過負荷な状態でスローダウン
    • Webサーバの処理が遅延
  • 疎結合の場合
    • Webサーバからのログ送信は一旦キューに格納
    • イベントログ記録サービスが非同期でdynamo DBに格納
    • ログ記録サービスの負荷が足りなかったら、そのコンポーネントを増やすことでWebサーバに影響なくシステムをスケールさせることができる

クラウドは、オンプレミスと異なり、必要なインスタンスを短時間で起動してシステムを組めることがメリット
- 密結合の場合
- サーバを増やしてシステム処理をスケールした場合、結合相手に影響が出る
- 柔軟なスケーラビリティを確保できない
- 障害で停止したり、過負荷により反応が遅いと他コンポーネントに影響が出る

  • 疎結合の場合
    • 性能不足になった自身のコンポーネントを増強することが容易。
    • システム全体のスケーラビリティを向上
    • コンポーネント同士のやり取りを非同期で行い、他のコンポーネントをブラックボックスとみなすことができる

疎結合に利用されるサービス

  • SQS
  • ELB
  • ELP
  • Route 53

1.4 可用性や耐障害性に優れたソリューションの設計方法を決定する

高可用性の観点では、いつ故障してもおかしくないという前提でシステムを設計/構築することが重要

高可用性とは

システムを構成するコンポーネントが故障しても、長時間停止することなく、自動的にすばやく復旧すること。
- 各コンポーネントをグループ化する
- 単一障害点を避ける

実現方法

  • 複数のAZとリージョンを使用する。
  • オートスケーリングを使用する。
  • 可用性の高いマネージド型サービスを利用する。

耐障害性とは

アプリケーション内のコンポーネントが備える冗長性。
アーキテクチャ内のいずれかのコンポーネントが完全に機能を失っても、アプリケーションはパフォーマンスを低下させることなく機能し続けること。
フォールトトレランスと呼ぶ場合もある。

コンポーネント同士を疎結合にすることで、高可用性に不可欠な対象外も向上する。
- 疎結合のシステムでは、1つのコンポーネントの障害はレイヤー内に完結して管理。
- ほかのインスタンスに飛び越えて障害が拡散することはない。

可用性の例

前提:SLAを満たすには4つのEC2インスタンスが必要。AZで障害あった場合を考える

  • 2つのAZに2つずつEC2インスタンスを起動
  • AZ-aに障害が起きた場合、残るのはAZ-bに2つのインスタンスのみ
  • 残ったAZ-bに、新しい2つのEC2インスタンスが起動してくるまではSLAが満たせない。(オートスケーリングで起動)
  • コスト的には、通常運用時は4インスタンス分のコストのみ

耐障害性の例

前提:SLAを満たすには4つのEC2インスタンスが必要。AZで障害あった場合を考える

  • 2つのAZに4つずつEC2インスタンスを起動
    • AZ-aに障害が起きた場合でも、AZ-bに4つのインスタンスがある
    • SLAは満たし続けることができる
    • コストが通常運用でも8インスタンス分のコストが発生する

耐障害性の構成をとると、コストが高くなる傾向がある。

CloudFormation

システムの復旧には、必要なリソースを1から構成する必要があるが、手動だと時間がかかる。
そのため、自動構成する仕組みが重要。
CloudFormationは、必要なリソースをテンプレートとして定義し、実行させることで必要なリソースが自動構成される。
1つのテンプレートから作成されたリソースは、スタックによって一元管理される。
リソースへの更新が必要な場合は、テンプレートを変更する。

Lambda

回復性の高いアーキテクチャを構築/運用しやすくなる。
サーバレスアーキテクチャを実現できる。
コードを用意して、Lambda関数として登録すれば、AWSで管理している実行基盤で自動的に実行される。
実行タイミングは、様々なAWS上のイベントで紐付けることが可能。

  • メリット
    • 運用上の様々な操作を自動化できる。
    • 障害などのシステム上のイベントを契機にして、自動化するような処理を実装しておけば、Lambdaが自動実行される。
    • 運用者が可用性やスケーラビリティを管理する必要がない(マネージド型サービスで、コードの実行環境はAWSが全て管理しているため。)

まとめ

  • 「単一のAZ」が正解ではないという前提で考える
  • AWSマネージド型サービスの使用を常に優先する
  • 耐障害性と高可用性は同じではない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(メモ) AWS CDK Pythonサンプルお試し

まだGAではないですがAWS CDKがPython対応したのでサンプルを動かしながらメモ。
CDKサンプルは他言語も含めてここにあります。
https://github.com/aws-samples/aws-cdk-examples

動かしたサンプル

ECS Service on EC2 with ALBのサンプルアプリケーション
https://github.com/aws-samples/aws-cdk-examples/tree/master/python/ecs/ecs-load-balanced-service/

app.py
リソースが記述された本体ファイル。これだけの記述。

app.py
from aws_cdk import (
    aws_ec2 as ec2,
    aws_ecs as ecs,
    cdk,
)


class BonjourECS(cdk.Stack):

    def __init__(self, scope: cdk.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, *kwargs)

        vpc = ec2.VpcNetwork(
            self, "MyVpc",
            max_a_zs=2
        )

        cluster = ecs.Cluster(
            self, 'Ec2Cluster',
            vpc=vpc
        )

        cluster.add_capacity("DefaultAutoScalingGroup",
                             instance_type=ec2.InstanceType("t2.micro"))

        ecs_service = ecs.LoadBalancedEc2Service(
            self, "Ec2Service",
            cluster=cluster,
            memory_limit_mi_b=512,
            image=ecs.ContainerImage.from_registry("amazon/amazon-ecs-sample")
        )

        cdk.CfnOutput(
            self, "LoadBalancerDNS",
            value=ecs_service.load_balancer.dns_name
        )

app = cdk.App()
BonjourECS(app, "Bonjour")
app.run()

cdk.json
CDKがどのランタイムとコードでスタックを作成するかを指定するファイル

cdk.json
{
    "app": "python3 app.py"
}

requirements.txt
必要なライブラリ

requirements.txt
aws-cdk.cdk
aws-cdk.aws_ec2
aws-cdk.aws_ecs

# Work around for jsii#413
aws-cdk.aws-autoscaling-common

デプロイ手順

PyCharm CE 2018.3 on MacOS Sierra(古...)上で作業しています

コードのclone

コマンド
sudo git clone https://github.com/aws-samples/aws-cdk-examples.git
cd aws-cdk-examples/python/ecs/ecs-load-balanced-service/

aws cdk インストール/アップデート

コマンド
sudo npm -g install aws-cdk
cdk --version
0.30.0 (build 4740446)

ライブラリインストール

コマンド
pip install -r requirements.txt

リソースの差分確認 (オプション)

cdk diffでリソースの差分確認を実施します。初期構築なので全てのリソースが追加差分として表示されます。
セキュリティ関連の変更をわかりやすく表示してくれるようになっていました。
*これ以降はAWSクレデンシャルとデフォルトリージョンが設定されていないとエラーになります。

コマンド
cdk diff
Output
Stack Bonjour
IAM Statement Changes
┌───┬─────────────────────────────────────────┬────────┬─────────────────────────────────────────┬─────────────────────────────────────────┬───────────────────────────────────────────┐
│   │ Resource                                │ Effect │ Action                                  │ Principal                               │ Condition                                 │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/Dr │ Allow  │ lambda:InvokeFunction                   │ Service:sns.amazonaws.com               │ "ArnLike": {                              │
│   │ ainECSHook/Function.Arn}                │        │                                         │                                         │   "AWS:SourceArn": "${Ec2Cluster/DefaultA │
│   │                                         │        │                                         │                                         │ utoScalingGroup/DrainECSHook/Topic}"      │
│   │                                         │        │                                         │                                         │ }                                         │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/Dr │ Allow  │ sts:AssumeRole                          │ Service:lambda.${AWS::URLSuffix}        │                                           │
│   │ ainECSHook/Function/ServiceRole.Arn}    │        │                                         │                                         │                                           │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/Dr │ Allow  │ sns:Publish                             │ AWS:${Ec2Cluster/DefaultAutoScalingGrou │                                           │
│   │ ainECSHook/Topic}                       │        │                                         │ p/LifecycleHookDrainHook/Role}          │                                           │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/In │ Allow  │ sts:AssumeRole                          │ Service:ec2.${AWS::URLSuffix}           │                                           │
│   │ stanceRole.Arn}                         │        │                                         │                                         │                                           │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/Li │ Allow  │ sts:AssumeRole                          │ Service:autoscaling.${AWS::URLSuffix}   │                                           │
│   │ fecycleHookDrainHook/Role.Arn}          │        │                                         │                                         │                                           │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Service/TaskDef/TaskRole.Arn}      │ Allow  │ sts:AssumeRole                          │ Service:ecs-tasks.${AWS::URLSuffix}     │                                           │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ *                                       │ Allow  │ ecr:GetAuthorizationToken               │ AWS:${Ec2Cluster/DefaultAutoScalingGrou │                                           │
│   │                                         │        │ ecs:CreateCluster                       │ p/InstanceRole}                         │                                           │
│   │                                         │        │ ecs:DeregisterContainerInstance         │                                         │                                           │
│   │                                         │        │ ecs:DiscoverPollEndpoint                │                                         │                                           │
│   │                                         │        │ ecs:Poll                                │                                         │                                           │
│   │                                         │        │ ecs:RegisterContainerInstance           │                                         │                                           │
│   │                                         │        │ ecs:StartTelemetrySession               │                                         │                                           │
│   │                                         │        │ ecs:Submit*                             │                                         │                                           │
│   │                                         │        │ logs:CreateLogStream                    │                                         │                                           │
│   │                                         │        │ logs:PutLogEvents                       │                                         │                                           │
│ + │ *                                       │ Allow  │ autoscaling:CompleteLifecycleAction     │ AWS:${Ec2Cluster/DefaultAutoScalingGrou │                                           │
│   │                                         │        │ ec2:DescribeHosts                       │ p/DrainECSHook/Function/ServiceRole}    │                                           │
│   │                                         │        │ ec2:DescribeInstanceAttribute           │                                         │                                           │
│   │                                         │        │ ec2:DescribeInstanceStatus              │                                         │                                           │
│   │                                         │        │ ec2:DescribeInstances                   │                                         │                                           │
│ + │ *                                       │ Allow  │ ecs:DescribeContainerInstances          │ AWS:${Ec2Cluster/DefaultAutoScalingGrou │                                           │
│   │                                         │        │ ecs:DescribeTasks                       │ p/DrainECSHook/Function/ServiceRole}    │                                           │
│   │                                         │        │ ecs:ListContainerInstances              │                                         │                                           │
│   │                                         │        │ ecs:ListTasks                           │                                         │                                           │
│   │                                         │        │ ecs:SubmitContainerStateChange          │                                         │                                           │
│   │                                         │        │ ecs:SubmitTaskStateChange               │                                         │                                           │
│   │                                         │        │ ecs:UpdateContainerInstancesState       │                                         │                                           │
└───┴─────────────────────────────────────────┴────────┴─────────────────────────────────────────┴─────────────────────────────────────────┴───────────────────────────────────────────┘
IAM Policy Changes
┌───┬─────────────────────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                                                │ Managed Policy ARN                                                             │
├───┼─────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴─────────────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
Security Group Changes
┌───┬─────────────────────────────────────────────────────────────────────┬─────┬─────────────────┬─────────────────────────────────────────────────────────────────────┐
│   │ Group                                                               │ Dir │ Protocol        │ Peer                                                                │
├───┼─────────────────────────────────────────────────────────────────────┼─────┼─────────────────┼─────────────────────────────────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup.GroupId} │ In  │ TCP 32768-65535 │ ${Ec2Service/LB/SecurityGroup.GroupId}                              │
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup.GroupId} │ Out │ Everything      │ Everyone (IPv4)                                                     │
├───┼─────────────────────────────────────────────────────────────────────┼─────┼─────────────────┼─────────────────────────────────────────────────────────────────────┤
│ + │ ${Ec2Service/LB/SecurityGroup.GroupId}                              │ In  │ TCP 80          │ Everyone (IPv4)                                                     │
│ + │ ${Ec2Service/LB/SecurityGroup.GroupId}                              │ Out │ TCP 32768-65535 │ ${Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup.GroupId} │
└───┴─────────────────────────────────────────────────────────────────────┴─────┴─────────────────┴─────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np)

Resources
[+] AWS::EC2::VPC MyVpc MyVpcF9F0CA6F 
[+] AWS::EC2::Subnet MyVpc/PublicSubnet1/Subnet MyVpcPublicSubnet1SubnetF6608456 
[+] AWS::EC2::RouteTable MyVpc/PublicSubnet1/RouteTable MyVpcPublicSubnet1RouteTableC46AB2F4 
[+] AWS::EC2::SubnetRouteTableAssociation MyVpc/PublicSubnet1/RouteTableAssociation MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB 
[+] AWS::EC2::Route MyVpc/PublicSubnet1/DefaultRoute MyVpcPublicSubnet1DefaultRoute95FDF9EB 
[+] AWS::EC2::EIP MyVpc/PublicSubnet1/EIP MyVpcPublicSubnet1EIP096967CB 
[+] AWS::EC2::NatGateway MyVpc/PublicSubnet1/NATGateway MyVpcPublicSubnet1NATGatewayAD3400C1 
[+] AWS::EC2::Subnet MyVpc/PublicSubnet2/Subnet MyVpcPublicSubnet2Subnet492B6BFB 
[+] AWS::EC2::RouteTable MyVpc/PublicSubnet2/RouteTable MyVpcPublicSubnet2RouteTable1DF17386 
[+] AWS::EC2::SubnetRouteTableAssociation MyVpc/PublicSubnet2/RouteTableAssociation MyVpcPublicSubnet2RouteTableAssociation227DE78D 
[+] AWS::EC2::Route MyVpc/PublicSubnet2/DefaultRoute MyVpcPublicSubnet2DefaultRoute052936F6 
[+] AWS::EC2::EIP MyVpc/PublicSubnet2/EIP MyVpcPublicSubnet2EIP8CCBA239 
[+] AWS::EC2::NatGateway MyVpc/PublicSubnet2/NATGateway MyVpcPublicSubnet2NATGateway91BFBEC9 
[+] AWS::EC2::Subnet MyVpc/PrivateSubnet1/Subnet MyVpcPrivateSubnet1Subnet5057CF7E 
[+] AWS::EC2::RouteTable MyVpc/PrivateSubnet1/RouteTable MyVpcPrivateSubnet1RouteTable8819E6E2 
[+] AWS::EC2::SubnetRouteTableAssociation MyVpc/PrivateSubnet1/RouteTableAssociation MyVpcPrivateSubnet1RouteTableAssociation56D38C7E 
[+] AWS::EC2::Route MyVpc/PrivateSubnet1/DefaultRoute MyVpcPrivateSubnet1DefaultRouteA8CDE2FA 
[+] AWS::EC2::Subnet MyVpc/PrivateSubnet2/Subnet MyVpcPrivateSubnet2Subnet0040C983 
[+] AWS::EC2::RouteTable MyVpc/PrivateSubnet2/RouteTable MyVpcPrivateSubnet2RouteTableCEDCEECE 
[+] AWS::EC2::SubnetRouteTableAssociation MyVpc/PrivateSubnet2/RouteTableAssociation MyVpcPrivateSubnet2RouteTableAssociation86A610DA 
[+] AWS::EC2::Route MyVpc/PrivateSubnet2/DefaultRoute MyVpcPrivateSubnet2DefaultRoute9CE96294 
[+] AWS::EC2::InternetGateway MyVpc/IGW MyVpcIGW5C4A4F63 
[+] AWS::EC2::VPCGatewayAttachment MyVpc/VPCGW MyVpcVPCGW488ACE0D 
[+] AWS::ECS::Cluster Ec2Cluster Ec2ClusterEE43E89D 
[+] AWS::EC2::SecurityGroup Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroup149B0A9E 
[+] AWS::EC2::SecurityGroupIngress Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup/from BonjourEc2ServiceLBSecurityGroup2185A60D:32768-65535 Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroupfromBonjourEc2ServiceLBSecurityGroup2185A60D3276865535EC4EE766 
[+] AWS::IAM::Role Ec2Cluster/DefaultAutoScalingGroup/InstanceRole Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898 
[+] AWS::IAM::Policy Ec2Cluster/DefaultAutoScalingGroup/InstanceRole/DefaultPolicy Ec2ClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy6D2DC2FD 
[+] AWS::IAM::InstanceProfile Ec2Cluster/DefaultAutoScalingGroup/InstanceProfile Ec2ClusterDefaultAutoScalingGroupInstanceProfileDB232471 
[+] AWS::AutoScaling::LaunchConfiguration Ec2Cluster/DefaultAutoScalingGroup/LaunchConfig Ec2ClusterDefaultAutoScalingGroupLaunchConfig7B2FED3A 
[+] AWS::AutoScaling::AutoScalingGroup Ec2Cluster/DefaultAutoScalingGroup/ASG Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0 
[+] AWS::SNS::Topic Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Topic Ec2ClusterDefaultAutoScalingGroupDrainECSHookTopic798CDC5F 
[+] AWS::IAM::Role Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/ServiceRole Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3 
[+] AWS::IAM::Policy Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/ServiceRole/DefaultPolicy Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicy638C9E33 
[+] AWS::Lambda::Function Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionE0DEFB31 
[+] AWS::SNS::Subscription Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/TopicSubscription Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscription5DE5A98D 
[+] AWS::Lambda::Permission Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/Topic Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionTopic6C30136B 
[+] AWS::IAM::Role Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook/Role Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7 
[+] AWS::IAM::Policy Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook/Role/DefaultPolicy Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicyE499974B 
[+] AWS::AutoScaling::LifecycleHook Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHook5CB1467E 
[+] AWS::ElasticLoadBalancingV2::LoadBalancer Ec2Service/LB Ec2ServiceLB381329CE 
[+] AWS::EC2::SecurityGroup Ec2Service/LB/SecurityGroup Ec2ServiceLBSecurityGroup45FED6DF 
[+] AWS::EC2::SecurityGroupEgress Ec2Service/LB/SecurityGroup/to BonjourEc2ClusterDefaultAutoScalingGroupInstanceSecurityGroupE49ADAF5:32768-65535 Ec2ServiceLBSecurityGrouptoBonjourEc2ClusterDefaultAutoScalingGroupInstanceSecurityGroupE49ADAF53276865535AC4204BB 
[+] AWS::ElasticLoadBalancingV2::Listener Ec2Service/LB/PublicListener Ec2ServiceLBPublicListenerA941070C 
[+] AWS::ElasticLoadBalancingV2::TargetGroup Ec2Service/LB/PublicListener/ECSGroup Ec2ServiceLBPublicListenerECSGroup3DC8690E 
[+] AWS::IAM::Role Ec2Service/TaskDef/TaskRole Ec2ServiceTaskDefTaskRole27A5D642 
[+] AWS::ECS::TaskDefinition Ec2Service/TaskDef Ec2ServiceTaskDef8D94BAA3 
[+] AWS::ECS::Service Ec2Service/Service/Service Ec2Service398F0E46 

Outputs
[+] Output Ec2Service/LoadBalancerDNS Ec2ServiceLoadBalancerDNS6983C9B2: {"Value":{"Fn::GetAtt":["Ec2ServiceLB381329CE","DNSName"]}}
[+] Output LoadBalancerDNS LoadBalancerDNS: {"Value":{"Fn::GetAtt":["Ec2ServiceLB381329CE","DNSName"]}}

CloudFormationテンプレート確認 (オプション)

cdk synthコマンドで実際に展開されるCloudFormationテンプレートのResourcesを確認できます。

コマンド
cdk synth
Output
 MyVpcF9F0CA6F:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: Bonjour/MyVpc
    Metadata:
      aws:cdk:path: Bonjour/MyVpc/Resource
  MyVpcPublicSubnet1SubnetF6608456:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/18
      VpcId:
        Ref: MyVpcF9F0CA6F
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: Bonjour/MyVpc/PublicSubnet1
        - Key: aws-cdk:subnet-name
          Value: Public
        - Key: aws-cdk:subnet-type
          Value: Public
<...snip...>

長いので省略。792行ありました。CDKを使うとCloudFormationを生で書くより記述量を削減できることがわかります。

デプロイ

cdk deployでデプロイします。

コマンド
cdk deploy
Output
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬─────────────────────────────────────────┬────────┬─────────────────────────────────────────┬─────────────────────────────────────────┬───────────────────────────────────────────┐
│   │ Resource                                │ Effect │ Action                                  │ Principal                               │ Condition                                 │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/Dr │ Allow  │ lambda:InvokeFunction                   │ Service:sns.amazonaws.com               │ "ArnLike": {                              │
│   │ ainECSHook/Function.Arn}                │        │                                         │                                         │   "AWS:SourceArn": "${Ec2Cluster/DefaultA │
│   │                                         │        │                                         │                                         │ utoScalingGroup/DrainECSHook/Topic}"      │
│   │                                         │        │                                         │                                         │ }                                         │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/Dr │ Allow  │ sts:AssumeRole                          │ Service:lambda.${AWS::URLSuffix}        │                                           │
│   │ ainECSHook/Function/ServiceRole.Arn}    │        │                                         │                                         │                                           │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/Dr │ Allow  │ sns:Publish                             │ AWS:${Ec2Cluster/DefaultAutoScalingGrou │                                           │
│   │ ainECSHook/Topic}                       │        │                                         │ p/LifecycleHookDrainHook/Role}          │                                           │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/In │ Allow  │ sts:AssumeRole                          │ Service:ec2.${AWS::URLSuffix}           │                                           │
│   │ stanceRole.Arn}                         │        │                                         │                                         │                                           │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/Li │ Allow  │ sts:AssumeRole                          │ Service:autoscaling.${AWS::URLSuffix}   │                                           │
│   │ fecycleHookDrainHook/Role.Arn}          │        │                                         │                                         │                                           │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ ${Ec2Service/TaskDef/TaskRole.Arn}      │ Allow  │ sts:AssumeRole                          │ Service:ecs-tasks.${AWS::URLSuffix}     │                                           │
├───┼─────────────────────────────────────────┼────────┼─────────────────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
│ + │ *                                       │ Allow  │ ecr:GetAuthorizationToken               │ AWS:${Ec2Cluster/DefaultAutoScalingGrou │                                           │
│   │                                         │        │ ecs:CreateCluster                       │ p/InstanceRole}                         │                                           │
│   │                                         │        │ ecs:DeregisterContainerInstance         │                                         │                                           │
│   │                                         │        │ ecs:DiscoverPollEndpoint                │                                         │                                           │
│   │                                         │        │ ecs:Poll                                │                                         │                                           │
│   │                                         │        │ ecs:RegisterContainerInstance           │                                         │                                           │
│   │                                         │        │ ecs:StartTelemetrySession               │                                         │                                           │
│   │                                         │        │ ecs:Submit*                             │                                         │                                           │
│   │                                         │        │ logs:CreateLogStream                    │                                         │                                           │
│   │                                         │        │ logs:PutLogEvents                       │                                         │                                           │
│ + │ *                                       │ Allow  │ autoscaling:CompleteLifecycleAction     │ AWS:${Ec2Cluster/DefaultAutoScalingGrou │                                           │
│   │                                         │        │ ec2:DescribeHosts                       │ p/DrainECSHook/Function/ServiceRole}    │                                           │
│   │                                         │        │ ec2:DescribeInstanceAttribute           │                                         │                                           │
│   │                                         │        │ ec2:DescribeInstanceStatus              │                                         │                                           │
│   │                                         │        │ ec2:DescribeInstances                   │                                         │                                           │
│ + │ *                                       │ Allow  │ ecs:DescribeContainerInstances          │ AWS:${Ec2Cluster/DefaultAutoScalingGrou │                                           │
│   │                                         │        │ ecs:DescribeTasks                       │ p/DrainECSHook/Function/ServiceRole}    │                                           │
│   │                                         │        │ ecs:ListContainerInstances              │                                         │                                           │
│   │                                         │        │ ecs:ListTasks                           │                                         │                                           │
│   │                                         │        │ ecs:SubmitContainerStateChange          │                                         │                                           │
│   │                                         │        │ ecs:SubmitTaskStateChange               │                                         │                                           │
│   │                                         │        │ ecs:UpdateContainerInstancesState       │                                         │                                           │
└───┴─────────────────────────────────────────┴────────┴─────────────────────────────────────────┴─────────────────────────────────────────┴───────────────────────────────────────────┘
IAM Policy Changes
┌───┬─────────────────────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                                                │ Managed Policy ARN                                                             │
├───┼─────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴─────────────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
Security Group Changes
┌───┬─────────────────────────────────────────────────────────────────────┬─────┬─────────────────┬─────────────────────────────────────────────────────────────────────┐
│   │ Group                                                               │ Dir │ Protocol        │ Peer                                                                │
├───┼─────────────────────────────────────────────────────────────────────┼─────┼─────────────────┼─────────────────────────────────────────────────────────────────────┤
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup.GroupId} │ In  │ TCP 32768-65535 │ ${Ec2Service/LB/SecurityGroup.GroupId}                              │
│ + │ ${Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup.GroupId} │ Out │ Everything      │ Everyone (IPv4)                                                     │
├───┼─────────────────────────────────────────────────────────────────────┼─────┼─────────────────┼─────────────────────────────────────────────────────────────────────┤
│ + │ ${Ec2Service/LB/SecurityGroup.GroupId}                              │ In  │ TCP 80          │ Everyone (IPv4)                                                     │
│ + │ ${Ec2Service/LB/SecurityGroup.GroupId}                              │ Out │ TCP 32768-65535 │ ${Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup.GroupId} │
└───┴─────────────────────────────────────────────────────────────────────┴─────┴─────────────────┴─────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np)
Bonjour: deploying...
Bonjour: creating CloudFormation changeset...
  0/50 | 11:35:04 AM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata                        | CDKMetadata 
  0/50 | 11:35:04 AM | CREATE_IN_PROGRESS   | AWS::SNS::Topic                           | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Topic (Ec2ClusterDefaultAutoScalingGroupDrainECSHookTopic798CDC5F) 
  0/50 | 11:35:04 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role                            | Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook/Role (Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7) 
  0/50 | 11:35:04 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role                            | Ec2Service/TaskDef/TaskRole (Ec2ServiceTaskDefTaskRole27A5D642) 
  0/50 | 11:35:04 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role                            | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/ServiceRole (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3) 
  0/50 | 11:35:04 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role                            | Ec2Cluster/DefaultAutoScalingGroup/InstanceRole (Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898) 
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::EC2::EIP                             | MyVpc/PublicSubnet1/EIP (MyVpcPublicSubnet1EIP096967CB) 
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::ECS::Cluster                         | Ec2Cluster (Ec2ClusterEE43E89D) 
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::EC2::VPC                             | MyVpc (MyVpcF9F0CA6F) 
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role                            | Ec2Service/TaskDef/TaskRole (Ec2ServiceTaskDefTaskRole27A5D642) Resource creation Initiated
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::SNS::Topic                           | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Topic (Ec2ClusterDefaultAutoScalingGroupDrainECSHookTopic798CDC5F) Resource creation Initiated
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role                            | Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook/Role (Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7) Resource creation Initiated
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::EC2::EIP                             | MyVpc/PublicSubnet2/EIP (MyVpcPublicSubnet2EIP8CCBA239) 
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::ECS::Cluster                         | Ec2Cluster (Ec2ClusterEE43E89D) Resource creation Initiated
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::EC2::InternetGateway                 | MyVpc/IGW (MyVpcIGW5C4A4F63) 
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::EC2::EIP                             | MyVpc/PublicSubnet1/EIP (MyVpcPublicSubnet1EIP096967CB) Resource creation Initiated
  0/50 | 11:35:05 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role                            | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/ServiceRole (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3) Resource creation Initiated
  0/50 | 11:35:06 AM | CREATE_IN_PROGRESS   | AWS::EC2::VPC                             | MyVpc (MyVpcF9F0CA6F) Resource creation Initiated
  1/50 | 11:35:06 AM | CREATE_COMPLETE      | AWS::ECS::Cluster                         | Ec2Cluster (Ec2ClusterEE43E89D) 
  1/50 | 11:35:06 AM | CREATE_IN_PROGRESS   | AWS::IAM::Role                            | Ec2Cluster/DefaultAutoScalingGroup/InstanceRole (Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898) Resource creation Initiated
  1/50 | 11:35:06 AM | CREATE_IN_PROGRESS   | AWS::EC2::InternetGateway                 | MyVpc/IGW (MyVpcIGW5C4A4F63) Resource creation Initiated
  1/50 | 11:35:06 AM | CREATE_IN_PROGRESS   | AWS::EC2::EIP                             | MyVpc/PublicSubnet2/EIP (MyVpcPublicSubnet2EIP8CCBA239) Resource creation Initiated
  1/50 | 11:35:07 AM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata                        | CDKMetadata Resource creation Initiated
  2/50 | 11:35:07 AM | CREATE_COMPLETE      | AWS::CDK::Metadata                        | CDKMetadata 
  3/50 | 11:35:16 AM | CREATE_COMPLETE      | AWS::SNS::Topic                           | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Topic (Ec2ClusterDefaultAutoScalingGroupDrainECSHookTopic798CDC5F) 
  4/50 | 11:35:21 AM | CREATE_COMPLETE      | AWS::EC2::EIP                             | MyVpc/PublicSubnet1/EIP (MyVpcPublicSubnet1EIP096967CB) 
  5/50 | 11:35:22 AM | CREATE_COMPLETE      | AWS::EC2::EIP                             | MyVpc/PublicSubnet2/EIP (MyVpcPublicSubnet2EIP8CCBA239) 
  6/50 | 11:35:22 AM | CREATE_COMPLETE      | AWS::EC2::InternetGateway                 | MyVpc/IGW (MyVpcIGW5C4A4F63) 
  7/50 | 11:35:22 AM | CREATE_COMPLETE      | AWS::EC2::VPC                             | MyVpc (MyVpcF9F0CA6F) 
  8/50 | 11:35:23 AM | CREATE_COMPLETE      | AWS::IAM::Role                            | Ec2Service/TaskDef/TaskRole (Ec2ServiceTaskDefTaskRole27A5D642) 
  9/50 | 11:35:23 AM | CREATE_COMPLETE      | AWS::IAM::Role                            | Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook/Role (Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7) 
 10/50 | 11:35:23 AM | CREATE_COMPLETE      | AWS::IAM::Role                            | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/ServiceRole (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3) 
 11/50 | 11:35:23 AM | CREATE_COMPLETE      | AWS::IAM::Role                            | Ec2Cluster/DefaultAutoScalingGroup/InstanceRole (Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898) 
 11/50 | 11:35:26 AM | CREATE_IN_PROGRESS   | AWS::EC2::Subnet                          | MyVpc/PublicSubnet2/Subnet (MyVpcPublicSubnet2Subnet492B6BFB) 
 11/50 | 11:35:26 AM | CREATE_IN_PROGRESS   | AWS::EC2::Subnet                          | MyVpc/PublicSubnet1/Subnet (MyVpcPublicSubnet1SubnetF6608456) 
 11/50 | 11:35:26 AM | CREATE_IN_PROGRESS   | AWS::EC2::RouteTable                      | MyVpc/PublicSubnet1/RouteTable (MyVpcPublicSubnet1RouteTableC46AB2F4) 
 11/50 | 11:35:26 AM | CREATE_IN_PROGRESS   | AWS::EC2::Subnet                          | MyVpc/PrivateSubnet2/Subnet (MyVpcPrivateSubnet2Subnet0040C983) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::SecurityGroup                   | Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup (Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroup149B0A9E) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::RouteTable                      | MyVpc/PrivateSubnet2/RouteTable (MyVpcPrivateSubnet2RouteTableCEDCEECE) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::RouteTable                      | MyVpc/PrivateSubnet1/RouteTable (MyVpcPrivateSubnet1RouteTable8819E6E2) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::SecurityGroup                   | Ec2Service/LB/SecurityGroup (Ec2ServiceLBSecurityGroup45FED6DF) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::ElasticLoadBalancingV2::TargetGroup  | Ec2Service/LB/PublicListener/ECSGroup (Ec2ServiceLBPublicListenerECSGroup3DC8690E) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::Subnet                          | MyVpc/PrivateSubnet1/Subnet (MyVpcPrivateSubnet1Subnet5057CF7E) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::Subnet                          | MyVpc/PublicSubnet2/Subnet (MyVpcPublicSubnet2Subnet492B6BFB) Resource creation Initiated
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::VPCGatewayAttachment            | MyVpc/VPCGW (MyVpcVPCGW488ACE0D) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::IAM::Policy                          | Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook/Role/DefaultPolicy (Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicyE499974B) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::IAM::InstanceProfile                 | Ec2Cluster/DefaultAutoScalingGroup/InstanceProfile (Ec2ClusterDefaultAutoScalingGroupInstanceProfileDB232471) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::RouteTable                      | MyVpc/PrivateSubnet2/RouteTable (MyVpcPrivateSubnet2RouteTableCEDCEECE) Resource creation Initiated
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::IAM::Policy                          | Ec2Cluster/DefaultAutoScalingGroup/InstanceRole/DefaultPolicy (Ec2ClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy6D2DC2FD) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::RouteTable                      | MyVpc/PublicSubnet1/RouteTable (MyVpcPublicSubnet1RouteTableC46AB2F4) Resource creation Initiated
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::Subnet                          | MyVpc/PublicSubnet1/Subnet (MyVpcPublicSubnet1SubnetF6608456) Resource creation Initiated
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::Subnet                          | MyVpc/PrivateSubnet2/Subnet (MyVpcPrivateSubnet2Subnet0040C983) Resource creation Initiated
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::RouteTable                      | MyVpc/PrivateSubnet1/RouteTable (MyVpcPrivateSubnet1RouteTable8819E6E2) Resource creation Initiated
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::RouteTable                      | MyVpc/PublicSubnet2/RouteTable (MyVpcPublicSubnet2RouteTable1DF17386) 
 11/50 | 11:35:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::VPCGatewayAttachment            | MyVpc/VPCGW (MyVpcVPCGW488ACE0D) Resource creation Initiated
 11/50 | 11:35:28 AM | CREATE_IN_PROGRESS   | AWS::IAM::Policy                          | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/ServiceRole/DefaultPolicy (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicy638C9E33) 
 11/50 | 11:35:28 AM | CREATE_IN_PROGRESS   | AWS::ElasticLoadBalancingV2::TargetGroup  | Ec2Service/LB/PublicListener/ECSGroup (Ec2ServiceLBPublicListenerECSGroup3DC8690E) Resource creation Initiated
 11/50 | 11:35:28 AM | CREATE_IN_PROGRESS   | AWS::EC2::Subnet                          | MyVpc/PrivateSubnet1/Subnet (MyVpcPrivateSubnet1Subnet5057CF7E) Resource creation Initiated
 11/50 | 11:35:28 AM | CREATE_IN_PROGRESS   | AWS::ECS::TaskDefinition                  | Ec2Service/TaskDef (Ec2ServiceTaskDef8D94BAA3) 
 12/50 | 11:35:28 AM | CREATE_COMPLETE      | AWS::ElasticLoadBalancingV2::TargetGroup  | Ec2Service/LB/PublicListener/ECSGroup (Ec2ServiceLBPublicListenerECSGroup3DC8690E) 
 12/50 | 11:35:28 AM | CREATE_IN_PROGRESS   | AWS::EC2::RouteTable                      | MyVpc/PublicSubnet2/RouteTable (MyVpcPublicSubnet2RouteTable1DF17386) Resource creation Initiated
 13/50 | 11:35:28 AM | CREATE_COMPLETE      | AWS::EC2::RouteTable                      | MyVpc/PrivateSubnet2/RouteTable (MyVpcPrivateSubnet2RouteTableCEDCEECE) 
 13/50 | 11:35:28 AM | CREATE_IN_PROGRESS   | AWS::ECS::TaskDefinition                  | Ec2Service/TaskDef (Ec2ServiceTaskDef8D94BAA3) Resource creation Initiated
 14/50 | 11:35:28 AM | CREATE_COMPLETE      | AWS::EC2::RouteTable                      | MyVpc/PublicSubnet1/RouteTable (MyVpcPublicSubnet1RouteTableC46AB2F4) 
 15/50 | 11:35:28 AM | CREATE_COMPLETE      | AWS::EC2::RouteTable                      | MyVpc/PrivateSubnet1/RouteTable (MyVpcPrivateSubnet1RouteTable8819E6E2) 
 15/50 | 11:35:28 AM | CREATE_IN_PROGRESS   | AWS::IAM::InstanceProfile                 | Ec2Cluster/DefaultAutoScalingGroup/InstanceProfile (Ec2ClusterDefaultAutoScalingGroupInstanceProfileDB232471) Resource creation Initiated
 16/50 | 11:35:29 AM | CREATE_COMPLETE      | AWS::ECS::TaskDefinition                  | Ec2Service/TaskDef (Ec2ServiceTaskDef8D94BAA3) 
 16/50 | 11:35:29 AM | CREATE_IN_PROGRESS   | AWS::IAM::Policy                          | Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook/Role/DefaultPolicy (Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicyE499974B) Resource creation Initiated
 16/50 | 11:35:29 AM | CREATE_IN_PROGRESS   | AWS::IAM::Policy                          | Ec2Cluster/DefaultAutoScalingGroup/InstanceRole/DefaultPolicy (Ec2ClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy6D2DC2FD) Resource creation Initiated
 17/50 | 11:35:29 AM | CREATE_COMPLETE      | AWS::EC2::RouteTable                      | MyVpc/PublicSubnet2/RouteTable (MyVpcPublicSubnet2RouteTable1DF17386) 
 17/50 | 11:35:30 AM | CREATE_IN_PROGRESS   | AWS::IAM::Policy                          | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/ServiceRole/DefaultPolicy (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicy638C9E33) Resource creation Initiated
 17/50 | 11:35:32 AM | CREATE_IN_PROGRESS   | AWS::EC2::SecurityGroup                   | Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup (Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroup149B0A9E) Resource creation Initiated
 17/50 | 11:35:32 AM | CREATE_IN_PROGRESS   | AWS::EC2::SecurityGroup                   | Ec2Service/LB/SecurityGroup (Ec2ServiceLBSecurityGroup45FED6DF) Resource creation Initiated
 18/50 | 11:35:33 AM | CREATE_COMPLETE      | AWS::EC2::SecurityGroup                   | Ec2Service/LB/SecurityGroup (Ec2ServiceLBSecurityGroup45FED6DF) 
 19/50 | 11:35:34 AM | CREATE_COMPLETE      | AWS::EC2::SecurityGroup                   | Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup (Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroup149B0A9E) 
 20/50 | 11:35:37 AM | CREATE_COMPLETE      | AWS::IAM::Policy                          | Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook/Role/DefaultPolicy (Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicyE499974B) 
 21/50 | 11:35:37 AM | CREATE_COMPLETE      | AWS::IAM::Policy                          | Ec2Cluster/DefaultAutoScalingGroup/InstanceRole/DefaultPolicy (Ec2ClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy6D2DC2FD) 
 21/50 | 11:35:38 AM | CREATE_IN_PROGRESS   | AWS::EC2::SecurityGroupEgress             | Ec2Service/LB/SecurityGroup/to BonjourEc2ClusterDefaultAutoScalingGroupInstanceSecurityGroupE49ADAF5:32768-65535 (Ec2ServiceLBSecurityGrouptoBonjourEc2ClusterDefaultAutoScalingGroupInstanceSecurityGroupE49ADAF53276865535AC4204BB) 
 21/50 | 11:35:38 AM | CREATE_IN_PROGRESS   | AWS::EC2::SecurityGroupIngress            | Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup/from BonjourEc2ServiceLBSecurityGroup2185A60D:32768-65535 (Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroupfromBonjourEc2ServiceLBSecurityGroup2185A60D3276865535EC4EE766) 
 22/50 | 11:35:38 AM | CREATE_COMPLETE      | AWS::IAM::Policy                          | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/ServiceRole/DefaultPolicy (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicy638C9E33) 
 22/50 | 11:35:38 AM | CREATE_IN_PROGRESS   | AWS::EC2::SecurityGroupIngress            | Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup/from BonjourEc2ServiceLBSecurityGroup2185A60D:32768-65535 (Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroupfromBonjourEc2ServiceLBSecurityGroup2185A60D3276865535EC4EE766) Resource creation Initiated
 22/50 | 11:35:38 AM | CREATE_IN_PROGRESS   | AWS::EC2::SecurityGroupEgress             | Ec2Service/LB/SecurityGroup/to BonjourEc2ClusterDefaultAutoScalingGroupInstanceSecurityGroupE49ADAF5:32768-65535 (Ec2ServiceLBSecurityGrouptoBonjourEc2ClusterDefaultAutoScalingGroupInstanceSecurityGroupE49ADAF53276865535AC4204BB) Resource creation Initiated
 23/50 | 11:35:39 AM | CREATE_COMPLETE      | AWS::EC2::SecurityGroupIngress            | Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup/from BonjourEc2ServiceLBSecurityGroup2185A60D:32768-65535 (Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroupfromBonjourEc2ServiceLBSecurityGroup2185A60D3276865535EC4EE766) 
 24/50 | 11:35:40 AM | CREATE_COMPLETE      | AWS::EC2::SecurityGroupEgress             | Ec2Service/LB/SecurityGroup/to BonjourEc2ClusterDefaultAutoScalingGroupInstanceSecurityGroupE49ADAF5:32768-65535 (Ec2ServiceLBSecurityGrouptoBonjourEc2ClusterDefaultAutoScalingGroupInstanceSecurityGroupE49ADAF53276865535AC4204BB) 
 24/50 | 11:35:42 AM | CREATE_IN_PROGRESS   | AWS::Lambda::Function                     | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionE0DEFB31) 
 25/50 | 11:35:43 AM | CREATE_COMPLETE      | AWS::EC2::VPCGatewayAttachment            | MyVpc/VPCGW (MyVpcVPCGW488ACE0D) 
 25/50 | 11:35:43 AM | CREATE_IN_PROGRESS   | AWS::Lambda::Function                     | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionE0DEFB31) Resource creation Initiated
 26/50 | 11:35:44 AM | CREATE_COMPLETE      | AWS::EC2::Subnet                          | MyVpc/PrivateSubnet2/Subnet (MyVpcPrivateSubnet2Subnet0040C983) 
 27/50 | 11:35:44 AM | CREATE_COMPLETE      | AWS::EC2::Subnet                          | MyVpc/PublicSubnet2/Subnet (MyVpcPublicSubnet2Subnet492B6BFB) 
 28/50 | 11:35:44 AM | CREATE_COMPLETE      | AWS::Lambda::Function                     | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionE0DEFB31) 
 29/50 | 11:35:44 AM | CREATE_COMPLETE      | AWS::EC2::Subnet                          | MyVpc/PublicSubnet1/Subnet (MyVpcPublicSubnet1SubnetF6608456) 
 30/50 | 11:35:44 AM | CREATE_COMPLETE      | AWS::EC2::Subnet                          | MyVpc/PrivateSubnet1/Subnet (MyVpcPrivateSubnet1Subnet5057CF7E) 
 30/50 | 11:35:47 AM | CREATE_IN_PROGRESS   | AWS::EC2::Route                           | MyVpc/PublicSubnet2/DefaultRoute (MyVpcPublicSubnet2DefaultRoute052936F6) 
 30/50 | 11:35:47 AM | CREATE_IN_PROGRESS   | AWS::EC2::Route                           | MyVpc/PublicSubnet1/DefaultRoute (MyVpcPublicSubnet1DefaultRoute95FDF9EB) 
 30/50 | 11:35:47 AM | CREATE_IN_PROGRESS   | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PrivateSubnet2/RouteTableAssociation (MyVpcPrivateSubnet2RouteTableAssociation86A610DA) 
 30/50 | 11:35:47 AM | CREATE_IN_PROGRESS   | AWS::SNS::Subscription                    | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/TopicSubscription (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscription5DE5A98D) 
 30/50 | 11:35:47 AM | CREATE_IN_PROGRESS   | AWS::EC2::Route                           | MyVpc/PublicSubnet1/DefaultRoute (MyVpcPublicSubnet1DefaultRoute95FDF9EB) Resource creation Initiated
 30/50 | 11:35:48 AM | CREATE_IN_PROGRESS   | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PublicSubnet2/RouteTableAssociation (MyVpcPublicSubnet2RouteTableAssociation227DE78D) 
 30/50 | 11:35:48 AM | CREATE_IN_PROGRESS   | AWS::EC2::Route                           | MyVpc/PublicSubnet2/DefaultRoute (MyVpcPublicSubnet2DefaultRoute052936F6) Resource creation Initiated
 30/50 | 11:35:48 AM | CREATE_IN_PROGRESS   | AWS::Lambda::Permission                   | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/Topic (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionTopic6C30136B) 
 30/50 | 11:35:48 AM | CREATE_IN_PROGRESS   | AWS::EC2::NatGateway                      | MyVpc/PublicSubnet1/NATGateway (MyVpcPublicSubnet1NATGatewayAD3400C1) 
 30/50 | 11:35:48 AM | CREATE_IN_PROGRESS   | AWS::EC2::NatGateway                      | MyVpc/PublicSubnet2/NATGateway (MyVpcPublicSubnet2NATGateway91BFBEC9) 
 30/50 | 11:35:48 AM | CREATE_IN_PROGRESS   | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PrivateSubnet1/RouteTableAssociation (MyVpcPrivateSubnet1RouteTableAssociation56D38C7E) 
 30/50 | 11:35:48 AM | CREATE_IN_PROGRESS   | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PublicSubnet1/RouteTableAssociation (MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB) 
 30/50 | 11:35:48 AM | CREATE_IN_PROGRESS   | AWS::Lambda::Permission                   | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/Topic (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionTopic6C30136B) Resource creation Initiated
 30/50 | 11:35:48 AM | CREATE_IN_PROGRESS   | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PrivateSubnet2/RouteTableAssociation (MyVpcPrivateSubnet2RouteTableAssociation86A610DA) Resource creation Initiated
 30/50 | 11:35:48 AM | CREATE_IN_PROGRESS   | AWS::EC2::NatGateway                      | MyVpc/PublicSubnet1/NATGateway (MyVpcPublicSubnet1NATGatewayAD3400C1) Resource creation Initiated
 30/50 | 11:35:49 AM | CREATE_IN_PROGRESS   | AWS::EC2::NatGateway                      | MyVpc/PublicSubnet2/NATGateway (MyVpcPublicSubnet2NATGateway91BFBEC9) Resource creation Initiated
 30/50 | 11:35:49 AM | CREATE_IN_PROGRESS   | AWS::SNS::Subscription                    | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/TopicSubscription (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscription5DE5A98D) Resource creation Initiated
 31/50 | 11:35:49 AM | CREATE_COMPLETE      | AWS::SNS::Subscription                    | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/TopicSubscription (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscription5DE5A98D) 
 31/50 | 11:35:49 AM | CREATE_IN_PROGRESS   | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PublicSubnet2/RouteTableAssociation (MyVpcPublicSubnet2RouteTableAssociation227DE78D) Resource creation Initiated
 31/50 | 11:35:49 AM | CREATE_IN_PROGRESS   | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PublicSubnet1/RouteTableAssociation (MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB) Resource creation Initiated
 31/50 | 11:35:49 AM | CREATE_IN_PROGRESS   | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PrivateSubnet1/RouteTableAssociation (MyVpcPrivateSubnet1RouteTableAssociation56D38C7E) Resource creation Initiated
 32/50 | 11:35:58 AM | CREATE_COMPLETE      | AWS::Lambda::Permission                   | Ec2Cluster/DefaultAutoScalingGroup/DrainECSHook/Function/Topic (Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionTopic6C30136B) 
 33/50 | 11:36:03 AM | CREATE_COMPLETE      | AWS::EC2::Route                           | MyVpc/PublicSubnet1/DefaultRoute (MyVpcPublicSubnet1DefaultRoute95FDF9EB) 
 34/50 | 11:36:03 AM | CREATE_COMPLETE      | AWS::EC2::Route                           | MyVpc/PublicSubnet2/DefaultRoute (MyVpcPublicSubnet2DefaultRoute052936F6) 
 35/50 | 11:36:04 AM | CREATE_COMPLETE      | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PrivateSubnet2/RouteTableAssociation (MyVpcPrivateSubnet2RouteTableAssociation86A610DA) 
 36/50 | 11:36:04 AM | CREATE_COMPLETE      | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PublicSubnet2/RouteTableAssociation (MyVpcPublicSubnet2RouteTableAssociation227DE78D) 
 37/50 | 11:36:05 AM | CREATE_COMPLETE      | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PublicSubnet1/RouteTableAssociation (MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB) 
 38/50 | 11:36:05 AM | CREATE_COMPLETE      | AWS::EC2::SubnetRouteTableAssociation     | MyVpc/PrivateSubnet1/RouteTableAssociation (MyVpcPrivateSubnet1RouteTableAssociation56D38C7E) 
 38/50 | 11:36:08 AM | CREATE_IN_PROGRESS   | AWS::ElasticLoadBalancingV2::LoadBalancer | Ec2Service/LB (Ec2ServiceLB381329CE) 
 38/50 | 11:36:10 AM | CREATE_IN_PROGRESS   | AWS::ElasticLoadBalancingV2::LoadBalancer | Ec2Service/LB (Ec2ServiceLB381329CE) Resource creation Initiated
38/50 Currently in progress: Ec2ClusterDefaultAutoScalingGroupInstanceProfileDB232471, MyVpcPublicSubnet1NATGatewayAD3400C1, MyVpcPublicSubnet2NATGateway91BFBEC9, Ec2ServiceLB381329CE
 39/50 | 11:37:22 AM | CREATE_COMPLETE      | AWS::EC2::NatGateway                      | MyVpc/PublicSubnet1/NATGateway (MyVpcPublicSubnet1NATGatewayAD3400C1) 
 39/50 | 11:37:26 AM | CREATE_IN_PROGRESS   | AWS::EC2::Route                           | MyVpc/PrivateSubnet1/DefaultRoute (MyVpcPrivateSubnet1DefaultRouteA8CDE2FA) 
 39/50 | 11:37:27 AM | CREATE_IN_PROGRESS   | AWS::EC2::Route                           | MyVpc/PrivateSubnet1/DefaultRoute (MyVpcPrivateSubnet1DefaultRouteA8CDE2FA) Resource creation Initiated
 40/50 | 11:37:30 AM | CREATE_COMPLETE      | AWS::IAM::InstanceProfile                 | Ec2Cluster/DefaultAutoScalingGroup/InstanceProfile (Ec2ClusterDefaultAutoScalingGroupInstanceProfileDB232471) 
 40/50 | 11:37:34 AM | CREATE_IN_PROGRESS   | AWS::AutoScaling::LaunchConfiguration     | Ec2Cluster/DefaultAutoScalingGroup/LaunchConfig (Ec2ClusterDefaultAutoScalingGroupLaunchConfig7B2FED3A) 
 40/50 | 11:37:35 AM | CREATE_IN_PROGRESS   | AWS::AutoScaling::LaunchConfiguration     | Ec2Cluster/DefaultAutoScalingGroup/LaunchConfig (Ec2ClusterDefaultAutoScalingGroupLaunchConfig7B2FED3A) Resource creation Initiated
 41/50 | 11:37:35 AM | CREATE_COMPLETE      | AWS::AutoScaling::LaunchConfiguration     | Ec2Cluster/DefaultAutoScalingGroup/LaunchConfig (Ec2ClusterDefaultAutoScalingGroupLaunchConfig7B2FED3A) 
 42/50 | 11:37:38 AM | CREATE_COMPLETE      | AWS::EC2::NatGateway                      | MyVpc/PublicSubnet2/NATGateway (MyVpcPublicSubnet2NATGateway91BFBEC9) 
 42/50 | 11:37:39 AM | CREATE_IN_PROGRESS   | AWS::AutoScaling::AutoScalingGroup        | Ec2Cluster/DefaultAutoScalingGroup/ASG (Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0) 
 42/50 | 11:37:41 AM | CREATE_IN_PROGRESS   | AWS::AutoScaling::AutoScalingGroup        | Ec2Cluster/DefaultAutoScalingGroup/ASG (Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0) Resource creation Initiated
 42/50 | 11:37:42 AM | CREATE_IN_PROGRESS   | AWS::EC2::Route                           | MyVpc/PrivateSubnet2/DefaultRoute (MyVpcPrivateSubnet2DefaultRoute9CE96294) 
 43/50 | 11:37:43 AM | CREATE_COMPLETE      | AWS::EC2::Route                           | MyVpc/PrivateSubnet1/DefaultRoute (MyVpcPrivateSubnet1DefaultRouteA8CDE2FA) 
 43/50 | 11:37:43 AM | CREATE_IN_PROGRESS   | AWS::EC2::Route                           | MyVpc/PrivateSubnet2/DefaultRoute (MyVpcPrivateSubnet2DefaultRoute9CE96294) Resource creation Initiated
 44/50 | 11:37:59 AM | CREATE_COMPLETE      | AWS::EC2::Route                           | MyVpc/PrivateSubnet2/DefaultRoute (MyVpcPrivateSubnet2DefaultRoute9CE96294) 
 45/50 | 11:38:11 AM | CREATE_COMPLETE      | AWS::ElasticLoadBalancingV2::LoadBalancer | Ec2Service/LB (Ec2ServiceLB381329CE) 
 45/50 | 11:38:15 AM | CREATE_IN_PROGRESS   | AWS::ElasticLoadBalancingV2::Listener     | Ec2Service/LB/PublicListener (Ec2ServiceLBPublicListenerA941070C) 
 45/50 | 11:38:15 AM | CREATE_IN_PROGRESS   | AWS::ElasticLoadBalancingV2::Listener     | Ec2Service/LB/PublicListener (Ec2ServiceLBPublicListenerA941070C) Resource creation Initiated
 46/50 | 11:38:16 AM | CREATE_COMPLETE      | AWS::ElasticLoadBalancingV2::Listener     | Ec2Service/LB/PublicListener (Ec2ServiceLBPublicListenerA941070C) 
 46/50 | 11:38:20 AM | CREATE_IN_PROGRESS   | AWS::ECS::Service                         | Ec2Service/Service/Service (Ec2Service398F0E46) 
 46/50 | 11:38:21 AM | CREATE_IN_PROGRESS   | AWS::ECS::Service                         | Ec2Service/Service/Service (Ec2Service398F0E46) Resource creation Initiated
46/50 Currently in progress: Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0, Ec2Service398F0E46
 47/50 | 11:39:12 AM | CREATE_COMPLETE      | AWS::AutoScaling::AutoScalingGroup        | Ec2Cluster/DefaultAutoScalingGroup/ASG (Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0) 
 47/50 | 11:39:17 AM | CREATE_IN_PROGRESS   | AWS::AutoScaling::LifecycleHook           | Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook (Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHook5CB1467E) 
 47/50 | 11:39:18 AM | CREATE_IN_PROGRESS   | AWS::AutoScaling::LifecycleHook           | Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook (Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHook5CB1467E) Resource creation Initiated
 48/50 | 11:39:18 AM | CREATE_COMPLETE      | AWS::AutoScaling::LifecycleHook           | Ec2Cluster/DefaultAutoScalingGroup/LifecycleHookDrainHook (Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHook5CB1467E) 
48/50 Currently in progress: Ec2Service398F0E46
 49/50 | 11:40:22 AM | CREATE_COMPLETE      | AWS::ECS::Service                         | Ec2Service/Service/Service (Ec2Service398F0E46) 
 50/50 | 11:40:26 AM | CREATE_COMPLETE      | AWS::CloudFormation::Stack                | Bonjour 

 ✅  Bonjour

Outputs:
Bonjour.Ec2ServiceLoadBalancerDNS6983C9B2 = Bonjo-Ec2Se-1A3MUCXOPEOUH-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
Bonjour.LoadBalancerDNS = Bonjo-Ec2Se-1A3MUCXOPEOUH-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxx:stack/Bonjour/73780da0-6fa7-11e9-9437-0e72822fc3e0

動作確認

OutputされたELBのURLにアクセスしてみます。
image.png
サンプルアプリが表示されました。

ECS Serviceを確認します。

aws ecs describe-services --cluster  Bonjour-Ec2ClusterEE43E89D-10ZEEGEWTOSJM --services 
Bonjour-Ec2Service398F0E46-1D6RQQKKMTFQC 
{
    "services": [
        {
            "serviceArn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxx:service/Bonjour-Ec2Service398F0E46-1D6RQQKKMTFQC",
            "serviceName": "Bonjour-Ec2Service398F0E46-1D6RQQKKMTFQC",
            "clusterArn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxx:cluster/Bonjour-Ec2ClusterEE43E89D-10ZEEGEWTOSJM",
            "loadBalancers": [
                {
                    "targetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxx:targetgroup/Bonjo-Ec2Se-ZEBMGDX3701N/d7c55f76db539196",
                    "containerName": "web",
                    "containerPort": 80
                }
            ],
            "serviceRegistries": [],
            "status": "ACTIVE",
            "desiredCount": 1,
            "runningCount": 1,
            "pendingCount": 0,
            "launchType": "EC2",
            "taskDefinition": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxx:task-definition/BonjourEc2ServiceTaskDef2C3EE7C1:1",
            "deploymentConfiguration": {
                "maximumPercent": 200,
                "minimumHealthyPercent": 50
            },
            "deployments": [
                {
                    "id": "ecs-svc/9223370479744474602",
                    "status": "PRIMARY",
                    "taskDefinition": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxx:task-definition/BonjourEc2ServiceTaskDef2C3EE7C1:1",
                    "desiredCount": 1,
                    "pendingCount": 0,
                    "runningCount": 1,
                    "createdAt": 1557110301.205,
                    "updatedAt": 1557110412.881,
                    "launchType": "EC2"
                }
            ],
            "roleArn": "arn:aws:iam::xxxxxxxxxx:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS",
            "events": [
                {
                    "id": "37a6a1c7-a198-42b9-9af3-b1f6ba01b735",
                    "createdAt": 1557110412.889,
                    "message": "(service Bonjour-Ec2Service398F0E46-1D6RQQKKMTFQC) has reached a steady state."
                },
                {
                    "id": "24d2abc5-c8c1-4e82-ac1f-99ae8651b337",
                    "createdAt": 1557110390.039,
                    "message": "(service Bonjour-Ec2Service398F0E46-1D6RQQKKMTFQC) registered 1 targets in (target-group arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxx:targetgroup/Bonjo-Ec2Se-ZEBMGDX3701N/d7c55f76db539196)"
                },
                {
                    "id": "2cd54fa3-8f26-4f7c-9a0e-556920496323",
                    "createdAt": 1557110368.539,
                    "message": "(service Bonjour-Ec2Service398F0E46-1D6RQQKKMTFQC) has started 1 tasks: (task 9f502db6-5a7a-433b-858b-18feb80ce01a)."
                },
                {
                    "id": "8fe3d401-4dee-404e-bb30-b6154570177c",
                    "createdAt": 1557110302.67,
                    "message": "(service Bonjour-Ec2Service398F0E46-1D6RQQKKMTFQC) was unable to place a task because no container instance met all of its requirements. Reason: No Container Instances were found in your cluster. For more information, see the Troubleshooting section of the Amazon ECS Developer Guide."
                }
            ],
            "createdAt": 1557110301.205,
            "placementConstraints": [],
            "placementStrategy": [],
            "healthCheckGracePeriodSeconds": 0,
            "schedulingStrategy": "REPLICA",
            "enableECSManagedTags": false,
            "propagateTags": "NONE"
        }
    ],
    "failures": []
}

ECS Service、ホストのEC2インスタンスのAutoScalingはスケーリングポリシーは設定されずDesired Capacityを指定して手動スケールする設定となっていました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2サーバが重いのでインスタンスストアボリュームを初期化した

はじめに

EC2インスタンスでインスタンスストアのボリュームを使う際は、最初に初期化をしなければなりません。
このことを知らず、初期化をしないで使い続けた結果、サーバに負荷をかけてしまったことと、初期化によって解決したことの記録です。

起きたこと

・Webアプリの画面に、2時間ほど接続できなくなった
・Webアプリのバッチが、通常2分ほどで終わるものが30分かかった
・これらのことが不定期に起こった

調査

sarコマンドで調べたところ、iowaitとロードアベレージの負荷が高いことはわかりました。
しかし、根本的な原因がわからないため、AWSサポートへ質問したところ、

・インスタンスストアがボトルネックになった可能性がある
・インスタンスストアが高いディスクパフォーマンスを必要とする場合には一度初期化が必要だが、実施しているか
との回答をいただきました。

インスタンスストアとは
インスタンスに刺さっているストレージです。高速に読み書きができる一方、サーバを再起動するとデータが消えてしまうという特徴があります。
本件では一時ファイルの置き場として利用しています。

詳しくは公式ドキュメントを参照してください。
AWSドキュメント - インスタンスストア

初期化とは
インスタンスストアボリュームへの最初の書き込みは、速度が遅いという特性があります。
先にすべての場所に書き込みを行うことで、パフォーマンスを上げることを「初期化」と呼ぶようです。

数年前までは、EBSも同じように「プレウォーミング」と呼ばれる初期化を必要としていましたが、現在は不要になっています。

対応

インスタンスストアボリュームの初期化を実行しました。
方法は、 dd コマンドでインスタンスストアボリュームに書き込みをします。一度書き込むことで、そのあとの読み書きがスムーズになるという寸法です。

!!!注意!!!
インスタンスを作成した直後に行うこと。
既に運用が始まっている場合は、バッチやCGIに影響を与えないよう、一時ファイル置き場の参照先を変更するなどの対策が必要です。

$ dd if=/dev/zero of=/dev/sdb bs=1M          
$ dd if=/dev/zero of=/dev/sdc bs=1M          
$ dd if=/dev/zero of=/dev/sdd bs=1M          
$ dd if=/dev/zero of=/dev/sde bs=1M

結果

不定期に起きていたサーバの高負荷が起きなくなくなりました。

注意点

インスタンスストアボリュームの初期化は、インスタンスを停止/起動(再起動)したときに都度行う必要があります。
起動スクリプトを組んでおくとよいでしょう。

参考

AWSドキュメント - インスタンスストアボリュームのディスクパフォーマンスの最適化

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Machine Learning のサンプルを動かしてみる

あらすじ

これまで、混同行列とROC 曲線について学んでいきました。その知識を活かしてAWS Machine Learning を触っていきたいと思います。

p.png

前回までの話:
混同行列(Confusion Matrix) とは 〜 2 値分類の機械学習のクラス分類について
ROC 曲線とAUC を用いて2値分類機械学習モデルの性能を計測・チューニングする

ストーリー

今回はAWS 側のチュートリアルで準備されている「とあるお店にて新しい商品を出したときにそれに顧客は反応(購入)をしてくれるか?」という推測を機械学習(ロジスティック回帰)を利用して学んでいきます。

利用するデータについて

今回はMachine Learning(以下:ML)でチュートリアルとして公開されている以下のデータを使います。

ML チュートリアル用データ(banking.csv)
https://s3.amazonaws.com/aml-sample-data/banking.csv

このデータは過去の顧客の詳細な動向(商品の購入や銀行の引き落としなど)の情報がCSV 形式で記録されていてこれを学習用データとして使います。
そして学習後、特定の顧客があなたの新しい製品を出したときにアクションを起こす可能性が高いかどうかを機械学習を使って推測してみましょう。

学習用データの形式

学習用データはAmazon ML 用の独自CSV 形式で準備します。どういったところが独自CSV 形式なのかというと、例えばCSV の先頭1
行目にはカラム名を挿入する等といったルールがあります。
詳しいデータのフォーマット形式については、以下のドキュメントを参考にしてください。

Understanding the Data Format for Amazon ML
https://docs.aws.amazon.com/machine-learning/latest/dg/understanding-the-data-format-for-amazon-ml.html

実際に上記の学習用データをテキストエディタで開くと、以下のように1 行目にカラム名が来てそれ以降はアトリビュートとして顧客の実データが入っています。

AWSMachineLearning_Tutorial0000_02.png

データの解説を少しすると、例えば上記のjob はカスタマーの職業を表しており、contact は問合せしたときのデバイスが記録されています。
そして一番右のフィールドはML モデルに「このカスタマーは新しい製品をサブスクライブしますか?」という質問への回答になり、いわゆるこれが、機械学習でいわれる教師データになります(Amazon ML ではターゲット属性と呼んでいる)。 この値は0(no) or 1(yes) の値になっており、オリジナルのデータはyes, no という文字列から、ML で学習させるために変換されています。
データの変換が完了したらML から読み取らせるために、あなたのS3 バケットにアップロードしてください。ただし、今回はAmazon で用意されているチュートリアルのファイルs3://ami-sample-data/banking.csvを使ってを動かすため、S3 へのアップロード手順及び作業は割愛します。

その他の機械学習で使えそうなデータについて

今回利用するお店の顧客の動向以外にも、機械学習に使えそうなデータが公開されているUCI Machine Learning Repository という場所があります。

UCI Machine Learning Repository
http://archive.ics.uci.edu/ml/index.php

例えば上記サイトでは山火事が発生した場所の学習用データであったり、アイリスの花弁や萼片の長さと種類のデータであったり機械学習で使えるデータを無料で利用することができます。
CSV 形式で提供されてはいますが、Amazon ML で使う場合は一部CSV の形式をAmazon ML 形式に適用するよう修正する必要があります。

データソースを作成する

学習用のデータが準備できたら、ML のデータソース作成へ進みます。
データソースとは、Amazon ML で使用する入力データのロケーション情報といった重要なデータやメタデータを含むAmazon ML のオブジェクトです。 Amazon ML はトレーニングや評価といったオペレーションのために、このデータソースを使います。
データソースを作成するために、以下の情報を設定します。

  • データのS3 のロケーションとアクセス権限
  • データ属性の名前及び型(Numeric(数値), Text(文字), Categorical(分類), binary(0 か1 か))
  • ML が予測フェーズで回答してほしい属性名。すなわちターゲット属性、教師データの属性名

それでは実際にチュートリアル用のデータソースを作成していきましょう。まずはデータソース作成画面を開きます。

AWSMachineLearning_Tutorial0000.png

学習用データはAmazon S3 もしくはAmazon Redshift 上に格納されているものが利用できます。今回指定するのは公開S3 上に格納されているs3://ami-sample-data/banking.csv です。
必要事項を入力すると以下のようになります。

AWSMachineLearning_Tutorial0001.png

Verify をクリックして検証を開始します。 ML がS3 のデータにアクセスする権限がない場合は、ここで権限を与えてよいか質問が来ます。

AWSMachineLearning_Tutorial0002.png

検証が完了すると次へ進めるのでContinue をクリックします。
スキーマ作成画面へ進みます。スキーマを作成する方法は主に以下の2 通りの方法があります。

  • S3 にデータをアップロードする時に分割されたスキーマファイルをアップロードする方法
  • Amazon ML サービス側に推測させる方法

今回はAmazon ML サービス側に推測させる方法で行ない、推測の結果を我々でレビューし、適宜修正して上げる方法で実施していきます。

【補足】スキーマファイルについての説明
https://docs.aws.amazon.com/machine-learning/latest/dg/creating-a-data-schema-for-amazon-ml.html

スキーマ作成画面を見るとCSV データの1 行目に書いてあるカラム名からデータの型が推測されるので、これをレビューして正しいデータの型となるように適宜修正していきます。
設定できるデータの型のとしては以下のものがあります。

  • Binary: yes またはno 等の2 つの状態のみを持つ属性
  • Category: 種類を示すために数値または文字列が使用される属性
  • Numeric: 順序に意味を持つ数値が使用される属性
  • Text: 空白で区切られた、単語から成る文字列として使用される属性(「空白で区切られた」ということは現状英語しか対応してなさそう)
Binary yes またはno 等の2 つの状態のみを持つ属性
Category 種類を示すために数値または文字列が使用される属性
Numeric 順序に意味を持つ数値が使用される属性
Text 空白で区切られた、単語から成る文字列として使用される属性(「空白で区切られた」ということは現状英語しか対応してなさそう)

このデータが間違ったままだとML が正しく学習を行えず、予測フェーズにて支障をきたす場合がありますので、注意してレビューを行うようにします。

内容を確認したらContinue をクリックします(今回のチュートリアル用データでは、全て正しく推測されるようになっている)。

AWSMachineLearning_Tutorial0004.png

すると、次はターゲット属性(教師データ)の選択画面へ移動します。 y カラムを選択してContinue をクリックします。

AWSMachineLearning_Tutorial0005.png

任意で行識別子(予測の時に、インプットデータのどの行に対応するのかを指し示すためのもの)を挿入するかどうかの選択が聞かれますのでno を選択してReview をクリックします。

AWSMachineLearning_Tutorial0006.png

内容に問題なければContinue をクリックします。これでML モデルを作成する準備ができました。

ML(Machine Learning) モデルを作成する

データソースを作成後、ML モデルを作成してトレーニングし、その結果を評価します。 ML モデルとはAmazon ML がトレーニング中にデータを見つけるパターン(どのような解析法を使う、学習の方法等)の集合でモデルを作成して予測に利用することができます。

ML モデル設定画面で、モデルの設定を行ないます。

ML model type BINARY 2 値分類。ロジスティック回帰アルゴリズムを使用してトレーニングされます
ML model target y ターゲット属性。教師データとする属性
ML model target(任意) Banking tutorial ML model 名。デフォルトでデータソース作成時に指定した値が表示される
Select training and evaluation settings Default (Recommended) ML の学習プロセスにおいてそれをコントロールするレシピとトレーニングパラメータ。今回は推奨となっているDefault を選択
Evaluation Name Banking tutorial この評価の名前。デフォルトでデータソース作成時に指定した値が表示される

内容を確認したらReview をクリックします。

AWSMachineLearning_Tutorial0007.png

内容に問題がなければCreate ML model をクリックしてML モデルを作成します。

AWSMachineLearning_Tutorial0008.png

ボタンをクリックするとAmazon ML はプロセスキューにあなたのML 入れます。
ML を作成した直後はStatus がPending になり、学習処理が開始されるとIn Progress に変わり、完了するとCompleted に変わります。
なおML で行われている学習ですが、今回のML ではデータ全体のうち70% のデータを学習用のデータとして抽出して残り30% を評価(テスト)として利用します。

モデルの予測のパフォーマンスの確認と、スコアの閾値の設定

業界標準のArea Under the (Receiver Operating Characteristic) Curve (AUC: 曲線下面積)メトリクスというML モデルのパフォーマンス品質を計測する精度法を提供しています。 AUC はネガティブ(ポジティブ)なサンプルとポジティブ(ネガティブ)なサンプルに対する、正しさの精度(ML としての性能)を計測する指標を持っています。
ここでは実際にAUC を使ってML の性能を確認し、ML が使われるビジネスの戦略に適合した挙動となるように閾値を調整してみましょう。

Amazon ML のリストページに移動しましょう。すると、以下のようなリストが表示されます。

AWSMachineLearning_Tutorial0010.png

その中でEvaluation を選択します。

AWSMachineLearning_Tutorial0011.png

すると上記のようなページが表示されるのでAUC(Area Under the Curve)パフォーマンスメロリックを確認しましょう。 するとAUC の値が0.936 と表示されており性能が良いということを表しています。
上記のままML を利用するのもアリなのですが、閾値を調整してML 推測の傾向をチューニングしてみましょう。 別の言い方をすれば、ML が「顧客は新しい商品を買ってくれますか?」という推測に対して、甘めの判断でYes!と推測するようにするか、厳し目の判断でNo!と推測とするかといった塩梅を、閾値を変更することで調整することができます。

では閾値を調整するためにAdjust score threshold をクリックしてみましょう。

AWSMachineLearning_Tutorial0012.png

するとTP(True Positive), TN(True Negative), FP(False Positive), FN(False Negative)の成績及び閾値を調整する項目が出現します。
表示されている内容を確認すると、正しく識別できている確率は91%($\dfrac{TP + TN}{TP + TN + FP + FN}=0.91$) で、だいたいの推測は問題なくできていることを示しています。

この画面で調整できるのは主に閾値です。
閾値を上げたり下げたりすることで、ML の推測の性能がどのように変わっていくかという簡単な表は以下の通りです(ML モデルが出した推測結果の分布によっては必ずしもこうなるとは限りません)。

閾値 TP TN FP FN

また、Advanced Metric に表示されている各項目の意味は以下の通りです。

False Positive Rate (FPR: 誤検出率)

本当は0(negative) と判断すべきところを誤って1(positive) と判断してしまう割合です。値が小さいほど性能が良いことを意味します。

  • False Positive Rate(FPR)

$FPR=\dfrac{FP}{FP + TN}$

Presision(精度)

1(Positive) と予測されるべき全結果の中で実際に1(Positive) として予測できた割合です。この値が大きいほど性能が良いことを意味します。

  • Presision

$Presision=\dfrac{TP}{TP + FP}$

Recall(リコール)

実際に1(Positive) を識別されるべき全データ(TP + FN)の内、実際に1(Positive)と識別されたデータ(TP)の割合を示します。この値が大きいほど性能が良いことを意味します。

  • Recall

$Recall=\dfrac{TP}{TP + FN}$

Accuracy(正確さ)

全予測結果のうち、正しく1(Positive)と推測できたものと、正しく0(Negative)
と推測できたものの割合です。正しく予測できた割合です。この値が大きいほど性能が良いことを意味します。

  • Accuracy

$Accuracy = \dfrac{TP + TN}{TP + FP + FN + TN}$

実際どれくらいの閾値に設定するか

今回のように新しい製品を打ってどれだけの人が反応してくれるかという問題については、本当は0(Negative)と判断すべき所を1(Positive)と判断してしまう(False Positive(FP))ことによって、余計な広告コストを払ってしまう可能性が高くなります(FP な人は買ってくれる見込みが殆どないので)。
そのため今回は誤検出率(FPR)を【下げる】ために、閾値を【上げる】というチューニングをして、余計な広告コストを払わない戦略を取るようにしてみましょう。

ということで、画面より閾値を0.77 あたりまで上げてみることにします。
すると全体の約3% の顧客を1(Positive) として推測されるようになります。そして、誤検出率(FPR) については0.8% 程までに下がるようになります。
(幾つか注意してほしい点としては、この約3% の中にはTP もFP も含まれていること、閾値0.5 の時よりPositive の総数が下がっていること、閾値を上げることでTP をいくつか失うこと等があります)

AWSMachineLearning_Tutorial0014.png

以上で閾値のチューニングは完了です。設定を保存してML の完成です。

誤検出率を下げすぎる(閾値を上げすぎる)とどうなるか

誤検出率を下げすぎると、ML はほぼすべてのケースで1(Positive) を回答してくれなくなります。

AWSMachineLearning_Tutorial0015.png

そして最大限まで誤検出率を下げる(閾値を上げる)と、ML はすべての推測要求に対して0(Negative)を返すようになってしまいます。 そのため、閾値は適度に設定するよう注意しましょう。

ML を使って予測をする

ML ができたところで、次はML を使って予測を実施してみましょう。
予測を実施する方式として、Amazon ML では主に以下の2 つの方法が用意されています。

  • リアルタイム推測(real-time predictions)
  • バッチ推測(iBatch Predictions)

リアルタイム推測(Try real-time predictions)

Web 画面から気軽に単発の推測要求を出すことができる、リアルタイム推測のやり方です。
ML model report のナビゲーションペインからTry real-time predictions を選択します。

AWSMachineLearning_Tutorial0016.png

Paste a record を選択して以下の通りCSV レコードを入力します。

AWSMachineLearning_Tutorial0017.png

32,services,divorced,basic.9y,no,unknown,yes,cellular,dec,mon,110,1,11,0,nonexistent,-1.8,94.465,-36.1,0.883,5228.1

このCSV レコードはとある32 歳の人のデータになります。この32 歳の人が新しい商品を買ってくれそうか推測してみましょう。

ペーストして、Submit ボタンを押すとCSV の定義が画面に整形されてた状態で表示されます。

AWSMachineLearning_Tutorial0018.jpg

入力内容を確認したらCreate Prediction をクリックします。
すると、ページ右側に予測の結果が出力されます。

AWSMachineLearning_Tutorial0019.png

結果を確認するとpredictedLabel が0 となっています。すなわち、この32 歳の顧客は新しい製品を出しても、きっと購入してくれないだろうというML の予測結果になりました。
このようにしてリアルタイム推測を使うことで、単発の推測を即時で実行することができます。

バッチ推測(Batch Predictions)

バッチ推測はあらかじめ推測させたい対象をCSV 形式でまとめておき、それをバッチ処理として推測させる方式です。
バッチ推測を利用するには画面左上のAmazon Machine Learning のメニューをクリックしBatch Predictions をクリックします。

AWSMachineLearning_Tutorial0020.png

Create new batch predictionをクリックします。

AWSMachineLearning_Tutorial0021.png

作成したML モデルの一覧が表示されるので、先程作成したML を選択します。

AWSMachineLearning_Tutorial0022.png

内容を確認してContinue をクリックします。

AWSMachineLearning_Tutorial0023.png

バッチ推測で使用するデータは、サンプルとして用意されているs3://aml-sample-data/banking-batch.csv を利用します。
ラジオボタンでMy data is in S3, and I need to create a datasource を選択し、その他ファイルのロケーション情報等を入力しContinue ボタンをクリックします。

AWSMachineLearning_Tutorial0024.png

次に、結果を格納するS3 バケットのロケーションを設定します。このS3 ロケーションは予め作成しておく必要がありますが、S3 バケットの作成方法については説明を割愛します。

AWSMachineLearning_Tutorial0025.png

以下のように書き込み権限を要求されたら、Yes をクリックして書き込み権限を与えるようにしてください。

AWSMachineLearning_Tutorial0026.png

確認画面が表示されるので、確認してCreate batch prediction をクリックします。

AWSMachineLearning_Tutorial0027.png

すると処理キューの中に推測バッチ処理が入りBatch prediction summary が表示され、処理が完了していない間はStatus がIn progress で表示されます。
しばらくして画面を更新するとStatusCompleted に更新されます。

AWSMachineLearning_Tutorial0028.png

/batch-prediction/result/ ディレクトリ配下にCSV ファイルをgz 圧縮したファイルが格納されているので、それをダウンロードして展開して中身を確認してみましょう。
中身はbestAnswer, score の2 項目が表示されています。 score は指数表記になっている場合があるのでスプレッドシートなどに張り替えて変換すると良いでしょう。

結果を確認すると総計4119 件で、1: Positiveは122 件、0: Negativeは 3997件あり、Positive とNegative の境界はML の評価のページで設定した0.77 になっています。

1   0.771587
1   0.771126
1   0.770161
0   0.767255
0   0.765271

またPositive の中での最高スコアは約0.993179で最低スコアは約0.770161、Negative の中での最高スコアは約0.767255で最低スコアは約0.000678 となっていることがわかります。

本チュートリアルを通して、新しい商品に対して購入のアクションをしてくれそうな人をML で予測してコストを削減しながら効率的な宣伝をするといった戦略をとる手助けとしてAmazon ML を使うこともできるのです。

参考

Amazon Machine Learning
https://aws.amazon.com/jp/machine-learning/

UCI Machine Learning Repository
http://archive.ics.uci.edu/ml/datasets.html

チュートリアルの解説
https://docs.aws.amazon.com/machine-learning/latest/dg/tutorial.html

AUC の簡単な説明
https://docs.aws.amazon.com/machine-learning/latest/dg/binary-model-insights.html?shortFooter=true#measuring-ml-model-accuracy

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Glue&Scala:ETLジョブでAmazon AthenaのPartitionKeyを追加する

Glue ジョブ スクリプトの編集にて

1.ソースファイルにimport文を追加

import com.amazonaws.services.glue.DynamicFrame
import com.amazonaws.services.glue.DynamicRecord
import com.amazonaws.services.glue.types.StringNode //今回はStringのみ。

2.カラム追加処理(DynamicFrame.mapメソッド、DynamicRecord.addFieldメソッド/getFieldメソッド、StringNode)

val datasource0 = glueContext.getCatalogSource(
     -- 中略 --
 ).getDynamicFrame()

//カラム追加処理
//今回は、誕生日(birthday)を西暦、月、日にパーティショニングしたかったため以下の処理。
//用途に応じて読み替えてください。
val addField1 = datasource0.map((rec: DynamicRecord) => {
    val mbody = rec.getField("birthday")// DynamicRecord.getFieldメソッドで指定カラムの値をOption型で取得
    val datePattern = """(\d{4})-(\d{2})-(\d{2})""".r //パターンマッチ用の正規表現

    mbody match {
        case Some(mval:String) => {
            mval match {
                case datePattern(y,m,d)=>{
                    rec.addField("year",StringNode(y)) //西暦カラムを追加。birthdayの西暦をセット
                    rec.addField("month",StringNode(m)) //月カラムを追加。birthdayの月をセット
                    rec.addField("day",StringNode(d)) //日カラムを追加。birthdayの日をセット
                    rec
                }
                case _ => rec
            }
        }
        case _ => rec
    }
})

 (以下略)

以下、本家より

  • DynamicFrame

    • def map
      • def map( f : DynamicRecord => DynamicRecord, errorMsg : String = "", transformationContext : String = "", callSite : CallSite = CallSite("Not provided", ""), stageThreshold : Long = 0, totalThreshold : Long = 0 ) : DynamicFrame
        • 指定した関数 "f" をこの DynamicFrame の各レコードに適用することで生成された新しい DynamicFrame を返します。
        • このメソッドは、指定した関数を適用する前に各レコードをコピーするため、レコードを安全に変更できます。特定のレコードでマッピング関数から例外がスローされた場合、そのレコードはエラーとしてマークされ、スタックトレースがエラーレコードの列として保存されます。
  • DynamicRecord

    • def addField
      • def addField( path : String,dynamicNode : DynamicNode) : Unit
        • 指定したパスに DynamicNode を追加します。
          • path — 追加するフィールドのパス。
          • dynamicNode — 指定したパスに追加する DynamicNode。
    • def getField
      • def getField( path : String ) : Option[Any]
        • DynamicNode のオプションとして指定した path でフィールドの値を取得します。
        • scala.Some Some (値) を返します。

3.追加マッピング

// val applymapping1 = datasource0.applyMapping(
val applymapping1 = addField1.applyMapping(
    mappings = Seq(
      -- 中略 --
            ("year","string","year","string"), //西暦カラムをStringで追加マッピング
            ("month","string","month","string"), //月カラムをStringで追加マッピング
            ("day","string","day","string"), //日カラムをStringで追加マッピング
      -- 中略 --
    ), caseSensitive = false, transformationContext = "applymapping1"
)
 (中略)

4.パーティショニングの設定

    val datasink4 = glueContext.getSinkWithFormat(
            connectionType = "s3", 
            options = JsonOptions(
                Map( 
           "path" -> "s3://【s3のパス】",
                    "partitionKeys" -> Seq( "year", "month","day") //西暦、月、日のパーティショニングを設定
                )
            ),
            transformationContext = "datasink4", 
            format = "parquet"
        ).writeDynamicFrame(dropnullfields3)

あとがき

  • ScalaでのETLプログラミングに関する情報が少なく苦労した。(Pythonはそれなりにある)
  • DynamicFrameを Apache Spark の DataFrame に変換してからカラム追加し、DynamicFrameに戻すやり方もあった。が、DynamicFrameで行えることはDynamicFrameで行うほうがよい。
  • 困ったら本家のマニュアル

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む