tagprでPRベースでタグを管理する
はじめに
githubでタグでバージョン管理するときにtag打ちとリリースノートの作成を手動で行うのは面倒だと思っていました。 tagprというOSSがリリースされてtag打ちとリリースノートをworkflowで自動で行なってくれるので今回使ってみました。
設定
.github/workflows/tagpr.yml
name: tagpr on: push: branches: - main jobs: tagpr: runs-on: ubuntu-latest timeout-minutes: 3 steps: - name: Checkout uses: actions/checkout@v3 - name: Tagpr uses: Songmu/tagpr@main id: tagpr env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.github/release.yml
- リリースノートの設定について設定します。
- PRに
tagpr
をタグを付与したものは除外する設定を追加
changelog: exclude: labels: - tagpr
- .tagpr
- https://github.com/Songmu/tagpr#configuration
- tagprを設定するファイル
vPrefix = true
: tagにv
のprefixを付与するかどうかreleaseBranch
: リリースブランチを指定
- tagprを設定するファイル
- https://github.com/Songmu/tagpr#configuration
# config file for the tagpr in git config format # The tagpr generates the initial configuration, which you can rewrite to suit your environment. # CONFIGURATIONS: # tagpr.releaseBranch # Generally, it is "main." It is the branch for releases. The tagpr tracks this branch, # creates or updates a pull request as a release candidate, or tags when they are merged. # # tagpr.versionFile # A versioning file containing the semantic version needed to be updated at release. # It will be synchronized with the "git tag". # Often this is a meta-information file such as gemspec, setup.cfg, package.json, etc. # Sometimes the source code file, such as version.go or Bar.pm, is used. # If you do not want to use versioning files but only git tags, specify the "-" string here. # # tagpr.vPrefix # Flag whether or not v-prefix is added to semver when git tagging. (e.g. v1.2.3 if true) [tagpr] vPrefix = true releaseBranch = main versionFile = -
実際に利用してみる
- tag: v0.0.3 の状態で
#3
のPRをmainブランチに対してマージするとworkflowが走る - PR
Release for v0.0.4 #4
が作成される(下記画像)
- この状態で
#5
のPRをmainブランチに対してマージするとworkflowが走りRelease for v0.0.4 #4
のWhats Changed
に追加される
このように、tagprのworkflowで指定しているブランチに対してPRを作成していき任意のタイミングでtagをきるさいにこの Release for v0.0.4 #4
のPRをマージする
- この状態で
Release for v0.0.4 #4
のPRをマージします。そうるすと、workflowが走りtagとreleaseノートが発行される
特にタグを指定しないとパッチバージョンがインクリメントされます。
ここで、マイナーバージョンを上げるためには tagpr:minor
をラベルに付与します。メジャーバージョンを上げるには同様に tagpr:major
をラベルに付与します。
ref) GitHub - Songmu/tagpr: automatically creates and updates a pull request for unreleased items, tag them when they are merged, and create releases.
今回は試しに、マイナーバージョンを上げるために tagpr:minor
のラベルを Release for v0.0.5
に付与してPRをマージしました。↓
- PRに
tagpr:minor
ラベルを付与
- マイナーバージョンがアップデートされた (v0.0.4 --> v0.1.0)
tag発行をトリガーとして利用する
tagprで作成されたPRをマージするとworkflowが実行されtagが打たれてリリースされる。 このタイミングで何かしらアクション、(例えばデプロイ)などする場合に下記のようにして 任意の処理を実行することも可能です。
.github/workflows/tagpr.yml
name: tagpr on: push: branches: - main jobs: tagpr: runs-on: ubuntu-latest timeout-minutes: 3 outputs: tagpr-tag: ${{ steps.exec-tagpr.outputs.tag }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Check out source code uses: actions/checkout@v3 - name: Exec-tagpr id: exec-tagpr uses: Songmu/tagpr@v1 deploy_something: runs-on: ubuntu-latest timeout-minutes: 3 needs: tagpr env: TAG_VERSION: ${{ needs.tagpr.outputs.tagpr-tag }} steps: - name: Show tag if: needs.tagpr.outputs.tagpr-tag != '' run: | echo ${{ env.TAG_VERSION }}
さいごに
今回、githubのタグを管理する方法としてtagprを検証しました。PRベースでタグとリリースノートが自動で作成でき、バージョンアップのメジャー、マイナー、パッチも PRにタグを付与することで設定できたのでとても便利でした。個人開発のリポジトリに導入して利用していこうと思います。
GitHub-Actionsのworkflowについてまとめる
- workflow実行時に選択式で値を渡す
- workflow実行時に入力式で値を渡す
- 三項演算子ライクに値を設定する
- set-envを使わずに環境変数に値をセットする
- repository_dispatch を利用して別のworkflowから実行する
- Composite Actionを使う
今回はgithub-actionsのworkflowについてです。最近、workflowを書く機会が多く一度、整理するために書いていきます。
workflow実行時に選択式で値を渡す
name: test on: workflow_dispatch: inputs: env: type: choice description: "envを選択" required: true options: - dev - stg - prd
workflow実行時に入力式で値を渡す
name: test on: workflow_dispatch: inputs: env: description: "envを入力" required: true permissions: id-token: write contents: write jobs: test: name: "sample" runs-on: ubuntu-22.04 timeout-minutes: 3 steps: - name: Show ENV run: | echo "ENV=${{ github.event.inputs.env }}"
三項演算子ライクに値を設定する
- inputsのenvが
prd
ならsecrets.PRD
を設定し - inputsのenvが
stg
ならsecrets.STG
を設定し prd
でもstg
でもなければ、secrets.DEV
を設定する
name: test on: workflow_dispatch: inputs: env: type: choice description: "envを選択" required: true options: - dev - stg - prd env: ENV_VALUE: | ${{ github.event.inputs.env == 'prd' && secrets.PRD || github.event.inputs.env == 'stg' && secrets.STG || secrets.DEV }}
set-envを使わずに環境変数に値をセットする
echo "{name}={value}" >> $GITHUB_ENV
と記述する- workflow実行時に設定したenvの情報を環境変数に設定する
name: test on: workflow_dispatch: inputs: env: type: choice description: "envを選択" required: true options: - dev - stg - prd permissions: id-token: write contents: write jobs: test: name: "sample" runs-on: ubuntu-22.04 timeout-minutes: 3 - name: Set ENV run: | echo "ENV"=`echo ${{ github.event.inputs.env }}` >> $GITHUB_ENV - name: Show ENV run: | echo "ENV=${{ env.ENV }}"
■ 実行結果
repository_dispatch を利用して別のworkflowから実行する
- repository_dispatch
注: このイベントは、ワークフローファイルがデフォルト ブランチにある場合にのみワークフローの実行をトリガーします。
- 呼び出し元のworkflow
event_type
は任意の文字列で設定する
name: call on: push: branches: - test jobs: call: runs-on: ubuntu-22.04 timeout-minutes: 3 steps: - name: Set request body run: | echo "REQUEST_BODY"='{"event_type": "call_workflow", "client_payload": {"sample_key": "sample_value" }}' >> $GITHUB_ENV - name: Trigger repository dispatch run: | curl \ -X POST \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ https://api.github.com/repos/yhidetoshi/github-actions-workflow-dev/dispatches \ --data "$REQUEST_BODY"
■ 実行結果
- 呼び出し先のworkflow
types: [call_workflow]
呼び出し元のevent_type
に合わせる
name: test_repository_dispatch on: repository_dispatch: types: [call_workflow] permissions: id-token: write contents: write jobs: test: name: "sample" runs-on: ubuntu-22.04 timeout-minutes: 3 steps: - name: Hello world run: | echo "Hello world (test repository dispatch)"
■ 実行結果
Composite Actionを使う
workflowの処理をモジュール化するときに使える
name: use_composte on: push: branches: - test env: RELEASE_TAG: 0.0.2 jobs: call: runs-on: ubuntu-22.04 timeout-minutes: 3 steps: - name: Checkout composite-action uses: actions/checkout@v3 with: repository: yhidetoshi/workflow-composite-action-dev path: ./.github/actions/composite-action token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} ref: ${{ env.RELEASE_TAG }} - name: Call print composite uses: ./.github/actions/composite-action/print
- composite-actionのworkflow
- yhidetoshi/workflow-composite-action-dev
❯ tree . . ├── README.md └── print └── action.yml
name: print runs: using: "composite" steps: - run: | echo "Hello world at composite" shell: bash
Lambdaレイヤーを利用してラインタイムPython上でgoバイナリを実行 & レイヤーをバージョン管理する
今回はLambdaレイヤーについて簡単な検証とレイヤーのバージョン管理について検証しました。
Lambdaレイヤーとは
Lambda レイヤーは、Lambda 関数で使用できるライブラリとその他の依存関係をパッケージ化するための便利な方法を提供します。レイヤーを使用することで、アップロードされたデプロイメントアーカイブのサイズを削減し、コードをデプロイするスピードを速めることができます。 レイヤーは、追加のコードまたはデータを含むことができる .zip ファイルアーカイブです。レイヤーには、ライブラリ、 カスタムランタイム 、データ、または設定ファイルを含めることができます。レイヤーを使用すると、コードの共有と責任の分離を促進し、ビジネスロジックの記述をより迅速に繰り返すことができます。
レイヤーを利用することで
- 複数のlambdaで共通的に利用可能なライブラリを作れる
- ライブラリの依存関係をパッケージ化できる
- zipでコードやデータをアップロードして使える
- レイヤーに持たせることでlambdaにデプロイするデータサイズを削減できる
試してみる
やりたい事は、goのバイナリをLambdaレイヤーに登録してpythonランタイムの関数からの実行です。 レイヤーに登録するGoのコード。これをコンパイルして利用しました。
package main import "fmt" func main() { fmt.Println("hello world lambda layer") }
次はlambdaのレイヤーを作成するためのzipを用意します。
Lambda レイヤーの作成と共有 - AWS Lambda
このドキュメントの ライブラリの依存関係をレイヤーに含める
の項目に
すべてのランタイムは /bin
にPATHが通っているようなので、 ./bin/main にバイナリを配置して
zip化しました。
❯ ls bin main.go ❯ ls bin/ main ❯ zip -r go-cmd.zip bin
今回はAWSコンソールからポチポチしてレイヤーを作成しました。
レイヤーをLambda関数に追加して利用できる状態になったのでPythonでgoバイナリを実行します。 利用したのは以下のコードです。
- lambda_function.py
import subprocess def lambda_handler(event, context): cmd = ['main'] out = subprocess.run(cmd, stdout=subprocess.PIPE) print(out.stdout.decode())
■ 実行結果
Function Logs START RequestId: 68838428-bb76-4af7-86ab-4805669c676f Version: $LATEST hello world lambda layer END RequestId: 68838428-bb76-4af7-86ab-4805669c676f
hello world lambda layer
と表示されて実行できました。
レイヤーをバージョン管理する
レイヤーをコード管理する場合にバージョン管理する必要が出てくると思います。 そこで、バージョンに対してcommit-idやtag名などを付与できるか確認しました。
lambdaレイヤーを更新するために aws-cliを利用しました。(publish-layer-version
)
awscli.amazonaws.com
バージョンは1からインクリメントされていくので、レイヤーを更新するときに description
オプションを付与して仮のtag名(v0.0.1)を付与しました。
github actionsのworkflowを利用していればtag pushをトリガーにtag名を取得してレイヤーを更新するときに descriptionに付与できます。
- lambdaレイヤーを更新するコマンド (aws-vaultを利用した場合)
❯ aws-vault exec ${profile} -- aws lambda publish-layer-version \ --layer-name go-cmd \ --description "v0.0.1" \ --zip-file fileb://go-cmd.zip \ --compatible-runtimes python3.9
新しくレイヤーを更新したら以下のようになりました。説明の部分に v0.0.1
タグを埋め込めるので何のバージョンのものかすぐにわかるようにできそうです。
APIGateway + Lambda + Go(Echo)をSAM(Serverless Application Model)でAWSとローカルにデプロイする
はじめに
前回、APIGW + Lambda + Go(Echo)のローカル環境をSAMで構築しました。 今回は、samでローカルではなく、AWS環境にデプロイをしました。ローカルも同じ設定で利用可能です。
本記事ではAWSにデプロイする部分にだけ記載します。詳細は↑の記事に記載済みです。
また、LambdaでGoフレームワークのEchoを動かす事についてまとめた記事は↓。
コード
ディレクトリ構成
serverless-sam-go-local ├── README.md └── serverless ├── README.md ├── echo │ ├── Makefile │ ├── api │ │ └── healthcheck │ │ └── healthcheck.go │ ├── conf │ │ └── config.go │ ├── echo │ ├── go.mod │ ├── go.sum │ ├── handler │ │ └── auth │ │ └── auth.go │ └── main.go ├── events │ └── event.json ├── samconfig.toml └── template.yaml
ソースコードはこちら。 github.com
APIGWとLambdaの設定
- 設定したこと
- リソースポリシー(IP制限)
- リソースポリシーはこちらを参照: ResourcePolicyStatement - AWS Serverless Application Model
- REST API
/api/*
のパスを用意- ステージ名を
v1
- リソースポリシー(IP制限)
template.yaml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > description Globals: Function: Timeout: 30 Resources: MyAPI: Type: AWS::Serverless::Api Properties: Name: sam-echo-test StageName: v1 EndpointConfiguration: REGIONAL SAMEchoFunction: Type: AWS::Serverless::Function Properties: CodeUri: echo Handler: echo Runtime: go1.x Architectures: - x86_64 Events: GetApi: Type: Api Properties: Path: /api/{proxy+} Method: get RestApiId: !Ref MyAPI Auth: ResourcePolicy: CustomStatements: [{ "Effect": "Allow", "Principal": "*", "Action": "execute-api:Invoke", "Resource": "arn:aws:execute-api:ap-northeast-1:*:*/*", "Condition": { "IpAddress": { "aws:SourceIp": "X.X.X.X/32" } } }] #Environment: # Variables: # PARAM1: VALUE
デプロイ
AWSにデプロイする
初回実行に初期設定が必要なため --guided
オプションを付与して、2回目以降の設定反映には不要です。
❯ aws-vault exec my-dev -- sam deploy --guided Configuring SAM deploy ====================== Looking for config file [samconfig.toml] : Not found Setting default arguments for 'sam deploy' ========================================= Stack Name [sam-app]: sam-echo-test AWS Region [ap-northeast-1]: #Shows you resources changes to be deployed and require a 'Y' to initiate deploy Confirm changes before deploy [y/N]: y #SAM needs permission to be able to create roles to connect to the resources in your template Allow SAM CLI IAM role creation [Y/n]: y #Preserves the state of previously provisioned resources when an operation fails Disable rollback [y/N]: y HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y Save arguments to configuration file [Y/n]: y SAM configuration file [samconfig.toml]: SAM configuration environment [default]: dev Looking for resources needed for deployment: Creating the required resources... Successfully created! (処理の進捗が表示されるので省略)
生成されたsamconfig.tomlの中身は以下のとおりになりました。 スタック名やawsのリソース情報などが記載されています。
- samconfig.toml
version = 0.1 [dev] [dev.deploy] [dev.deploy.parameters] stack_name = "sam-echo-test" s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxx" s3_prefix = "sam-echo-test" region = "ap-northeast-1" confirm_changeset = true capabilities = "CAPABILITY_IAM" disable_rollback = true image_repositories = []
作成したstackを削除する場合は sam delete
コマンドを実行する
sam delete
を実行するとstack名を問われるので入力する
❯ aws-vault exec my-dev -- sam delete Enter stack name you want to delete: sam-echo-test Are you sure you want to delete the stack sam-echo-test in the region ap-northeast-1 ? [y/N]: y Are you sure you want to delete the folder sam-echo-test in S3 which contains the artifacts? [y/N]: y - Deleting S3 object with key sam-echo-test/xxxxxxxx - Deleting S3 object with key sam-echo-test/xxxxxxxx.template - Deleting Cloudformation stack sam-echo-test Deleted successfully
awsのAPIGWのエンドポイントに対してAPIコールしてOKを確認
❯ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1/api/healthcheck {"status":200,"message":"Success to connect echo"}
ローカルにデプロイする
template.yamlを特に変更せずlocalに展開するコマンドを実行してました。
❯ sam local start-api Mounting SAMEchoFunction at http://127.0.0.1:3000/api/{proxy+} [GET] You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template 2022-11-05 16:44:31 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
動作確認でヘルスチェックAPIを実行してOK
❯ curl http://127.0.0.1:3000/api/healthcheck {"status":200,"message":"Success to connect echo"}
さいごに
今回、SAMを使ってローカルとAWSにサーバレス環境をデプロイしました。 SAMの機能をまだまだ使いこなせていきたいので他にも試していきたいと思います。
APIGateway + Lambda + Go(Echo)で開発するためのローカル環境をSAM(Serverless Application Model)で再現する
はじめに
前回の記事では、サーバレス環境をローカルに用意するためにserverless frameworkを利用しました。そこで今回はSAMを利用して同じ環境を構築してみます。
基本的にServerlessFrameworkを利用してきたのでSAMを使うのは今回が初めてでした。
構成は Client
--> APIGW
--> Lambda
で進めます。
ローカル環境構築
brew tap aws/tap brew install aws-sam-cli
今回はGoで環境構築するので go1.x
をruntimeに指定してプロジェクトを作成します。
❯ sam init --runtime go1.x --name serverless Which template source would you like to use? 1 - AWS Quick Start Templates 2 - Custom Template Location Choice: 1 Choose an AWS Quick Start application template 1 - Hello World Example 2 - Infrastructure event management 3 - Multi-step workflow Template: 1 Based on your selections, the only Package type available is Zip. We will proceed to selecting the Package type as Zip. Based on your selections, the only dependency manager available is mod. We will proceed copying the template using mod. Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]: n Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)
sam-initすると自動でディレクトリとファイルが作成されます。今回は hello-world
を削除して echo
ディレクトリを配置しました。
こちらは前回の記事と同じもので、echoフレームワークでヘルスチェックAPIを実装しています。
ソースコードはこちらです。
ディレクトリ構成
serverless ├── README.md ├── echo │ ├── Makefile │ ├── api │ │ └── healthcheck │ │ └── healthcheck.go │ ├── conf │ │ └── config.go │ ├── echo │ ├── go.mod │ ├── go.sum │ ├── handler │ │ └── auth │ │ └── auth.go │ └── main.go ├── events │ └── event.json └── template.yaml
echoフレームワークの部分は前回の記事になどに記載していますので今回は省略します。 goをコンパイルするときに利用するMakefileと samを実行する template.yaml のコードを記載します。
- echo/Makefile
PHONY: deps clean build deps: go get -u ./... clean: rm -rf echo # 変更 build: GOOS=linux GOARCH=amd64 go build -o echo ./main.go
- template.yaml (
# 修正
の部分を自動生成されたファイルを修正しました)
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > description # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 30 # 修正 Resources: HelloWorldFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: echo # 修正 Handler: echo # 修正 Runtime: go1.x Architectures: - x86_64 Events: CatchAll: Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /api/{proxy+} # 修正 Method: GET Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object Variables: PARAM1: VALUE Outputs: # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function # Find out more about other implicit resources you can reference within SAM # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldAPI: Description: "API Gateway endpoint URL for Prod environment for First Function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" HelloWorldFunction: Description: "First Lambda Function ARN" Value: !GetAtt HelloWorldFunction.Arn HelloWorldFunctionIamRole: Description: "Implicit IAM Role created for Hello World function" Value: !GetAtt HelloWorldFunctionRole.Arn
このtemplate.yamlですが、AWSに展開することも考えて以下のように書き換えました。どちらもローカルでは同じ動きをします。
- template.yaml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > description Globals: Function: Timeout: 30 Resources: MyAPI: Type: AWS::Serverless::Api Properties: Name: sam-echo-test StageName: v1 EndpointConfiguration: REGIONAL SAMEchoFunction: Type: AWS::Serverless::Function Properties: CodeUri: echo Handler: echo Runtime: go1.x Architectures: - x86_64 Events: GetApi: Type: Api Properties: Path: /api/{proxy+} Method: get RestApiId: !Ref MyAPI Auth: ResourcePolicy: CustomStatements: [{ "Effect": "Allow", "Principal": "*", "Action": "execute-api:Invoke", "Resource": "arn:aws:execute-api:ap-northeast-1:*:*/*", "Condition": { "IpAddress": { "aws:SourceIp": "X.X.X.X/32" } } }] #Environment: # Variables: # PARAM1: VALUE
デプロイと動作確認
❯ make build GOOS=linux GOARCH=amd64 go build -o echo ./main.go
- ローカルでsam実行
❯ sam local start-api Mounting HelloWorldFunction at http://127.0.0.1:3000/api/{proxy+} [GET] You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template 2022-11-03 16:35:45 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit) Invoking echo (go1.x) Skip pulling image and use local one: public.ecr.aws/sam/emulation-go1.x:rapid-1.61.0-x86_64. Mounting /Users/hidetoshi/github-repos/github.com/yhidetoshi/serverless-sam-go-local/serverless/echo as /var/task:ro,delegated inside runtime container START RequestId: 22427e50-7eb8-425a-a06e-2739449f6d7a Version: $LATEST 2022/11/03 07:38:23 echo cold start END RequestId: 22427e50-7eb8-425a-a06e-2739449f6d7a REPORT RequestId: 22427e50-7eb8-425a-a06e-2739449f6d7a Init Duration: 113.63 ms Duration: 735.62 ms Billed Duration: 736 ms Memory Size: 128 MB Max Memory Used: 128 MB 2022-11-03 16:38:23 127.0.0.1 - - [03/Nov/2022 16:38:23] "GET /api/healthcheck HTTP/1.1" 200 -
- clientからヘルスチェクAPIを実行
❯ curl http://127.0.0.1:3000/api/healthcheck {"status":200,"message":"Success to connect echo"}
リクエスト実行中には以下のコンテナが起動して lambdaをエミュレートして処理終了したらコンテナは削除されました。
❯ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7eb1599fd838 public.ecr.aws/sam/emulation-go1.x:rapid-1.61.0-x86_64 "/var/rapid/aws-lamb…" 1 second ago Up Less than a second 127.0.0.1:5959->8080/tcp focused_wilson
(追記) ローカルではなく、AWSにデプロイする方法については、↓の記事にまとめました。
さいごに
前回の記事と同様にサーバーレス環境(APIGW + Lambda + Go(echo))のローカル環境を構築することができました。 この程度のローカル環境を構築するだけだと SAMとServerlessFrameworkはほとんど負担感は変わらない感じでした。 ServerlessFrameworkとSAMで機能や特徴が異なると思うので構築する環境で使い分けていければと思います。
APIGateway + Lambda + Go(Echo)で開発するためのローカル環境をServerlessFrameworkで再現する
はじめに
以前の記事で APIGW + Lambdaで GoのフレームワークであるEchoを動かす検証を実施しました。 実際にデプロイすることができたので今回は開発を行うためにローカル環境をServerlessFrameworkで構築しました。
構成
Client --> APIGW --> Lambda(Goコンテナ)
をローカル環境に構築します。ServerlessFrameworkで実行すれば、この構成を簡単に用意できます。
ローカル環境
serverless-offline
をインストールする
npm install --save-dev serverless-offline
- プロジェクトを作成する
sls create -t <テンプレート> -n <プロジェクト名>
sls create -t aws-go -n serverless-echo-lambda
*) テンプレートについては以下が対応していました。色々ありますね。
Supported templates are: "aws-clojure-gradle", "aws-clojurescript-gradle", "aws-nodejs", "aws-nodejs-docker", "aws-nodejs-typescript", "aws-alexa-typescript", "aws-nodejs-ecma-script", "aws-python", "aws-python3", "aws-python-docker", "aws-groovy-gradle", "aws-java-maven", "aws-java-gradle", "aws-kotlin-jvm-maven", "aws-kotlin-jvm-gradle", "aws-kotlin-jvm-gradle-kts", "aws-kotlin-nodejs-gradle", "aws-scala-sbt", "aws-csharp", "aws-fsharp", "aws-go", "aws-go-dep", "aws-go-mod", "aws-ruby", "aws-provided", "tencent-go", "tencent-nodejs", "tencent-python", "tencent-php", "azure-csharp", "azure-nodejs", "azure-nodejs-typescript", "azure-python", "cloudflare-workers", "cloudflare-workers-enterprise", "cloudflare-workers-rust", "fn-nodejs", "fn-go", "google-nodejs", "google-nodejs-typescript", "google-python", "google-go", "kubeless-python", "kubeless-nodejs", "knative-docker", "openwhisk-java-maven", "openwhisk-nodejs", "openwhisk-php", "openwhisk-python", "openwhisk-ruby", "openwhisk-swift", "spotinst-nodejs", "spotinst-python", "spotinst-ruby", "spotinst-java8", "twilio-nodejs", "aliyun-nodejs", "plugin", "hello-world".
プロジェクト作成コマンドを実行すると自動で以下が作成されました。
❯ tree . . ├── Makefile ├── hello │ └── main.go ├── serverless.yml └── world └── main.go
今回は、前回の記事で作成したコードを利用するので、サンプルで自動作成された hello/
と world
のディレクトリを削除しました。
今回用意したディレクトリ構成は以下になります。Echoで ヘルスチェックのAPIを実装しており、ソースコードはこちらのGitHubにあります。
- ディレクトリ構成
❯ tree . . ├── Makefile ├── README.md ├── api │ └── healthcheck │ └── healthcheck.go ├── bin │ └── main ├── conf │ └── config.go ├── go.mod ├── go.sum ├── handler │ └── auth │ └── auth.go ├── main.go └── serverless.yml
- serverless.ymlも自動で作成されますが、以下のとおりに変更しました。
- "追記" の部分を追加しています。自動作成されたコメント部分は削除しています。
plugins
でserverless-offline
を指定しますcustom
でuseDocker
で trueを指定しますtimeout
はデフォルト値では timeoutになってしまったので定義しました- 主な原因は、Lambda(go) のコンテナを起動するのに時間がかかるためでした
- 利用されるコンテナイメージ: https://hub.docker.com/r/lambci/lambda
service: serverless-echo-lambda frameworkVersion: '3' # 追記 custom: serverless-offline: useDocker: true provider: name: aws runtime: go1.x stage: v1 #追記 timeout: 30 #追記 package: patterns: - '!./**' - ./bin/** functions: main: handler: bin/main events: - http: # RESTAPI path: /api/{proxy+} method: get # 追記 plugins: - serverless-offline
デプロイと動作確認
.PHONY: build clean deploy deploy-local build: env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/main ./main.go clean: rm -rf ./bin deploy-local: build sls offline deploy: clean build sls deploy --verbose
- ローカル環境を起動します。
sls offline
- goのコードをコンパイルしてデプロイする場合は
make deploy-local
でもOK
❯ sls offline Starting Offline at stage v1 (us-east-1) Offline [http for lambda] listening on http://localhost:3002 Function names exposed for local invocation by aws-sdk: * main: serverless-echo-lambda-v1-main ┌────────────────────────────────────────────────────────────────────────┐ │ │ │ GET | http://localhost:3000/v1/api/{proxy*} │ │ POST | http://localhost:3000/2015-03-31/functions/main/invocations │ │ │ └────────────────────────────────────────────────────────────────────────┘ Server ready: http://localhost:3000 🚀
- ヘルスチェックAPIをコールする
❯ curl http://localhost:3000/v1/api/healthcheck {"status":200,"message":"Success to connect echo"}
リクエストを投げると Dockerイメージを起動してからリクエストを受け付けてくれました。リクエスト終了後に確認したら割とすぐにコンテナは停止していました。
❯ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ❯ curl http://localhost:3000/v1/api/healthcheck {"status":200,"message":"Success to connect echo"}% ❯ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8179a26361e2 lambci/lambda:go1.x "/var/runtime/aws-la…" 4 seconds ago Up 3 seconds 0.0.0.0:53718->9001/tcp great_grothendieck
- APIGWの機能はServerlessFrameworkで再現してくれています。
- serverlessFrameworkが 3000番Listenして apigw --> lambdaコンテナに接続されています
❯ sudo lsof -i -P | grep "LISTEN" | grep 3000 node 44358 hidetoshi 32u IPv6 0x8764e5xxxxxxxxxx 0t0 TCP localhost:3000 (LISTEN)
さいごに
今回は、ServerlessFrameworkで Client --> APIGW --> Lambda(Echo)
の環境をローカルに再現しました。
ローカルでAWS環境を再現して開発できるのはとても便利ですね。
APIGateway(HTTP API) + Lambda環境に echo(Goのweb Framework) を ServerlessFramework でデプロイする
はじめに
前回の記事で APIGatewayの REST API
で EchoをAWS Lambdaで簡単なAPIをデプロイしました。
今回は、APIGatewayの HTTP API
に変更します。差分について記述していきます。
変更部分
main.goのみを記載します。他のコードは前回の記事に記載しています。
- main.go
package main import ( "context" "log" "yhidetoshi/go-echo-lambda/api/healthcheck" "yhidetoshi/go-echo-lambda/handler/auth" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" echoadapter "github.com/awslabs/aws-lambda-go-api-proxy/echo" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) //var echoLambda *echoadapter.EchoLambda // APIGW(REST) var echoLambda *echoadapter.EchoLambdaV2 // APIGW(HTTP) <--- V2 を利用する func init() { log.Printf("echo cold start") e := echo.New() e.Use(middleware.Recover()) e.Use(auth.BasicAuth()) e.GET("/api/healthcheck", healthcheck.Healthcheck) //echoLambda = echoadapter.New(e) // APIGW(REST) echoLambda = echoadapter.NewV2(e) // APIGW(HTTP) <--- V2を利用する } func main() { lambda.Start(Handler) } //func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { // APIGW(REST) func Handler(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { // APIGW(HTTP) // V2HTTPRequest と V2HTTPResponseを利用する return echoLambda.ProxyWithContext(ctx, req) }
変更した部分の V2をそれぞれのライブラリで見てみると、APIGatewayの HTTP APIに対応している事がわかりました。
■ echoadapter "github.com/awslabs/aws-lambda-go-api-proxy/echo"
から抜粋
// EchoLambdaV2 makes it easy to send API Gateway proxy V2 events to a echo.Echo. // The library transforms the proxy event into an HTTP request and then // creates a proxy response object from the http.ResponseWriter type EchoLambdaV2 struct { core.RequestAccessorV2 Echo *echo.Echo } // NewV2 creates a new instance of the EchoLambda object. // Receives an initialized *echo.Echo object - normally created with echo.New(). // It returns the initialized instance of the EchoLambdaV2 object. func NewV2(e *echo.Echo) *EchoLambdaV2 { return &EchoLambdaV2{Echo: e} } // Proxy receives an API Gateway proxy V2 event, transforms it into an http.Request // object, and sends it to the echo.Echo for routing. // It returns a proxy response object generated from the http.ResponseWriter. func (e *EchoLambdaV2) Proxy(req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { echoRequest, err := e.ProxyEventToHTTPRequest(req) return e.proxyInternal(echoRequest, err) } // ProxyWithContext receives context and an API Gateway proxy V2 event, // transforms them into an http.Request object, and sends it to the echo.Echo for routing. // It returns a proxy response object generated from the http.ResponseWriter. func (e *EchoLambdaV2) ProxyWithContext(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { echoRequest, err := e.EventToRequestWithContext(ctx, req) return e.proxyInternal(echoRequest, err) }
■ github.com/aws/aws-lambda-go/events
から抜粋
aws-lambda-go/apigw.go at 67965281f13ade83c809a78acdac095e84d701fd · aws/aws-lambda-go · GitHub
// APIGatewayV2HTTPRequest contains data coming from the new HTTP API Gateway type APIGatewayV2HTTPRequest struct { Version string `json:"version"` RouteKey string `json:"routeKey"` RawPath string `json:"rawPath"` RawQueryString string `json:"rawQueryString"` Cookies []string `json:"cookies,omitempty"` Headers map[string]string `json:"headers"` QueryStringParameters map[string]string `json:"queryStringParameters,omitempty"` PathParameters map[string]string `json:"pathParameters,omitempty"` RequestContext APIGatewayV2HTTPRequestContext `json:"requestContext"` StageVariables map[string]string `json:"stageVariables,omitempty"` Body string `json:"body,omitempty"` IsBase64Encoded bool `json:"isBase64Encoded"` }
ServerlessFramework
- serverless.yamlの変更部分
- 全体は前回の記事に記載しています
functions: GoEchoLambda: handler: main role: GoEchoLambda timeout: 10 description: go echo lambda test memorySize: 128 events: - httpApi: # httpApi に変更する #- http: path: /api/{proxy+} method: any #integration: lambda
動作確認
❯ curl -u test:pass https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/healthcheck {"status":200,"message":"Success to connect echo"
さいごに
今回は、前回の記事においてAPIGatewayの REST API
ではなく HTTP API
を使うように変更しました。
HTTP APIでも利用できる事を確認できました!