目的・やりたいこと
よくあるAuto Scaling構成で、AMIの更新を現状構築Gでは手動でやっているところが多い。
コンテンツの更新頻度が多いと、運用に工数が取られるので、工数削減のため自動化をインフラ技術で検討する。
Auto Scaling構成でのAMI自動更新ソリューションについてお話しさせて頂きます。
よくあるAuto Scaling構成で、AMIの更新を手動でやっているところが多いかと思います。
しかし、コンテンツの更新頻度が多いと、運用に工数が取られてしまいます。
そこで、工数削減のための自動化を試みましたので、その方法を紹介いたします。
対象者
Systems Managerなどで自動化を行っているあるいは行ってみたい方(中級者~上級者)
対象となる技術
- Systems Manager
- Lambda
- Auto Scaling
- AMI
条件
・自動で、AMI取得、Auto Scaling起動テンプレート更新、ローリングアップデートまで行えること
・メンテが極力いらない構成であること
・失敗時にエラーを検知できること
・CFやTerraformなどで横展開できること
参考
- EC2 Auto Scaling ゴールデンイメージの更新と Auto Scaling グループのインスタンス更新を自動化しました
- [アップデート] Amazon EC2 Auto Scaling で Auto Scaling Group 内のインスタンスを最新化できるようになりました
- Tutorial: Deploy WordPress to an Amazon EC2 instance (Amazon Linux or Red Hat Enterprise Linux and Linux, macOS, or Unix)(Linuxデプロイ用)
- Tutorial: Deploy a “hello, world!” application with CodeDeploy (Windows Server)(Windowsデプロイ用)
注意事項
- 対象AMIがWindowsの場合、デプロイコマンドをAWS-RunPowerShellScriptで実行している関係上、事前にAWS CLIを入れておく必要があります。運用上そういったものを入れておくことはできない場合は、Windowsでは本スクリプトは使えません。
- ローリングアップデートは今動いているEC2に対しても更新をかけるため、ローリングアップデート前に何かしらアップデートや変更作業を実施していた場合、それが消えてしまいます。
- EC2SSMRoleにcodedeployを実行するためのポリシーを付与する必要があります。
- 途中どうしてもデプロイに失敗してハマってたが、どうやら権限やエージェントの再起動が必要だったらしい。CodeDeployでEC2へデプロイした結果、処理が1つも進まずに失敗してしまうときの対処方法が大変参考になった。起動設定でインスタンスプロファイルを割り当ててなかったのが原因
- このため、起動テンプレートにCodeDeployDemo-EC2-Instance-Profileというインスタンスプロファイルを設定する必要があり、新しいEC2のロールにはこれが付与されています
作業の流れ
事前作業
- デプロイ環境が作成されていること
今回はLinuxでデプロイ環境を作ってみました。Apacheを起動してWordPressを構築するサイトを作るものです。
/tmp/ |--WordPress/ |-- appspec.yml |-- scripts/ | |-- change_permissions.sh | |-- create_test_db.sh | |-- install_dependencies.sh | |-- start_server.sh | |-- stop_server.sh |-- wp-admin/ | |-- (various files...) |-- wp-content/ | |-- (various files...) |-- wp-includes/ | |-- (various files...) |-- index.php |-- license.txt |-- readme.html |-- (various files ending with .php...)
これらのファイルを用意し、アプリケーションのファイルを単一のアーカイブファイルにバンドルし、アーカイブファイルをS3にプッシュします。
aws deploy create-application --application-name WordPress_App aws deploy push \ --application-name WordPress_App \ --s3-location s3://codedeploydemobucket/aaa/WordPressApp.zip \ --ignore-hidden-files
更なる準備としては、あらかじめSSMエージェントが入っているAmazon Linuxに、Codedeployエージェントを入れたEC2が対象になります。
Amazon Linux または RHEL 用の CodeDeploy エージェントをインストールする
wget https://aws-codedeploy-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/install
ちなみにWindowsでデプロイ環境を作る場合は、IISを起動して「Hello、World!」を表示する簡単なWebサイトになります。
REM Install Internet Information Server (IIS). c:\Windows\Sysnative\WindowsPowerShell\v1.0\powershell.exe -Command Import-Module -Name ServerManager c:\Windows\Sysnative\WindowsPowerShell\v1.0\powershell.exe -Command Install-WindowsFeature Web-Server
2.今回使うコードをGitHub – YoshiiRyo1/aws-autoscaling-amiupdateからクローンしておく
3.autoscaling-amiupdate.yaml の69行目「# deployment command here」を実際のデプロイコマンド(自分の場合は以下)に置き換えます。awsコマンド実行時に「You must specify a region.」と怒られないように、–region ap-northeast-1を指定しておくのがポイントです。
- aws deploy create-deployment --application-name WordPress_App --deployment-config-name CodeDeployDefault.OneAtATime --deployment-group-name WordPress_DepGroup --s3-location bucket=nozaki-codedeploybucket,bundleType=zip,key=aaa/WordPressApp.zip --region ap-northeast-1
4.自動化の中で起動する仮EC2で使用するインスタンスプロファイルを作成
$ bash 00-createinstanceprofile.sh
5.SSM ドキュメント実行時に使用する IAM ロールを作成
$ bash 01-createrole.sh
6.Lambda 関数実行時に使用する IAM ロールを作成
$ bash 02-createlambbarole.sh
7.自動化プロセスの一部であるLambda関数 Automation-UpdateAsg を作成
$ bash 02-createlambdafunction.sh
8.自動化プロセスを記述したSSMドキュメント CreateGoldenImageandupdateASG を作成
$ bash 03-createdocument.sh
SSMドキュメント作成ステップ
おおまかには以下のステップとなります。
No | 作業内容 |
---|---|
1 | Systems Manager Automationを実行 |
2 | 既存のAMIから仮のEC2を起動 |
3 | S3からデプロイコマンドを実行 |
4 | 仮EC2をシャットダウンし、新しいAMIを作成 |
5 | 起動テンプレートのAMI IDを新しいAMIのものに修正 |
6 | Lambda関数Automation-UpdateAsgを呼び出す |
7 | Auto Scalingグループの起動テンプレートを新しいものに修正 |
8 | ローリングアップデートにより、新しいAMIからAuto Scaling配下でEC2を起動 |
9 | 古くなったAMIを削除(都合上無効にしています) |
SSMドキュメント入力項目
SSMドキュメントであるCreateGoldenImageandupdateASG実行時の入力項目として、主要なものを記載
設定項目 | 説明 | 入力値 |
---|---|---|
AutomationAssumeRole | CustomRoleSSMCreateImageASG を探す(実装手順通り実行した場合) | CustomRoleSSMCreateImageASG |
subnetId | 起動するサブネット | subnet-017d0155c89841afe |
instanceprofileName | EC2SSMRole(実装手順通り実行した場合) | EC2SSMRole |
targetASG | 対象のAutoScalingグループ名 | nozaki-ASG |
sourceAMIid | メモした最新ゴールデンイメージのAMI ID | ami-0e5229fd4d28b4601(その都度入力) |
securityGroupId | 仮EC2用のセキュリティグループ | sg-0ae5c41a8ae289cd9 |
targetAMIname | GoldenImage-{{global:DATE_TIME}} | |
launchtemplateId | 更新対象の起動テンプレートID | lt-083faf0904270e2b9 |
なお、自分は都度入力が面倒だったので、autoscaling-amiupdate.yamlを編集して、
4行目から「default」を追加してパラメータを定義しています。
例)
parameters: AutomationAssumeRole: default: arn:aws:iam::532152701269:role/CustomRoleSSMCreateImageASG
準備
起動テンプレートと、それに対応したAuto Scaling Groupを用意します。
起動テンプレート
起動テンプレート | AMI | |
---|---|---|
現 | lt-083faf0904270e2b9 | ami-0f4b172cebf2c200c |
新 | lt-083faf0904270e2b9 | 新ゴールデンイメージのAMI(ami-0568aaf88ab30a442) |
Auto Scaling Group nozaki-ASG
- 起動テンプレート: lt-083faf0904270e2b9
- 希望・最小・最大容量:2
AMI IDが ami-0f4b172cebf2c200c のEC2が2台立ち上がっている事を確認しておきます。
では実際に動きを見てみよう
SSMドキュメントのオートメーションを実行し、上記二つのEC2のAMI IDが新しいものに変わっていれば成功です。
1.まずはSSMドキュメントで上記の入力項目を設定し、[実行]を押します
2.1台がSSMオートメーションによって新規に起動しています
3.オートメーションが全て成功していることを確認
4.現状のEC2で、AMIがami-0568aaf88ab30a442のものに置き換わっています。これは今回作成したGoldenImage-2022-08-09_04.58.58という名前のゴールデンイメージから生成したEC2です。このうちの1台のWebサイト(〜/WordPress/)にアクセスし、WordPressが見れれば成功です
5.WordPressが見れることを確認
6.起動テンプレートlt-0c630cffe6b0de93aにおいても、起動AMIがちゃんとami-0568aaf88ab30a442に書き変わっています。
7.ちなみに、デプロイに時間がかかるため、SSM上ではデプロイ成功として次のステップのEC2停止にすぐに移行してますが、実際はデプロイ完了までにさらに数分かかります。この仕組みに気づかずになぜ新規AMIにはデプロイが反映されてないんだろう?としばらくハマりましたw このため、EC2停止のステップに行く前に5分間sleepを入れました
8.今回デプロイグループの設定対象をAuto Scalingグループに設定してますが、これを既存のEC2単体に設定しても、後ほど更新されるゴールデンイメージで残りもローリングアップデートされます。
9.1で入力したソースAMI(ami-01871b0f817a0d150)が削除されていることを確認(後始末)
(カスタマイズ等について)
①からではなく途中からやりたい場合、どこを変更すればよい?といった柔軟性を求める要望もあると思います。その場合、設定ファイルのautoscaling-amiupdate.yamlをいじります。
例えば、④の新規AMI作成から実行したい場合、autoscaling-amiupdate.yamlのmainStepsにおいて、①〜③に相当する部分を削ります。
mainSteps: ① - name: startInstances action: aws:runInstances maxAttempts: 1 (略) ② - name: ec2AppDeploy action: aws:runCommand maxAttempts: 1 (略) - name: sleep action: aws:sleep (略) ③ - name: stopInstance action: aws:changeInstanceState maxAttempts: 1
つまり、mainStepsは④に相当するcreateImageから始まることになります。
↓
mainSteps: ④ - name: createImage action: aws:createImage maxAttempts: 1 onFailure: Continue inputs: InstanceId: "{{InstanceIds}}" ImageName: "{{targetAMIname}}" NoReboot: true ImageDescription: "AMI created by EC2 Automation" 〜
あとは微調整として、インスタンスが既にある前提なので、①〜③で必要だったAMI IDの引数の代わりに、インスタンスIDを引数として求めるよう、parameters:の部分を修正します。
parameters: sourceInstanceIds: type: String description: "(Required) Source InstanceIds"
一応こちらのカスタマイズ版であるautoscaling-amiupdate2.yamlを添付しておきます。
description: "Create GoldenImage and Update ASG" schemaVersion: "0.3" assumeRole: "{{AutomationAssumeRole}}" parameters: AutomationAssumeRole: default: arn:aws:iam::532152701269:role/CustomRoleSSMCreateImageASG type: String description: "(Required) The ARN of the role that allows Automation to perform the actions on your behalf. If no role is specified, Systems Manager Automation uses your IAM permissions to execute this document." sourceInstanceIds: type: String description: "(Required) Source InstanceIds" subnetId: default: subnet-9f6222c7 type: String description: "(Required) The SubnetId where the instance is launched from the sourceAMIid." securityGroupId: type: String description: "(Required) Security Group ID that associate the instance is launched from the sourceAMI" default: sg-06491851b120bb029 instanceprofileName: default: "EC2SSMRole" type: String description: "(Required) EC2 instance profile name that associate source AMI" targetAMIname: type: String description: "(Required) Name of new AMI" default: "GoldenImage-{{global:DATE_TIME}}" targetASG: type: String description: "(Required) Auto Scaling group to Update" default: nozaki-ASG launchtemplateId: default: lt-0c630cffe6b0de93a type: String description: "(Required) EC2 Launch Template ID" mainSteps: - name: createImage action: aws:createImage maxAttempts: 1 onFailure: Continue inputs: InstanceId: "{{InstanceIds}}" ImageName: "{{targetAMIname}}" NoReboot: true ImageDescription: "AMI created by EC2 Automation" - name: updateLaunchTemplate action: aws:executeAwsApi timeoutSeconds: 120 maxAttempts: 1 onFailure: Abort inputs: Service: ec2 Api: CreateLaunchTemplateVersion LaunchTemplateId: "{{launchtemplateId}}" LaunchTemplateData: ImageId: "{{createImage.ImageId}}" SourceVersion: '$Latest' - name: instanceRefresh action: aws:invokeLambdaFunction timeoutSeconds: 300 maxAttempts: 1 onFailure: Abort inputs: FunctionName: "Automation-UpdateAsg" Payload: "{\"targetASG\":\"{{targetASG}}\"}"