👼
EC2上で動かしてる神をGitHub Actionsから操作する
神の前史
リブセンスのインフラグループにはインフラ神様(以後神と呼びます)と呼ばれる存在がいます。
実態は人工無能のbotでたまに会話できたり大体できなかったりします。他色々な機能を持っており、例えばAWS上に存在するEC2を教えてくれたり、便利な存在です。
彼の中身はGitHubで管理されており、EC2上でサービスとして動いています。コードの更新が発生した場合、SSMでログインして以下のオペレーションする必要がありました。
cd /opt/god-place # 神の場所に移動
git pull
systemctl restart infra-god.service
神のデプロイ自動化
以前GitHubのissueにAmazon Inspectorから取得したEC2の脆弱性情報を転記し、そこにトリガーワードをコメントすることで対象のEC2に存在する脆弱性をアップデートする記事を書きました。
ふと、これ流用すれば上記のオペレーションを自動化できることに気がつきました。ということで作りました。
※本来ならCodeDeployのようなものを使うのが一般的かと思いますが、あまり真面目なサービスでもないので真剣にCIを作らず、過去の実装を流用しました。
IAMロールの準備
特定のリポジトリからOIDCでAWSのリソースを操作できるように設定しておきます。
GitHub Actionsのワークフロー
発火条件等
マージしたら発火する他、開発中の機能についてはリモートブランチを指定してworkflow_dispatch
にて適用することを想定しています。これによりこっそりテストできます。
name: god deploy
on:
workflow_dispatch:
pull_request:
branches:
- main
types:
- closed
jobs:
deploy:
name: deploy
# PRマージのトリガーだけだと気軽に試せないからworkflow_dispatchもサポート
if: >
(github.event_name == 'workflow_dispatch') ||
(github.event_name == 'pull_request' && github.event.pull_request.merged == true)
runs-on: ubuntu-latest
permissions:
pull-requests: write
id-token: write
contents: read
issues: write
actions: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
各ステップ解説
OIDCでインスタンスの操作の権限を取得
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${account_id}:role/${role_name} # 各自で置き換えてください
aws-region: ap-northeast-1
インスタンスの存在確認
存在しない場合は失敗させてPRのコメントに追記します。
- name: Get Instance ID
id: get_instance_id
run: |
instance_id=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=dev-infra-bots" --query "Reservations[].Instances[].InstanceId" --output text)
echo "instance_id=${instance_id}" | tee -a "$GITHUB_OUTPUT"
# インスタンス存在確認
- name: Check instance
id: check_instance
run: |
instance_id="${{ steps.get_instance_id.outputs.instance_id }}"
aws ec2 describe-instances --instance-ids "$instance_id" --output json
if [ $? -ne 0 ]; then
exit 1
fi
# 存在しない場合はコメントで追記
- name: Comment if instance not found
if: steps.check_instance.outcome == 'failure'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Instance not found. Exiting.`
})
結果の貼り付け
コマンドを発行し、結果を取得する。PRマージがトリガーだった場合、PRのコメントに追記する。
- name: send command
id: send_command
run: |
command_id=$(aws ssm send-command \
--document-name "AWS-RunShellScript" \
--instance-ids "${{ steps.get_instance_id.outputs.instance_id }}" \
--parameters commands="systemctl status infra-slackbot.service && \
cd /opt/infra-slackbot && \
git stash save && \
git pull && \
systemctl restart infra-slackbot.service && \
systemctl status infra-slackbot.service" \
--output json|jq -r '.Command.CommandId') &&
echo "command_id=${command_id}" | tee -a "$GITHUB_OUTPUT"
- name: get command status
id: get_command_status
run: |
command_id="${{ steps.send_command.outputs.command_id }}"
status=""
while [[ "$status" != "Success" && "$status" != "Failed" ]]
do
output=$(aws ssm list-command-invocations --command-id "$command_id" --details --output json)
status=$(echo $output | jq -r ".CommandInvocations[0].Status")
sleep 10 # sleep for 10 seconds
done
echo "Final command status: $status"
echo "Full command output: $output" #debug
if [[ "$status" == "Failed" ]] ; then
echo "Command failed. Exiting."
exit 1
fi
# 複数行の出力を取得するためにEOFを使う
# https://qiita.com/Ets/items/f93a48d40c81ea1e0c99
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "report<<$EOF" | tee -a "$GITHUB_OUTPUT"
jq -r ".CommandInvocations[0].CommandPlugins[0].Output" <<< "$output" | tee -a "$GITHUB_OUTPUT"
echo "$EOF" | tee -a "$GITHUB_OUTPUT"
- name: Post result to issue
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const output = `${{steps.get_command_status.outputs.report}}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Here is the result of the command execution:\n\`\`\`${output}\`\`\``
});
- name: Comment Result
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
env:
GHA_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
AWS_URL: https://ap-northeast-1.console.aws.amazon.com/systems-manager/run-command/${{ steps.send_command.outputs.command_id }}/${{ steps.get_instance_id.outputs.instance_id }}?region=ap-northeast-1
with:
script: |
const output = `## ${ context.workflow }
#### Command Result 📖\`${{ steps.get_command_status.outcome }}\`
*↑successじゃなかったら何かがおかしいよ↑、GHA Result Detailsで確認してね*
*GHA Result Details: [${ process.env.GHA_URL }](${ process.env.GHA_URL })*;
*AWS Result Details: [${ process.env.AWS_URL }](${ process.env.AWS_URL })*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
神の自動化による成果
インフラグループにデプロイをお願いしなくて良くなったためメソッドたくさん生えました!おもしろ機能の追加によりコミュニケーションの活性化につながります!
宣伝
ということでCI/CDの勉強会を開くのでご興味のある方はぜひ!
Discussion