アーカイブ
はじめに
前回の投稿(Github Actions の手動デプロイを設定してみた)で記載したDEV環境用のデプロイyamlの設定を、本番用のものを用意する際に環境差異でハマりポイントがあったので投稿。
DEV環境と本番環境の違い
DEV環境ではデプロイユーザーの秘密鍵でsshログインを行っていたが、本番ではsshログイン後にデプロイユーザーに切り替える必要があった。
・DEV環境: sshユーザー・デプロイユーザー共に DEV_EC2_USER ユーザー
・本番環境: sshユーザーが PROD_EC2_USER、デプロイユーザーが www ユーザー
最初にやったこと
最初に以下の設定でデプロイを実行し、権限エラーでデプロイに失敗。
name: Deploy to Production
on:
workflow_dispatch:
inputs:
run_migrate:
description: 'migrate を実行する'
type: boolean
default: true
run_pip_install:
description: 'pip install を実行する'
type: boolean
default: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up SSH key
env:
EC2_KEY: ${{ secrets.PROD_EC2_KEY }}
run: |
mkdir -p ~/.ssh
printf '%s\n' "$EC2_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -t rsa ${{ secrets.PROD_EC2_HOST }} >> ~/.ssh/known_hosts 2>&1 || true
cat ~/.ssh/known_hosts
- name: Deploy to EC2
env:
RUN_MIGRATE: ${{ github.event.inputs.run_migrate }}
RUN_PIP_INSTALL: ${{ github.event.inputs.run_pip_install }}
run: |
echo "Deploying to ${{ secrets.PROD_EC2_HOST }}..."
# 入力値をチェック
RUN_MIGRATE=${{ inputs.run_migrate }}
RUN_PIP_INSTALL=${{ inputs.run_pip_install }}
# SSH接続テスト
ssh -i ~/.ssh/id_rsa -o ConnectTimeout=10 ${{ secrets.PROD_EC2_USER }}@${{ secrets.PROD_EC2_HOST }} "echo 'SSH connection successful'" || exit 1
# コードを転送(古いファイルを削除して更新)
rsync -avz --exclude '.git' -e "ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no" ./ ${{ secrets.PROD_EC2_USER }}@${{ secrets.PROD_EC2_HOST }}:${{ secrets.PROD_EC2_APP_DIR }}
# EC2上でコマンドを実行
ssh -i ~/.ssh/id_rsa ${{ secrets.PROD_EC2_USER }}@${{ secrets.PROD_EC2_HOST }} << "EOF"
set -e
sudo su www # ← ここが追加のコマンド!
cd ${{ secrets.PROD_EC2_APP_DIR }}
echo "Activating virtualenv..."
source ${{ secrets.PROD_EC2_ACTIVATE_PATH }}
if [ "${RUN_PIP_INSTALL}" = "true" ]; then
echo "Installing requirements..."
pip install -r requirements.txt
else
echo "Skipped pip install..."
fi
if [ "${RUN_MIGRATE}" = "true" ]; then
echo "Applying migrations..."
python manage.py migrate --noinput --settings=${{ secrets.PROD_DJANGO_SETTINGS_MODULE }}
else
echo "Skipped migrations..."
fi
echo "Restarting Gunicorn..."
sudo systemctl restart gunicorn
echo "Restarting Nginx..."
sudo systemctl restart nginx
echo "Deployment completed!"
EOF
- name: Cleanup sensitive files
if: always()
run: |
echo "Cleaning up sensitive files..."
rm -f ~/.ssh/id_rsa
rm -f ~/.ssh/known_hosts
rm -f ~/.ssh/config
echo "Cleanup completed!"
エラーの原因
DEV用のyamlとの違いは、50行目の以下のコードを追加し、sshログイン後のユーザーの切り替えを行おうとしていたが、この書き方だと後続の処理では www で実行されずに PROD_EC2_USER で実行されてしまうことが原因だった。
修正方法
後続の処理を www で実行させるには、heredoc を多重化させて www でコマンドを実行させる必要があった。
さらにデプロイ時のオプション設定(inputの内容)を引き継いで www ユーザーに実行させるには定数の再定義が必要だったため、その処理の追加も行った。
修正後のyaml
修正後の yaml に気を付けなければいけない内容を、該当箇所にコメントで記載しているので参考にしてもらえれば幸いです。
name: Deploy to Production
on:
workflow_dispatch:
inputs:
run_migrate:
description: 'migrate を実行する'
type: boolean
default: true
run_pip_install:
description: 'pip install を実行する'
type: boolean
default: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up SSH key
env:
EC2_KEY: ${{ secrets.PROD_EC2_KEY }}
run: |
mkdir -p ~/.ssh
printf '%s\n' "$EC2_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -t rsa ${{ secrets.PROD_EC2_HOST }} >> ~/.ssh/known_hosts 2>&1 || true
cat ~/.ssh/known_hosts
- name: Deploy to EC2
run: |
echo "Deploying to ${{ secrets.PROD_EC2_HOST }}..."
# 入力値をチェック
RUN_MIGRATE=${{ inputs.run_migrate }}
RUN_PIP_INSTALL=${{ inputs.run_pip_install }}
# SSH接続テスト
ssh -i ~/.ssh/id_rsa -o ConnectTimeout=10 ${{ secrets.PROD_EC2_USER }}@${{ secrets.PROD_EC2_HOST }} "echo 'SSH connection successful'" || exit 1
# コードを転送
rsync -avz --exclude '.git' --rsync-path="sudo -u www rsync" -e "ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no" ./ ${{ secrets.PROD_EC2_USER }}@${{ secrets.PROD_EC2_HOST }}:${{ secrets.PROD_EC2_APP_DIR }}
# EC2上でコマンドを実行
ssh -i ~/.ssh/id_rsa ${{ secrets.PROD_EC2_USER }}@${{ secrets.PROD_EC2_HOST }} << EOF
set -e
RUN_MIGRATE="${RUN_MIGRATE}" # ← 入力値を引き継ぐために再度設定
RUN_PIP_INSTALL="${RUN_PIP_INSTALL}" # ← 入力値を引き継ぐために再度設定
sudo -u www bash << 'APP_EOF' # ← www ユーザーに切り替えて実行するコマンドを heredoc で定義
set -e
cd ${{ secrets.PROD_EC2_APP_DIR }}
echo "Activating virtualenv..."
source ${{ secrets.PROD_EC2_ACTIVATE_PATH }}
echo "RUN_PIP_INSTALL: $RUN_PIP_INSTALL"
echo "RUN_MIGRATE: $RUN_MIGRATE"
if [ "$RUN_PIP_INSTALL" = "true" ]; then
echo "Installing requirements..."
pip install -r requirements.txt
else
echo "Skipped pip install..."
fi
if [ "$RUN_MIGRATE" = "true" ]; then
echo "Applying migrations..."
python manage.py migrate --noinput --settings=${{ secrets.PROD_DJANGO_SETTINGS_MODULE }}
else
echo "Skipped migrations..."
fi
python manage.py collectstatic --noinput --settings=${{ secrets.PROD_DJANGO_SETTINGS_MODULE }}
APP_EOF # ← 余計なインデントを入れるとスペースも文字列として捉えられエラーとなるため、EOF と同じインデントで記載
echo "Restarting Gunicorn..."
sudo systemctl restart gunicorn.service
echo "Restarting Nginx..."
sudo systemctl restart nginx
echo "Deployment completed!"
EOF
- name: Cleanup sensitive files
if: always()
run: |
echo "Cleaning up sensitive files..."
rm -f ~/.ssh/id_rsa
rm -f ~/.ssh/known_hosts
rm -f ~/.ssh/config
echo "Cleanup completed!"
最後に
今回の本番用 yaml の設定で、コマンドの影響範囲や変数のスコープをようやく理解することができた。
今後とも GitHub Actions を使って積極的に自動化を行っていき、新たな知見を得たら投稿していきます。
積極採用中!尖ったPythonエンジニアへの第一歩はこちらから


