AWS 認定ソリューションアーキテクト – アソシエイト を1ヵ月で合格するためにしたこと

AWSの認定資格「ソリューションアーキテクト – アソシエイト」に合格することができました。
1ヵ月の間にどのような対策を行ったかメモとして残したいと思います。

自分のスペックは、

  • インフラ業務3年
  • AWS実務経験無し

です。

勉強法

取っ掛かりは以下の2つを平行して行いました。

  • 実際に手を動かしてみる
  • AWSで使用する単語を理解する

何も分からない状態からのスタートでしたが、アカウントの登録から始めてAWSが公開している公式ガイドを参考に環境を構築することができました。

WordPress ウェブサイトを構築する

ガイドに従って操作して、分からない単語があれば調べます。
ガイドの表現で分かりにくい部分はSlideShareのスライドを参考にしました。
検索窓に「AWS」と入力して検索するとたくさんのドキュメントが見つかります。

環境構築の費用ですが、AWSの12か月無料利用枠を活用すれば殆ど費用は掛かりません。
検証する時だけリソースを起動し、終了時にはリソースを停止するようにして進めたところ、発生したコストは$1ぐらいでした。


その後は問題集サイトのAWS WEB問題集で学習しようで有料会員登録を行い、問題を解き続けました。
問題集には参考になる資料へのリンクが多く載せられており理解が進みます。

問題を通して何が分かっていないかを確認し、分からない箇所があれば繰り返し復習しました。

500問程度回答したところで模擬試験を受講、1000問程度回答したところで本番試験に臨みました。

結果

総合評点: 83%

トピックレベルスコアリング:
1.0  Designing highly available, cost-efficient, fault-tolerant, scalable systems: 81%
2.0  Implementation/Deployment: 66%
3.0  Data Security: 90%
4.0  Troubleshooting: 100%

WEB問題集で出題された問題と同じ内容の問題は1割、2割程度でした。
問題集の回答を丸暗記する方法では難しいと感じました。

まとめ

個人的には試験の難易度は簡単ではないと感じましたが、既に参考になるブログやドキュメントが数多くあるため、実務経験が無くとも試験対策は十分に可能でした。

このメモがこれから試験に臨む方の役に立てば幸いです。

続きを読む

AWS MediaTailor を使ってADブロッカーを回避する方法

初めまして、streampackチームの Tanaka です。

背景

広告動画をスキップしたいがために ADブロッカーを入れたり、ブラウザ側が制御したりして、
広告配信側にとって本来流すものが流せなかったり、マネタイズにも影響したりことがあるかと思います。
直近では Safari 11 から Google IMA などを使った動画広告が正常に流れないケースがあったりということがありました。
動画広告ブロックの対策として、Server-Side Ad Insertion(SSAI) という技術が出て来ており、
AWSでも Media Services の一機能として MediaTailor というものが去年発表され SSAI が使えるようになっております。

そもそもServer Side Ad Insertionとは?

動画広告挿入方法には主に二つあります。

  • Client-Side Ad Insertion(CSAI)
  • Server-Side Ad Insertion(SSAI)

CSAIでは Google IMA ライブラリなどを使って、
プレイヤーのイベントを検知しVASTタグを解析を行い広告サーバへリクエストし、
動画の本編をストップさせ、動画広告を再生させることが主流です。

SSAIとはサーバサイドで広告動画と本編動画を結合(アドスティッチ)することを言います。
実際は一つの動画ファイルにするわけではなく、変換済みのプレイリストファイルを結合してあたかも一つの動画のように振舞います。
TVのように自然な流れで広告が流れるのが SSAI の特徴でもあります。

AWS MediaTailor について

すでにHLS動画とVASTタグをお持ちであれば、MediaTailor を使って、ストリームの中に広告動画の挿入かつ配信が可能です。MediaTailor は on-the-fly で動画広告をHLSに変換ししダイナミックに配信します。動画や広告毎にそれぞれの組み合わせの HLS を準備する必要がありません。また一度変換された動画広告はキャッシュされるので、不必要な変換はされないようです。

MediaTailorを使う前の準備

ここから、実際に使って試してみましょう。

Amazon S3 に HLS フォーマットのマニフェストファイル(.m3u8)とセグメントファイル(.ts)を事前に置く必要があります。
HLSへの出力は Amazon Elastic Transcoder を使ってもできますし、オープンソースの FFMpeg などで
HLSへのトランスコーディングし S3 に置くだけで完了です。
ただし、master.m3u8 のマニフェストファイルを準備する必要があるようなので、ご注意ください。playlist.m3u8ではエラーが返ってきます。

s3 の s3://xxx-sandbox/m3u8/ のパスは下記のようになっております。

master.m3u8 <- どのストリームを呼ぶか
playlist.m3u8 <- 動画の読み込む順番を決めたsegmentリスト
segment-00000.ts <- 動画の分割1
segment-00001.ts <- 動画の分割2
segment-00002.ts <- 動画の分割3

master.m3u8
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2265000,RESOLUTION=1024x576,CODECS="avc1.4d001f,mp4a.40.2"
playlist.m3u8
playlist.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:13
#EXTINF:12.100644,
segment-00000.ts
#EXTINF:9.025344,
segment-00001.ts
#EXTINF:3.743844,
segment-00002.ts
#EXT-X-ENDLIST

VAST/VMAPタグの準備

CSAIと同様にVASTタグを準備する必要があります。
今回は Google DFP で準備されているデモのタグを使います。

https://pubads.g.doubleclick.net/gampad/ads?sz=640×480&iu=/124319096/external/ad_rule_samples&ciu_szs=300×250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&vid=

初期設定

MediaTailor にアクセスします。
https://console.aws.amazon.com/mediatailor/home

Configurationの登録

AWS_MediaTailor.png

HLS Playback URLの取得

AWS_MediaTailor-detail.png

再生用の URL が出力されますので、そのURLの末尾に master.m3u8 をつけて、アクセスします。

https://#{HLS playback URL}/master.m3u8

再生確認

https___0aeee0c445304b619031824e149a4b3b_mediatailor_us-east-1_amazonaws_com_v1_master_4922321333d15f2b0483b46c6a724a8352b44854_AdTest_master_m3u8.png

動画の再生には HLS をサポートしているプレイヤーもしくはブラウザで確認します。
ブラウザだと Safari が対応していますので、Safari のURLに直接ペーストし、
動画広告が再生されればOKです。

まとめ

  • 登録は数ステップで可能、ただし、動画コンテンツ毎に Configuration が必要。
  • Tokyoリージョンでのサポートは未定
  • プレイヤーに動画広告の実装が不要

上記のことから、実践で配信システムと連携して採用するにはまだ難しそうですが、
プレイヤーの広告対応も不要なことから単体の動画コンテンツを MediaTailor を使って配信するには、悪くはないかもしれません。
Tokyoリージョンで使えるようになることに期待です!

続きを読む

NixOpsを使ってAWS EC2にGPUクラスタを作ってChainerMNを動かしてみた

使用するもの

NixOS

  • Linuxディストリビューションの1つ

    • 関数型パッケージ管理ツールNixを使用している
    • パッケージ自体はnixpkgsのリポジトリで管理・開発されている(個々のパッケージはhttps://nixos.org/nixos/packages.html# で検索できる)
  • 環境の構築が楽にできる
    • 1つのファイルでシステム全体の設定ができる -> 管理が楽
    • システムのアップデート -> システムのロールバック ということが楽にできる
    • sudoとかしなくてもパッケージを用意できる(たとえばpythonでnumpyとmatplotlibを使いたい時は以下のようにする)
$ nix-shell -p python3 -p pythonPackages.numpy -p pythonPackages.matplotlib

NixOps

  • NixOSの環境をリモートマシンにdeployするためのツール
  • 基本的な使い方は以下のような感じ
登録
$ nixops create [deployする環境を書いた.nixファイル(複数可)] -d [環境の名前]

インスタンスの作成&deploy
$ nixops deploy -d [環境の名前]

インスタンスの破棄
$ nixops destroy -d [環境の名前]

Chainer

  • ニューラルネットワーク記述用のpythonフレームワーク

ChainerMN

  • Chainerで複数のGPU上での分散処理を行うための追加パッケージ

はじめに

NixOpsでは、Nixのコードを書くことで、Nixパッケージマネージャを利用した環境をリモートにdeployすることができます。
これを使ってGPUクラスタをデプロイできないかということでやってみました。

利用環境

AWSのEC2のp2.xlargeのインスタンスを使用しました。
GPUはTesla K80になります。

CUDAはCUDA 9.0、cudnnはv7.0、NCCLはv2.1.2を使用します。
MPIはOpenMPIのv1.10です。
これらはすべて利用したnixpkgsに含まれているものを使用します。
(これを書いている時点ではnixpkgsは自前で用意しています)

コード

NixOpsで使用するコード
https://github.com/hyphon81/ec2-gpu-cluster-with-NixOps

./ec2-gpu-cluster-with-NixOps というディレクトリがある想定で書きます。

nixpkgs
https://github.com/hyphon81/nixpkgs/tree/add/chainermn

これについても、 ./nixpkgs が存在する前提で書きます。

事前準備

AWSの準備

  • 当然、アカウントを作ります。
  • NixOpsのマニュアルで説明されてるように、AWS IAMのアクセスキーIDとアクセスキーを入手します。
  • AWS EC2インスタンスで使用するセキュリティグループの設定をします。これは以下の設定を含む必要があります。
    • sshポートの許可
    • 同じセキュリティグループ内でのパケットの許可
  • AWS EC2で使用するsshキーペアを入手します。
  • p2.xlargeインスタンスを使用する場合、インスタンス作成の上限数が1に設定されていると思うので、上限規制緩和の申請を行います。

※これらの手順等についてはここでは説明しません。

ファイルの準備

~/.ec2-keys
<アクセスキーID> <アクセスキー> <IAMユーザ名>
~/.aws/credentials
[<IAMユーザ名>]
aws_access_key_id = <アクセスキーID>
aws_secret_access_key = <アクセスキー>
  • ./ec2-gpu-cluster-with-NixOps/ec2-info.nix を次のように用意します。
./ec2-gpu-cluster-with-NixOps/ec2-info.nix
{ config, pkgs, ... }:

with pkgs.lib;

{
  deployment.ec2.accessKeyId = "<アクセスキーID>";
  deployment.ec2.keyPair = "<AWS EC2のsshキーペア名>";
  deployment.ec2.privateKey = "<ssh秘密鍵ファイルのパス>";
}
  • インスタンス間のssh接続に使用するsshキーペア(ed25519)を用意します。
$ ssh-keygen -t ed25519
...
$ cp ~/.ssh/id_ed25519* ./ec2-gpu-cluster-with-NixOps/

NixOpsによるデプロイ

①. nixops create コマンドを実行し、設定を作ります。

$ nixops create ./ec2-gpu-cluster-with-NixOps/ec2gpu-cluster.nix -d ec2-gpu-cluster

②. 早速 nixops deploy コマンドでAWS EC2インスタンスの作成→環境のデプロイを行います。

$ nixops deploy -d ec2-gpu-cluster -I nixpkgs=./nixpkgs

以上です。
コマンドは2行とあっけないですが、デプロイに結構時間がかかります。
私の場合は1〜2時間ぐらい待ちました。
NVIDIAのドライバやCUDAなどを含むため、デプロイするファイルサイズの合計が5GBぐらいになるようです。

デプロイする環境の設定について

今回用意したファイルは以下のようになっています。

./ec2-gpu-cluster-with-NixOps/ec2gpu-cluster-conf.nix
{ region, ami, instanceType, securityGroups, publicKey, secretKey, hosts }:

{ config, pkgs, resources, ...}:
let
  ## この辺は使用するパッケージの設定です。
  python = pkgs.python3;
  cudatoolkit = pkgs.cudatoolkit;
  mpi = pkgs.openmpi.overrideDerivation (attrs: {
    configureFlags = [ "--with-cuda=${pkgs.cudatoolkit}" ];
  });
  gpu-test1 = pkgs.callPackage ./gpu-test1.nix {
    mpi = mpi;
  };
  gpu-test2 = pkgs.callPackage ./gpu-test2.nix {
    python = python;
    cython = python.pkgs.cython;
    mpi = mpi;
    cudnnSupport = true;
    cudatoolkit = cudatoolkit;
    cudnn = pkgs.cudnn;
    nccl = pkgs.nccl;
  };
in
{ imports = [ ./ec2-info.nix ];
  deployment.targetEnv = "ec2";
  deployment.ec2.ami = ami;
  deployment.ec2.region = region;
  deployment.ec2.instanceType = instanceType;
  deployment.ec2.securityGroups = securityGroups;
  deployment.ec2.ebsInitialRootDiskSize = 20;

  nixpkgs.config.allowUnfree = true;

  environment.systemPackages = [
    python
    mpi
    cudatoolkit
    gpu-test1
    gpu-test2
  ];

  ## 以下が環境設定です。
  services.xserver = {
    enable = true;
    videoDrivers = [
      "nvidia"
    ];
  };

  services.openssh = {
    enable = true;
    hostKeys = [
      { bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; type = "rsa"; }
      { path = "/etc/ssh/ssh_host_ed25519_key"; type = "ed25519"; }
      { path = "/etc/root/ssh/id_ed25519"; type = "ed25519"; }
    ];
    knownHosts = hosts;
    passwordAuthentication = false;
    challengeResponseAuthentication = false;
  };

  environment.etc = {
    "/root/ssh/id_ed25519" = {
      enable = true;
      user = "root";
      group = "root";
      mode = "0600";
      source = secretKey;
    };
  };

  ## LD_LIBRARY_PATHに必要なライブラリを含めるためにこんなことやってしまっています。
  ## もっとかっこいい方法をご存じの方がいたらぜひ教えてください。
  hardware.opengl = {
    extraPackages = [
      mpi
      pkgs.linuxPackages.nvidia_x11
      cudatoolkit
      cudatoolkit.lib
    ];
  };

  networking.firewall.enable = false;

  users.users.root = {
    openssh.authorizedKeys.keyFiles = [ publicKey ];
  };

  programs.bash.shellInit = ''
    if [ ! -e ~/.ssh/id_ed25519 ] ; then
      if [ -f /etc/root/ssh/id_ed25519 ] ; then
        /run/current-system/sw/bin/ln -s /etc/root/ssh/id_ed25519 .ssh/id_ed25519
      fi
    fi
  '';
}
./ec2-gpu-cluster-with-NixOps/ec2gpu-cluster.nix
let
  ## ここでデプロイする先のAWS EC2の設定を記述しています
  region = "ap-northeast-1"; ## 東京リージョン
  ami = "ami-89b921ef"; ## 東京リージョンのNixOSのAMI https://nixos.org/nixos/download.html
  instanceType = "p2.xlarge"; ## インスタンスタイプ
  ec2 = import ./ec2gpu-cluster-conf.nix;

  securityGroups = [ "allow-ssh" ]; ## インスタンスが所属するセキュリティグループ名を入れてください
  secretKey = ./id_ed25519;
  publicKey = ./id_ed25519.pub;

  hosts = [
    { hostNames = [ "node1" ]; publicKeyFile = publicKey; }
    { hostNames = [ "node2" ]; publicKeyFile = publicKey; }
    { hostNames = [ "node3" ]; publicKeyFile = publicKey; }
    { hostNames = [ "node4" ]; publicKeyFile = publicKey; }
  ];
in
{
  network.description = "An ec2 gpu cluster create test";

  node1 = ec2 {
    inherit region ami instanceType securityGroups publicKey secretKey hosts;
  };
  node2 = ec2 {
    inherit region ami instanceType securityGroups publicKey secretKey hosts;
  };
  node3 = ec2 {
    inherit region ami instanceType securityGroups publicKey secretKey hosts;
  };
  node4 = ec2 {
    inherit region ami instanceType securityGroups publicKey secretKey hosts;
  };
  ## 4ノード作成します
}

デプロイ完了後の操作(動作確認)

①. NVIDIAドライバインストールがあったため、ドライバの読み込み等必要になるので、一度全ノードをrebootしたほうが良いです。

$ nixops ssh-for-each -d ec2-gpu-cluster -p reboot

②. その後少し待ってnode1に接続します。

$ nixops ssh -d ec2-gpu-cluster node1
...
[root@node1]#

③. 今回用意したnixコードでは以下のパッケージを含み、テストができます。
(こちらに記載されているテストコードをそのまま使用しています。)

// gpu-mpi-test
// mpiパッケージがCUDA-Awareに対応しているか確認するプログラムです。
// 問題なければOKと出るだけです。
[root@node1]# gpu-mpi-test
OK.

// check_mpi4py
// mpi4pyが動作するかの確認です。
// node1〜node4が表示されれば問題ありません。
[root@node1]# mpiexec --allow-run-as-root -n 4 -host node1,node2,node3,node4 check_mpi4py
node1 0
node2 1
node3 2
node4 3

まぁ、Nixなので一度動作を確認できた環境なら同じコード使えば同じように動くはずなんですが・・・

ChainerMNを使用したMNISTの学習の動作

コード自体はここに従って用意しています。

実際に動作させてみたときの動画がこちらです
※ 6分程度と長いのでBGMつけました。音が出ます。

ビデオが開けなかった場合に表示されます

一応、動作していました。

余談

ChainerMNでMNISTの学習が動作できたのですが、
ノードを1つだけ使ったときのほうが計算が早く終わりました。

ビデオが開けなかった場合に表示されます

ChainerMNの論文を見る限り、
ノード増やすとほぼリニアにスピードアップするグラフが乗っているので、
何か間違ったかと思っていました。
https://arxiv.org/abs/1710.11351

ですが、こちらのスライドではそもそもMNISTの学習は
ChainerMNの速度の測定に使用するには向かない旨の注意が書いてありました。
https://www.slideshare.net/pfi/20171128chainermn

というわけで、ノード増やしてスピードアップすることを確認するには、
ちゃんとしたベンチマークになりうるプログラムを用意する必要がありそうです。

とりあえず、NixOpsでChainerMNが動作する環境の構築はできたということで、
今回はここまでです。

続きを読む

AWS Auto Scaling勉強まとめ

・需要に応じて自動的にサーバーが増減し、コストカット

Auto Scaling Group
・設定した最小値~最大値に起動インスタンスを収める
・起動台数をAZ間でバランシング
・AZ障害時は他のAZでインスタンス起動

Launch Configuration
・AMIやインスタンスタイプ、IAMなどを設定して起動する

Scaling Plan
・どのようにインスタンスを起動するか
①Auto Scaling Planの維持
最小台数を維持する。
Auto Healing:インスタンスに障害発生時に自動的にサービスから切り離し、健全なインスタンスをサービスイン

②手動管理
インスタンスを手動で変更

③スケジュールベース
CLI/SDKで定義
スケーリング開始は最大2分遅れる場合があるので注意

④動的スケーリング
監視:CloudWatchに応じたインスタンスの増減

上記の複数のプランを組み合わせることも可能

ヘルスチェック
①EC2ヘルスチェック:インスタンスのステータスがrunning以外を以上と判断
②ELBヘルスチェック
・ヘルスチェックの猶予期間がある。インスタンス起動からヘルスチェック開始までの時間。アプリケーションデプロイを考慮
・異常と判断されたインスタンスは自動的に終了

クールダウン
スケーリングアクション実行後指定した時間は次にスケーリングアクションを実行しない仕組み
→インスタンス初期化中の無駄なスケーリングを回避するため
※シンプルスケーリングポリシーにのみ対応

ターミネーションポリシー
①OldestInstance/NewestInstance:起動時刻
②OldestLaunchConfiguration:最も古いLaunch Configuration
③ClosestToNextInstanceHour:課金のタイミングが近い
④Default:②③の順に適用。複数インスタンスが残ればランダム

インスタンス保護
任意のインスタンスを削除されないよう保護できる

・インスタンスのデタッチ、アタッチ、スタンバイ

ライフサイクルフック
Auto Scalingの起動/終了時に一定時間(デフォルトは1時間、最大48時間)待機させ、カスタムアクションを実行できる

・スケールアウト時の初期化処理
①設定済みのAMIを用いる
②user-dataで初期化スクリプトを実行(Bootstrap処理)
③ライフサイクルフックで初期化

・サーバーをステートレスにする
ステートレス:サーバーにセッション情報などがない。スケールアウトに向いている
ステートフル:サーバーにセッション情報あり。常にサーバー間で同期が必要なので、スケールアウトに向いていない

・突発的なスパイクには向いていない
→インスタンス作成~アプリ起動の時間がかかるため。
対応策としては、
①CloudFrontなど大きなキャパシティを持ったAWSサービスに処理をオフロードする
②スパイクを裁くのを諦め、スロットリング機能(処理性能の上限)を設ける
③一定以上の負荷を超えたら静的ページに切り替える

・ユースケース
①ELB配下のWebサーバーをAuto Scaling
→EC2は複数AZに分散し、高可用性
ELBのリクエスト数、EC2の平均CPU使用率などがトリガー

②SQSのジョブを処理するWorkerをAuto Scaling
キューのメッセージ数などがトリガー

③Blue/Green
Blue/Green
デプロイ時に、既存のインスタンスとは違うインスタンスを作成し、一気に/徐々に作成したインスタンスを使用するようにする。
移行方法は下記
・DNSのWeighted round robinを使用して、徐々にトラフィックを移行
→DNSのTTLを考慮する必要あり。
・ELBを利用して、移行する
→Elastic Container Service(ECS。Dockerコンテナを格納する場所)を利用して、新しいインスタンスを作成することも可能

In place
デプロイ時に既存のインスタンスを操作することで対応する

参考URL:http://aws.typepad.com/sajp/2015/12/what-is-blue-green-deployment.html

Elastic MapReduce(EMR)

<Hadoop(分散処理をしてくれるソフトウェア)を動かせる環境を提供してくれるサービス
参考URL:http://mgi.hatenablog.com/entry/2014/05/04/085148

参考URL:https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-online-seminar-2017-auto-scaling

続きを読む

AWS Fargate

AWS ECS Fargate とは Docker コンテナを動かすための仕組み。Docker コンテナを起動して外から URL でアクセス出来るようになるまでやってみた。

  • コンテナは 8001 (WebSocket) と 8002 (HTTP) の2つのポートを受け付ける。
  • 外から両方のポートを使いたい。

AWS ECS には Clusters, Task Definitions, Repository の3つの設定項目がある

  • Clusters:

    • 複数の Service をまとめて動作させる箱。
    • Service:
      • 複数の Task をまとめて動作させる箱。
  • Task Definitions:
    • Task とは、Docker コンテナの動作単位。使う Docker image は AWS の Repositories から取ってきても良いし、他の Docker repository を使っても良い。
  • Repositories:
    • AWS 用の Docker Repository. Docker image を保存する場所。

作業手順は次のようになる。

  1. Repositories に Docker image を登録
  2. Task Definition に Docker repository を指定
  3. Cluster を作成
  4. Application Load Balancer を作成
  5. Cluster 内に Service と Task を作成
  6. Security Group の設定

AWS Fargate First run

  • https://qiita.com/riywo/items/b223bdad2b3ae3bebf55 の通りに First runチュートリアル試した。
  • 次へ次へと押すだけ
  • Unable to assume the service linked role. Please verify that the ECS service linked role exists というエラーが出る。
  • 二回目同じ事をやると何故か問題なく完了した。
  • Cluster : default > sample-app-service
  • AWS Console > Elastic Container Service > Clusters : default > Tasks > Task をクリック
  • IP が割り当てられている事を確認。

AWS Cluster を削除する時の注意点

  • The vpc ‘vpc-xxx’ has dependencies and cannot be deleted. のエラーが出て消せなかった。
  • EC2 security group が参照していたので消したら OK。

AWS ECS Repositories に Docker image を登録

AWS FargateでFlaskを動かしてみる を参考に作業。

  • Console で Region N. Virginia を選択
  • AWS Console > Elastic Container Service (ECS) > Repositories
  • Create repository
  • Repository name: hoge-websocket-server
  • Repository URL: xxx.dkr.ecr.us-east-1.amazonaws.com/hoge-websocket-server

画面に表示されるコマンドを実行してログイン

$ aws ecr get-login --no-include-email --region us-east-1
docker login -u AWS -p eyJwYXlsb2...
$ docker login -u AWS -p eyJwYXlsb2... (表示された長いコマンドをそのまま実行)

先程出来た Docker image にタグを追加

$ docker tag hoge-websocket-server:latest xxx.dkr.ecr.us-east-1.amazonaws.com/hoge-websocket-server:latest

Docker image を Registory に追加

$ docker push xxx.dkr.ecr.us-east-1.amazonaws.com/hoge-websocket-server:latest
file integrity checksum failed for "usr/lib/x86_64-linux-gnu/libjpeg.a"

Docker image が大きいと上のようなエラーが出た。小さい node のイメージを使ってイメージをダイエットすると成功。

Task Definition に Docker repository を指定

ここで Docker image の場所や使いたいリソースを定義する。

  • AWS Console > ECS > Task Definitions > Create a new definition

    • 1: Select launch type compatibility

      • FARGATE
    • 2: Configure task and container definitions
      • Task Definition Name: hoge-websocket-server
      • Task memory (GB): 0.5GB
      • Task CPU (vCPU): 0.25 vCPU
      • Add container
        • Container name: hoge-websocket-server
        • Image: xxx.dkr.ecr.us-east-1.amazonaws.com/hoge-websocket-server:latest
        • Port mappings
          • 8001
          • 8002
      • Create

Cluster を作成

  • AWS Console > ECS > Clusters > Create Cluster

    • 1: Select cluster template

      • Networking only
    • 2: Configure cluster
      • Culster name: hoge-cluster
      • Create VPC: チェック (意味はよくわからない)
        • 作成されるネットワーク情報に目を通しておく。
        • デフォルトで2つのサブネットが定義されるようだが、そのままにしておいた。
    • View Cluster

これで Cluster 本体の他、色々な Cluster Resources が作成される。これらの大事な情報を後から参照する方法が分からなかったので必ずメモっておく。

Load Balancer の作成

このままだと起動停止のたびに IP アドレスが変わってしまうので、Application Load Balanceer (ALB) を使ってドメイン名を割り当てる。

  • AWS Console > EC2 > Load Balancers
  • Application Load Balancer > Create
  • 1: Configure Load Balancer
    • Name: hoge
    • Scheme: internet-facing
    • IP address type: ipv4
    • Listeners
      • HTTP: 8001 (Websocket も HTTP で良い)
      • HTTP: 8002 (HTTP 用の Listener は ECS Service 設定時に出来るのでここで作らなくて良い)
    • Availability Zones
      • VPC (Create Cluster で設定した VPC)
      • 下の Subnet は2つとも選択する
    • Tag
      • project : hoge
  • 2: Configure Security Settings
    • 設定なし
  • 3: Security Groups
    • Create a new security group
    • Security group name: hoge-security-group
    • 使いたいポート番号を設定
    • あとで AWS Console > ECS > Clusters > hoge-cluster > Tasks > Task : a44f… から編集出来る。
  • 4: Configure Routing
    • ヘルスチェックの設定らしい
    • Name: hoge-8002
    • Port: 8002
  • 5: Register Targets
    • 何も設定しない

Cluster 内に Service を作成

  • AWS Console > ECS > Clusters > hoge-cluster > Services > Create

    • 1: Configure service

      • Launch type: FARGATE
      • Task Definition: (作成した Task)
      • Service name: hoge-service
      • Number of taksk: 1
    • 2: Configure network
      • Cluster VPC: (Create Cluster で設定した VPC)
      • Subnets: (Create Cluster で設定した subnet)
      • Security groups > Edit
        • Assigned security groups: Select existing security group
        • Load Balancer で作った hoge-security-group を選択
      • Load balancing
        • Load balancer type: Application Load Balancer
        • Load balancer name: hoge
      • Container to load balance
        • ここで設定すると、コンテナの IP が変わっても自動的に一つのポートだけ Load Balancer に割り当てられる。
        • hoge-websocket-server:8002 > Add to load balancer
          • Listner port: 8002:HTTP
          • Path pattern: /*
          • Evaluation order: 1
          • Health check path: /
        • hoge-websocket-server:8002
    • 3: Set Auto Scaling
      • デフォルトのまま
    • 4: Review
      • 特に VPC Id と Subnets に気をつける。間違うとややこしい事になる。
    • Create Service

コンテナに割り当てられた IP アドレスの確認

  • AWS Console > ECS > Clusters > hoge-cluster > Tasks > Task : a44f… を選択
  • Private IP と Public IP が表示される。

もう一つのポート Port 8001 の設定

ECS Service の作成時に Load Balancer を割り当てると、IP が変化しても Load Balancer が勝手に面倒を見てくれる。ただしポート番号は一つしか設定出来ない!!!!この例のように2つ目のポートを Load Balancer に見せるには、手動で IP を指定した Target Groups を作る必要があった。なので、更新の際にはこの IP をわざわざ再入力する必要がある。

  • AWS Console > EC2 > LOAD BALANCING Target Groups

    • Target group name: hoge-8001
    • Protocol: HTTP
    • Port: 8001
    • Target type: ip
    • VPC: Cluster の VPC
    • Create
  • AWS Console > EC2 > LOAD BALANCING Target Groups > hoge-8001
    • Targets > Edit
    • Task の IP を登録
  • AWS Console > EC2 > LOAD BALANCING Load Balancers > hoge > Listeners
    • HTTP:8001 > View/edit rules
    • THEN に hoge-8001 を登録

Docker image の更新

  • AWS Console > ECS > Clusters > hoge-cluster > hoge-service > Update

    • Force new deployment: Check
    • 次へ次へ。。。
  • しばらく待つと新しいコンテナが起動して古いコンテナが停止する。
  • IP アドレスが変わっているので Load Balancer の Target Group も更新する。

Task 削除

Task は service が管理するので、直接止められないらしい。Service の Update で Number of tasks を 0 にすると止められた。

  • AWS Console > ECS > Clusters > hoge-cluster > hoge-service > Update
  • Number of tasks: 0
  • 次へ次へ

やりたかった事は node 一つの単純なサーバをテストしたいだけだったんだけど、随分大げさな構成になってしまった。

続きを読む

AWSLambdaがGo言語に対応したのでDynamoDBと絡ませながらデモってみた

はじめに

去年のRe:Inventで「来年の早い時期にLambdaがGoに対応するよ!」なんてアナウンスされた時は、来たかっ!という気持ちになりました。
首を長くして待っていたら、本日サポートしたと発表があったので早速試してみました。

デモdemo

普段Node.jsを使ってLambda+DynamoDBの構成を組んでいるので、今回もDynamoDBを絡めてデモってみました。

公式が出している記事を参考にしていただければいいのですが、
現時点でサポートしているバージョンは 1.x です。
基本的なデプロイの流れは
1. Go書く
2. Linux用にビルドする
3. zipでアップロード
という流れになると思います。

Go書く

基本形は下記のような感じです。

package main

import (
    "github.com/aws/aws-lambda-go/lambda"
    "fmt"
)

type Response struct {
    Message string `json:"message"`
    Ok      bool   `json:"ok"`
}

func Handler() (Response, error) {
    // 処理部分
}

func main() {
    lambda.Start(Handler)
}

lambda.Start(Handler) でLambdaの処理を実行します。
なんか少し違和感があるのは私だけでしょうか…
んで、 Handler の中に実際の処理を書いていく感じになります。

DynamoDB(GetItem, PutItem, Query)

aws-sdk for goを使ってDynamoDBにリクエストを送っていきます。
上記のソースコードに追記したのが下記になります。

package main

import (
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/dynamodb"
    "github.com/aws/aws-lambda-go/lambda"
    "fmt"
)

type Response struct {
    Message string `json:"message"`
    Ok      bool   `json:"ok"`
}

func Handler() (Response, error) {
    // session
    sess, err := session.NewSession()
    if err != nil {
        panic(err)
    }

    svc := dynamodb.New(sess)

    // GetItem
    getParams := &dynamodb.GetItemInput{
        TableName: aws.String("go-demo"),
        Key: map[string]*dynamodb.AttributeValue{
            "id": {
                S: aws.String("1"),
            },
        },
    }

    getItem, getErr := svc.GetItem(getParams)
    if getErr != nil {
        panic(getErr)
    }
    fmt.Println(getItem)

    // PutItem
    putParams := &dynamodb.PutItemInput{
        TableName: aws.String("go-demo"),
        Item: map[string]*dynamodb.AttributeValue{
            "id": {
                S: aws.String("2"),
            },
            "name": {
                S: aws.String("hoge"),
            },
        },
    }

    putItem, putErr := svc.PutItem(putParams)
    if putErr != nil {
        panic(putErr)
    }
    fmt.Println(putItem)

    // Query
    queryParams := &dynamodb.QueryInput{
        TableName: aws.String("go-demo"),
        KeyConditionExpression: aws.String("#ID=:id"),
        ExpressionAttributeNames: map[string]*string{
            "#ID": aws.String("id"),
        },
        ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
            ":id": {
                S: aws.String("1"),
            },
        },
    }

    queryItem, queryErr := svc.Query(queryParams)
    if queryErr != nil {
        panic(queryErr)
    }
    fmt.Println(queryItem)

    return Response{
        Message: fmt.Sprintln(getItem.Item),
        Ok:      true,
    }, nil
}

func main() {
    lambda.Start(Handler)
}

GetItemで返ってくるレスポンスはこんな感じ

{
  Item: {
    id: {
      S: "1"
    },
    name: {
      S: "test"
    }
  }
}

Queryで返ってくるレスポンスはこんな感じ

{
  Count: 1,
  Items: [{
      id: {
        S: "1"
      },
      name: {
        S: "test"
      }
    }],
  ScannedCount: 1
}

ビルドして、zipに固める

$ go build -o main.go
$ zip deployment.zip main

linux用にビルドしないといけないので、macの方は下記を実行してからzipに固めてください。

$ GOOS=linux go build -o main

Lambdaにデプロイする

Lambdaのコンソールから新しく関数を作成して、ランタイムを Go 1.x を選択します。
アタッチするロールには今回はDynamoDB関係の権限も忘れずに。
スクリーンショット 2018-01-16 15.05.24.png

関数が作成されたら、先ほど作成したZipファイルをアップロードすれば完了です。
スクリーンショット 2018-01-16 15.06.08.png

これでGoを使ってDynamoDBのデータを操作できるぞ!

あとがき

正直Go言語はほとんど触ったことなかったので、ソースコードはきったねぇと思いますがご了承ください。
Node.jsの感覚で実装したら動いたって感じです…(汗)

Node.jsで実装していると「シングルスレッドじゃあしんどいな…」と感じている時もあったりします。
Go言語はNode.jsと違って並列処理に長けているので、ケースバイケースで「Node.jsよりもGoの方がよいんでは?」となりそうなので、今のうちにGo言語に慣れておかなければと実感しました。

ではまた!

続きを読む