AWS Batch とは何か

AWS re:Invent 2016 で発表された AWS Batch。
語感から、誤解されるサービス No.1 な気がします。
定時バッチなどとは何がどう違うのかをメモ。

機能概要

以下公式資料とドキュメント、実際さわってみた所感を合わせて。

結局何なのか

科学技術計算・ハイパフォーマンスコンピューティング用途で真価を発揮する、
大規模なスケール、ジョブの依存定義 が可能なマネージド 並列分散 処理基盤。

主な機能、ポイント

  • クラスタ管理、ジョブキュー、ジョブスケジューラを AWS にお任せできる
  • 処理すべきジョブの数に応じ、適切に 自動伸縮1 するクラスタ
  • ジョブに 依存関係 が定義できる(B は A に依存した処理である2、など)
  • 優先度 を持ったキューを複数定義できる
  • 処理能力ごとにクラスタを分割管理することもできる
  • リソース調達が EC2 より直感的、かつ Spot Fleet も統合済み
  • クラスタは ECS 上に構築される
  • サードパーティのデータ分析ワークフローエンジンのサポートあり

これがマネージドされると僕らは何がうれしいのか

  • 本来やりたい、ジョブの実行依頼(submit)と実処理だけを考えればよくなる
  • クラスタ全体で利用可能なリソースの把握、過不足に応じたその調整が不要3
  • 前処理、集約処理、後処理といった流れのある処理も基盤側に制御を移譲できる
  • 依頼者や状況に応じた優先的リソース配分が容易に実現できる
  • CPU / GPU でそれぞれクラスタを作り、前処理 CPU、本処理 GPU なども簡単
  • クラスタごとに可用性、パフォーマンス、コストのバランスが定義しやすい
  • Docker イメージにさえしてしまえばどんな処理も AWS Batch に乗せられる
  • すでにデータ分析パイプラインの定義があれば移行しやすい(かもしれない)

逆に現在4サポートしていないこと

以下 API を駆使してプログラムは書けるものの、標準機能にはありません。

  • cron のように事前指定した時間での起動など、定期タスク管理
  • ジョブの処理そのものや待機状態のタイムアウト
  • 処理が失敗した場合のリトライ
  • リソース不足時、どのリソースがどれだけ足りないかの把握

定時バッチのマネージドサービスではないよという話

再掲:「AWS Black Belt Online Seminar 2017 AWS Batch」10 ページ目
https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-online-seminar-2017-aws-batch/10

ストリーミングではなく、(本来の意味の)バッチ的に渡ってくるタスクを
スケーラブルに並列処理させるための仕組みとのことです。

ユースケース

公式

以下からも明らかに主なターゲットはシミュレーションやデータ解析のための処理。
変数を変えながら大量に流す処理細切れにして並列に流せる処理 が向いている。

公式(番外編)

膨大なパラメタを探索したり、予め大量の画像に推論したラベルを貼るといった
昨今話題となっている深層学習分野にも有用、今後利用は広がりそう。

個人的印象

上の深層学習の例では API 消費にレートリミットをかけるという位置付けで
AWS Batch を使っていますが、この使い方はとても汎用的だと思います。
ということで、以下あたりも向いていそう。

  • レートリミットをかけたい処理の実行

例えば同時に流してよい処理がクラスタ全体で一つだけであるとか、
3 つ程度にしたいといった時にクラスタとジョブの定義だけで調整可能。

  • ECS で RunTask していた非同期タスク

科学技術計算といった高度なものではなく、もっとシンプルなタスク。
Web / スマホアプリでも定期的に必要になる小さなジョブなども
予め Docker イメージになっているようであればすぐ使えて便利。

リソースの管理とジョブのスケジューリング

業界によって意味の異なる言葉の整理。

HPC 界隈、コンテナ(Web)界隈、業務システム界隈それぞれで
少しずつ意味が異なるようなので、比較しながら併記します。
(AWS Batch での意味 = HPC 分野での意味)

クラスタ、リソース管理

AWS Batch のいうクラスタは、将来的には HPC 方向に機能強化されるはず・・

  • HPC: 施設・研究者で共有される 巨大なリソース をクラスタとして管理。
    ジョブの要求するハードウェア性能は一般にとてもシビアであり、
    どこでどんな性能のノードが使えるかといった情報の収集も厳密。
  • コンテナ: 複数のサービスでシェアされる特定サーバ群のことで
    一般には可用性やスケーラビリティが重視された構成が取られる。
    どのホストで稼働するかは重要ではなく、むしろどこでも動くよう設計される。
  • 業務: データセンタ内のリソースを管理。あまり包括的には扱われない。
    稼働するアプリケーションは頻繁に変わるものではなく
    とにかく安定的・効率的にサービスが稼働することが大切。

スケジューラ

  • HPC: 実行したいジョブはキューに投げる。リソースが空いたら次の処理が始まる。
    ジョブ同士の依存関係が強い。順序や処理間のデータ移動も重要。
    優先的に処理したいジョブは割り込める必要がある。
    多くは試行錯誤のためのジョブ、FAIL してもいいが処理時間はとにかく長い。
  • コンテナ: 各サービスの要求するリソースを見つけ次第どこかに投入される。
    サービスの理想状態を別途管理する必要があり、異常終了時などの再起動や
    すでに起動しているサービスの状態を考慮した配置も必要になる。
  • 業務: 業務やその時間、ワークフローに応じてジョブを起動・停止する。
    ジョブ同士の依存関係も強く、処理に失敗した場合のワークフローは最も複雑。
    ユーザごとにそのあたりを設定変更できる柔軟さが必要。

AWS Batch の概念・機能

コンピューティング環境

AWS Batch には「コンピューティング環境」という概念がありますが
いわゆる「クラスタ」のようなもので、複数インスタンスの集合です。

そしてそれは、用途に応じて 2 種類あります。

Managed 環境

Unmanaged でなければいけない理由がなければ Managed 環境がオススメ。
Managed 環境の特徴は以下の通り。

  • 待機ジョブの深さに応じてクラスタがオートスケールアウト / イン
  • ECS クラスタを裏で作成・管理してくれる
  • Spot Fleet が簡単に使えて便利

Unmanaged 環境

ちょっと変わったクラスタにカスタマイズする必要がある場合はこちら。
AWS Batch の生成する ECS クラスタに「関連づけるインスタンス」を
自由にカスタマイズ可能。例えばこんなとき。

  • 特定の AMI を使いたい
  • あれこれ調整した AutoScaling グループを使いたい
  • EFS を使いたい
  • GPU を使いたい

Unmanaged 環境の場合はキューの深さによるオートスケールが働かないため
このようなアーキテクチャ で別途用意する必要があります。

ジョブ定義・実行

ジョブの定義は JSON で事前に宣言 することになります。
ジョブの処理ロジックについては、さらにそれ以前に Docker イメージにして
DockerHub や ECR などに push しておく必要があります。

実行時パラメタ

ジョブの基本的な起動パラメタは事前に JSON に定義するものの、
実行時に挙動を変える手段が AWS Batch には 2 つのあります

  • Ref:XXX & –parameters : 事前に XXX と定義したパラメタを実行時に指定
  • 環境変数 : コンテナへ渡す環境変数を実行時に指定

依存関係のあるジョブ

ジョブ投入時に --depends-on オプションで、すでに投入済みの
依存するジョブの ID を渡すことで依存関係を定義できます。

ジョブ間のデータ連携

HPC 系バッチコンピューティングを支援するマネージドサービスということで
そのデータの連携には以下サービス群の利用が想定されているようです。

  • EFS: まだ東京リージョンに来てないけども・・
  • EBS: 前処理したタイミングでスナップショットを取り展開、など
  • S3: AWS といえば S3 の活用ですが、もちろん大活躍
  • RDS / DynamoDB: 使いどころはたくさんありそう

他サービスとの連携

AWS を使い倒せば、よりセキュアで安定したサービスにも

  • CloudWatch Logs: ジョブの標準出力は全てここに連携されます
  • IAM: ECS ベースなこともあり、ジョブごとの権限管理にはロールが使える
  • KMS: 秘密情報の管理には KMS + IAM ロールがほんとに便利
  • Lambda + CloudWatch Events: Unmanaged 環境の自前オートスケールなど

他サービスとの使い分け

Lambda や EMR、ECS とどう使い分けるの?という。Batch を選択する意味。

なぜ Batch がでたのか

Azure には AWS より以前に、同名の Azure Batch というサービスがありましたが
こちらも狙いは、汎用的な 科学技術計算や HPC 基盤のマネージド提供でした。

AWS にも EMR や MachineLearning といったある種の問題解決に特化した
マネージドサービスはありましたが、汎用的なバッチ環境としては
AWS Batch が最も適切なサービスとなりそうです。

使い分け

上記のメリットに合致したジョブなら AWS Batch。
悩ましいとしたら、例えば

  • Lambda では難しい(運用・開発効率、サーバ性能、タイムアウト、言語)
  • EMR 向けに作りこまれたものではない(既存の処理を使いたい)

のならば AWS Batch を使う、など。

使ってみよう

実際にジョブを流してみると感覚がつかめると思います。

ハンズオン


  1. Managed 環境だった場合の挙動 

  2. A が正常終了(SUCCEEDED)すると B が開始され、A が異常終了(FAILED)した場合は B も FAILED になる 

  3. 厳密には待機ジョブの数で調整がかかるので、CPU やメモリなどに応じたリソースの調達・解放がしたい場合は API を利用したプログラムが別途必要 

  4. 2017/03/01 時点 

続きを読む

Terraform で AWS環境を実運用する上で困ったことと、その対処

忘備録的なもの。
2017年2月時点、Terraformは0.8.6.

操作用AWSアカウントの認証情報の扱い

困ったこと

ネットの参考情報だと、awsの認証情報(credentials)を直接書くサンプルが非常に多かった。
しかし、tfファイルを書き換える運用だと、いつか間違えてcommit & pushしてインシデントになりそう。

Terraformを最初に動かすためのユーザーの認証情報は、その性質上大きな権限を持っていることが多いと思うので、慎重になりたい。

解決策

AWS-CLIのNamed Profile使おう。

$ aws configure --profile my-profile-name
AWS Access Key ID [None]: xxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]: 

事前にこうして設定しておけば、認証情報は$home/.aws/credentialsに名前付きで入る。

スクリプトからはこう参照する。これならばcommitしてしまっても問題ない。

example1.tf
provider "aws" {
  profile = "my-profile-name"
}

あとは、上記の設定手順をREADME.md なんかに書いて、KEYIDとSECRET自体はいつも通り正しく管理してあげればいい。

.tfstateファイルの扱い

困ったこと

.tfstateファイルを紛失してしまうと、作成したインスタンスを、Terraformから管理できなくなってしまうので、最重要ファイルだ。
かといって、gitにcommitするルールだと、commit忘れが怖いし、その際pullし忘れるとつらい。
一方、手動であちこち引き回しても同じことで、別の開発者が古いstateに基づいて、重複したインスタンスを立ててしまうかもしれない…

解決策

Backendの、remote state機能使おう。

ちゃんと公式ドキュメントある。 https://www.terraform.io/docs/state/remote/s3.html

こんな感じ。profileも指定できるので、そちらにregionを書いておけば省略できる。

$ terraform remote config 
    -backend=s3 
    -backend-config="bucket=my-tfstate-store-name-at-s3" 
    -backend-config="key=hogehoge/terraform.tfstate" 
    -backend-config="profile=my-profile-name"

これを実行した時点で、存在していたterraform.tfstateは、./.terraform/terraform.tfstate に移動されるようだ。

あとは自動でtfstateファイルをアップロード、ダウンロードしてくれる。
指定するバケットはacl=privateにしておくこと!!
あと、上記リンクでは、S3のversioningとかもつけておくことを勧めている。(私はやらなかった)

S3以外にも、いろいろ手段は用意されているようだ。

環境ごとのvariableの扱い

困ったこと

ステージングと本番、みたいに分けようと思って、環境ごとにvariableで設定値を与えられるようにしたけど、
-var オプションで引数に全部指定するの辛い

解決策

共通の設定ならば、terraform.tfvars という名前のファイルに書いておけば、指定しないでも勝手に読み込んでくれるとのこと。
https://www.terraform.io/intro/getting-started/variables.html#from-a-file

環境ごとに違う変数は、-var-fileオプションを使ってスイッチするのがよさそうだ。

$ terraform plan -var-file staging.tfvars
$ terraform plan -var-file production.tfvars

的な。

varは後ろに指定したものが有効(上書きされる)とのことなので、上手に使えば強いはず。

Packerで作ったAMIでEC2インスタンスを生成したい

困ったこと

auto-scalingを考えたときに、元となるAMIをちゃんと運用したい。
(注意、私はAWSのAutoScaling をよくわかっていないかも。)

そこで、Packerでイミュータブルなイメージ作ったらすごーいはず。
イミュータブルということは、イメージにDB接続情報がないといけない気がする。
よって、Terraformで先にRDS立てないといけないけど、そのTerraform側でAMIを使いたいからこういう話になっているわけで…
循環参照してしまう。

そもそも、AutoScaling配下のインスタンスを入れ替える際に全インスタンスを落とさないようにする、というのがなかなか厳しいようだ。
AutoScalingとの相性は、改善まち、という感じか。

参考: http://qiita.com/minamijoyo/items/e32eaeebc906b7e77ef8

準解決策1 null_resource

最終的にはうまくいかなかったが、最初に試した方法

null_resourceというものがある。
https://www.terraform.io/docs/provisioners/null_resource.html

何もしない代わりに、自身の状態(=変更が起きるトリガー)を定義するtriggers属性と、provisionerを設定できるリソースだ。
これを使って、

example2.tf
variable "ami_name_prefix" {}

resource "null_resource" "packer" {
  triggers {
    db_host = "${aws_db_instance.hogehoge.address}"
  }

  provisioner "local-exec" {
    command = <<EOS
packer build 
  -var 'DB_HOST=${aws_db_instance.hogehoge.address}' 
  -var 'AMI_NAME_PREFIX=${ami_name_prefix}' 
  packer.json"
EOS
  }
}

data "aws_ami" "packer_ami" {
  most_recent = true
  name_regex = "^${var.ami_name_prefix}.*"
  owners = ["self"]
  depends_on = ["null_resource.packer"]
}

resource "aws_instance" "hoge" {
  ami_id = "${data.aws_ami.packer_ami.id}"
  ...
}

とこんな感じ。

しかし、Terraform 0.8.6だと、triggersの中にinterpolationを混ぜると、問答無用でcomputed扱いになってしまうようで、
この記述では毎回AMIが作成されてしまって、差分のみ実行というTerraformの観点からは、使えなかった。

準解決策2 イミュータブルを諦める

オートスケーリング自体はこのパターンでは不可能か。AMI化を再度行う必要があると思う。

ほぼ完成品のAMIを組み立てておいて、aws_instanceのprovisionerで最後の仕上げをする。
Dockerなんかは、環境変数で外部情報を読み込ませる、なんてことをするらしいので、この手法に踏み切った。

// .envが欠けた状態でAMIをつくる
$ packer packer.json
example3.tf
data "aws_ami" "packer_ami" {
  most_recent = true
  name_regex = "^packer-ami-.*$"
  owners = ["self"]
  depends_on = ["null_resource.packer"]
}

data "template_file" "envfile" {
  # template = "${file(./env.tpl)}" などとした方が望ましいが例示のため。
  template = <<EOS
export DB_HOST="${db_host}"
export DB_PORT="${db_port}"
....
EOS

  vars {
    db_host = "${aws_db_instance.main.address}"
    db_port = "${aws_db_instance.main.port}"
  }
}

resource "aws_instance" "ec2" {
  ami_id = "${data.aws_ami.packer_ami.id}"
  ...

  # envファイルをuploadする。envファイルはdirenvとかdotenvとかで読み込まれるようにしておく。
  provisioner "file" {
    content = "${data.template_file.envfile.rendered}"
    destination = "/home/ec2-user/.env"
  }

  # 上で入れた環境変数を使ってサービスを立ち上げる。
  provisioner "remote-exec" {
    inline = ["sudo service unicorn start"]
  }

  # provisionerは、実行した環境からssh(のgolang実装。sshコマンドではない)で接続することになる。
  # 当然security_groupとvpc_internet_gatewayが適切に設定されている必要がある。
  connection {
    type = "ssh"
    ...
  }
}

tfファイルを構造化したい

困ったこと

コピペや反復は悪だし、再利用性も下がる。

COUNT

COUNT = nで配列のように同じタイプのリソースを複数作れる。
それぞれ微妙に違う値を書きたいなら、

COUNT = 2
ATTR = "${element(list("a", "b", "c", "d"), count.index)}"

などとできる。
listは変数化するとなおよい。

module

複数のリソースにまたがっての反復パターンなら、module化してしまうとよい。
module自体の書き方はここでは説明しないが、

./modules/my_module/variables.tf
./modules/my_module/main.tf
./modules/my_module/output.tf

を作り、

example4.tf
module "use_of_my_module" {
  source = "./my_module"
  var1 = ...
  var2 = ...
}

と書くことで使用。

$ terraform get

で、モジュールをterraformに読み込ませる準備をさせる。(./.terraform/modules/ にsym-linkが作成されるようだ)

様々に公開されているmoduleもあるようなので、むしろ自分でresourceを書かないほうが良いのかもしれない。

その他

また何かあれば書く。

続きを読む

CloudWatch エージェントは SSM エージェントと呼ばれる別のエージェントに移行されましたとさ

ログは外に出しますよね

EC2 の Windows インスタンスでイベントログやアプリケーションログを CloudWatch Logs に転送して運用しています。
ログを見るためにいちいちインスタンスに入る必要もないし、必要なログがインスタンス外にあることで AutoScaling でインスタンスが勝手に破棄されても大丈夫という設計です。

これまで

ログ転送設定を C:Program FilesAmazonEc2ConfigServiceSettingAWS.EC2.Windows.CloudWatch.json に書いて EC2Config サービスを再起動することでインスタンスごとにログ転送の仕方を設定していました。
これを CodeDeploy の Hook スクリプトで行い、アプリケーションの配置と同時にログが指定した(アプリケーションごとの)ロググループにロストすることなく集めることができていました。
ちなみに Windows Server 2012 R2 です。

EC2Config ログ転送やめるってよ

あるとき、こんな記事を見つけました。
Windows ServerのCloudWatch LogsをSSMで行う | Developers.IO

従来のEC2 Configの設定を行っても、CloudWatch Logsへログの転送が行われないので注意してください。

え?まじで?
試しに新しい AMI で試してみましたがやっぱりログは転送されません。
今後は SSM で設定しなくちゃいけないのかと調べましたが、どうもやりたいことができません。
やりたいことって何よ?を書くと長くなりそうなんで書きませんが、とにかく、インスタンスに置いたローカルファイルからログ転送設定をしたいんです。

どうも SSMAgent です

EC2Config バージョン履歴的なドキュメント を確認したらなんだかローカル設定ファイルを使えそうな雰囲気が・・・

ローカル設定ファイル (AWS.EC2.Windows.CloudWatch.json) を使用して、インスタンスで CloudWatch の統合を有効にしている場合、SSM エージェントと連携して動作するようファイルを設定する必要があります。詳細については、「Windows Server 2016 インスタンスで CloudWatch 統合用のローカル設定ファイルを使用する」を参照してください。

お?・・・え? Windows Server 2016 だけなん?

結局、サポートに問い合わせて回答をいただきました。

CloudWatch Logsの設定方法

これまでと同様にローカルのファイルをお使い頂けますが、CloudWatchもしくはCloudwath Logsの設定変更が必要な場合には、下記の手順を実施頂くようお願いいたします。

  1. Amazonssmagent サービスを停止します。
    コマンドプロンプトで net stop amazonssmagent を実行することで停止できます。
  2. C:Program FilesAmazonSSMPluginsawsCloudWatchAWS.EC2.Windows.CloudWatch.json を削除いたします。
  3. C:Program FilesAmazonEc2ConfigServiceSettingAWS.EC2.Windows.CloudWatch.json の内容を編集いたします。
  4. Amazomssmagent サービスを開始します。
    コマンドプロンプトで net start amazonssmagent を実行することで開始できます。

新しい設定が反映されますと、AWS.EC2.Windows.CloudWatch.json の内容が C:Program FilesAmazonSSMPluginsawsCloudWatchAWS.EC2.Windows.CloudWatch.json に出力されます。
テキストファイルの改行コードが変わり、Headerが追加されますが、影響はございませんので、無視頂くようお願いいたします。

・・・ふむ。
これ通りに試したところ、無事にログ転送がされました。

極端なことを言ってしまえば僕のケースでは、再起動するサービスを EC2Config から SSMAgent に変えるだけで良かったと言う話です。(担当部署が変わったからそっちに聞いて的な)
でもあくまで設定ファイルの大本は今まで通り EC2Config の下にあるファイルで、それを読み込んで SSM の下のワークスペース的なところに保持するって感じなんですね。

まとめ

  • 新しい Windows AMI でも CloudWatch Logs のログ転送設定をローカルファイルで行える
  • ただし扱うサービスは SSMAgent
  • ファイルの置き場に注意
  • AWS のドキュメントはやっぱり追いつくのが遅い
  • AWS のサポートは丁寧で助かる

続きを読む

CloudFormationでのAutoScalingGroup配下のインスタンス入れ替え方法

AWS::AutoScaling::AutoScalingGroupの更新時にはインスタンスの入れ替えが発生する場合がある。
LaunchConfigurationに変更があった場合、VPCの設定が変わった場合など。

この時、どのようにインスタンスを入れ替えるかをUpdatePolicy属性で指定することができる。
指定できる方式は2種類。

  • AutoScalingRollingUpdate
    既存のAutoScalingGroup配下のインスタンスを数台ずつ入れ替えていく方法。

  • AutoScalingReplacingUpdate
    新しいAutoScalingGroupを作成して、新旧のグループを入れ替える方法。

どちらも指定しないとインスタンスの入れ替えが発生せず変更が反映されないので注意。
両方指定した場合はAutoScalingReplacingUpdateWillReplaceの値によってどちらの方式が優先されるか決まる。
trueならAutoScalingReplacingUpdateが優先、falseならAutoScalingRollingUpdateが優先、となる。

AutoScalingRollingUpdate

下記の例では、最低1台は稼働させつつ、1台ずつ入れ替える。
SignalResourceの受信を持って起動完了とみなす。
シグナル受信のタイムアウトは10分。

AutoScalingRollingUpdate
Resources:
  ASG:
    Type: "AWS::AutoScaling::AutoScalingGroup"
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MaxBatchSize: 1
        MinInstancesInService: 1
        PauseTime: PT10M
        WaitOnResourceSignals: true
          :
    Properties:
      :

その他の設定はドキュメントを参照。
AutoScalingRollingUpdate Policy

実行時のCloudFormationのイベントはこんな感じ。下に行くほど古いイベント。
新旧インスタンスの起動と停止は同時に行われる。

Received SUCCESS signal with UniqueId i-555...
New instance(s) added to autoscaling group - Waiting on 1 resource signal(s) with a timeout of PT10M.
Successfully terminated instance(s) [i-222...] (Progress 100%).
Terminating instance(s) [i-222...]; replacing with 1 new instance(s).

Received SUCCESS signal with UniqueId i-444...
New instance(s) added to autoscaling group - Waiting on 1 resource signal(s) with a timeout of PT10M.
Successfully terminated instance(s) [i-111...] (Progress 67%).
Terminating instance(s) [i-111...]; replacing with 1 new instance(s).

Received SUCCESS signal with UniqueId i-333...
New instance(s) added to autoscaling group - Waiting on 1 resource signal(s) with a timeout of PT10M.
Successfully terminated instance(s) [i-000...] (Progress 33%).
Terminating instance(s) [i-000...]; replacing with 1 new instance(s).

Rolling update initiated. Terminating 3 obsolete instance(s) in batches of 1, while keeping at least 1 instance(s) in service. Waiting on resource signals with a timeout of PT10M when new instances are added to the autoscaling group.

ロールバック時の挙動

スタック更新失敗時のロールバックの挙動について。
デフォルトの設定では1台でも入れ替えに失敗するとロールバックされる。
UpdatePolicyMinSuccessfulInstancesPercentが100%の場合(デフォルトで100%)。

MinSuccessfulInstancesPercent
UpdatePolicy:
  AutoScalingRollingUpdate:
    MinSuccessfulInstancesPercent: 100

それまでに起動に成功したインスタンスがあれば数台ずつ停止され、前回の設定を使って起動したインスタンスに入れ替わっていく。
ロールバック時もUpdatePolicyの設定に従って入れ替えられる。

前回の設定でインスタンスを起動できるようにしておかないとロールバックに失敗するので注意。
参考:CloudFormationのロールバック時に設定ファイルも元に戻す

メリット

  • 余分なインスタンスの起動がない
    数台ずつ入れ替えるのでインスタンスの同時起動数がさほど増えない。
    なのでEC2のリミットに引っかかりにくい。

  • 暖機運転が不要
    これは作りと設定次第。
    徐々に入れ替わるので、キャッシュなどが消えても結果的に影響が少なくなる。

デメリット

  • 入れ替え完了まで時間がかかる
    徐々に入れ替わるので当然時間がかかる。
    MaxBatchSizeを最大にしてMinInstancesInServiceを0にすれば全台一斉に入れ替えることもできる。
    サービスは死ぬ。すでに死んでいる時などに有効。

  • 入れ替え中は新旧インスタンスが混在する
    アプリケーションの作りによってはデータの整合性が取れなくなったりする。

  • 途中で起動失敗するとロールバックにも時間がかかる
    起動時と逆に数台ずつ古いインスタンスに入れ替わっていくため。

  • ロールバック時に旧設定で起動できるよう工夫する必要がある
    「ロールバック時の挙動」に書いた通り。

AutoScalingReplacingUpdate

下記の例では、新規AutoScalingGroupを作成し、3台分のSignalResourceの受信をもって作成完了とする。
シグナル受信のタイムアウトは10分。
CreationPolicyを指定しないとSignalResourceFAILUREを送信してもロールバックが行われず入れ替えが完了してしまうので注意。

AutoScalingReplacingUpdate
Resources:
  ASG:
    Type: "AWS::AutoScaling::AutoScalingGroup"
    CreationPolicy:
      ResourceSignal:
        Count: 3
        Timeout: PT10M
    UpdatePolicy:
      AutoScalingReplacingUpdate:
        WillReplace: true
    Properties:
      :

その他の設定はドキュメントを参照。
AutoScalingReplacingUpdate Policy

実行時のCloudFormationのイベントはこんな感じ。下に行くほど古いイベント。

Received SUCCESS signal with UniqueId i-555...
Received SUCCESS signal with UniqueId i-444...
Received SUCCESS signal with UniqueId i-333...

Resource creation Initiated
Requested update requires the creation of a new physical resource; hence creating one.

ロールバック時の挙動

スタック更新失敗時のロールバックの挙動について。
デフォルトの設定では1台でも入れ替えに失敗するとロールバックされる。
CreationPolicyMinSuccessfulInstancesPercentが100%の場合(デフォルトで100%)。
UpdatePolicyではないので注意。

MinSuccessfulInstancesPercent
CreationPolicy:
  AutoScalingCreationPolicy:
    MinSuccessfulInstancesPercent: 100

新規のAutoScalingGroupと配下の新規インスタンスは削除される。
インスタンス起動時にELBには登録されるが、InServiceになる前に外される。

メリット

  • 入れ替え完了まで時間がかからない
    全台同時に入れ替えるので完了までの時間が短い。

  • 入れ替え中に新旧インスタンスが混在しない
    新旧インスタンスが共存している時間が短いので、混在することによるリスクが少ない。

  • ロールバック時に現行システムに影響がない
    スタックの更新に失敗しても、現行のインスタンスは影響を受けない。

デメリット

  • 一時的なインスタンス同時起動数が2倍になる
    全台同時に入れ替えるので一時的に2倍のインスタンス数が必要になる。
    なのでEC2のリミットに引っかかりやすい。

  • 暖機運転が必要
    これは作りと設定次第。
    全台同時に入れ替わるので、例えば全サーバでキャッシュが消えてサービスに影響がでたりする。
    その場合、暖機運転を行ったあとSignalResourceを送信するなどの対応が必要。

続きを読む

AWS ECSでDockerコンテナ管理入門(基本的な使い方、Blue/Green Deployment、AutoScalingなどいろいろ試してみた)

はじめに

Dockerを本番環境で利用するに当たり、私レベルではDockerのクラスタを管理することはなかなか難しい訳です。凄くめんどくさそうだし。
ということでAWS ECS(EC2 Container Service)ですよ。

記事書くまでも無いかなと思ったんですけど意外と手順がWEBにない(気がしました)。ということで、今回は社内でハンズオンでもやろうかと思って細かく書いてみました。

こんな感じのシナリオをやってみたいと思います。

  1. Dockerのイメージを用意する
  2. ECSの使い方の基本
  3. コンテナのリリース
  4. Blue/Green Deployment
  5. AutoScaling/ScaleIn

前準備:Dockerのイメージを用意する

FROM uzresk/docker-oracle-jdk
MAINTAINER uzresk

ADD demo-1.0.jar /root/demo-1.0.jar
CMD java -jar /root/demo-1.0.jar
  • Docker build
[root@centos7 build_demo_ver1.0]# docker build -t uzresk/demo:ver1.0 /vagrant/build_demo_ver1.0
Sending build context to Docker daemon 33.11 MB
Step 1 : FROM uzresk/docker-oracle-jdk
 ---> df2f575c2a0d
Step 2 : MAINTAINER uzresk
 ---> Using cache
 ---> 1995a4e99748
Step 3 : ADD demo-1.0.jar /root/demo-1.0.jar
 ---> 705df0209779
Removing intermediate container cd9ef33d8812
Step 4 : CMD java -jar /root/demo-1.0.jar
 ---> Running in b7bd939a8b5a
 ---> add0783a851f
Removing intermediate container b7bd939a8b5a
Successfully built add0783a851f
  • 起動します

    • アプリケーションは8080で起動しているのでポートフォワードしておきます。
[root@centos7 build_demo_ver1.0]# docker run -itd -p 80:8080 --name demo uzresk/demo:ver1.0
92bda2419bf7285d78f12be5877ae3242b5b13ac14409b3c47d38e2d74a06464
  • ブラウザでこんな画面がでれば成功です。

image

  • Dockerhubにコミットしてpushしておきます。
[root@centos7 build_demo_ver1.0]# docker commit -m "update ver1.0" demo uzresk/demo:ver1.0
[root@centos7 build_demo_ver1.0]# docker push uzresk/demo:ver1.0

ECSの使い方の基本

AWS ECSとはなんなのか?

  • 今回は利用手順について書こうと思うので割愛しますが、AWS Black Belt ECSを読むのがよろしいかと思います。

構成する順番を抑えよう

  • こんな感じの順番で構成していきます。大事なので押さえておきましょう。
  1. クラスタの作成

    • クラスタを動かすためのEC2インスタンスの設定を行います。具体的にはインスタンスタイプ、インスタンス数、VPC、サブネットの設定になります。
  2. タスク定義

    • クラスタ上で動かすコンテナの情報を登録します。コンテナイメージのURLやCPU、メモリのハード/ソフト制限、アプリケーションで利用する環境変数の定義などを行います。
  3. ロードバランサの作成

    • クラスタの上位に位置するロードバランサの設定を行います。スケールアウトやスケールインしてもロードバランサはサービスを見つけ出し配下に組み込むことができます。
  4. サービスの作成

    • クラスタとサービスを結びつけるのがサービスの役割です。タスクの最少数やAutoScalingの設定を行ったりできます。
    • 1つのクラスタに複数サービスを登録することももちろん可能です。

それではさっそくクラスタの作成からやってみましょう。

クラスタの作成

image

image

  • 正常に作成されると「クラスターの表示」ボタンが押せるようになります。

image

タスク定義

  • 次はタスクの定義です。タスクでは

image

  • タスク定義名を入力し、「コンテナの追加」をクリックします。

image

image

  • 作成を押せばタスク定義の作成が完了します。

ELBの作成

  • ELBは以下の設定で作っておきましょう

    • ELB名:app-demo-lb
    • 種類:アプリケーションロードバランサ
    • 2つのAZそれぞれのSubnetを指定
    • セキュリティグループで80/HTTPを通すように設定
  • ターゲットグループは以下のようにクラスタで設定したインスタンスIDをそれぞれ登録してください。

image

サービスの作成

  • クラスターのTOPからdemo-clusterを選択し、サービスタブで「作成」

image

  • タスク定義とクラスタ名は自動で埋まりますので、サービス名とタスクの数を設定します。
  • 今回はAZにそれぞれコンテナを作りたいので2としました。

image

  • 画面の下の方にあるELBの追加を選択します。

image

  • ELB名は作成したもの、リスナーポートは80、ターゲットグループ名は作成したものを選択します。

image

image

  • 「作成」を押して、サービスの画面をみるとPENDINGになっています。

image

  • 少し経つとRUNNINGになっている事が確認できると思います。

image

  • ELBのエンドポイント/app/をブラウザで叩くと画面が表示されるはずです。

image

コンテナを落としてみるとどうなるのか

  • タスクの一覧から、タスクを一つ消してみましょう。

image

  • 数十秒後に見てみると別のタスクIDのインスタンスが表示されているはずです。

image

  • コンテナが起動する数十秒間の間はアプリケーションロードバランサが生きているタスクの方にうまくルーティングしてくれるのかな?と思ったら「502BadGateway」というエラーが画面に返ってきました。
  • ここはALBのヘルスチェックの閾値を短くすることである程度は短くできそうです。
  • ここをさらに短くするには、コンテナ自体を軽くすることと、すぐに起動できるアプリケーションを利用するしかなさそうですね。

コンテナのリリース

  • 新しいコンテナをリリースするには、タスク定義に新しいリビジョンを登録し、サービスを更新することで実現可能です。さっそくやってみましょう。

image

  • コンテナのバージョンを2.0にして、新しいリビジョンを登録します。

image

  • 追加されたリビジョンを選択し、アクション→サービスの更新を押します。

image

  • タスク定義に新しいリビジョンが指定されていることを確認して、「サービスの更新」

image

  • サービスの「デプロイ」タブを覗くと、今はVer1.0が2つ動いていることが確認できます。

image

  • コンテナを一つ落としてみましょう

image

image

  • 実行中の数がそれぞれ1になり、タスクの一覧からもそれぞれが動いていることがわかりますね。
    image

image


Blue/Green Deployment

  • Blue/GreenDeploymentでは新しいリビジョンのアプリ用に、新しいインスタンスを構築して入れ替える必要があります。
  • この為のパラメータがサービスのデプロイメントオプションにある最大率(maximumPercent)です。2台の時にこの値を200%にしておくと、4台まで同時に動かしておくことができることを意味します。
  • 4台のインスタンス上で動かすにはECSのインスタンス台数を事前に追加しておくか、AutoScalingさせておく必要があります。もしECSインスタンスが2台の状態で4つのコンテナを動かそうとすると以下のようなメッセージがでてしまいます。(ポートかぶってるから上がらないよ。ってことですね)

  • さっそくやってみます

service demo-service was unable to place a task because no container instance met all of its requirements. The closest matching container-instance xxxxxxxxxxxxxxxxxxxx is already using a port required by your task. For more information, see the Troubleshooting section.

image

image

  • この状態でサービスの更新画面でタスク定義を新しいリビジョンに指定して「サービスの更新」を押してみます。
  • おお。4台分のコンテナが起動しましたね。

image

  • ちょっと経つと(3分ほど?)、古いタスクから削除されていきます・・・・

image

  • 最期は新しいタスク定義しか残らなくなりました。自動ですよ。自動。便利ですねー。

image


AutoScaling/ScaleIn

  • 次はオートスケールとスケールインを試してみます。
  • 通常のオートスケールではインスタンスだけでしたが、インスタンス上で動くコンテナもスケールする必要があります。
  • 今回は2つのインスタンスで2つのコンテナで動いていたものを、負荷をかけることにより4つのインスタンス上に4つのコンテナにスケールアウトさせて、スケールインさせたいと思います。

サービスのAutoScaling設定

  • タスクの最大数を4にして、スケーリングポリシーの追加を押します。

image

  • スケールアウトポリシーの設定を行います。
  • CPU使用率の1分間の平均が20%超えた場合2タスクスケールアウトする設定にしました。

image

  • スケールインポリシーの設定を行います。

image

  • ポリシーが追加できたら「保存」を押しましょう

image

  • ポリシーが追加されていることを確認して、「サービスの更新」を押します。

image

  • これでサービスの設定はおしまいです。

ClusterのAutoScaling/ScaleInの設定

  • ECSインスタンスのオートスケールのポリシーは、EC2インスタンスのAutoScalingGroupの設定で行います。
  • 最大数を4にします。

image

  • Scaleout/ScaleInのポリシーを設定します。
  • サービスの設定と同じく、クラスタのCPU使用率が20%以上だと2台スケールアウトするように設定しました。

image

うごかしてみる

  • ECSインスタンス上でCPU使用率を強引に(openssl speed -multi 1)あげてみたのですがうまく動きませんでした。
  • ありがちですけどabで負荷をかけてみます。
  • abをインストール
sudo yum install -y httpd24-tools
  • 負荷をかける
ab -n 100000 -c 100 http://localhost/app/loginForm
  • CloudWatch Alerm(CPUが20%以上)があがる

image

  • サービスの必要数が4に変更される

image

  • インスタンスがオートスケールする

image

  • タスクが起動する

image

  • 負荷を解除する

  • CloudWatch Alerm(CPUが20%より小さい)があがる

image

  • 必要数が2に変更される

image

  • コンテナとインスタンスが2ずつに変わる

  • ここまでやって思ったんですが、インスタンス→コンテナの順に起動されたんですからコンテナ→インスタンスの順に落としていった方がよさそうですね。それはスケーリングポリシーのクールダウン時間で調整ができそうです。

ECSインスタンスの起動を待つのか?

  • ECSのインスタンスの起動を行った後にコンテナが起動されるので結局時間が掛かってしまいます。負荷の時間が予測されるのであればECSインスタンスを事前に起動しておいた方がECSのスケールアウトが高速にできそうです。
  • Dockerのメリットの一つである起動の高速化のメリットを享受するにはこのスケジューリングがキーになりそうですね。

どのインスタンスがスケールインするのか?


さいごに

ここまでコンテナ管理のハードルが下がると、今後はアプリケーションをコンテナにして配布するのが普通になってくると思います。
そうなるときのためにDockerについてしっかりと理解し、良い設計を心がけたいものですね

続きを読む