背景
前回AWS CloudFormationで管理するAmazon S3に対し、CI/CDによる継続的なテストと更新を検証しましたが、今回はAmazon EC2を対象に検証してみました。
Amazon EC2で気を付けた点
まず、Amazon EC2に対してテストを行う前に、以前のAmazon S3と比べて異なる点をいくつか記載しておきます。今回は扱うリソースが異なるのは当然ですが、それによって前回は気にしなくてもよかった部分も少し工夫が必要となります。
OSレイヤーの管理が必要
Amazon EC2はAWS上で仮想サーバーを利用できるサービスです。EC2インスタンスは多くの場合インスタンスを作成するだけでなく、インスタンス内部にソフトウェアや実行ファイルなどを配置し、何らかの機能を提供するよう設定します。
そのため、EC2インスタンスを管理するには、AWS CloudFormationだけでは完結できず、別の方法が必要になります。例えば Ansible
を使えばNginxのインストールやファイルの配置などの操作を宣言的に定義することが可能です。
リソース内部の状態が正常であることの確認が必要
Amazon EC2を作成すると、インスタンス自体は起動して Running
の状態であるにもかかわらず、起動直後は内部プロセスの初期化が完了していないため、インスタンスに接続できないことがあります。これは更新後のテストにも影響するため、インスタンス内部の状態が安定してからテストを行うよう制御したいです。
Amazon EC2は DescribeInstanceStatus というAPIを提供しています。このAPIではインスタンスが起動しているかだけでなく、インスタンス内部の状態がどうなっているかも提供します。具体的には instanseStatus
がインスタンス内部に関する障害を、systemStatus
がインスタンスをサポートするシステムに関連した障害を、それぞれチェックします。
またこれだけではなく、インスタンス内部で起動するサービス・プロセスが正常に起動しているかも確認が必要となります。
インスタンスへの接続が必要
前項の DescribeInstanceStatus
APIなどは、AWS APIを通じてリソースにアクセスすることで情報を取得できます。しかし、EC2の設定変更、特にインスタンスへのファイルの配置やプロセスの再起動などを行う場合、SSHなどのプロトコルを用いて直接インスタンスにアクセスしなければなりません (AWSでは AWS Systems Manager Session Manager や EC2 Instance Connect など別の接続方法も提供されていますが、ここでは割愛します) 。
SSH接続の場合、デフォルトでは22番ポートを開放する必要がありますが、セキュリティの観点から必要以上のポートを開放するのは避けたいところです。そのため、インスタンスにアクセスして設定変更を行う場合だけ、一時的にポートを開放するような処理を追加しました。
テストの観点
続いて今回実施するテストの観点を整理します。ただし前回実施した内容は継続して行うため、今回新しく導入したテストについてのみ触れます。
利用するリソースの大部分をコードで管理する
前回記事の検証では、一連の処理で扱うリソースの大部分をコードで管理するようにしました。Amazon S3の場合はAWS CloudFormationでリソースの定義を完結できますが、上述の通りAmazon EC2はCloudFormationだけでリソースを管理することはできません。
以前紹介した書籍のPractice に従うのであれば、本来は Ansible
などOSレイヤーも宣言的に管理できるIaCツールを利用すべきです。ただし今回はEC2で扱うリソース・プロセスを最小限にし、scp
コマンドなどを利用してファイルを配置する形としました。理由は、「サーバーに対して継続的テストを行う」ことに集中するため、CloudFormation以外のIaCツールを使わないようにしたかったからです。
こう考えていたのですが結果的にはかなり複雑な処理になってしまいました。。。
オンラインテストを導入し、起動後のサーバーの状態を確認する
前回は構文チェックなど静的解析を中心にしましたが、今回はインスタンス起動後の動的解析も実施しています。
まず、前回実施した cfn-lint
trivy
による静的解析は、AWSの幅広いリソースに適用可能なため、今回もテストに組み込んでいます。
一方、サーバーに対して継続的テストを行う場合、サーバー上のリソースやプロセスに対して更新を適用した後は、該当のファイルやプロセスなどが正常か、確認する必要があります。今回は、以下の点を確認しています。
- ファイルの同一性: サーバー上に配置したファイルの内容は、GitHub上のファイルと一致すること。
md5sum
コマンドで確認。 - Nginxプロセスの正常性: 更新後にNginxのプロセスが正常に起動していること。
systemctl list-units
コマンドで確認。 - Webページへの疎通性: 更新後にサーバーにアクセスし、Webページがアクセス可能であること。
curl
コマンドで確認。
※以前紹介した書籍の中では Verification: Making Assertions About Infrastructure Resources の箇所が該当します。
検証
ここから検証した内容を紹介します。前回に引き続き GitHub Actionsを使ってCI/CDとAWS CloudFormationとの組み合わせになります。
環境
今回の検証環境は以下の通りです。
- 環境:
AWS
- IaCツール:
AWS CloudFormation
- CI/CDツール:
GitHub Actions
- テストツール:
cfn-lint
/trivy
- その他: あらかじめEC2インスタンスに
Nginx
のインストールと、テスト用のファイル (index.html
) を配置したAMIを用意しておきます。
構成は以下の通りです。
処理の流れ
処理の流れは以下の通りです。今回はCloudFormation定義ファイルと index.html
という2つのファイルを更新対象としており、それぞれのファイルで更新があれば別の処理を起動するようにしています。また同時に2つのファイルが更新される場合も想定し、追加の処理を行います。
- (更新対象のCloudFormation Stackはあらかじめ作成しておく)
- Amazon S3を定義するIaCファイルを更新し、GitHub上でPRを作成する
- PRの作成をトリガーに、GitHub ActionsがCIを実行する
- コード解析
- ドリフトのチェック
- Change setの作成
- CIの結果を確認し、問題なければマージする
- マージをトリガーにGitHub ActionsがCDを実行する
今回使用したコード、およびGitHubリポジトリ上のディレクトリ構成は以下の通りです。
. ├── .github │ └── workflows │ ├── ec2-github-actions-check.yaml │ └── ec2-github-actions-apply.yaml ├── README.md ├── ec2-github-actions-check │ ├── ec2.yaml │ └── src │ └── index.html └── iam.yaml
ec2-github-actions-check.yaml
name: EC2 check at Pull Request on: pull_request: types: - opened - synchronize paths: - ec2-github-actions-check/** permissions: id-token: write contents: read env: AWS_REGION: ap-northeast-1 AWS_IAM_ROLE: ${{ secrets.AWS_IAM_ROLE }} AWS_STACK_NAME: ec2-github-actions-check jobs: pr: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_IAM_ROLE }} aws-region: ${{ env.AWS_REGION }} - name: Testing with CFN Lint Command uses: scottbrenner/cfn-lint-action@v2 with: command: cfn-lint -t ./ec2-github-actions-check/*.yaml - name: Testing with Trivy uses: aquasecurity/trivy-action@master with: scan-type: 'config' exit-code: '1' severity: 'CRITICAL' - name: Drift Detection run: | STACK_DRIFT_DETECT_ID=`aws cloudformation detect-stack-drift --stack-name ${{ env.AWS_STACK_NAME }} --query 'StackDriftDetectionId' --output text` sleep 10s DRIFT_STATUS=`aws cloudformation describe-stack-drift-detection-status --stack-drift-detection-id $STACK_DRIFT_DETECT_ID --query 'StackDriftStatus' --output text` echo "DRIFT_STATUS=${DRIFT_STATUS}" >> $GITHUB_ENV - name: Stop workflow when drift detected if: contains(env.DRIFT_STATUS, 'IN_SYNC') == false run: | aws cloudformation describe-stack-resource-drifts --stack-name ${{ env.AWS_STACK_NAME }} exit 1 - name: Get changed files id: changed-files uses: tj-actions/changed-files@v39 with: files: | ec2-github-actions-check/** - name: Create changeset if: contains(steps.changed-files.outputs.all_changed_files, 'ec2-github-actions-check/ec2.yaml') run: | CHANGE_SET_NAME="changeset-$(date "+%Y%m%d-%H%M%S")" echo "CHANGE_SET_NAME=${CHANGE_SET_NAME}" >> $GITHUB_ENV aws cloudformation create-change-set --template-body file://ec2-github-actions-check/ec2.yaml --stack-name ${{ env.AWS_STACK_NAME }} --change-set-name $CHANGE_SET_NAME --capabilities CAPABILITY_NAMED_IAM aws cloudformation wait change-set-create-complete --stack-name ${{ env.AWS_STACK_NAME }} --change-set-name $CHANGE_SET_NAME aws cloudformation describe-change-set --stack-name ${{ env.AWS_STACK_NAME }} --change-set-name $CHANGE_SET_NAME
ec2-github-actions-apply.yaml
name: EC2 check & apply at Pull Request Merge on: pull_request: types: - closed paths: - ec2-github-actions-check/** permissions: id-token: write contents: read env: AWS_REGION: ap-northeast-1 AWS_IAM_ROLE: ${{ secrets.AWS_IAM_ROLE }} AWS_STACK_NAME: ec2-github-actions-check INSTANCE_NAME: ec2-github-actions-check-ec2 jobs: merge: runs-on: ubuntu-22.04 if: github.event.pull_request.merged == true outputs: changed-files: ${{ steps.changed-files.outputs.all_changed_files}} steps: - uses: actions/checkout@v3 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_IAM_ROLE }} aws-region: ${{ env.AWS_REGION }} - name: Testing with CFN Lint Command uses: scottbrenner/cfn-lint-action@v2 with: command: cfn-lint -t ./ec2-github-actions-check/*.yaml - name: Testing with Trivy uses: aquasecurity/trivy-action@master with: scan-type: 'config' exit-code: '1' severity: 'CRITICAL' - name: Drift Detection run: | STACK_DRIFT_DETECT_ID=`aws cloudformation detect-stack-drift --stack-name ${{ env.AWS_STACK_NAME }} --query 'StackDriftDetectionId' --output text` sleep 10s DRIFT_STATUS=`aws cloudformation describe-stack-drift-detection-status --stack-drift-detection-id $STACK_DRIFT_DETECT_ID --query 'StackDriftStatus' --output text` echo "DRIFT_STATUS=${DRIFT_STATUS}" >> $GITHUB_ENV - name: Stop workflow when drift detected if: contains(env.DRIFT_STATUS, 'IN_SYNC') == false run: | aws cloudformation describe-stack-resource-drifts --stack-name ${{ env.AWS_STACK_NAME }} exit 1 - name: Get changed files id: changed-files uses: tj-actions/changed-files@v39 with: files: | ec2-github-actions-check/** - name: Check EC2 instance status run: | INSTANCE_ID=`aws ec2 describe-instances --filter "Name=tag:Name,Values=${{ env.INSTANCE_NAME }}" --query 'Reservations[].Instances[].InstanceId' --output text` INSTANCE_STATUS=`aws ec2 describe-instance-status --instance-ids $INSTANCE_ID --query 'InstanceStatuses[0].InstanceStatus.Status' --output text` SYSTEM_STATUS=`aws ec2 describe-instance-status --instance-ids $INSTANCE_ID --query 'InstanceStatuses[0].SystemStatus.Status' --output text` INSTANCE_STATE=`aws ec2 describe-instance-status --instance-ids $INSTANCE_ID --query 'InstanceStatuses[0].InstanceState.Name' --output text` echo "INSTANCE_STATUS=${INSTANCE_STATUS}" >> $GITHUB_ENV echo "SYSTEM_STATUS=${SYSTEM_STATUS}" >> $GITHUB_ENV echo "INSTANCE_STATE=${INSTANCE_STATE}" >> $GITHUB_ENV - name: Stop workflow if EC2 instance have problems if: >- contains(env.INSTANCE_STATUS, 'ok') == false || contains(env.SYSTEM_STATUS, 'ok') == false || contains(env.INSTANCE_STATE, 'running') == false run: | echo "INSTANCE_STATUS = ${{ env.INSTANCE_STATUS }}" echo "SYSTEM_STATUS = ${{ env.SYSTEM_STATUS }}" echo "INSTANCE_STATE is ${{ env.INSTANCE_STATE }}" exit 1 cfn-apply: runs-on: ubuntu-22.04 needs: merge if: contains(needs.merge.outputs.changed-files, 'ec2-github-actions-check/ec2.yaml') steps: - uses: actions/checkout@v3 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_IAM_ROLE }} aws-region: ${{ env.AWS_REGION }} # CloudFormation change - name: Create changeset run: | CHANGE_SET_NAME="changeset-$(date "+%Y%m%d-%H%M%S")" echo "CHANGE_SET_NAME=${CHANGE_SET_NAME}" >> $GITHUB_ENV aws cloudformation create-change-set --template-body file://ec2-github-actions-check/ec2.yaml --stack-name ${{ env.AWS_STACK_NAME }} --change-set-name $CHANGE_SET_NAME --capabilities CAPABILITY_NAMED_IAM aws cloudformation wait change-set-create-complete --stack-name ${{ env.AWS_STACK_NAME }} --change-set-name $CHANGE_SET_NAME aws cloudformation describe-change-set --stack-name ${{ env.AWS_STACK_NAME }} --change-set-name $CHANGE_SET_NAME - name: Execute changeset run: | aws cloudformation execute-change-set --change-set-name ${{ env.CHANGE_SET_NAME }} --stack-name ${{ env.AWS_STACK_NAME }} aws cloudformation wait stack-update-complete --stack-name ${{ env.AWS_STACK_NAME }} aws cloudformation describe-stacks --stack-name ${{ env.AWS_STACK_NAME }} --query 'Stacks[*].StackStatus' --output text os-file-apply: runs-on: ubuntu-22.04 needs: merge if: >- contains(needs.merge.outputs.changed-files, 'ec2-github-actions-check/src/index.html') && !contains(needs.merge.outputs.changed-files, 'ec2-github-actions-check/ec2.yaml') steps: - uses: actions/checkout@v3 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_IAM_ROLE }} aws-region: ${{ env.AWS_REGION }} # index.html change - name: Get public ip id: ip uses: haythem/public-ip@v1.3 - name: Open security group run: | aws ec2 authorize-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 aws ec2 authorize-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 80 --cidr ${{ steps.ip.outputs.ipv4 }}/32 - name: Update index.html run: | echo "${{ secrets.SSH_PRIVATE_KEY }}" > private_key chmod 400 private_key scp -oStrictHostKeyChecking=no -i private_key ec2-github-actions-check/src/index.html ${{ secrets.EC2_USER_NAME }}@${{ secrets.EC2_HOST_NAME }}:/tmp/index.html ssh -oStrictHostKeyChecking=no -i private_key ${{ secrets.EC2_USER_NAME }}@${{ secrets.EC2_HOST_NAME }} "sudo cp /tmp/index.html /usr/share/nginx/html/sample/index.html" - name: Check whether index.html is correctly updated run: | ORIGINAL_FILE_HASH=`md5sum ec2-github-actions-check/src/index.html | awk '{ print $1 }'` COPIED_FILE_HASH=`ssh -oStrictHostKeyChecking=no -i private_key ${{ secrets.EC2_USER_NAME }}@${{ secrets.EC2_HOST_NAME }} "sudo md5sum /usr/share/nginx/html/sample/index.html" | awk '{ print $1 }'` echo "ORIGINAL_FILE_HASH=${ORIGINAL_FILE_HASH}" >> $GITHUB_ENV echo "COPIED_FILE_HASH=${COPIED_FILE_HASH}" >> $GITHUB_ENV - name: Stop workflow if index.html file is not matched if: env.ORIGINAL_FILE_HASH != env.COPIED_FILE_HASH run: | echo "ORIGINAL_FILE_HASH = ${{ env.ORIGINAL_FILE_HASH }}" echo "COPIED_FILE_HASH = ${{ env.COPIED_FILE_HASH }}" exit 1 - name: Check OS status after update index.html run: | NGINX_PROCESS_STATUS=`ssh -oStrictHostKeyChecking=no -i private_key ${{ secrets.EC2_USER_NAME }}@${{ secrets.EC2_HOST_NAME }} "sudo systemctl list-units -t service | grep nginx"` CURL_STATUS_CODE=`curl ${{ secrets.EC2_HOST_NAME }}/sample/ -o /dev/null -w '%{http_code}\n' -s` echo "NGINX_PROCESS_STATUS=${NGINX_PROCESS_STATUS}" >> $GITHUB_ENV echo "CURL_STATUS_CODE=${CURL_STATUS_CODE}" >> $GITHUB_ENV - name: Stop workflow if OS status has problem if: >- contains(env.NGINX_PROCESS_STATUS, 'nginx.service') == false || contains(env.CURL_STATUS_CODE, '200') == false run: | echo "NGINX_PROCESS_STATUS = ${{ env.NGINX_PROCESS_STATUS }}" echo "CURL_STATUS_CODE = ${{ env.CURL_STATUS_CODE }}" exit 1 - name: close security group if: always() run: | aws ec2 revoke-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 aws ec2 revoke-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 80 --cidr ${{ steps.ip.outputs.ipv4 }}/32 os-file-apply-with-cfn: runs-on: ubuntu-22.04 needs: [merge, cfn-apply] if: >- contains(needs.merge.outputs.changed-files, 'ec2-github-actions-check/src/index.html') && contains(needs.merge.outputs.changed-files, 'ec2-github-actions-check/ec2.yaml') steps: - uses: actions/checkout@v3 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_IAM_ROLE }} aws-region: ${{ env.AWS_REGION }} - name: Wait if CloudFormation file is modified run: | INSTANCE_ID=`aws ec2 describe-instances --filter "Name=tag:Name,Values=${{ env.INSTANCE_NAME }}" --query 'Reservations[].Instances[].InstanceId' --output text` while : do INSTANCE_STATUS=`aws ec2 describe-instance-status --instance-ids $INSTANCE_ID --query 'InstanceStatuses[0].InstanceStatus.Status' --output text` SYSTEM_STATUS=`aws ec2 describe-instance-status --instance-ids $INSTANCE_ID --query 'InstanceStatuses[0].SystemStatus.Status' --output text` if [ "$INSTANCE_STATUS" = "ok" ] && [ "$SYSTEM_STATUS" = "ok" ]; then echo "Instance is running successfully" break elif [ "$INSTANCE_STATUS" = "initializing" ] || [ "$SYSTEM_STATUS" = "initializing" ]; then echo "Instance is initializing..." sleep 10 else echo "INSTANCE_STATUS = $INSTANCE_STATUS" echo "SYSTEM_STATUS = $SYSTEM_STATUS" exit 1 fi done # index.html change - name: Get public ip id: ip uses: haythem/public-ip@v1.3 - name: Open security group run: | aws ec2 authorize-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 aws ec2 authorize-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 80 --cidr ${{ steps.ip.outputs.ipv4 }}/32 - name: Update index.html run: | echo "${{ secrets.SSH_PRIVATE_KEY }}" > private_key chmod 400 private_key scp -oStrictHostKeyChecking=no -i private_key ec2-github-actions-check/src/index.html ${{ secrets.EC2_USER_NAME }}@${{ secrets.EC2_HOST_NAME }}:/tmp/index.html ssh -oStrictHostKeyChecking=no -i private_key ${{ secrets.EC2_USER_NAME }}@${{ secrets.EC2_HOST_NAME }} "sudo cp /tmp/index.html /usr/share/nginx/html/sample/index.html" - name: Check OS status after update index.html run: | NGINX_PROCESS_STATUS=`ssh -oStrictHostKeyChecking=no -i private_key ${{ secrets.EC2_USER_NAME }}@${{ secrets.EC2_HOST_NAME }} "sudo systemctl list-units -t service | grep nginx"` CURL_STATUS_CODE=`curl ${{ secrets.EC2_HOST_NAME }}/sample/ -o /dev/null -w '%{http_code}\n' -s` echo "NGINX_PROCESS_STATUS=${NGINX_PROCESS_STATUS}" >> $GITHUB_ENV echo "CURL_STATUS_CODE=${CURL_STATUS_CODE}" >> $GITHUB_ENV - name: Stop workflow if OS status has problem if: >- contains(env.NGINX_PROCESS_STATUS, 'nginx.service') == false || contains(env.CURL_STATUS_CODE, '200') == false run: | echo "NGINX_PROCESS_STATUS = ${{ env.NGINX_PROCESS_STATUS }}" echo "CURL_STATUS_CODE = ${{ env.CURL_STATUS_CODE }}" exit 1 - name: close security group if: always() run: | aws ec2 revoke-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 aws ec2 revoke-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 80 --cidr ${{ steps.ip.outputs.ipv4 }}/32
ec2.yaml
AWSTemplateFormatVersion: "2010-09-09" Parameters: EnvName: Type: String Default: ec2-github-actions-check EC2InstanceType: Type: String Default: t3.micro SubnetId: Type: String Default: <Subnet IDを指定> EC2ImageId: Type: AWS::EC2::Image::Id Default: <AMI IDを指定> EC2SecurityGroup: Type: String Default: <Security Group IDを指定> KeyPair: Type: String Default: <Key名を指定> Resources: ElasticIP: Type: AWS::EC2::EIP Properties: Domain: vpc EC2Instance: Type: AWS::EC2::Instance Properties: InstanceType: !Ref EC2InstanceType SubnetId: !Ref SubnetId ImageId: !Ref EC2ImageId SecurityGroupIds: - !Ref EC2SecurityGroup IamInstanceProfile: !Ref EC2InstanceProfile KeyName: !Ref KeyPair Tags: - Key: Name Value: !Sub ${EnvName}-ec2 EIPAssociationToEC2: Type: AWS::EC2::EIPAssociation Properties: InstanceId: !Ref EC2Instance AllocationId: !GetAtt ElasticIP.AllocationId EC2IAMRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${EnvName}-SSM-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore EC2InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - Ref: EC2IAMRole InstanceProfileName: !Sub ${EnvName}-EC2InstanceProfile
検証結果
ここから実際の検証した様子を紹介します。
CloudFormationファイルを更新した場合
まずはCloudFormationファイルを更新した場合です。この場合は前回のS3バケットの更新とほぼ同じ流れとなります。
index.htmlファイルを更新した場合
続いて index.html
ファイルを更新した場合です。この場合は今までのCloudFormationを扱う場合と大きくプロセスが異なります。
まず、GitHub Actions runnerからインスタンスにアクセスできるよう、Security Groupの変更を行います。今回はGitHub Actions GitHub-hosted runnerを利用しており、毎回アクセス元のサーバーのIPが更新されます。そのため、haythem/public-ip というActionを利用し、GitHub Actions runnerのIPアドレスを取得します。
次に index.html
ファイルの配置ですが、SSHでアクセスができればいくつか方法はあります。今回は宛先のディレクトリに直接SCPで配置できなかったため、SCPとSSHを組み合わせて実現しています。
ファイルの配置を完了したら、ファイルが正常に配置できていること、Nginxのプロセスが起動していること、Webページに正常にアクセスできることを確認し、最後にSecurity Groupを閉じます。なおSecurity Groupを開放後にCDプロセスが途中で失敗しても、Security GroupからSSHアクセス許可設定を削除するよう、Stepの条件に always()
を指定しています。
処理が正常に完了した場合は以下のようになります。またWebブラウザなどからインスタンスにアクセスしても、 index.html
への更新内容が反映されていることを確認しています。
両方のファイルを更新した場合
最後に両方のファイルを更新した場合です。前述の2つの処理は、GitHub Actions上でjobを別々に定義すれば、それぞれ並行で実施されます。ただしこの場合、 index.html
ファイルの配置とインスタンスの更新のタイミングがバッティングすると、処理が失敗する可能性があります。そこで今回、両方のファイルが更新された場合は先にCloudFormationの更新を行い、その後 index.html
ファイルの配置を行うよう、Jobの実行順を制限しています。
CloudFormationファイル更新後、 DescribeInstanceStatus
APIからインスタンスの状態をチェックし、インスタンスの初期化が完了するのを待ってから後続の処理を実行するようにしました。
処理が正常に完了した場合は以下のようになります。CloudFormationファイルの更新Jobの完了後に index.html
の処理が行われていることが視覚的にも確認できました。