My Note

自己理解のためのブログ

Lambdaレイヤーを利用してラインタイムPython上でgoバイナリを実行 & レイヤーをバージョン管理する

今回はLambdaレイヤーについて簡単な検証とレイヤーのバージョン管理について検証しました。

Lambdaレイヤーとは

docs.aws.amazon.com

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環境にデプロイをしました。ローカルも同じ設定で利用可能です。

yhidetoshi.hatenablog.com

本記事ではAWSにデプロイする部分にだけ記載します。詳細は↑の記事に記載済みです。

また、LambdaでGoフレームワークのEchoを動かす事についてまとめた記事は↓。

yhidetoshi.hatenablog.com

コード

ディレクトリ構成

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の設定

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で進めます。

yhidetoshi.hatenablog.com

ローカル環境構築

aws-cliがインストールされている前提で進めます。

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を実装しています。 ソースコードはこちらです。

github.com

ディレクトリ構成

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 のコードを記載します。

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に展開することも考えて以下のように書き換えました。どちらもローカルでは同じ動きをします。

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にデプロイする方法については、↓の記事にまとめました。

yhidetoshi.hatenablog.com

さいごに

前回の記事と同様にサーバーレス環境(APIGW + Lambda + Go(echo))のローカル環境を構築することができました。 この程度のローカル環境を構築するだけだと SAMとServerlessFrameworkはほとんど負担感は変わらない感じでした。 ServerlessFrameworkとSAMで機能や特徴が異なると思うので構築する環境で使い分けていければと思います。

APIGateway + Lambda + Go(Echo)で開発するためのローカル環境をServerlessFrameworkで再現する

はじめに

以前の記事で APIGW + Lambdaで GoのフレームワークであるEchoを動かす検証を実施しました。 実際にデプロイすることができたので今回は開発を行うためにローカル環境をServerlessFrameworkで構築しました。

yhidetoshi.hatenablog.com

構成

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にあります。

github.com

❯ 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も自動で作成されますが、以下のとおりに変更しました。
    • "追記" の部分を追加しています。自動作成されたコメント部分は削除しています。
    • pluginsserverless-offline を指定します
    • customuseDocker で trueを指定します
      • 設定しないと以下のエラーで apiを実行できませんでした
        • GET /v1/api/healthcheck (λ: main) ✖ Unhandled exception in handler 'main'. ✖ ENOENT: no such file or directory, open '.go'

    • timeout はデフォルト値では timeoutになってしまったので定義しました
    • 主な原因は、Lambda(go) のコンテナを起動するのに時間がかかるためでした
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 に変更します。差分について記述していきます。

yhidetoshi.hatenablog.com

変更部分

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" から抜粋

aws-lambda-go-api-proxy/adapterv2.go at fb2efb1b553c23556ff79b843e2c11fa989db78f · awslabs/aws-lambda-go-api-proxy · GitHub

// 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

www.serverless.com

  • 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でも利用できる事を確認できました!

ソースコード

github.com

APIGateway(REST API) + Lambda環境に echo(Goのweb Framework) を ServerlessFramework でデプロイする

はじめに

今回は goのwebフレームワークであるEchoをLambdaにデプロイして簡単なAPIを実行する環境を構築します。 環境構築はServerlessFrameworkで実施しました。

echoをLambda環境で動かすためには以下のライブラリが必要になります。 このライブラリを使えばAPIGatewayを組み合わせるとGinやEchoやその他のGoフレームワークの実行ができます。

github.com github.com

コード

今回作成したコードはこのリポジトリです。

github.com

今回は試しに、Basic認証の機能とヘルスチェックのAPIを用意しました。

echoのコード

❯ tree go-echo-lambda
go-echo-lambda
├── api
│   └── healthcheck
│       └── healthcheck.go
├── conf
│   └── config.go
├── go.mod
├── go.sum
├── handler
│   └── auth
│       └── auth.go
├── main.go
└── serverless.yml
  • 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

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)
}

func main() {
    lambda.Start(Handler)
}

func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    return echoLambda.ProxyWithContext(ctx, req)
}
  • api/healthcheck.go
package healthcheck

import (
    "encoding/json"
    "log"
    "net/http"

    "github.com/labstack/echo/v4"
)

type HealthcheckMessage struct {
    Status  int    `json:"status"`
    Message string `json:"message"`
}

func Healthcheck(c echo.Context) error {
    msg := &HealthcheckMessage{
        Status:  http.StatusOK,
        Message: "Success to connect echo",
    }
    res, err := json.Marshal(msg)
    if err != nil {
        log.Println(err)
    }
    return c.String(http.StatusOK, string(res))
}
  • handler/auth.go
package auth

import (
    "yhidetoshi/go-echo-lambda/conf"

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

var (
    id = conf.BASIC_AUTH_ID
    pw = conf.BASIC_AUTH_PASS
)

func BasicAuth() echo.MiddlewareFunc {
    return middleware.BasicAuth(func(username string, password string, context echo.Context) (bool, error) {
        if username == id && password == pw {
            return true, nil
        }
        return false, nil
    })
}
  • conf/config.go
package conf

const (
    BASIC_AUTH_ID   = "test"
    BASIC_AUTH_PASS = "pass"
)

ライブラリをざっくり確認

  • それぞれのライブラリの役割

    • github.com/aws/aws-lambda-go/events(events.APIGatewayProxyRequest)
      • APIGatewayのリクエストイベントを受け取る
    • github.com/awslabs/aws-lambda-go-api-proxy/echo
      • コンテキストとAPIGatewayのイベントをhttp.Requestオブジェクトへ変換してechoのルーティングに渡す
  • github.com/aws/aws-lambda-go/events にて Handler(ctx context.Context, req events.APIGatewayProxyRequest) でcontextとAPIGatewayのリクエストをlambdaで受けます。

  • contextの役割

処理の締め切りを伝達
キャンセル信号の伝播
リクエストスコープ値の伝達

github.com

→ lambdaでAPIGatewayのイベントを受け取ってハンドリングするためのライブラリ

  • main.go
func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    return echoLambda.ProxyWithContext(ctx, req)
}

echoLambda.ProxyWithContext (echo/adapter.go)に渡せば以下の処理をしてくれる。

ProxyWithContextはcontextとAPI Gatewayのイベントをhttp.Request オブジェクトに変換し、echo.Echo に送信してルーティングを行います

// ProxyWithContext receives context and an API Gateway proxy 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 *EchoLambda) ProxyWithContext(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    echoRequest, err := e.EventToRequestWithContext(ctx, req)
    return e.proxyInternal(echoRequest, err)
}

これで、この部分だけ用意しておけばlambdaを使ったサーバレスを意識せずに開発できそうですね!

環境構築 ( ServerlessFramework )

ServerlessFrameworkで Lambda関数とAPI Gatewayを作成します。

GoをコンパイルしてLambda関数にデプロイするには以下のコマンドを実行します。なお、 AWSアカウントIDと許可するFromIPを --param に付与します。 また、リソースポリシーで接続元のIP制限をかけています。

  • デプロイコマンド
$ GOARCH=amd64 GOOS=linux go build "-ldflags=-s -w" ./main.go
$ sls deploy --param="account_id=${AWS_ACCOUNT_ID}" --param="allow_ip=X.X.X.X/32"
  • serverless.yml
service: go-echo-lambda
frameworkVersion: "3"

provider:
  name: aws
  stage: dev
  runtime: go1.x
  region: ap-northeast-1
  apiName: ${self:service}-${self:provider.stage}
  endpointType: REGIONAL
    
  apiGateway:
    resourcePolicy:
      - Effect: Allow
        Principal: '*'
        Action: execute-api:Invoke
        Resource:
          - arn:aws:execute-api:ap-northeast-1:${param:account_id}:*/*
        Condition:
          IpAddress:
            aws:SourceIp:
              - ${param:allow_ip}

functions:
  GoEchoLambda:
    handler: main
    role: GoEchoLambda
    timeout: 10
    description: go echo lambda test 
    memorySize: 128
    events:
      - http:
          path: /api/{proxy+}
          method: any
          #integration: lambda

resources:
  Resources:
    GoEchoLambda:
      Type: AWS::IAM::Role
      Properties:
        RoleName: GoEchoLambda
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action: sts:AssumeRole
        Policies:
          - PolicyName: GoEchoLambda
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - "logs:CreateLogGroup"
                    - "logs:CreateLogStream"
                    - "logs:PutLogEvents"
                  Resource: "*"

作成されるリソース

Lambda

APIGateway

  • リソース

  • ステージ

  • リソースポリシー

APIGWのリソース

  • 用意したAPIリソースは Catch-allパス変数"を利用しました。
    • /api/{proxy+} : /api/ 配下をすべてキャッチしてくれます
    events:
      - http:
          path: /api/{proxy+}

動作確認

$ curl -u test:pass https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/api/healthcheck
{"status":200,"message":"Success to connect echo"}

Basic認証が通りヘルスチェック成功のメッセージを受け取り動作確認ができました。

備考

  • ServerlessFrameworkで API Gatewayにlambda integrationするために以下のように設定をしましたがダメでした。lambda関数のeventとして integration: lambda せずともlambda integrationされました。
    events:
      - http:
          path: /api/{proxy+}
          method: any
          #integration: lambda <-- ここで integrationを指定すると以下のエラーになりました
{"errorMessage":"json: cannot unmarshal object into Go struct field APIGatewayProxyRequest.body of type string","errorType":"UnmarshalTypeError"}

さいごに

今回、echoをlambdaで動かすために以下のライブラリを利用しました。 https://github.com/awslabs/aws-lambda-go-api-proxy

個人開発でEchoを利用するときはGoogle App Engineにデプロイしていましたが、今回のこのライブラリを知ったのでAWS(APIGW + Lambda)も利用でき選択肢が増えてよかったです!個人開発ではなるべくクラウド利用料を減らしたいので、Fargateのように常時起動させて利用外でも課金されるよりもリクエストをベースに実行できる環境はとても良いですね!

個人開発中 ( 金融資産管理サイト) のここまでのまとめ ( Go Echo on GAE + PlanetScale(DB) + Upstash(Redis) )

はじめに

個人開発で取り組んでいる内容について一区切りついたので一度ここまでをまとめてみようと思います。 開発内容は自分の投資情報を管理して可視化するためのサイト構築。 以前に作成した下記の記事で データ保存に GAS + SpreadSheet と可視化に Google Data Portalを利用していました。 その SpreadSheetのデータ保存を MySQL + Redisに、可視化に Go + Templateレンダリング + go-echarts に置き換えるイメージで実装しています。

yhidetoshi.hatenablog.com

Google CloudやSaaSを利用していますが、ランニングコストをかけずに運用するために基本的に無料枠、無料プランを活用しています。

システム

システム構成図

■ 技術スタック

  • 言語
    • Go1.6 html css js
  • ORM
    • gorm
  • サーバーサイド
    • Echo
  • 実行環境
    • 開発 (Docker)
      • Go + MySQLコンテナ + Redisコンテナ
    • リリース
  • データベース ( SaaS )
  • CI / CD
    • Github Actions
      • go test & lint
      • app engine deploy
  • バッチ実行クライアント
  • モニタリング
    • Cloud logging + Google Monitoring
      • ログ監視 + メール通知
    • Mackerel
      • google cloudインテグレーション
        • GAEのリソース監視

システムの概要

ディレクトリ構成

構成

├── README.md
├── TEST_Client
│   └── cors
│       ├── index.php
│       └── script.js
├── api
│   ├── crypto
│   │   ├── coincheck
│   │   │   └── coincheck.go
│   │   ├── crypto_bat
│   │   │   └── bat.go
│   │   └── gmo
│   │       └── gmo.go
│   ├── healthcheck
│   │   ├── healthcheck.go
│   │   └── healthcheck_test.go
│   ├── metal
│   │   └── metal.go
│   └── stock
│       ├── kakakucom
│       │   └── kakakucom.go
│       └── stock_bat
│           └── bat.go
├── app.yaml
├── conf
│   └── config.go
├── docker
│   ├── mysql
│   │   ├── Dockerfile
│   │   ├── data
│   └── redis
│       └── data
├── docker-compose.yml
├── go.mod
├── go.sum
├── handler
│   ├── auth
│   │   └── auth.go
│   ├── crypto
│   │   └── crypto.go
│   ├── graph
│   │   ├── crypto.go
│   │   └── stock.go
│   ├── hash
│   │   └── hash.go
│   ├── stock
│   │   └── stock.go
│   └── top.go
├── main.go
├── model
│   ├── base.go
│   ├── crypto
│   │   ├── daily_rate
│   │   │   └── daily_rate.go
│   │   ├── exchange
│   │   │   └── exchange.go
│   │   ├── exchange_result
│   │   │   └── result.go
│   │   ├── token
│   │   │   └── token.go
│   │   ├── token_result
│   │   │   └── result.go
│   │   └── trade_history
│   │       └── trade_history.go
│   ├── date
│   │   └── date.go
│   ├── stock
│   │   ├── daily_price
│   │   │   └── daily_price.go
│   │   ├── hold
│   │   │   └── hold.go
│   │   ├── investment_trust
│   │   │   └── investment_trust.go
│   │   ├── investment_trust_result
│   │   │   └── investment_trust_result.go
│   │   ├── isin_code
│   │   │   └── isin_code.go
│   │   └── securities_company
│   │       └── securities_company.go
│   └── user
│       └── user.go
├── public
│   └── assets
│       └── css
│           ├── crypto_register.css
│           ├── crypto_summary.css
│           ├── crypto_trade_history.css
│           ├── login.css
│           ├── mypage.css
│           ├── signup.css
│           └── stock_register.css
├── script
│   └── setup_local_env.sh
├── secret.yaml
├── staticcheck.conf
└── view
    ├── crypto_register.html
    ├── crypto_summary.html
    ├── crypto_trade_history.html
    ├── login.html
    ├── mypage.html
    ├── password_update.html
    ├── signup.html
    ├── stock_hold.html
    ├── stock_register.html
    ├── stock_summary.html
    └── top.html

機能

  • Sign Up
  • Login / Logout
  • Password更新
  • セッション管理
  • バッチ処理
    • 外部APIコールして価格取得
    • 評価額、損益額の更新
  • 取引データ入力ページ
  • 取引履歴確認ページ
  • 取引状況のサマリーページ(評価額と損益額)
    • トークンごと
      • 最新結果(テーブル表示)
      • トークン指定してグラフ表示
    • 取引所ごと
      • 最新結果(テーブル表示)
      • 取引所指定してグラフ表示

各ページ画面のスクリーンショット

■ SignUp ( localhost:8080/signup )

■ Login ( localhost:8080/login )

■ MyPage(メニュー) ( localhost:8080/mypage )

■ Cryptoの取引データ入力 ( localhost:8080/crypto/trade )

■ Cryptoの取引履歴 ( localhost:8080/crypto/trade_history?page=1 )

■ Cryptoのサマリーページ ( localhost:8080/crypto/summary )

■ Crypto 表示するグラフの選択して "表示" ボタンをクリック(トークン別か取引所別か)

■ Crypto 全期間のトークン評価額のグラフ ( localhost:8080/crypto/summary/token/btc )

■ Crypto 全期間のトークン評価額のグラフ ( localhost:8080/crypto/summary/exchange/gmo )

(他にも違う期間でのグラフを用意してます)

■ パスワード更新 ( localhost:8080/mypage/password_update )

投資信託の情報更新 ( localhost:8080/stock/trade )

投資信託コモディティ保有情報 ( localhost:8080/stock/hold )

投資信託コモディティの評価額、損益額情報 ( localhost:8080/stock/summary )

投資信託を選択したときのチャート(評価額と損益額) ( localhost:8080/stock/summary/investment_trust/${NAME_OF_INVESTMENTTRUST} )

テーブル構成

MySQL WorkBench のDatabase → Reverse Engineer から図を作成。

以下のツールを利用してGithubで資料化するのもいいと思います。(生成された画像が svgはてブロが非対応形式だったので画像は WorkBenchで作成しました。) github.com

(テーブル設計も勉強しつつ取り組んだのでもっといい方法があると思います。ご参考までに)

データベース

PlanetScale( DB )

無料でデータベースを利用する手段を探した結果、"PlanetScale" というサービスを見つけました。 見つからない場合は Google Compute Engineの無料インスタンスMySQLをローカルにインストールするなど考えていましたが、アクセスや運用が大変そうだったのでできれば避けたかったのでとても助かりました。(後ほど記載する Upstash (Redis) も同様です)

データベースはAWSのサービスで提供されており利用時にリージョンを選択することができました。 操作に関しては管理コンソール、もしくは pscaleCLIも用意されています。管理コンソールからSQLも発行可能です。

planetscale.com

  • Price

    • PlanetScale Pricing
    • 2022/05 時点ではストレージが10GBまで無料でしたが、記事作成した時点では5GBまでのようです。(要確認)
    • 今回の個人開発で利用するに無料プランで十分利用できています。
  • pscaleコマンドの例

pscale auth login # 認証
pscale database dump <db_name> <brach_name> # dump取得

■ 接続情報について

データベースを作成すると、接続情報が発行されます。 EchoでのDBの情報は以下のように設定しました。

  • secret.yaml(一部抜粋)
  DB_USER: "xxx"
  DB_NAME: "xxx"
  DB_PORT: "3306"
  DB_HOST: "xxx.ap-northeast-2.psdb.cloud"
  DB_PASS: "pscale_pw_xxx"
  DB_OPTION: "?tls=true&parseTime=true"
  • conf/config.go(一部抜粋)
var (
    DB_USER     = os.Getenv("DB_USER")
    DB_NAME     = os.Getenv("DB_NAME")
    DB_PORT     = os.Getenv("DB_PORT")
    DB_HOST     = os.Getenv("DB_HOST")
    DB_PASS     = os.Getenv("DB_PASS")
    DB_OPTION   = os.Getenv("DB_OPTION")
    DB_ENDPOINT = DB_USER + ":" + DB_PASS + "@tcp(" + DB_HOST + ":" + DB_PORT + ")/" + DB_NAME + DB_OPTION
)
  • model/base.go(一部抜粋)
var db *gorm.DB

func init() {
    var err error
    dsn := conf.DB_ENDPOINT
    db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})

Upstatsh (Redis)

こちらもPlanetScaleと同様でSaaSとして無料プランが用意されており利用しました。詳細は下記の公式サイト。 また、環境はAWS上で提供されています。

upstash.com

Redisは、セッション管理とバッチ処理の結果を保存して利用。 詳しくは以前書いた記事で。

yhidetoshi.hatenablog.com

■ 接続情報について

データベースを作成すると接続情報が発行されます。

  • secret.yaml(一部抜粋)
  REDIS_HOST: "xxx"
  REDIS_PORT: "33046"
  REDIS_PASS: "xxx"
  • conf/conf.go
var (
    REDIS_HOST = os.Getenv("REDIS_HOST")
    REDIS_PORT = os.Getenv("REDIS_PORT")
    REDIS_PASS = os.Getenv("REDIS_PASS")
)
  • redis接続には github.com/go-redis/redis/v8 を利用した場合
   client := redis.NewClient(&redis.Options{
        Addr:     conf.REDIS_HOST + ":" + conf.REDIS_PORT,
        Password: conf.REDIS_PASS,
    })

CI / CD について

CI/CDにはGithub Actionsを利用しています。こちらも無料枠が用意されているので料金コストをかけずに利用しています。 主に、go test & lint、Google App Engineにデプロイ。 ブランチは mainブランチにマージして、GAEにデプロイするときは releaseブランチにマージして自動デプロイする運用にしています。

yhidetoshi.hatenablog.com

yhidetoshi.hatenablog.com

yhidetoshi.hatenablog.com

バッチ処理の実行について

評価額と損益額はバッチ処理で更新しています。1日1回実行しています。 バッチ実行するためのAPIを叩きます。 今回用意したパスは /api/exec_crypto_bat

GASに関しては、以前の記事を参照。

yhidetoshi.hatenablog.com

function execBatGAEWebAppInvestment() {

    const gaeEndpoint = PropertiesService.getScriptProperties().getProperty("GAE_INVESTMENT_ENDPOINT") // GAEのエンドポイント
    const requestBatCryptoPath = "/api/exec_crypto_bat"                              // バッチ実行APIパス
    const basicAuthId = PropertiesService.getScriptProperties().getProperty("ID")       // Basic認証のID(セットプロパティから取得)
    const basicAuthPass = PropertiesService.getScriptProperties().getProperty("PASS")   // Basic認証のPW(セットプロパティから取得)


    // Basic認証
    let urlOptions = {
        method: "POST",
        headers: { "Authorization": "Basic " + Utilities.base64Encode(basicAuthId + ":" + basicAuthPass) }
    }
    execBatCrypto(gaeEndpoint, requestBatCryptoPath, urlOptions)

}


function execBatCrypto(gaeEndpoint, requestPath, urlOptions) {
    let url = gaeEndpoint + requestPath
    UrlFetchApp.fetch(url, urlOptions);
}

ログインパスワードの暗号化について

ログインパスワードの暗号化に bcrypt 利用しています。詳細は下記の記事にまとめています。

yhidetoshi.hatenablog.com

ORマッパーについて

ORマッパーに gorm を利用しています。詳細は下記の記事にまとめています。 yhidetoshi.hatenablog.com yhidetoshi.hatenablog.com

CORSについて

サーバサイドのEchoにCORSを設定しています。詳細は下記の記事にまとめています。

yhidetoshi.hatenablog.com

ヘルスチェックについて

今回実装したヘルスチェックについては書きの記事にまとめています。

yhidetoshi.hatenablog.com

ページの作成について

"Template Rendering" を利用してサーバーサイドからデータを渡して表示させました。詳細は下記の記事にまとめています。

yhidetoshi.hatenablog.com

グラフの描画について

評価額と損益額の推移をグラフ化するために go-echarts を利用しました。詳細は下記の記事にまとめています。

yhidetoshi.hatenablog.com

金額表示のカスタム

3桁区切りにカンマを入れないと表示が見づらいのでスター数も多く下記のライブラリを利用しました。 Goで自前で書くとコード量が多くなるので。

github.com

GitHub - dustin/go-humanize: Go Humans! (formatters for units to human friendly sizes)

モニタリングについて

バッチ実行のログ監視

Google App Engineのログは Cloud loggingに送られます。

今回は特定のログを抽出するために以下のクエリにしました。バッチの結果のステータスコードが 200 以外だった場合にメール通知します。

resource.type="gae_app"
jsonPayload.host="xxx"
jsonPayload.uri="/api/exec_crypto_bat"
jsonPayload.status!=200

以前は Cloud loggingと Cloud Pub/Sub と Cloud Functions も用意する必要があったんですが、今回設定するにあたり調べたら Cloud Monitoringだけで設定が可能になっていました。かなり楽になりました。

今回、設定するにあたり下記の記事の手順を参考にさせていただきました。

Google Cloud ログベースのアラート機能を使って エラーログが出たら Slack に通知する

また、バッチの実行クライアントはGoogle App Scriptなので、クライアントの実行エラーもメールが届くようにしています。

Mackerel (Google Cloud インテグレーション)

Mackerelに Google Cloud インテグレーション機能でモニタリングしています。取得できるメトリクスや設定方法は 公式ドキュメントに記載されています。

Google Cloudインテグレーション - App Engine - Mackerel ヘルプ

(ありがたい事にMackerelアンバサダーの特典で無料で利用させていただいています。)

さいごに

今回の個人開発は趣味から派性してあったらいいなぁという気持ちから作り始めました。普段はインフラエンジニア/SREの担当なので個人的に サーバサイドやフロント側をいじれるのは勉強になり面白いです。 まだまだ追加したい機能があるので適宜リファクタリングしつつ徐々に開発を続けていきたいと思います。 GAEやMySQL、RedisがSaaSでかつ費用をかけずに利用できるのは本当にありがたい...。