#はじめに
はじめまして!株式会社オークンのNHatanakaと申します!
現在会社の同僚と趣味でフットサルのLINEアプリケーションを作っていて、
せっかくなので初めてGithub Actionsを触ってみました!
なので今回はその時実装したコードを紹介したいと思います!
#使ったもの
AWS CDK
Github Actions
direnv
#構成
まず構成としては下図のようなパイプラインでデプロイ時にAPIGatewayとLambdaが作成されます。
#ディレクトリ構成
iam:GithubActions上で使用するロールを作成するプロジェクトです。
futsal-backend:今回自動デプロイを組むプロジェクトでLambdaとAPIGatewayを作成します。
.
├── README.md
├──.github
│ └──workflows
│ └──cdk-deploy.yml
├── futsal-backend
│ └── ...
└── iam
│ └── ...
└──.envrc
.
├── README.md
├── bin
│ └── futsal-backend.ts
├── cdk.json
├── config
│ └── stack-const.ts
├── functions
│ └── message-api
│ └── index.py
├── jest.config.js
├── lib
│ └── futsal-backend-stack.ts
├── package-lock.json
├── package.json
├── test
│ └── futsal-backend.test.ts
└── tsconfig.json
.
├── README.md
├── bin
│ └── iam.ts
├── cdk.json
├── jest.config.js
├── lib
│ └── iam-stack.ts
├── package-lock.json
├── package.json
├── test
│ └── iam.test.ts
└── tsconfig.json
#iamスタックの内容
Github Actions上で使用するロールを作成するスタックです。
環境変数として.envrcに登録が必要な情報としては下記になります。
GITHUB_ORG:GitHubリポジトリのOrganization名
REPOSITORY_NAME:GitHubリポジトリ名
iam-stack.ts
import * as cdk from '@aws-cdk/core';
import * as iam from '@aws-cdk/aws-iam';
import {Duration} from '@aws-cdk/core';
export class IamStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const githubProvider = new iam.OpenIdConnectProvider(this, 'GithubProvider', {
url: 'https://token.actions.githubusercontent.com',
clientIds: ['sts.amazonaws.com'],
thumbprints: ['a031c46782e6e6c662c2c87c76da9aa62ccabd8e']
});
const deploy_role = new iam.Role(this, 'FutsalDeployRole', {
assumedBy: new iam.FederatedPrincipal(
githubProvider.openIdConnectProviderArn,
{
StringLike: {
'token.actions.githubusercontent.com:sub': `repo:${process.env.GITHUB_ORG}/${process.env.REPOSITORY_NAME}:*`
}
},
'sts:AssumeRoleWithWebIdentity',
),
roleName: `FutsalDeployRole`,
maxSessionDuration: Duration.seconds(3600)
});
deploy_role.addToPolicy(
new iam.PolicyStatement({
resources: ['*'],
actions: [
'cloudformation:CreateStack',
'cloudformation:CreateChangeSet',
'cloudformation:DeleteChangeSet',
'cloudformation:DescribeChangeSet',
'cloudformation:DescribeStacks',
'cloudformation:ExecuteChangeSet',
'cloudformation:GetTemplate',
'cloudformation:DescribeStackEvents',
'cloudformation:DeleteStack',
's3:ListBucket',
's3:getBucketLocation',
's3:CreateBucket',
's3:*Object',
'iam:PassRole',
],
})
);
new iam.Role(this, 'FutsalBackendStackDeployRoleForCloudFormation', {
assumedBy: new iam.ServicePrincipal('cloudformation.amazonaws.com'),
roleName: `FutsalBackendStackDeployRoleForCloudFormation`,
maxSessionDuration: Duration.seconds(3600),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AWSLambda_FullAccess'),
iam.ManagedPolicy.fromAwsManagedPolicyName('AWSCloudFormationFullAccess'),
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonAPIGatewayAdministrator'),
iam.ManagedPolicy.fromAwsManagedPolicyName('IAMFullAccess'),
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3FullAccess'),
],
});
}
}
上記のコードを手動でデプロイして作成されたロールのARNをGitHubにSecretに登録します。
余談ですがGithub Actionsは最近AWSクレデンシャルを直接渡さずにIAMロールが使えるように
なるという最高すぎることがあり、なんとなくCloudFormationを眺めながらCDKにしてみました。(あまり意味がないことを理解しつつ)
#futsal-backendの内容
ここでは実際にGithub Actionsで自動デプロイするリソースの定義を行なっています。
futsal-backend-stack.ts
import * as cdk from '@aws-cdk/core';
import {Duration} from '@aws-cdk/core';
import {resolve} from 'path';
import lambda = require('@aws-cdk/aws-lambda');
import apigw = require('@aws-cdk/aws-apigateway');
export class FutsalBackendStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, systemEnv: string, props?: cdk.StackProps) {
super(scope, id, props);
const messageApiFunction = new lambda.Function(this, 'MessageApiFunction', {
functionName: `MessageApiFunction${systemEnv}`,
code: lambda.Code.fromAsset(resolve(__dirname, '../functions/message-api')),
timeout: Duration.minutes(5),
handler: 'index.handler',
runtime: lambda.Runtime.PYTHON_3_8,
environment: {
CHANNEL_ACCESS_TOKEN: String(process.env.CHANNEL_ACCESS_TOKEN),
SECRET_KEY: String(process.env.SECRET_KEY)
}
})
new apigw.LambdaRestApi(this, 'Endpoint', {
handler: messageApiFunction,
deployOptions: {
stageName: systemEnv
}
});
}
}
またbin/futsal-backend.tsで環境名(systemEnv)を環境変数から取得するようにし
その値をCloudFormationやリソースに含めることで環境別にリソースが生成されるようにしています。
futsal-backend.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { FutsalBackendStack } from '../lib/futsal-backend-stack';
const systemEnv: string = process.env.SYSTEM_ENV || '';
const app = new cdk.App();
new FutsalBackendStack(app, `FutsalBackendStack${systemEnv}`, systemEnv);
#Github Actionsのワークフロー
さてやっとGithub Actionsのワークフローです。
今回はGitLabFlowを想定しておりmain・staging・productionの各ブランチに
マージされた際に起動しマージされたブランチ名を取得してSYSTEM_ENVに対応した環境名を
設定するように設計しました。
cdk-deploy.yml
name: AWS CDK
on:
push:
branches:
- main
- staging
- production
paths:
- futsal-backend/**
env:
FUTSAL_DEPLOY_ROLE: ${{ secrets.FUTSAL_DEPLOY_ROLE }}
FUTSAL_BACKEND_STACK_DEPLOY_ROLE: ${{ secrets.FUTSAL_BACKEND_STACK_DEPLOY_ROLE }}
permissions:
id-token: write
contents: read
jobs:
aws_cdk:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: "${{ env.FUTSAL_DEPLOY_ROLE }}"
aws-region: ap-northeast-1
role-duration-seconds: 900
role-session-name: GitHubActionsTestSession
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '14'
- name: npm ci
run: npm ci
working-directory: ./futsal-backend
- name: Set System Development
if: contains(toJSON(github.ref), 'main')
run: echo SYSTEM_ENV=Dev >> $GITHUB_ENV
- name: Set System Env Staging
if: contains(toJSON(github.ref), 'staging')
run: echo SYSTEM_ENV=Stg >> $GITHUB_ENV
- name: Set System Env Production
if: contains(toJSON(github.ref), 'production')
run: echo SYSTEM_ENV=Prod >> $GITHUB_ENV
- name: CDK Deploy
if: contains(github.event_name, 'push')
run: npx cdk deploy --require-approval never --role-arn "${{ env.FUTSAL_BACKEND_STACK_DEPLOY_ROLE }}"
working-directory: ./futsal-backend
#感想
今回初めてGithub Actionsを触って見ましたが非常に勉強になりました!
普段の業務では主にCircleCIを使っているのですが今後の新しいプロジェクトでは
Github Actionsも選択肢に入る気がします。(IAMユーザー作らなくていいのが大きすぎる)
また今回私自身ブログの投稿が初めてでエンジニア経験がそこまで長いわけではないので、
おかしなところもあると思いますがご容赦頂けたら幸いです。