LoginSignup
7
1

More than 3 years have passed since last update.

GitHub ActionsとAWS CDKを使ってAWS Lambdaを自動デプロイしていく

Last updated at Posted at 2019-12-03

この記事はぷりぷりあぷりけーしょんず 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)しましょうというあたりでしょうか。

勘弁カツオなLambda
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)にリソースを定義していきます。

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で参照ジャンプしましょう。

VPCのところ
    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("キー名")でバリューを取り出すことがきます。
実際には以下のようになります。

cdk.json
{
  "context": {
    "account": "YOUR_AWS_ACCOUNT",
    "region": "YOUR_AWS_REGION"
  },
  "app": "npx ts-node bin/cdk.ts"
}
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のアクセスキー情報などを登録しておきます。プロジェクトなんかでやるならマシンユーザー的なものでやるのが良いのでしょうかね。

accesskey.png

下記のようにAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYみたく登録できればおけーです。

addkeys.png

actions本題

ではここからactionsを作成していきます。
まずはリポジトリの真ん中あたりのActionsタブを選択します。

repository.png

actionsタブをクリックするとあらかじめ用意されたactionが沢山出てきますが残念ながらCDKは用意されていません。

actions-list.png

こんなようなものもありましたがタスクの途中でこけてしまい解決もできず...
なのでactionのフロー上で自分でCDKをUbunts環境に用意しデプロイをしてみました。
aws-cliが必要になるのはpython, cdknode, そして今回のgoのビルド環境をあらかじめ用意されているactionからコピペしながら作成したものが以下の通りです。
自分で追加したのはpipのupgradeとaws-cli, cdk, typescriptのインストール、goのビルドコマンドくらいです。

actions.yml
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さんの記事です!

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1