0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【後編】「よし、じゃあこのRailsアプリ、自動デプロイされるようにして」「へ、へぃ...。」って人のためのAWS環境構築

Last updated at Posted at 2024-11-22

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用のサービスロール

  1. AWS管理コンソールでIAMを開きます
  2. 左側のメニューから「ロール」を選択し、CodeDeployのサービスロールAWSCodeDeployRoleを探してクリックします
  3. 名前はneon-cyber-code-deploy-roleとでもしておきます

EC2インスタンス用のロール

  1. EC2インスタンスにアタッチされているロールを確認します
    ec2にアタッチされているロールの確認-2.png

  2. CodeDeployを実行するためにAWSCodeDeployRoleをアタッチします
    ec2のロールにcodedeployアタッチ-2.png

  3. s3へのアクセス権限を付与するためAmazonS3FullAccessをアタッチします
    ec2にアタッチされているロール-2.png
    CodePipelineはデプロイのログをs3に書き込みます。なのでs3に書き込みの権限がないとデプロイ中にAccess Deniedというエラーがでてしまいます。

codepipeleがs3にログを書き込もうとするが、権限がなくエラー.png

ステップ2:EC2インスタンスにCodeDeployエージェントをインストール

neon-cyber-webとneon-cyber-workerインスタンスにエージェントをインストール

  1. EC2インスタンスにSSHで接続します

  2. 以下のコマンドを実行して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
    
  3. エージェントのステータスを確認します

    systemctl status codedeploy-agent
    

    Active: active (running) と表示されれば、エージェントが正常に動作しています。

  4. エージェントが正常に動作していることを確認できたら、AMIのベースとなるインスタンスにも同じ作業を行ない、起動テンプレートを差し替えておきましょう。
    オートスケーリングを利用したインスタンスの更新が行われた際にもちゃんとコードデプロイエージェントが利用できるように。

ステップ3:appspec.ymlの作成

appspec.yml ファイルは、AWS CodeDeployがデプロイを実行するために必要な設定ファイルです。このファイルにより、CodeDeployはデプロイプロセス中に実行するスクリプトや必要な設定を記述します。

appspec.ymlが行っていること

  • ソースコードのどのファイルをどのディレクトリにコピーするかを指定
  • CodeDeployのライフサイクルに紐づくコマンドを実行する

appspec.ymlの作成と配置

Gitで管理しているリポジトリ直下に配置します。

appspec.yml
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

deployment_scripts/stop_application.sh
#!/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の処理に移行してしまい、下記のエラーが起きたのでこのように記述しています。(もっと最適な方法はいくらでもあると思います)
アプリケーションサーバーを停止する処理のあと古いファイルを消す処理を行うが、サーバーが停止する前に実行されてエラー-2.png

BeforeInstall

deployment_scripts/remove_old_files.sh
#!/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

deployment_scripts/get_parameters.sh
#!/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
deployment_scripts/install_gems.sh
#!/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
deployment_scripts/run_db_migrations.sh
#!/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

deployment_scripts/restart_application.sh
sudo systemctl restart rails.service

ここまでできればCodeDeployが実行できるはずです。

ステップ4:CodeDeployの設定

デプロイアプリケーションの作成

  1. CodeDeployコンソールを開きます。
  2. 「アプリケーションを作成」をクリックし、アプリケーション名(例: neon-cyber-app)を入力します。
  3. コンピューティングプラットフォームに「EC2/オンプレミス」を選びます。

デプロイグループの作成

neon-cyber-web-code-deploy

  • デプロイグループ名(例: neon-cyber-web-code-deploy
  • サービスロールに先ほど作成したneon-cyber-code-deploy-roleを選択します。
  • デプロイタイプは「インプレース」を選択します。
  • ロードバランシングを有効にし、前回の記事で設定したロードバランサーを選択します。

デプロイグループの作成-2.png

neon-cyber-worker用の設定

  1. 上記と同様の手順で、neon-cyber-appアプリケーションを選択し、デプロイグループ名neon-cyber-worker-code-deployというグループを作成します。
  2. 同様にインプレース方式とし、ロードバランシングは無効にします。

デプロイの確認

「デプロイの作成」をクリックしてデプロイが実行されるか試してみたいと思います。
この時初回はロールバックを無効にしておいた方が良いと思います。初めてなのでロールバックのしようがありません。一度デプロイに成功してからは失敗した際にロールバックするようにした方が良いと思います。
codedeployでデプロイできるか実験-2.png

ステップ5:CodePipelineの設定

  1. CodePipelineコンソールを開きます。

  2. 「パイプラインを作成」をクリックします。
    codepipelineのダッシュボード-2.png

  3. パイプライン名(例: neon-cyber-pipeline)を入力します。

  4. 新たにサービスロールを作成します。
    codepipelineの作成-2.png

ソースステージの設定

  1. ソースプロバイダーに「GitHub」を選択し、GitHubアカウントに接続します。
    codepipeline_githubとの初回の連携.png
  2. リポジトリとブランチを選択します。
    codepipelineソースステーじ-2.png

ビルドステージの設定

codepipeline_githubとの初回の連携.png

  1. このステップでは、簡略化のためビルドステージをスキップします(必要に応じてビルドステージを追加してください)。

デプロイステージの設定

  1. デプロイプロバイダーに「CodeDeploy」を選択します。
  2. 先ほど作成したneon-cyber-web-code-deployneon-cyber-worker-code-deployのデプロイグループをそれぞれ選択します。
    codepipelineデプロイステージ-2.png

ステップ6:デプロイの確認

  1. GitHubリポジトリに変更をプッシュすると、自動的にCodePipelineがトリガーされ、デプロイが開始されます。
  2. CodeDeployコンソールでデプロイの進捗を確認できます。

実際に特定のブランチにコミットがプッシュされた際にcode-pipelineが実行されるか確認してみます。

  git commit -m "code-pipeline test" --allow-empty
  git push

【その他】デプロイ中にハマったことハマったときに真っ先に疑うところ

EC2にアタッチされているロールの確認

CodeDeployのロールの確認

CodeDeployエージェントの再起動

EC2インスタンスにSSHで接続し、以下のコマンドを実行してCodeDeployエージェントを再起動してみたらすんなり解決することもありました

sudo service codedeploy-agent restart

ロールバックを無効にする

初回デプロイ時にロールバックを無効にしておくとよいです。初回は失敗してもロールバック使用がないからです。
codedeployのエラー.png

rm -rf /root/.aws/credentials とCodeDeployエージェントの再起動

codedeployのエラー.png

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?