はじめに
こちらは AWS for Games Advent Calendar 2023 18 日目の記事となります。
迅速なアイデアの実装と検証は、あらゆるソフトウェア開発において重要です。このブログでは、AWS における最速の開発手法を lambda-container-deploy を利用し解説します。あなたのアイデアを速やかに実現しましょう!
このブログで学べること:
- Go言語を用いた hello world のウェブアプリの作り方
- 10 分以内で aws にホスティングする手法
- IP 制限の実装
- AWS における分散トレーシング、ロギングの実現
- ローカル環境の構築
Go ウェブアプリケーションの準備
最初に、Go言語で hello world のプログラムを書きます。ポート 8000 番で Listen します。
package main
import (
"fmt"
"io"
"net/http"
)
func topHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("hello world")
io.WriteString(w, "hello world")
}
func main() {
http.Handle("/", http.HandlerFunc(topHandler))
http.ListenAndServe(":8000", nil)
}
Dockerfile の作成
マルチステージビルドを利用します。今回は Lambda Container にホスティングするため、aws-lambda-adapter を使って実現しています。
FROM golang:1.21 as builder
ENV GOPROXY=direct
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . ./
RUN go build -v -o myapp
FROM debian:stable-slim
WORKDIR /app
# tell lambda-adapter which port is listening
ENV PORT=8000
# handle lambda runtime api, convert requests to localhost:8000
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.7.1 /lambda-adapter /opt/extensions/lambda-adapter
COPY --from=builder /app/myapp /app/myapp
CMD ["/app/myapp"]
Lambdaにデプロイ
Lamdba に必要なリソースの作成、更新、デプロイは lambda-container-deploy を利用します。lambda.sh deploy
一つのコマンドでビルドからデプロイまでを行います。入力項目としては、AWSのリージョンだけとなります。また、サービス名とリージョンは、.lambda/config.ini
に自動保存されるため、2回目以降は入力なしで実行が可能です。
$ ./lambda.sh deploy
Deploying Lambda function...
Enter service name [lambda-container-blog]:
Enter AWS region: ap-northeast-1
[+] Building 10.7s (18/18) FINISHED docker:orbstack
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 542B 0.0s
=> [internal] load metadata for public.ecr.aws/awsguru/aws-lambda-adapter:0.7.1 2.3s
=> [internal] load metadata for docker.io/library/golang:1.21 2.7s
=> [internal] load metadata for docker.io/library/debian:stable-slim 2.5s
=> [builder 1/6] FROM docker.io/library/golang:1.21@sha256:2ff79bcdaff74368a9fdcb06f6599e54a71caf520fd2357a55feddd504bcaffb 0.0s
=> => resolve docker.io/library/golang:1.21@sha256:2ff79bcdaff74368a9fdcb06f6599e54a71caf520fd2357a55feddd504bcaffb 0.0s
=> FROM public.ecr.aws/awsguru/aws-lambda-adapter:0.7.1@sha256:97c8f81e19e64841df0882d3b3c943db964c554992c1bac26100f1d6c41ea0bb 0.0s
=> [stage-1 1/4] FROM docker.io/library/debian:stable-slim@sha256:375fb84f3c64691a1b9a9ff5ff3905173dcd0c5e11bc2aebd5c3472a139fa2b4 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 17.80kB 0.0s
=> CACHED [builder 2/6] WORKDIR /app 0.0s
=> CACHED [builder 3/6] COPY go.* ./ 0.0s
=> CACHED [builder 4/6] RUN go mod download 0.0s
=> [builder 5/6] COPY . ./ 0.0s
=> [builder 6/6] RUN go build -v -o myapp 7.8s
=> CACHED [stage-1 2/4] WORKDIR /app 0.0s
=> CACHED [stage-1 3/4] COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.7.1 /lambda-adapter /opt/extensions/lambda-adapter 0.0s
=> [stage-1 4/4] COPY --from=builder /app/myapp /app/myapp 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:6c2f79b6321f0cd9b4e7693763d1fb56208c85cc54edf4c2ea1ce9ea25ab4733 0.0s
=> => naming to docker.io/library/lambda-container-blog:1afe4a5 0.0s
Login Succeeded
The push refers to repository [xxx.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container-blog]
9bec1d67c6e9: Pushed
a809e9675065: Pushed
e7f40a6b4e08: Pushed
03459226eed7: Pushed
1afe4a5: digest: sha256:9affcfcb65c0846891960046a8c25aadf5d3ac24a0117eebc39349688a9b3d61 size: 1157
Lambda function lambda-container-blog does not exist. Creating function...
Function URL for lambda-container-blog does not exist. Creating Function URL...
Public Lambda Function URL: https://xxx.lambda-url.ap-northeast-1.on.aws/, you can change auth-type to AWS_IAM
あっという間に、外部からアクセス可能な URL が表示されました。https://xxx.lambda-url.ap-northeast-1.on.aws/
の部分です。
IP 制限機能
ここまでは、外部からだれもアプリケーションをアクセスすることができますが、IP 制限をかけたい場合も多いと思います。Golang で IP 制限機能を入れてみましょうー
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/jpillora/ipfilter"
)
func topHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("hello world")
io.WriteString(w, "hello world")
}
func addWhiteListIPs(f *ipfilter.IPFilter) {
// Get the WHITE_IP_LIST environment variable
whiteIPList := os.Getenv("WHITE_IP_LIST")
// Check if the environment variable is set
if whiteIPList == "" {
fmt.Println("WHITE_IP_LIST environment variable is not set")
return
}
// Split the WHITE_IP_LIST into separate IP addresses/CIDR ranges
ipList := strings.Split(whiteIPList, ",")
// Iterate over each IP/CIDR in the list
for _, ip := range ipList {
fmt.Println("IP/CIDR:", ip)
f.AllowIP(ip)
}
}
func main() {
f := ipfilter.New(ipfilter.Options{
AllowedIPs: []string{"192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12"},
BlockByDefault: true,
TrustProxy: true,
})
addWhiteListIPs(f)
http.Handle("/", f.Wrap(http.HandlerFunc(topHandler)))
http.ListenAndServe(":8000", nil)
}
環境変数 WHITE_IP_LIST
に自分の IP アドレスを入れて、デプロイします。
# .env に自分の IP を設定します。
$ cat << EOF > .env
WHITE_IP_LIST=$(curl --silent https://checkip.amazonaws.com)/32
EOF
$ cat .env
WHITE_IP_LIST=xx.xx.xx.xx/32
# --env-vars-file を利用し、Lambda の環境変数を設定し、デプロイします。
$ ./lambda.sh deploy --env-vars-file .env
これで、IP 制限を実現できました!
分散トレーシング、ロギング
開発はトラブルシューティングが一番時間かかっていること、皆さんはよくご存知かと思います。X-Ray のような分散トレーシングの機能を利用すれば、どの関数がエラー出ていたとか、その時関数が使用した時間、関連するログなどをワン画面で表示することができます。Lambda では、手軽くに利用することができます。ここで Go アプリからの利用方法をご紹介します。
トレーシングに関しては、xray.Handler()
およびxray.NewFixedSegmentNamer()
を入れるだけで完了です。
http.Handle("/", xray.Handler(xray.NewFixedSegmentNamer("myApp"), f.Wrap(http.HandlerFunc(topHandler))))
たとえば、必要なデバッグ情報を入れたりもできます。ここでは、xray.AddAnnotation()
やxray.AddMetadata()
を使って、必要な情報を入れたり、xraylogger
の方は、ログとトレーシングを結びつけることができます。
func topHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := xraylogger.New(ctx)
// Add annotation and metadata
xray.AddAnnotation(ctx, "User", "example-user2")
xray.AddMetadata(ctx, "debug-info", "sample metadata2")
log.Info("hello world")
io.WriteString(w, "hello world")
}
また、ローカルでログを確認するケースもありますので、こちらのコマンドを利用すると便利です。
# 直近1分以内のログを live tail しながら表示します
$ aws logs tail --follow --since 1m /aws/lambda/関数名
ローカルでのテスト
コンテナを利用するメリットの一つは、ローカルだけで確認したい場合でも、同じ Dockerfile を使うことができ、。docker-compose.yaml
のサンプルコードはこちらとなります。
version: '3.8'
services:
my-lambda-app:
build: .
ports:
- "8000:8000"
ローカル環境を起動
$ docker-compose build
$ docker-compose up -d
http://localhost:8000/ にアクセスすれば、hello world が表示されています。
まとめ
AWS Lambda コンテナを使用したアプローチは、開発者が迅速にアイデアを実装し、手軽に検証するための強力なソリューションです。より簡単に利用できるように lambda-container-deploy
を開発し、設定やデプロイメントの手間を減らし、開発に集中できる環境を提供しました。開発がある程度進んでいたら、忘れずに IaC を利用して、環境を構築していきましょう。では楽しい開発を是非体験してください。
(免責) 本記事の内容はあくまでも個人の意見であり、所属する企業や団体は関係ございません。