SAKURUG TECHBLOG

GitHub ActionsによるAWS Lambdaへの自動デプロイ方法まとめ

timestampauthor-name
Syun

こんにちは、Syunです。

GitHub ActionsによるAWS Lambdaへの自動デプロイ方法をまとめます。

AWSコンソールにログイン

AWSマネージメントコンソールにログインします。

IAMで登録

AWS IAMでユーザー、ポリシー、ロールの三つを登録します。

ユーザー

適宜IAMユーザーを作成します。詳しくは省略します。

IAM → ユーザーでユーザーを選択し、認証情報タブ → アクセスキーの作成をクリックします。
ここでアクセスキーを作成します。
作成したアクセスキーはCSVファイルでダウンロードできるので、これを保存しておきます。
このCSVファイルにはアクセスキーIDとシークレットアクセスキーが書かれており、これを後ほど使います。
シークレットアクセスキーは再確認できないので、ここで忘れずに保存する必要があります。

ポリシー

ポリシーを作成します。
IAM → ポリシー → ポリシーの作成で、以下のJSONを貼り付けます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:*",
                "iam:*",
                "cloudformation:*",
                "apigateway:*",
                "logs:*",
                "lambda:*"
            ],
            "Resource": "*"
        }
    ]
}


名前を設定し、新しいポリシーとして登録します。

※注意!

Create SLR With Star In Action And Resource:  
ワイルドカード (*) をアクションおよびリソースで使用すると、  
すべてのリソースで iam:CreateServiceLinkedRole アクセスが許可されるので、  
意図しないサービスにリンクされたロールが作成されることがあります。  
これを避けるために、代わりにリソース ARN を指定することをお勧めします。  


と警告が表示されます。
ワイルドカード(*)は本来使うべきではありません。

ロール

ロールを作成します。
IAM → ロール → ロールの作成で、「信頼されたエンティティを選択」の画面が出るので、

エンティティタイプをウェブアイデンティティに、
プロバイダーをtoken.actions.githubusercontent.comに、
Audienceをsts.amazonaws.comに、

それぞれ選択して、次に進みます。

「許可を追加」の画面で、先ほど作成したポリシーを選択します。
選択後、「次へ」で確認画面に進みます。

確認画面ではロール名を設定し、信頼されたエンティティの設定を変更します。
信頼されたエンティティは、以下のようにします。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::{ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": [
                "sts:AssumeRoleWithWebIdentity",
                "sts:TagSession"
            ],
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                },
                "StringLike": {
                    "token.actions.githubusercontent.com:aud": "repo:{GITHUB_ORG_NAME}/{GITHUB_REPO_NAME}:*"
                }
            }
        },
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::{ACCOUNT_ID}:user/{USER_NAME}"
            },
            "Action": "sts:AssumeRole"
        },
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::{ACCOUNT_ID}:user/{USER_NAME}"
            },
            "Action": "sts:TagSession"
        }
    ]
}


{GITHUB_ORG_NAME}はGitHubの組織名、
{GITHUB_REPO_NAME}はGitHubの配下のレポジトリ名、
{ACCOUNT_ID}はAWSのアカウントID(4桁-4桁-4桁の数字)、
{USER_NAME}はAWSのユーザー名です。
適宜書き換えてください。

GitHub Actionsにシークレットを登録

GitHub上でシークレットを登録します。
GitHubのレポジトリに入り、「上のメニューバーからSettings → 左のメニューからSecrets and variables → Actions」と進むと「Actions secrets and variables」という画面が出ます。
下のような画面です。

上の画面で、「New repository secret」を押し、シークレットを登録します。
ここではAWS_ACCESS_KEY_IDAWS_REGIONAWS_ROLE_ARNAWS_SECRET_ACCESS_KEYの四つのシークレットを登録します。

AWS_ACCESS_KEY_IDは上で作成したIAMユーザーのアクセスキーIDです。
AWS_SECRET_ACCESS_KEYは上で作成したIAMユーザーのシークレットアクセスキーです。
なお、アクセスキーIDとシークレットアクセスキーは同じCSVファイルに書かれています。
保存しておいたものを登録します。

AWS_REGIONはAWSの地域です。
東京ならば「ap-northeast-1」です。
これを登録します。

AWS_ROLE_ARNはロールのARN(アマゾンリソースネーム)です。
これはIAMのロールのページで確認できます。
先ほど作成したロールのARNを登録します。

これにより、GitHubとAWSの接続ができるようになりました。

GitHub Actionsのワークフローを記述

GitHubからAWSにデプロイするためのワークフローを記述します。
まずはaws-lambda-testというディレクトリを作ります。
ワークフローファイルは、aws-lambda-test/.github/workflows/の配下に以下のようなyamlファイルとして記述します。

aws-lambda-test/.github/workflows/deploy.yml

name: Deploy to AWS Lambda
on:
  push:
    branches:
      - main
    workflow_dispatch:

env:
  NODE_VERSION: '14.x'

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: npm install, build, and test
        run: |
          npm install
          npm run build --if-present

      - name: Upload artifact for deployment job
        uses: actions/upload-artifact@v3
        with:
          name: node-app
          path: .
        
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          role-session-name: GitHubActions

      - name: lambda update
        run: |
          cd lambda && npx serverless deploy


このデプロイファイルは「mainブランチにプッシュされた際に自動でワークフローが実行される」ということです。
それはこの部分で指定しています。

on:
  push:
    branches:
      - main


なお、${{ secrets.AWS_ACCESS_KEY_ID }}などの記述が出てきますが、ここで先ほどGitHub上に登録した各シークレットが使われます。

Lambda関数をローカル上で記述

AWS LambdaにデプロイするLambda関数とAPIを、ローカル上で記述します。

aws-lambda-test/lambda/app.js

const express = require('express');
const app = express();

const router = require('./router');

app.use('/', router);

module.exports = app;


aws-lambda-test/lambda/lambda.js

const serverlessExpress = require('@vendia/serverless-express')
const app = require('./app')

const server = serverlessExpress.createServer(app)

exports.handler = (event, context) =>
    serverlessExpress.proxy(server, event, context)


aws-lambda-test/lambda/router.js

const express = require('express');
const router = express.Router();

const usersList = ["Alice", "Bob", "Carol", "Dave", "Emily"];
const petsList = ["Cat", "Dog", "Rabbit", "Parakeet", "Turtle"];

router.get("/", (_, res) => {
    res.status(200).send({
        message: "Hello from Lambda!!!"
    })
})

router.get("/users", (_, res) => {
    res.status(200).send({
        message: String(usersList)
    })
})

router.get("/users/:id", (req, res) => {
    const user_id = req.params.id
    const user_name = String(usersList[Number(user_id) - 1])
    res.status(200).send({
        id: user_id,
        name: user_name
    })
})

router.get("/pets", (_, res) => {
    res.status(200).send({
        message: String(petsList)
    })
})

router.get("/pets/:id", (req, res) => {
    const pet_id = req.params.id
    const pet_name = String(petsList[Number(pet_id) - 1])
    res.status(200).send({
        id: pet_id,
        name: pet_name
    })
})

module.exports = router;


上のaws-lambda-testレポジトリでは、lambdaディレクトリを作って、その中に記述しています。

なお、ここではnodejsのExpressというフレームワークを使用するために、Serverlessというフレームワークを使います。

aws-lambda-test/lambda/serverless.yml

service: lambda

provider:
  name: aws
  runtime: nodejs18.x
  region: ap-northeast-1

package:
  include:
    - serverless.yml
    - app.js
    - lambda.js
    - local-app.js
    - router.js
  exclude:
    - '**'

functions:
  func:
    handler: lambda.handler
    events:
      - http:
          path: /
          method: get
      - http:
          path: /users
          method: get
      - http:
          path: /users/{id}
          method: get
      - http:
          path: /pets
          method: get
      - http:
          path: /pets/{id}
          method: get


serverless.ymlファイルを記述し、これをGitHub Actionsからnpx serverless deployコマンドでデプロイするという流れです。

デプロイを実行

GitHub ActionsからAWS Lambdaへのデプロイを実行します。
プルリクエストを作ってmainブランチにマージするなどして、mainブランチにpushが入ると、デプロイが自動で実行されます。

※なお、ここでは15分~20分ほど時間がかかりました。

デプロイが成功すると、GitHub Actions上で緑色のチェックマークが表示され、失敗すると赤色のバツマークが表示されます。

デプロイ完了後は、AWS Lambda上にLambda関数が表示されます。
すでにあるLambda関数を更新して再デプロイした場合は、Lambda関数の新しいバージョンが生成されます。

Lambda関数の名前ですが、serverless.ymlファイルの
サービス名(service: lambdaよりlambda)、
デプロイされたステージ名(dev)、
関数名(functions: func:よりfunc)、
の三つが連結された名前(lambda-dev-func)になるようです。

また、同時にLambda関数のトリガーとなるAPIも生成され、そのAPI名は、
デプロイされたステージ名(dev)、
サービス名(service: lambdaよりlambda)、
の二つが連結された名前(dev-lambda)になるようです。

レイヤーを登録

このままだとAPIを実行してもサーバーエラーになります。
Lambda関数内で用いる各種パッケージ(express@vendia/serverless-expressなど)が使えないためです。
これらを使うためには、レイヤーというものを登録する必要があります。

aws-lambda-test/lambda/nodejsという名のディレクトリを作成し(nodejsという名前は強制)、その中で

npm init
npm install express
npm install @vendia/serverless-express


を実行します。

これでnodejs配下にpackage.jsonpackage-lock.jsonnode_modulesディレクトリが作成されます。
nodejsディレクトリをzip化し、AWS Lambdaの「レイヤーの作成」でこのzipファイルをアップロードします。
ここで作成したレイヤーを「レイヤーの追加」でLambda関数に追加すると、APIが実行できるようになります。

GitHub ActionsによらないローカルからのAWS Lambdaへのデプロイ方法

npx serverless deployコマンドをローカル上で実行すれば、ローカルからAWS Lambdaへデプロイもできます。
ただし、その際はローカル上に、GitHubに登録したようなAWS_ACCESS_KEY_IDAWS_REGIONAWS_ROLE_ARNAWS_SECRET_ACCESS_KEYなどを登録する必要があります。
また、その際はIAMユーザーの許可ポリシーに、

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:*",
                "iam:*",
                "cloudformation:*",
                "apigateway:*",
                "logs:*",
                "lambda:*"
            ],
            "Resource": "*"
        }
    ]
}


このJSONを登録する必要があります。

aws-lambda-test/lambdaディレクトリに、以下のファイルを追加します。

aws-lambda-test/lambda/local-app.js

const app = require('./app')

app.listen(3000, () => {
    console.log('listening');
})


ローカル上でサーバーを起動させ、localhost:3000をたたいてAPIが正しく動いているか確認してください。

AWS Lambda 関数


AWS Lambda コード

参考記事

記事をシェアする

ABOUT ME

author-image
Syun
2023年中途入社のエンジニアです。クラウド、ビッグデータに興味があります。

© SAKURUG co.,ltd.