AWSでGitHubと連携した自動デプロイの方法
はじめに
前回の記事では、EC2インスタンスをオートスケーリングに連携するところまで解説しました。本記事では、動作しているアプリケーションをAWS CodeDeployとCodePipelineを利用して自動デプロイするところまでを解説します。具体的には、neon-cyber-webインスタンス(ロードバランサーあり)とneon-cyber-workerインスタンス(ロードバランサーなし)のデプロイを自動化します。
前提条件
- AWSアカウント
- GitHubリポジトリ
- 前回の記事で設定したEC2インスタンス(neon-cyber-web、neon-cyber-worker)
用語の説明
AWS CodeDeploy
AWS CodeDeployは、サーバー、Lambda関数、ECSサービスなどにアプリケーションのデプロイを自動化するサービス
AWS CodePipeline
AWS CodePipelineは、ビルド、テスト、デプロイの各ステージを自動化する継続的デリバリーサービス
IAM>ロール
AWS IAMロールは、特定のAWSサービスに対して一時的な権限を付与するためのもの
ステップ1:必要なロールの設定
CodeDeploy用のサービスロール
- AWS管理コンソールでIAMを開きます
- 左側のメニューから「ロール」を選択し、CodeDeployのサービスロール
AWSCodeDeployRole
を探してクリックします - 名前は
neon-cyber-code-deploy-role
とでもしておきます
EC2インスタンス用のロール
-
s3へのアクセス権限を付与するため
AmazonS3FullAccess
をアタッチします
CodePipelineはデプロイのログをs3に書き込みます。なのでs3に書き込みの権限がないとデプロイ中にAccess Deniedというエラーがでてしまいます。
ステップ2:EC2インスタンスにCodeDeployエージェントをインストール
neon-cyber-webとneon-cyber-workerインスタンスにエージェントをインストール
-
EC2インスタンスにSSHで接続します
-
以下のコマンドを実行してCodeDeployエージェントをインストールします
sudo apt update sudo apt install ruby-full -y sudo apt install wget -y wget https://aws-codedeploy-us-east-1.s3.us-east-1.amazonaws.com/latest/install chmod +x ./install sudo ./install auto
-
エージェントのステータスを確認します
systemctl status codedeploy-agent
Active: active (running)
と表示されれば、エージェントが正常に動作しています。 -
エージェントが正常に動作していることを確認できたら、AMIのベースとなるインスタンスにも同じ作業を行ない、起動テンプレートを差し替えておきましょう。
オートスケーリングを利用したインスタンスの更新が行われた際にもちゃんとコードデプロイエージェントが利用できるように。
ステップ3:appspec.ymlの作成
appspec.yml ファイルは、AWS CodeDeployがデプロイを実行するために必要な設定ファイルです。このファイルにより、CodeDeployはデプロイプロセス中に実行するスクリプトや必要な設定を記述します。
appspec.ymlが行っていること
- ソースコードのどのファイルをどのディレクトリにコピーするかを指定
- CodeDeployのライフサイクルに紐づくコマンドを実行する
appspec.ymlの作成と配置
Gitで管理しているリポジトリ直下に配置します。
version: 0.0
os: linux
files:
- source: /
destination: /var/www/rails/neon-cyber #必ずGithubで管理しているディレクトリ直下
permissions:
- object: /var/www/rails/neon-cyber
pattern: "**"
owner: root
mode: 775
type:
- file
- directory
hooks:
ApplicationStop:
- location: deployment_scripts/stop_application.sh
runas: root
BeforeInstall:
- location: deployment_scripts/remove_old_files.sh
runas: root
AfterInstall:
- location: deployment_scripts/get_parameters.sh
runas: root
- location: deployment_scripts/install_gems.sh
runas: root
- location: deployment_scripts/run_db_migrations.sh
runas: root
ApplicationStart:
- location: deployment_scripts/restart_application.sh
runas: root
# 本当はタイムアウトの秒数を設定することが多いようなのでひつようであれば適宜調べて追記してくださいm(_ _)M
次にそれぞれのライフサイクルで行われる処理の内容を記述します。
ApplicationStop
#!/bin/bash
echo 'stop_application.sh////////////////////////////////////////////////////////////////////////'
sudo su -
sudo systemctl stop rails.service
PROCESS_ID=$(pgrep -f puma)
if [ -n "$PROCESS_ID" ]; then
echo 'pumaの停止'
kill -9 $PROCESS_ID
fi
PROCESS_ID=$(pgrep -f sidekiq)
if [ -n "$PROCESS_ID" ]; then
echo 'sidekiqの停止'
kill -9 $PROCESS_ID
fi
# プロセスが終了するまで待つ
sleep 10
sleepを入れないとすぐ、次のBeforeInstall
の処理に移行してしまい、下記のエラーが起きたのでこのように記述しています。(もっと最適な方法はいくらでもあると思います)
BeforeInstall
#!/bin/bash
echo 'remove_old_files.sh////////////////////////////////////////////////////////////////////////'
sudo su -
# ディレクトリの操作権限、ディレクトリの削除、新たにディレクトリの作成までを行う
directory=/var/www/rails/neon-cyber/
if [ -n "$(ls $directory)" ]; then
chmod -R 777 $directory
rm -rf $directory
mkdir $directory
fi
AfterInstall
#!/bin/bash
# ssmからパラメータを取得し.envに書き出す
sudo -i
echo 'get_parameters.sh////////////////////////////////////////////////////////////////////////'
# SystemManagerのパラメータストアから環境変数を取得し、`.env`ファイルを作成
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") \
&& INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") \
&& ZONE=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/availability-zone)
REGION=$(echo $ZONE | sed 's/.$//')
FILENAME="/var/www/rails/neon-cyber/backend/.env"
NAME=$(aws --region ${REGION} ec2 describe-instances --instance-ids "${INSTANCE_ID}" --query 'Reservations[0].Instances[0].Tags[?Key==`Name`]|[0].Value')
NAME=$(echo ${NAME} | awk '{print substr($0, 2, length($0)-2)}')
if [ "${NAME}" = "neon-cyber-web" ] || [ "${NAME}" = "neon-cyber-worker" ]; then
SSM_PARAMS=$(aws --region ${REGION} ssm get-parameters-by-path --path "/neon-cyber-production" --with-decryption)
for params in $(echo $SSM_PARAMS | jq -r --raw-output '.Parameters[] | .Name + "='\''" + .Value + "'\''"'); do
echo ${params#"/neon-cyber-production/"}
done > ${FILENAME}
fi
#!/bin/bash
echo 'install_gems.sh////////////////////////////////////////////////////////////////////////'
export INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
NAME=$(aws ec2 describe-instances --instance-ids "${INSTANCE_ID}" --query 'Reservations[0].Instances[0].Tags[?Key==`Name`]|[0].Value')
NAME=$(echo ${NAME} | awk '{print substr($0, 2, length($0)-2)}')
if [ "${NAME}" = "neon-cyber-web" ] || [ "${NAME}" = "neon-cyber-web-test" ]; then
su -l -c "cd /var/www/rails/neon-cyber/backend && bundle install"
elif [ "${NAME}" = "neon-cyber-worker" ] || [ "${NAME}" = "neon-cyber-worker-test" ]; then
su -l -c "cd /var/www/rails/neon-cyber/backend && bundle install"
else
echo "ec2インスタンスに適切なNameタグが付与されていないため、アプリケーションの起動に失敗しました。" 1>&2
fi
#!/bin/bash
su -l -c 'cd /var/www/rails/neon-cyber/backend && bundle exec dotenv -f ".env" ridgepole -c config/database.yml -E production -f db/Schemafile --apply'
ApplicationStart
sudo systemctl restart rails.service
ここまでできればCodeDeployが実行できるはずです。
ステップ4:CodeDeployの設定
デプロイアプリケーションの作成
- CodeDeployコンソールを開きます。
- 「アプリケーションを作成」をクリックし、アプリケーション名(例:
neon-cyber-app
)を入力します。 - コンピューティングプラットフォームに「EC2/オンプレミス」を選びます。
デプロイグループの作成
neon-cyber-web-code-deploy
- デプロイグループ名(例:
neon-cyber-web-code-deploy
) - サービスロールに先ほど作成した
neon-cyber-code-deploy-role
を選択します。 - デプロイタイプは「インプレース」を選択します。
- ロードバランシングを有効にし、前回の記事で設定したロードバランサーを選択します。
neon-cyber-worker用の設定
- 上記と同様の手順で、
neon-cyber-app
アプリケーションを選択し、デプロイグループ名neon-cyber-worker-code-deploy
というグループを作成します。 - 同様にインプレース方式とし、ロードバランシングは無効にします。
デプロイの確認
「デプロイの作成」をクリックしてデプロイが実行されるか試してみたいと思います。
この時初回はロールバックを無効にしておいた方が良いと思います。初めてなのでロールバックのしようがありません。一度デプロイに成功してからは失敗した際にロールバックするようにした方が良いと思います。
ステップ5:CodePipelineの設定
-
CodePipelineコンソールを開きます。
-
パイプライン名(例:
neon-cyber-pipeline
)を入力します。
ソースステージの設定
ビルドステージの設定
- このステップでは、簡略化のためビルドステージをスキップします(必要に応じてビルドステージを追加してください)。
デプロイステージの設定
- デプロイプロバイダーに「CodeDeploy」を選択します。
- 先ほど作成した
neon-cyber-web-code-deploy
とneon-cyber-worker-code-deploy
のデプロイグループをそれぞれ選択します。
ステップ6:デプロイの確認
- GitHubリポジトリに変更をプッシュすると、自動的にCodePipelineがトリガーされ、デプロイが開始されます。
- CodeDeployコンソールでデプロイの進捗を確認できます。
実際に特定のブランチにコミットがプッシュされた際にcode-pipelineが実行されるか確認してみます。
git commit -m "code-pipeline test" --allow-empty
git push
【その他】デプロイ中にハマったことハマったときに真っ先に疑うところ
EC2にアタッチされているロールの確認
CodeDeployのロールの確認
CodeDeployエージェントの再起動
EC2インスタンスにSSHで接続し、以下のコマンドを実行してCodeDeployエージェントを再起動してみたらすんなり解決することもありました
sudo service codedeploy-agent restart
ロールバックを無効にする
初回デプロイ時にロールバックを無効にしておくとよいです。初回は失敗してもロールバック使用がないからです。
rm -rf /root/.aws/credentials
とCodeDeployエージェントの再起動
InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Error polling for host commands: Aws::CodeDeployCommand::Errors::AccessDeniedException -
なぜだかわかりませんが、古い認証情報が原因で問題が発生することがあります。以下のコマンドを実行して認証情報を削除し、CodeDeployエージェントを再起動すると治ることがありました。
https://velog.io/@gingaminga/AwsCodeDeployCommandErrorsAccessDeniedException
sudo rm -rf /root/.aws/credentials
sudo service codedeploy-agent restart
appspec.ymlでdestinationの設定を間違えたとき
InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Calling PutHostCommandComplete: "Code Error"
2024-06-05T05:31:45 INFO [codedeploy-agent(395492)]: Version file found in /opt/codedeploy-agent/.version with agent version OFFICIAL_1.7.0-92_deb.
2024-06-05T05:31:45 INFO [codedeploy-agent(395492)]: [Aws::CodeDeployCommand::Client 200 0.037543 0 retries] put_host_command_complete(command_status:"Failed",diagnostics:{format:"JSON",payload:"{\"error_code\":5,\"script_name\":\"\",\"message\":\"The CodeDeploy agent did not find an AppSpec file within the unpacked revision directory at revision-relative path \\\"appspec.yml\\\".
ファイルのディレクトリが違うとこのようなエラーが出ます。
codedeploy-agentのログを確認したいとき
tail -n 100 /var/log/aws/codedeploy-agent/codedeploy-agent.log
などとすると良いと思います。
最後に
一応形になりましたが、最適でない部分も多分にあるかと思われます。おかしなところがあったらご指摘いただけますと幸いです。
以下、大変勉強になった動画です。
https://www.youtube.com/watch?v=8mPm7jolnVk&t=855s
https://www.youtube.com/watch?v=_Yj1VgVkvVw&list=PLjVOh_I4G7Vd2fMtsDMyFWtcjdKD2vZTj