この記事は mediba Advent Calendar 2023 12日目の記事です。
20231215追記
re:iventで、gitから直接デプロイができるアップデートが発表されたようです。
こちらの方が全然サクッと作成できますね。
https://dev.classmethod.jp/articles/cloudformation-git-sync-update/
記事の方にもありますが、CI/CDをしっかり作成する場合はまだGitHubActionsもアリなのかもしれません。
今のところ承認機能の有無がこの記事内容との違いになりますが、もう少ししたらそれもアップデートで追加されそうですね。
はじめに
株式会社medibaでインフラエンジニアをしております、馬淵と申します。
新規案件立ち上げ時に最初に作るIAM Role、Policy等の、ある程度形が決まっているが、ぽちぽち手作業が必要な作業って面倒臭い大変ですよね。
今回は、そういったリソースをパッと作るような仕組みを作ってみました。
例として、この仕組みで承認フローを挟んだ踏み台側のスイッチロールのポリシーを作ってみたので、それの紹介をしていきたいと思います。
同じようなものを作る方の参考になればと思います。
前提条件
GitHubActionsを使った事がある
対象環境にGitHubActions用のRoleが用意されている
構成図
この仕組みの動作のイメージ
1.IAMポリシーのCfnテンプレートを作成する
2.環境毎のファイルを作成する
3.tag pushでGitHubActionsを発火
4.slackに承認通知が飛ぶ
5.責任者が承認
6.作成完了!
作成するまでの流れ
1.事前準備
2.階層を作成
3.GitHubActions用テンプレートファイルを作成
4.GitHubでEnvironmentを作成
5.Cfnを動かすシェルを作成
6.各環境用ファイルを作成
7.Cfn用テンプレートファイルを作成
8.完了!
1.事前準備
最初に、次の準備をします。
・リポジトリの用意
・GitHubActions用Roleの用意
・slackのWebhook URLの用意
■リポジトリの用意
リポジトリを作成します。説明は省略させていただきます
■GitHubActions用Roleの用意
踏み台側AWSアカウントにGitHubActions用Roleを用意します。
参考はこちら
このRoleの名前は後述のactions用のソースコードで使用します。
■slackのWebhook URLの用意
slackのWebhook URLを用意します。
参考はこちら
こちらで用意したURLは、GitHub>[Setting]>[Actions secrets and variables]>[Repository secrets]に保存しておいてください。
後述のactionsで使います。
2.階層を作成
このような階層でフォルダ/ファイルを作成します。
フォルダの名前はなんでもいいですが、以降のコードにパスがあるので、そちらと併せてください。
switch_from_accountというフォルダの下に、それぞれのプロダクトを配置し、さらにその下に各環境ファイルがあるような階層になっております。
3.GitHubActions用テンプレートファイルを作成
次に、GitHubActions用のソースコードを用意します。
今回は、review-(env)-(projectname)-(date)-(num)とい名前でgitでtagをpushすると発火するように作りました。
OIDC_AWS_ROLEは事前準備で用意した時のロールのarnに書き換えてください。
name: GitHub Actions
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on:
push:
tags:
- 'review-**-**'
# review-(env)-(projectname)-(date)-(num)
# ex: review-dev-projectA-20231212-01
env:
OIDC_AWS_ROLE: arn:aws:iam::0123456789:role/GitHubActionsOidcRole
AWS_DEFAULT_REGION: ap-northeast-1
jobs:
check_pushed_tag_name:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
outputs:
deploy_target_env: ${{ steps.deploy_target.outputs.env }}
deploy_project_type: ${{ steps.deploy_project.outputs.type }}
steps:
- name: Filter pushed tag name
uses: actions-ecosystem/action-regex-match@v2
id: regex_match
with:
text: ${{ github.event.ref }}
regex: '^refs\/tags\/review\-(dev|stg|prd)\-([a-z]+)\-(2[0-9]{3}(0[1-9]|1[0-2])(0[1-9]|1[0-9]|2[0-9]|3[01]))_[0-9]?[1-9]+$'
- name: No matched available tag
if: steps.regex_match.outputs.match == ''
run: |
exit 1
- name: Set up deploy targeted environment
id: deploy_target
run: |
echo "env=${{ steps.regex_match.outputs.group1 }}" >> $GITHUB_OUTPUT
- name: Set up deploy artifacts type
id: deploy_project
run: |
result=`echo -n '${{ steps.regex_match.outputs.group2 }}' | tr -d '-'`
if [ -z $result ]; then
result="all"
fi
echo "type=${result}" >> $GITHUB_OUTPUT
- name: Notify pushed tag details
run: |
echo 'Target environment: ${{ steps.deploy_target.outputs.env }}'
echo 'Artifacts type: ${{ steps.deploy_project.outputs.type }}'
notice_deploy_target:
needs:
- check_pushed_tag_name
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Send GitHub Action trigger data to Slack workflow
id: slack
uses: slackapi/slack-github-action@v1.16.0
with:
payload: |
{
"text": ":github: ビルド結果: ${{ job.status }}\n\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "switch roleの設定が申請されました。以下URLから承認してください\n"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "承認: <https://github.com/org/repository-url/actions/runs/${{ github.run_id }}|こちら>"
}
]
}
]
}
env:
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }}
approval:
needs:
- notice_deploy_target
environment:
name: approval_proper
runs-on: ubuntu-latest
steps:
- name: approve deployment
run: echo 'デプロイ確認 承認完了'
deploy_cfn:
needs:
- check_pushed_tag_name
- approval
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-session-name: github-actions-session
aws-region: ${{ env.AWS_DEFAULT_REGION }}
role-to-assume: ${{ env.OIDC_AWS_ROLE }}
- run: ./cloudformation/switch_from_account/${{ needs.check_pushed_tag_name.outputs.deploy_project_type }}/deploy.sh -t "swith_from_policy" -e "cloudformation/switch_from_account/${{ needs.check_pushed_tag_name.outputs.deploy_project_type }}/env/${{ needs.check_pushed_tag_name.outputs.deploy_target_env }}/.env"
4.GitHubでEnvironmentを作成
GitHubの[Setting]>[Environment]から、approval_properという名前で承認ルールを作成します。
上記のActionsを実行する際は、ここで設定したメールアドレスの人間に承認させるようにします。
5.Cfnを動かすシェルを作成
Actionsの最後に動かすシェルを作ります。
ここまでが共通部分となります。
これは特にいじるところはないと思います。
#!/usr/bin/env bash
set -euo pipefail
export LC_ALL=C
while getopts t:e: opt; do
case $opt in
"t") template_dir="${OPTARG}" ;;
"e") target_env="${OPTARG}" ;;
esac
done
script_dir=$(cd $(dirname $0); pwd)
source ${target_env}
# 各パラメーターの取得
stack_name=${account_alias_name}_$(cd ${script_dir}/${template_dir}; basename "$(pwd)")
echo "stack_name:"${stack_name}
# パラメーターチェック
if [ "${s3_cfn_bucketname}" = "" ];then
echo "ERROR: Unable to get an 's3_cfn_bucketname'"
exit 1
fi
if [ "${organization_name}" = "" ];then
echo "ERROR: Enter organization_name in the .env file"
exit 1
fi
# CloudFormation Validate
echo "Validate CloudFormation template..."
aws cloudformation validate-template \
--template-body file://${script_dir}/${template_dir}/sam.yml
# CloudFormation Package
echo "Package CloudFormation template..."
mkdir -p ${script_dir}/${template_dir}/.packaged
aws cloudformation package \
--template-file ${script_dir}/${template_dir}/sam.yml \
--output-template-file ${script_dir}/${template_dir}/.packaged/cfn.yml \
--s3-bucket "${s3_cfn_bucketname}" \
--s3-prefix "${stack_name}"
# S3 upload
echo "Sync S3 CloudFormation templates..."
aws s3 sync ${script_dir}/${template_dir} \
"s3://${s3_cfn_bucketname}/${stack_name}/"
echo "ok!"
6.各環境用ファイルを作成
ここからは環境別のファイルを作っていきます。
以下の項目を環境毎の情報に書き換えてください。
switch_from_account_no=0123456789
switch_to_account_no=9876543210
organization_name=mediba
s3_cfn_bucketname=cf-templates-ap-northeast-1
account_alias_name=projectAalias
env=dev
7.Cfn用テンプレートファイルを作成
最後に、作りたいAWSリソースのCfnテンプレートを用意します。
今回はスイッチロール用Policyなので、次のように、Admin、Developer、ReadOnlyという3つのポリシーについて書きました。
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
OrgName:
Type: String
SwitchFromAccountNo:
Type: String
SwitchToAccountNo:
Type: String
AccountAliasName:
Type: String
Env:
Type: String
Resources:
AdminPolicy:
Type: "AWS::IAM::ManagedPolicy"
Properties:
ManagedPolicyName: !Sub ${AccountAliasName}-${Env}-Admin
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "sts:AssumeRole*"
Resource:
- !Sub "arn:aws:iam::${SwitchToAccountNo}:role/Admin"
DeveloperPolicy:
Type: "AWS::IAM::ManagedPolicy"
Properties:
ManagedPolicyName: !Sub ${AccountAliasName}-${Env}-Developer
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "sts:AssumeRole*"
Resource:
- "*"
- !Sub "arn:aws:iam::${SwitchToAccountNo}:role/Developer"
ReadOnlyPolicy:
Type: "AWS::IAM::ManagedPolicy"
Properties:
ManagedPolicyName: !Sub ${AccountAliasName}-${Env}-ReadOnly
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "sts:AssumeRole*"
Resource:
- !Sub "arn:aws:iam::${SwitchToAccountNo}:role/ReadOnly"
8.完了!
これで完了です。
以下のようにtagをpushすれば動き、slackに通知が届くと思います。
$ git push origin review-dev-projectA-20231212-01
終わりに
以上になります。ここまで長い記事をご覧いただきありがとうございました。
日々の業務で触るうちに、Cfnの事が好きになってきました。
今回はスイッチロールでの例でしたが、当然、テンプレート次第で色々なパターンに対応できると思います。
どこかでトイル削減に役立てると幸いです。