この記事はぷりぷりあぷりけーしょんず Advent Calendar 2019の4日目の記事です。
はじめに
個人開発をする上でAWS Lambdaというのは色んな面で低コストで様々なことができ、アプリエンジニアな私からするとなんとなく「やった」感が得られるものが有りついつい使ってしまうし、そんな人はきっと沢山いるでしょう。知らんけど。
デプロイも楽だし、Node.jsやPythonならなんかミスったらコンソールで直接いじいじ直しちゃえば何とかなるもんね、うん最高。満足満足。
と、満足せずに今日はより開発現場でも使いやすく、Gitで管理しながらプルリクが通ってmasterにマージされたらあら不思議、本番に反映されている!な事をGitHub ActionsとAWS CDKを使って実現して行こうと思います。
スキルセット
GitHub Actions
AWS CDK 1.18.0(2019.12.03時点最新)
AWS Lambda
AWS APIGateway
Go
ディレクトリ構造
- .github
|
- workflows
|
- actions.yml
- bin
|
- main(goのバイナリができる予定のdir)
- cdk
|
- cdk initした資材群(省略)
- src
|
- main.go
- go.mod
- .gitignore
なお、今回は各スキルセットにはあまり深入りせずサクサク実装していこうと思います(なんせ自分もまだよくわかってない)
今回の成果物はこちらになります。
Go
GoのLambdaを作っていきます。
今回はGitHub ActionsとAWS CDKを使って自動デプロイする
がメインなお題なのでここはシンプルにHello WorldなLambdaで勘弁カツオです。
ギリギリ補足するとしたらGoでLambda書くときはlambda.Start(func)
しましょうというあたりでしょうか。
package main
import (
"fmt"
"github.com/aws/aws-lambda-go/lambda"
)
func hello() (events.APIGatewayProxyResponse, error) {
return events.APIGatewayProxyResponse{
StatusCode: 200,
Body: "Hello Katsuo",
}, nil
}
func main() {
lambda.Start(hello)
}
CDK
今回はAWS APIGateway
からAWS Lambda
を発火するあるあるCDKを作成していきます。
CDKの環境構築な記事は沢山あるので今回は用意されている前提で進めます。
ディレクトリ構造にならってcdkディレクトリを作成しその中でinitします。
今回はtypescriptで実装していきます(というかこれしかやった事ない)
$ mkdir cdk
$ cd cdk
$ cdk init app --language=typescript
続いて必要なライブラリをインストールしておきます。
今回使うのは@aws-cdk/aws-lambda
@aws-cdk/aws-apigateway
この二つを使っていきます。
$ npm install --save @aws-cdk/aws-lambda @aws-cdk/aws-apigateway
さて実装していく中身は主にlib
パッケージの中なのですがその他のbin
, test
パッケージを見ていただいてわかるように.tsファイルがcdk.ts
というようになっているかと思います。
initしたディレクトリ名で名前解決されているような感じです。
このままだとカッコわりいという方はいい感じの名前に変更してください(今回はこのままいきます)
cdk-stack.tsの実装
では実装です。lib
ディレクトリの直下にある***-stack.ts
(今回はcdk-stack.ts
)にリソースを定義していきます。
import cdk = require('@aws-cdk/core');
import lambda = require("@aws-cdk/aws-lambda")
import apigateway = require("@aws-cdk/aws-apigateway")
import {Duration} from "@aws-cdk/core";
export class CdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Lambda
const lambdaHandler = new lambda.Function(this, "api", {
code: lambda.Code.fromAsset("../bin"),
handler: "main",
runtime: lambda.Runtime.GO_1_X,
timeout: Duration.seconds(10),
environment: {
AUTO_DEPLOY_TEST: "success"
}
});
// API Gateway
const api = new apigateway.RestApi(this, "auto-deploy-sample-api", {
restApiName: "auto-deploy-sample-api"
});
const lambdaHandlerIntegration = new apigateway.LambdaIntegration(lambdaHandler, {proxy: true});
api.root.addResource("auto-deploy-sample-api")
api.root.addMethod("GET", lambdaHandlerIntegration)
}
}
なんと不思議なことにインフラちんぷんかんぷんなアプリエンジニアな私でもソースコードになると途端に何がされているかが一目瞭然。
一応リソースごとに見ていきます。
Lambda
key | value |
---|---|
code | デプロイするソースコード |
handler | エントリーポイントとなる関数名 |
runtime | 使用言語 |
timeout | 実行時のタイムアウト設定 |
environment | 環境変数 |
code
は今回プロジェクト直下のbin/main
にビルドされるものを参照する想定です。
ここでのソースはlib
からではなくcdk
から参照します。
大体これくらいが必須なものになってくるかと思います。用途によってはVPCの設定などもあるかと思いますがもちろんできますし、ドキュメントを見れば基本大丈夫な上、ソースコードの参照元を見れば使い方は大体わかるので積極的にIDEで参照ジャンプしましょう。
readonly vpc?: ec2.IVpc;
/**
* Where to place the network interfaces within the VPC.
*
* Only used if 'vpc' is supplied. Note: internet access for Lambdas
* requires a NAT gateway, so picking Public subnets is not allowed.
*
* @default - Private subnets.
*/
readonly vpcSubnets?: ec2.SubnetSelection;
/**
* What security group to associate with the Lambda's network interfaces.
*
* Only used if 'vpc' is supplied.
*
* @default - If the function is placed within a VPC and a security group is
* not specified, a dedicated security group will be created for this
* function.
*/
API Gateway
もはや解説するまでもないと思います。
lambdaのインテグレーションを定義し作成したapigateのResourceとMethodにaddするだけで紐づけられます。簡単便利。
cdk.tsの実装
ここにはAWSのアカウント情報とリージョンを定義していきたいのですがソースコードに直接書き込んでしまうのはいささかナンセンスです。
そこでcdk.json
を使います。cdk init
した際に作成されているファイルで内部にcontext
というキーと更にキーバリューを定義しておくことで.ts
ファイルからapp.node.tryGetContext("キー名")
でバリューを取り出すことがきます。
実際には以下のようになります。
{
"context": {
"account": "YOUR_AWS_ACCOUNT",
"region": "YOUR_AWS_REGION"
},
"app": "npx ts-node bin/cdk.ts"
}
#!/usr/bin/env node
import 'source-map-support/register';
import {CdkStack} from '../lib/cdk-stack';
import cdk = require('@aws-cdk/core');
const app = new cdk.App();
new CdkStack(app, 'CdkStack',
{
env: {
account: app.node.tryGetContext("account"),
region: app.node.tryGetContext("region")
}
});
ここまで実装したら一旦cdk bootstrap
しておきます。
これをやり忘れると自動デプロイの際にどこにデプロイすれば良いのかわからなくなり怒られます。
$ cdk bootstrap // --profile *** <- 必要ならつけましょう
GitHub Actions
あと一歩で自動デプロイです。
最後にGitHub Actionsにタスクを登録していきます。
AWS access情報周りを登録
タスクの登録の前に予めSettingsタブのSecretsにAWSのアクセスキー情報などを登録しておきます。プロジェクトなんかでやるならマシンユーザー的なものでやるのが良いのでしょうかね。
下記のようにAWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
みたく登録できればおけーです。
actions本題
ではここからactionsを作成していきます。
まずはリポジトリの真ん中あたりのActions
タブを選択します。
actionsタブをクリックするとあらかじめ用意されたactionが沢山出てきますが残念ながらCDKは用意されていません。
こんなようなものもありましたがタスクの途中でこけてしまい解決もできず...
なのでactionのフロー上で自分でCDKをUbunts環境に用意しデプロイをしてみました。
aws-cli
が必要になるのはpython
, cdk
はnode
, そして今回のgo
のビルド環境をあらかじめ用意されているactionからコピペしながら作成したものが以下の通りです。
自分で追加したのはpipのupgradeとaws-cli
, cdk
, typescript
のインストール、goのビルドコマンドくらいです。
name: CI
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
# python
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Install dependencies
run: |
python -m pip install --upgrade pip
# node.js
- name: Use Node.js 10.x
uses: actions/setup-node@v1
with:
node-version: 10.x
# go
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go
# aws cli & cdk
- name: aws cli install
run: |
pip install awscli --upgrade --user
npm install -g aws-cdk
- name: build and deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
GOOS=linux GOARCH=amd64 go build -o ./bin/main ./src
cd cdk
npm install --save typescript
npx cdk deploy --require-approval "never"
cdk
のデプロイ時に--require-approval "never"
をつけているのはCloudFormationの変更内容に問題ないかのチェックが入らず、問答無用でデプロイするためです。
終わりに
これでめでたくmaster
リポジトリにマージされると自動でAWSにCDKで定義されたスタックがデプロイされるようになりました。
いちいち実装が終わったらローカルでCDKコマンドを打つ必要もなければ開発ユーザーの権限ではAWSのリソースをいじれないようにしマシンユーザーからだけLambdaの更新ができるようになればプロジェクトはよりセキュアでGitでソースは管理され健康的になります。
果たしてGitHub Actionsの使い方としてこれはあってるのか疑問はありますが何となく「こんなことができる」感はわかったのでこれからまたキャッチアップしていきます。
本当はCodePipeLineとか使うとより良いのでしょうが今日はここまで。
明日は@Black-Spiderさんの記事です!