CDKで構築したリソースのプレビュー環境をGithub Actionで構築してみる
こんにちは、会社ではインフラ関連で色々やっているNHatanakaです。
最近プルリクエストのマージ前にAWSの環境上で確認できないか考え、なんとなく形になったと思うのでまとめたいと思います。(実務で運用はしてません)
構想
プレビュー環境を用意したい時にGithub Actionsを手動で実行しブランチ名をCloudFormationのスタック名に含ませることでプレビュー環境を構築したいと思います。
今回プレビュー環境としてデプロイする内容は下記で紹介されているStepFunctionsのチュートリアルです。
ディレクトリ構成
├── .github/
│ └── workflows/
│ ├── create-preview.yml
│ └── delete-preview.yml
│
├── lib/
│ ├── iam-stack.ts
│ └── step-stack.ts
│
以下略
CDKのセットアップ
まずCDKのセットアップを行います。
mkdir cdk-preview && cd $_
cdk init --language typescript
GitHubActions上で使用するロールを作成
GitHubActions上でcdk deploy
を実行するためのポリシーを持ったロールを構築します。
lib
ディレクトリ直下にiam-stack.ts
を作成します。
<GitHubユーザー名>と<GitHubリポジトリ名>はご自身の環境に合わせ修正してください。
iam-stack.ts
import * as cdk from 'aws-cdk-lib';
import { FederatedPrincipal, PolicyStatement, Role } from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
export class IamStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const deployRole = new Role(this, 'DeployRole', {
assumedBy: new FederatedPrincipal(
`arn:aws:iam::${this.account}:oidc-provider/token.actions.githubusercontent.com`,
{
StringLike: {
'token.actions.githubusercontent.com:sub': `repo:<GitHubユーザー名>/<GitHubリポジトリ名>:*`
}
},
'sts:AssumeRoleWithWebIdentity'
),
roleName: `DeployRole`,
maxSessionDuration: cdk.Duration.seconds(3600)
});
deployRole.addToPolicy(
new PolicyStatement({
resources: [
`arn:aws:iam::${this.account}:role/cdk-*-${this.account}-${this.region}`
],
actions: [
'sts:AssumeRole'
],
})
);
}
}
cdk-preview.ts
でIamStackをインポートします。
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { IamStack } from '../lib/iam-stack';
const app = new cdk.App();
new IamStack(app, 'IamStack');
最後にcdk deploy IamStack
を実行します。
StepFunctionsのスタックファイル作成
lib
ディレクトリ直下にstep-stack.ts
を作成します。
step-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Code, Function, Runtime } from 'aws-cdk-lib/aws-lambda';
import { StateMachine, Succeed } from 'aws-cdk-lib/aws-stepfunctions';
import { LambdaInvoke } from 'aws-cdk-lib/aws-stepfunctions-tasks';
import { Construct } from 'constructs';
export class StepStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const helloFunction = new Function(this, 'MyLambdaFunction', {
code: Code.fromInline(`
exports.handler = (event, context, callback) => {
callback(null, "Hello World!");
};
`),
runtime: Runtime.NODEJS_18_X,
handler: "index.handler",
timeout: cdk.Duration.seconds(3)
});
new StateMachine(this, 'MyStateMachine', {
definition: new LambdaInvoke(this, "MyLambdaTask", {
lambdaFunction: helloFunction
}).next(new Succeed(this, "GreetedWorld"))
});
}
}
cdk-preview.ts
でStepStackをインポートします。
この時スタック名にブランチ名を含ませるためにenv
をコンテキストとして渡せるようにします。
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { IamStack } from '../lib/iam-stack';
const app = new cdk.App();
const env = app.node.tryGetContext('env');
new IamStack(app, 'IamStack');
new StepStack(app, `${env}-StepStack`);
Githubのシークレットに必要な情報を登録
最初にIamStackで構築したロールを使用するためにはaws-actions/configure-aws-credentials
を使用します。
この時にアカウントIDとリージョンが必要なのでGithubのシークレットに登録します。
プレビュー用ワークフローの構築
プレビュー環境用ワークフローを構築しますが、注意点としてデフォルトブランチにマージしてないワークフローを手動で実行するためには、一度ワークフローのトリガーにpush
を追加し実際にpush
してワークフローを認識させる必要があります。
下記のように一覧にワークフローが出現すればトリガーのpush
は削除してください。
ただこの方法で認識させたワークフローはコマンドでしか起動できないため、ブラウザ上で起動したい場合、先に下記のワークフローをデフォルトブランチにマージした方が楽かもしれません。
まずプレビュー環境を構築するワークフローを構築します。
create-preview.yml
name: Create Preview
on:
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
create-preview:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v3-node20
with:
role-to-assume: "arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/DeployRole"
aws-region: ${{ secrets.AWS_REGION }}
role-duration-seconds: 900
role-session-name: GitHubActionsSession
- name: Npm CI
run: npm ci
- name: CDK Deploy
run: npx cdk deploy ${{ github.ref_name }}-StepStack -c env=${{ github.ref_name }} --require-approval never
ワークフロー内でgithub.ref_name
を使うことによりブランチ名を取得しcdkのコンテキストに渡すことでスタック名にブランチ名を持たせます。
- name: CDK Deploy
run: npx cdk deploy ${{ github.ref_name }}-StepStack -c env=${{ github.ref_name }} --require-approval never
次にプレビュー環境を削除するワークフローを構築します。
delete-preview.yml
name: Delete Preview
on:
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
delete-preview:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v3-node20
with:
role-to-assume: "arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/DeployRole"
aws-region: ${{ secrets.AWS_REGION }}
role-duration-seconds: 900
role-session-name: GitHubActionsSession
- name: Npm CI
run: npm ci
- name: CDK Destroy
run: npx cdk destroy ${{ github.ref_name }}-StepStack -c env=${{ github.ref_name }} --force
検証
下記のコマンドを実行し実際にプレビュー環境構築用ワークフローを起動します。
(ブランチ名はpreview作成しました)
gh workflow run "Create Preview" --ref preview
すると下記のように起動すると思います。
完了すればコンソールにログインしCloudFormationとStepFunctions確認してください。
次は削除するので下記コマンドを実行します。
gh workflow run "Delete Preview" --ref preview
CloudFormationがリソースを削除していきます。
余談ですがデフォルトブランチにマージすればブラウザで実行できます。
この時指定したブランチ名がスタック名に含まれます。
まとめ
トリガーを手動起動にするかプルリクエストにするかは迷いましたが、状況によって不要な場合もあるかと思ったので今回は手動にしました。
また個人的にはメンバーがローカルで実際にデプロイする運用等もあるかと思いますが、メンバーがCDKの知見があまりなかったり、環境変数等で色々ややこしい場合選択肢になりうるかと思っています。
Discussion