😊

停止中のEC2インスタンスにもリリースしたい

2022/01/20に公開

EC2インスタンスが3AZに展開されていて、障害時以外のときには2インスタンスだけ稼働させて他の2つは停止させています。
停止インスタンスはこのままの状態でリリースができません。
停止されているインスタンスと生きているインスタンスでリソースの差分を発生させないように、リリースの際に一旦起動するというフローをCodePipeline + Lambda + CodeCommit + CodeDeployを使ってやっていきます。


CodeDeploy

まず最初にEC2をデプロイターゲットとするCodeDeployのアプリケーションを作成していきます。
その際の事前準備・留意点は以下の4つ。

  1. デプロイ対象のEC2にCodeDeployエージェントをインストールする。
  2. デプロイ対象のEC2にS3に出力されるCodePipelineのアーティファクトを持ってくることのできる権限を持つIAMロールを割り当てる。
  3. 対象のリポジトリのルートにappspec.ymlを配置する。
  4. デプロイ対象のインスタンスを識別するタグをEC2インスタンスに付与する。

この4つについて本記事では解説しないので参考になるドキュメントを記載します。
https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/instances-ec2-configure.html
https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/reference-appspec-file.html#appspec-reference-server

Lambda

リリース前にEC2を起動するファンクションとリリース後に不要なインスタンスを停止するファンクションの2つを作成していきます。
CodePipelineから呼び出すので結果を返却する処理を追加します。この処理がないとPipelineの後続が動きません。

EC2Start
import boto3
region = 'ap-northeast-1'
instances = ['i-インスタンスID','i-インスタンスID']
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    ec2.start_instances(InstanceIds=instances)
    print('start your instances: ' + str(instances))
    
    # CodePipelineに結果を返却する
    codepipeline = boto3.client('codepipeline')
    codepipeline.put_job_success_result(jobId = event['CodePipeline.job']['id'])
EC2Stop
import boto3
region = 'ap-northeast-1'
instances = ['i-インスタンスID','i-インスタンスID']
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    ec2.stop_instances(InstanceIds=instances)
    print('stop your instances: ' + str(instances))
    
    # CodePipelineに結果を返却する
    codepipeline = boto3.client('codepipeline')
    codepipeline.put_job_success_result(jobId = event['CodePipeline.job']['id'])

EC2を起動、停止できるポリシーとCodePipelineに結果を返すポリシーを作成して2つ関数のロールにアタッチします。
今回はポリシーにまとめています。リソースは適宜制限してください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            "Resource": "*"
        },
	{
	    "Effect": "Allow",
            "Action": [
                "codepipeline:PutJobSuccessResult",
                "codepipeline:PutJobFailureResult"
            ],
            "Resource": "*"
        }
    ]
}

CodePipeline

デプロイする対象のリポジトリをSourceとするパイプラインを作成し、作成したCodeDeployのアプリケーションをデプロイステージに選択します。
デプロイステージでCodeDeployの処理の前後に作成したLambdaを呼び出すアクションを追加します。
これで問題なくPipelineが稼働できれば完了です。

イメージ図

余談

実際にこの形で運用しているわけではないので、LambdaのEC2起動が非同期なのでタイミングによっては失敗するかもという懸念があります。

Discussion