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についてしっかりと理解し、良い設計を心がけたいものですね

続きを読む

ELB暖気運転(Pre-Warming)申請についていくつかサポートに問い合わせた

はじめに

急激なトラッフィク増が見込まれる際に、
予めELBをスケール(アウト/アップ)しておいてくれる暖気運転。

実際の申請方法等はこちらのサイト様の記述が詳しいです。

http://dev.classmethod.jp/cloud/aws/elastic-load-balancing-pre-warming/
http://qiita.com/takachan/items/80c343e03789b01adcf9

上記のページを参照していくつか疑問に思ったことがありましたので、
サポートに問い合わせを行いました。

以下に回答の要旨を記載していきます。

補足

  • 問い合わせを行ったサポートプランは「Developer」です。
  • 質問および回答を丸写しで載せるわけではないので、私の解釈の仕方が誤っている可能性があります。

Q1.暖気運転前のELBの処理性能の目安はあるか

申請する項目に、リクエスト数 (リクエスト/秒)やスループット (bit/秒)などがあるので、
「このくらいの数字だったらわざわざ暖気する必要ないよ」という目安が聞ければという意図で聞いてみました。

A1.目安としてお答えできるものはない

  • AWSでは日々設備増強/構成改善を行っており、ELB のベースラインパフォーマンスも変動する
  • サポート内でも具体的な情報は共有されておらず、目安として一概に案内できない
  • 実際に申請が出された後に、ELB担当部署がスケールの必要性も含めて検討している

Q2.暖気運転申請が出されるケースの参考情報はあるか

実際に暖気運転申請が出されるケースとしては、どの程度の規模の数字になるのか。
(1時間でxxxリクエスト…)
(バックエンドのインスタンスとしてがインスタンスタイプxxが○○台…など)
参考として聞ける情報があればと思い質問しました。

A2.参考としてお答えできるものはない

  • A1.とほぼ同様
  • 暖気運転の必要性も兼ねて一度申請を出してもらうことが多い」

※ここで「一旦情報をもらえれば必要性だけでも確認します」と回答いただいたのですが、
 サポートプラン「Developer」のままで確認いただけるのかは聞き損ねました。

Q3.暖気運転により追加料金はかかるか

料金表の記載からかからないだろうと想定していましたが、
念のため聞いてみました。

  • Elastic Load Balancing 時間(または 1 時間未満)あたり
  • Elastic Load Balancing によって処理されるデータ 1 GB あたり

https://aws.amazon.com/jp/elasticloadbalancing/classicloadbalancer/pricing/

A3.いいえ

かかりませんでした。

Q4.ELBのスケールイン時にセッション切れは起こらないか

例えば、AutoScalingによってバックエンドのインスタンスがスケールインすると、
それによってセッション切れが生じるという可能性があります。
(それを防ぐためにキャッシュサーバにセッション情報を外出しにする)

同じようなことが、暖気運転でスケールアウトされたELBの
スケールイン時に起こらないか、という疑問が生じたので質問しました。

A4.基本的に起こらない

  • スケール対象のノードは一定時間維持するようにしている
  • 新規セッションは受け付けないが既存のセッションに対しては一定の猶予時間を設けている
  • 猶予時間は正確な値を公表していないが、数十分以上である

Q5.暖気運転の期間はどこまで指定できるか

スケールさせておくことによる追加料金は発生しないので、
極端な話「1年間ずっと暖めておいてください」もできるのか聞いてみました。

A5.基本的には2週間程度

  • 特別な事情があっても一ヶ月程度
  • それ以上必要な場合は個別にヒアリングして妥当性を判断する

おわりに

以上、質問およびその回答でした。
情報の正確性を保証するものではありませんので、
参考として捉えていただければと思います。

マネージドであるがゆえに、
ユーザーによる設定ではなく申請という形になるのが興味深いですね。

以下、蛇足です。

蛇足1.スケールされたことの確認

IPアドレスによってスケールを確認できるようです。
http://itpro.nikkeibp.co.jp/atcl/column/16/121200298/121200002/

ALB、CLBともスケーリングの仕組みは公開されていないが、各テストケース実施前と実施後にnslookupコマンドを実行し、ALBおよびCLBの状態を監視。IPアドレスが変更された場合はスケールアップ、IPアドレスが増えた場合はスケールアウトが発生したとみなす。

蛇足2.ELBによって自動的にENIもできる

他のENIと同じように、AWSマネジメントコンソールから確認できます。
ちょっと特殊なのは下記のあたり。
– アタッチメントの所有者:amazon-elb
– 説明:ELB <ELB名>

蛇足3.暖気運転?暖機運転?

恐らく暖気運転が正。下記は誤字か。

WS000000.JPG

追記

コメントにてご指摘いただき、「暖機」のほうが日本語として正しそうです!

以上です。

続きを読む

AWS上のZabbixを、耐障害性を考慮した冗長構成としてどう組むか考えてみた

Stylez Advent Calendar 2016の23日目です。

以前、取引先の方と「Zabbix自体には冗長化の機能って無いよね。AWS上でZabbix構築する際、耐障害性を考慮した冗長構成のベストって何?」って話になり、色々議論したことがあったので、その内容をまとめました。

前提

  • AWS上で構築
  • 負荷分散的な要素は考慮しない
  • 監視サービスの継続性を重視

案1. クラスタウェア

zabbixha_clusterware.png

絵が雑ですいません。。。

ネットで探すと良く出てくるパターンでしょうか。
PacemakerとかDRBDでWebやDBのプロセス・データをサーバ間で監視・同期させて、障害が起きたら自動的に切り替える構成です。

オンプレ時代からの枯れた構成ですし、実績は今回紹介するパターンでは最も多そうな気がしますが、クラスタウェアの管理が運用上負担になりそうです。(個人的にトラウマ)

参考:AWS上のZabbixをPacemaker(HeartbeatV2)で冗長化

案2. AutoScaling + RDS

zabbixha_autoscaling.png

WebとDBを分離し、DBはRDSを利用。
WebはAutoScaling(上限下限:1)にして、インスタンスが死んだら自動的に再構築されるという構成です。
クラスタウェア構成と比較して、冗長部分はAWSにお任せするパターンです。

この構成は少しクセがあり、AutoScalingなのでインスタンス起動の度にIPアドレスが変わります。
従って、管理画面アクセス用と監視対象サーバからの通信(アクティブチェック)用の内部ELBを入れる必要があります。
また、Zabbixエージェント側の設定も工夫が必要で、ホスト自動登録設定を仕込んだ際に、内部ELBのIPアドレスでホスト登録されてしまうので、実サーバのIPを通知できるようにエージェント設定を変更する必要があります。

ここまでAutoScalingのクセに悩むのであれば、AutoRecoveryでも良いじゃんって話もありますが、AutoRecoveryだとAZをまたいで起動しないので、AZ障害を鑑みてAutoScalingを選んでいます。

クセをつかむまでは大変ですが、一度設定してしまえば、冗長部分の運用負荷は大きくないので、おすすめです。

案3. Zabbix製バックアップツール

zabbixha_buckuptool.png

Zabbix公式で出ている Zabbix設定バックアップ同期ツール(以下、バックアップツール)というものがあります。
通常、エクスポート/インポート出来る設定項目は限られていて、アクションやユーザとか一部の項目はエクスポートできません。
もちろん、DBダンプすれば可能ですが、まるっとダンプすると監視データごとぶっこぬいてしまうので、監視設定のバックアップという意味合いでは難があります。
このツールを使えば、通常エクスポート出来ない部分含めた設定をファイルとして出したり入れたりすることが出来ます。

従って、このツールを双方のシングルZabbixに入れて、マスターからバックアップしたファイルをスレーブのZabbixに適用し続ければ、設定を同期することが可能になります。

この構成は文字通り設定だけ同期する事になるので、Active/Active構成で動くZabbixが出来上がります。
しかも、スレーブにバックアップ設定を適用する際、障害通知を無効にするオプションが指定できるので、双方のZabbixからアラートが飛ぶという事も回避できます。

問題点として、クラスタ構成と違ってマスター障害時はスレーブ側の障害通知を手動で有効にし、管理画面はスレーブ側を参照する必要があります。
あと、バックアップファイルをスレーブに転送する処理は、自分で作る必要があります。(rsyncするとか、共有ディレクトリに置くとか)
また、Active/Active故に、双方の監視データは異なる値が取れている可能性があります。運用する上ではこのあたり意識する必要がありそうです。

最後に、一番の問題は、このツール自体が、Zabbix社の技術サポート契約に加入する必要があるという事です。
という事で、お金かかります。

総括

個人的にはクラスタの管理はしたくないので、案2か案3。構成のシンプルさを見れば、案3を推したいです。
最近では、Dockerとか今までの常識を覆すようなインフラも出てきて、こういった専用サーバがある前提でという考えも無くなってくるのかもしれません。
他にも良い案知ってる方がいれば教えてください。

続きを読む

AWS CodeDeploy を使うときに気をつけたいこと

エムティーアイ Advent Calender 2016 の23日目の記事です。

AWS CodeDeploy (以下、CodeDeploy) は AWS の中の開発者ツール(通称 Code シリーズ)の中の1つで、デプロイ作業をコードで記述することで自動化できるサービスです。
開発者ツールや CodeDeploy 自体の説明は以下などを参考にしてください。

この記事では実際に CodeDeploy を数ヶ月使っていてハマってしまった経験を書いていきます。
どれも経験に基づいた情報であり個人の見解等も入っていますので、正確な情報は公式ドキュメントや、AWS サポート等へご確認ください。

Hooks スクリプトはどんなときでも動くように

Hooks スクリプトにデプロイ時の様々な付随処理を書くことができます。
スクリプトで目的の処理を実行できるようになったら、いろんな条件でデプロイを試してみてエラーが出ないことを確認します。
例えば、まっさらな状態のインスタンスに対してのデプロイ、アプリケーションが稼働中の状態でのデプロイなどです。

僕の場合はとある C# の Web システムで、開発中は正常にデプロイできてたけど本稼働後にデプロイをさせたら失敗するようになってしまいました。
原因は対象インスタンスへの Web リクエストの停止がうまく行えていなかったために、処理中の dll の置き換えができなくなってしまいました。

また、AutoScaling で新たに生成されたインスタンスに対してのデプロイで失敗してしまうこともありました。
特にこの CodeDeploy のターゲットに AutoScaling グループを指定する場合は注意が必要です。
AutoScaling ではインスタンス生成時に CodeDeploy によるデプロイを実施しますが、デプロイに失敗した場合はインスタンスが正常に起動しなかったものとして、インスタンスのを破棄して再生成します。
すると再度失敗して、破棄と生成の無限ループに突入してしまいます。
気づかなければインスタンスがいつまでも InService にならないだけでなく、インスタンスの料金もかかってしまい、目も当てられなくなります・・・。

PowerShell は 32bit 版が動く

Windows での Hooks スクリプトでは直接 PowerShell を実行することはできず、bat ファイルから powershell script.ps1 のように呼び出して使います。
このとき、たとえ OS が 64bit 版だったとしても 32bit 版の PowerShell が実行されてしまいます。
そのため、一部のコマンドレットが使用できません。

これについて困ってる人は結構いるようで、ググるとそれなりに出てきます。
64bit 版の方を実行させるやり方を書いてる人もいましたが、僕はうまく行きませんでした。
仕方なく通常の Windows コマンドや 32bit 版でなんとか回避しましたが、「こうやると 64bit 版が使えたぜ!」っていう情報があったら教えていただけるとうれしいです。

CodeDeploy で配置したファイルはいじってはいけない

AppSpec ファイルの files セクションに書くことでファイル群の配置を行うことができます。
その配置されたファイルに CodeDeploy 以外で変更を加えてしまうと、次のデプロイ時に失敗してしまいます。

CodeDeploy では2回目以降のデプロイの際、 files の destination で指定されたファイルやディレクトリ以下を削除して新しいファイルをコピーしています。
配置したファイルの更新日などのメタデータを見ているようで、デプロイ後にファイルが更新されていると「勝手に消したらまずいんじゃね?」と判断(イメージです)してデプロイを中断します。

ファイルを更新してしまった場合は手動で削除するか、CodeDeploy の作業ディレクトリにある元ファイルをコピーして元に戻すかしてから再デプロイしましょう。
もしくはインスタンスごと作り直してもいいと思います。

インスタンスの時刻がずれてるとデプロイできない

ある日突然、特定のインスタンスだけデプロイに失敗するようになり、しかもマネジメントコンソールで確認するとデプロイライフサイクルにすら入る前にエラーとなっていました。
インスタンスにログインして CodeDeploy エージェントのログを見るとこんなログがありました。

codedeploy-agent-log.txt
2016-11-30 15:46:26 INFO  [codedeploy-agent(2776)]: [Aws::CodeDeployCommand::Client 400 0.062357 0 retries] poll_host_command(host_identifier:"arn:aws:ec2:ap-northeast-1:NNNNNNNNNNNN:instance/i-XXXXXXXX") Aws::CodeDeployCommand::Errors::InvalidSignatureException Signature expired: 20161130T064626Z is now earlier than 20161130T070808Z (20161130T071308Z - 5 min.)

2016-11-30 15:46:26 ERROR [codedeploy-agent(2776)]: InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Cannot reach InstanceService: Aws::CodeDeployCommand::Errors::InvalidSignatureException - Signature expired: 20161130T064626Z is now earlier than 20161130T070808Z (20161130T071308Z - 5 min.)

どうやらインスタンスの時刻同期が長いこと失敗し続けていて、インスタンスの時刻が大幅にずれてしまっていたようです。
CodeDeploy ではインスタンスの時刻が5分以上ずれていると、認証に失敗し CodeDeploy エージェントと CodeDeploy との通信が行えません。

ドキュメントにもちゃんと書いてありました。

Troubleshooting “InvalidSignatureException – Signature expired: [time] is now earlier than [time]” deployment errors

AWS CodeDeploy requires accurate time references in order to perform its operations. If your instance’s date and time are not set correctly, they may not match the signature date of your deployment request, which AWS CodeDeploy will therefore reject.

OneAtATime で最後の1台のデプロイ失敗は無視される

インスタンス1台1台のデプロイの成功・失敗とは別に、デプロイ全体(デプロイ ID ごと)の成功・失敗という概念があります。
通常、Deployment config が OneAtATime の場合、対象インスタンスに順次デプロイしていく途中で1台でも失敗するとデプロイが中断され全体として失敗となります。

しかし、最後の1台(4台構成なら4番目にデプロイされるもの)が失敗しても全体として成功と評価されます。
これもドキュメントに書かれているので仕様のようですが、その理由は “1台ずつオフラインになることを前提とした設定だからいいよね” (超意訳。あってるよね?)
・・・そうっすか笑。

The overall deployment succeeds if the application revision is deployed to all of the instances. The exception to this rule is if deployment to the last instance fails, the overall deployment still succeeds. This is because AWS CodeDeploy allows only one instance at a time to be taken offline with the CodeDeployDefault.OneAtATime configuration.

このため、全体の評価が成功になっていたとしても失敗したインスタンスがないか確認したほうがいいかもしれません。

停止しているインスタンスにもデプロイの試行がされる

CodeDeploy のターゲットはインスタンスの任意のタグを指定することができます。
この場合、指定したタグの付いたインスタンスであれば停止しているインスタンスに対してもデプロイの施行が行われてしまいます。
もちろん、停止しているインスタンスにあるエージェントがこの指示を受け取れるわけはないので、しばらくするとタイムアウトで必ず失敗します。

ちなみにマネジメントコンソールで Deployment Group を作成する際にタグを指定するとデプロイ対象のインスタンスが確認できますが、このタイミングで停止しているインスタンスも破棄されたインスタンスもマッチします。
(破棄されたインスタンスへはデプロイの施行はされません)

AWS サポートに問い合わせたところ「現時点ではそういう動作をするようになっているが、ドキュメントへの記載はなく、好ましい動作とは言えないので開発部門へフィードバックする」との旨の返答をいただきました。
(「単純にバグなのでは?」という気もしますが、AWS サポートは丁寧で非常に好感が持てます!)

とりあえず、何かの都合で一部インスタンスを破棄はせず停止しておきたいような場合の対応は、現時点ではインスタンスのタグを一時的に変更するのが手っ取り早くていいと思います。

最後に

AWS のサービスは実際に使ってみて初めて知る仕様や、ちょっとしたノウハウが必要なものが多くあり苦戦することもありますが、それもまた面白いところかもしれません。
公式ドキュメントでも日本語化されていないものがあったり、日本語ドキュメントには載ってなくて英語版にだけあるといったことも多々あって英語力の低い僕は大変です・・・。

CodeDeploy もそれに漏れず、いろいろありました。
とは言え、その恩恵は大きく、デプロイのコード化で Excel 方眼紙のデプロイ手順ともおさらばできますね!
AWS CodeBuild が2016年の Re:Invent で発表され、デリバリもどんどん楽になりそうです。

続きを読む

Terraformで始めるRolling deployment

:santa:この記事は Recruit Engineers Advent Calendar 2016 の22日目の記事です。:santa:

昨日はmookjpさんのLet It Crashとは何かでした!ゴイスー!

本日はサーバサイドエンジニアとして従事している私がAWSでインフラ構築をした記事となります。
この記事を書く1週間程前はそろそろ記事の下書きをしておくかー。
と、やる気に満ち溢れていたのですが、悲しきかな・・・PS4 proが届き当時の気持ちはどこかに置いてきてしまったようで、急いで先程記事を書き終えました。

はじめに

日々やらねばいけないことが満ち溢れている中、運用サーバトラブルに対して時間は割きたくないものです。例えば、EC2の突然死やメンテナンスです。
限られた時間の中でより効率的に時間を使いたいという欲求を皆様お持ちではないでしょうか。
そこで楽をしようと思い立ち、本エントリーの構成にしたのです。

使うツール

細かいものは省略して大枠だけ。

AWSで利用したサービス

細かいものは省略します。

  • ALB
  • AutoScaling
  • EC2
  • CloudWatch Events
  • Lambda
  • SNS

Rolling deployment

デプロイにおいてTerraformでやっていること

結論だけ先に書くと、

[Terraformでやっていること]

  • DataSourceを使って最新版のAMIを取得
  • ASGのLaunchConfigurationを作成
  • ASGの更新

その他は基本Lambdaに任せています。
AMIの作成はDroneを使ってPackerを実行しています。

デプロイフロー

まずはアプリケーションリリースまでのデプロイフローを見てみましょう。

AWS Design.png

あまり複雑にならないよう心がけたつもりで、それぞれのSTEPで見たときにシンプルになれば良いかなと思いこのフローにしています。

ステップごとに見るフロー

1. AMI作成

CIサーバ

弊社ではGitHub Enterpriseを使っているので、今までJenkins2を使ってCIを回していましたが、やはりコンテナベースでテストを回さないとジョブがconflictするわけです。
一々レポジトリごとにDockerfile用意するのも面倒だったので、OSS版のDroneに切り替えました。
Droneを一言で表すなら「最高」。

DroneからAMIをビルドする

PackerとAnsibleを使ってAMIを作成しています。
後述しますが、この時新しいAMIを作るうえで利用するAMIは、予め用意しておいたベースのAMIで、そこから新しいバージョンのシステムをデプロイ & ビルド(npm install / bundle install…)してAMIを作成しています。

2. ASG更新

Terraformを使ってASGを更新する

LaunchConfigurationの更新は行えないため、Terraformを使ってLaunchConfigurationを作成し、既存のASGに紐付けています。

3. スケールアウト

新しいAMIを利用したEC2インスタンスを立ち上げ

ASGの更新をトリガーにCloudWatch EventsからLambdaを起動しています。
このLambdaでは下記を実行しています。

  1. 更新したAutoScalingGroupのDesiredCapacityを取得
  2. setDesiredCapacity(DesiredCapacity * 2)を実行して、新しいAMIのインスタンスを立ち上げる

ここまでで新しいバージョンのシステムをリリースすることが出来ました。

4. スケールイン

古いAMIインスタンスの破棄方法

特に何かしていません。
setDesiredCapacity()を使って、一時的にスケールアウトしている状態なので、ASGに設定されたcool down経過後は古いインスタンスからスケールインされます。

とはいえ、いきなりTerminateされても困る

Fluentdを使っているので、いきなりTerminateされてバッファがflushされないまま破棄されても困ります。
なので、Lifecycle Hookを使って下記を実行します。

  1. スケールイン前にSNSへ通知を送る
  2. SNS通知をトリガーにLambdaを実行する
  3. LambdaからSSMのRun Commandを実行
    1. Fluentdのバッファをflush
    2. CompleteLifecycleActionを実行

これでFluentdのバッファをflushさせつつ、スケールインさせることが出来ました。

それぞれ工夫したところ

ゴールデンイメージの作成

デプロイの度に1からAnsibleのプロビジョニングを実行していると、とても時間がかかります。
なのでAMIをbase / application_base / applicationと分けていて、アプリケーションの更新だけであれば、baseから作成したapplication_baseを使って新しいAMIを作っています。

それぞれのAMIは、下記の役割で作っています。

base

基本的にOSの設定(Timezone / Kernel parameters…)であったり、インタプリタのインストールであったり頻繁に変更が行われないものをプロビジョニングしています。

application_base

アプリケーションが依存するLinuxライブラリのインストール等を行っています。

application

アプリケーションのデプロイとビルドを行います。npm installnpm run hogeであったりbundle installはここで行っています。

システムの更新であればapplicationのビルドしか行いませんが、他のレポジトリで管理しているbaseもしくはapplication_baseの構成が変わった場合は、そちらのビルドが走ります。

Packer / Ansible

ディレクトリ構成

実際のものとは異なりますが、共通で読み込む変数とそうでないものでPackerもAnsibleもファイルを分けています。↓のcommon*です。

├── provisioners
│   ├── ansible.cfg
│   ├── base.yml
│   ├── inventories
│   │   ├── common.yml
│   │   ├── common_secrets.yml
│   │   ├── development
│   │   │   ├── group_vars
│   │   │   │   └── development
│   │   │   │       ├── secrets.yml
│   │   │   │       └── vars.yml
│   │   │   └── inventory
│   │   ├── production
│   │   │   ├── group_vars
│   │   │   │   └── production
│   │   │   │       ├── secrets.yml
│   │   │   │       └── vars.yml
│   │   │   └── inventory
│   ├── requirements.yml
│   ├── roles
│   ├── site.yml
├── packer.json
└── variables
    ├── base.json
    ├── common.json

ファイル名が*secretsになっているものはAnsible-vaultで暗号化したファイルです。
復号化はプロビジョニング実行時にtemporaryのpasswordファイルを用意してansibleに読み込ませています。
こんな感じです。

packer.json
"extra_arguments": [
  "--tags",
  "{{user `tags`}}",
  "--vault-password-file",
  ".vault"
]

Ansible Galaxy

極力Ansibleのroleは、Ansible Galaxyのroleとして使えるように書いて、GitHub EnterpriseにあるAnsible Galaxy organizationにレポジトリを作っています。
なので、provisioners/requirements.ymlが置いてあります。

site.yml

実際のものとは異なりますが、こんな感じにしてtagでincludeするymlファイルを制御しています。

---
- vars_files:
    - inventories/common.yml
    - inventories/common_secrets.yml
- include: base.yml tags=base
- include: hoge.yml tags=hoge

site.ymlは極力シンプルに。includeしている各ymlからroleを読み込んでいます。

Terraform

ディレクトリ構成

こちらも実際のものとは異なりますが、PackerやAnsible同様、共通で読み込む変数とそうでないものでこんな感じにしています。
また、Stageごとにtfstateを分けています。

├── environments
│   ├── common.tfvars
│   ├── development
│   │   ├── ami.tf
│   │   ├── main.tf
│   │   ├── provider.tf
│   │   ├── terraform.tfvars
│   │   └── variables.tf
│   └── production
│       ├── ami.tf
│       ├── main.tf
│       ├── provider.tf
│       ├── terraform.tfvars
│       └── variables.tf
├── provider.tf
├── vpc.tf
├── terraform.tfvars
└── variables.tf

Terraform Modules

Ansible Galaxy同様、こちらもorganizationを用意しレポジトリを作っています。
なので上のenvironments以下のディレクトリにあるmain.tfはmoduleを読み込み変数をセットするだけに留めています。

Lambda

Runtime

利用しているLambdaは全てnodejs4.3で記述していて、AWSリソースの操作はAWS SDKを利用しています。

デプロイ

apexを利用してLambdaのデプロイを行っています。
基本はapexのMultiple Environmentsに従った構成にしています。

├── functions
│   ├── hoge1
│   │   ├── function.development.json
│   │   ├── function.production.json
│   │   ├── index.js
│   │   └── package.json
│   └── hoge2
│       ├── function.development.json
│       ├── function.production.json
│       ├── index.js
│       └── package.json
├── project.development.json
└── project.production.json

project.jsonはこんな感じにして、

"nameTemplate": "{{.Project.Name}}_{{ .Project.Environment }}_{{.Function.Name}}",

同一ソースで異なるfunction名かつ、それぞれが異なる環境変数の値を保持することが出来ました。

最後に

AMIのビルドまでをアプリケーションのパッケージングに見立て構築しています。
本来、この思想であればコンテナを使うほうが楽だと思っていますが、アプリケーションのcontainerizedが出来ていなかったのでこうなっています。

また、当初はALBのリスナー付け替えでBlue-Green Deploymentにしようか悩んでいたのですが、瞬断が気になるので結局Rolling deploymentを選択しました。

全体的にざっくりとしか書いていません。本当は短い時間の中で紆余曲折あり今の形になっているのですが、それぞれのもっと細かい話は別の記事で書こうと思います。:innocent:

次の記事は、kadoppeさんです!よろしくお願いします!

続きを読む