LambdaをGoとCodePipelineとServerlessで作る(認証情報 in SSM)
やりたいこと
プロダクトの開発に関わる各開発者が気軽にSlackBotなどのツール開発を行えるようにしたい。
当初の構成
当初は既にBot用に立ち上げたEC2インスタンスがあったので、次のように構成していた。
- Go言語で開発しMac上でLinux用バイナリへコンパイルしBotサーバへ転送
- 時間起動のSlackBotはcron実行を設定
- イベント駆動のSlackBotはプロセスを起動し Real Time Messaging API に接続
- 認証情報(各種外部連携用のToken等)は実行バイナリと共に設定ファイルとして配置
この構成で一定のSlackBotは実現できたが、本格的にBot運用を考え出すと次の問題に当たった。
- EC2インスタンス上で可動している処理の冗長化
Botのためにここまで金銭的人的コストを掛けたくない - 常駐プロセスのデーモン化
systemd登録やnode.jsのforeverといった方法もあるがBotを増やす際にいちいち考慮したくない - 各開発者に開示する必要の無い認証情報等は見えないようにする EC2サーバに開発者がログインして設定が見えてしまうのは回避したいが実行バイナリと共に配置しているので回避策がない
- EC2のセキュリティの確保
EIPを付与してインターネット側からリクエストを受けることを考えた場合に、セキュリティパッチの自動適用などを考えたくない
AWS Lambdaでよくないか
悩みをインフラ強い方に相談したところ、もはやEC2を止めてLambdaでやるのはどうかとアドバイスを貰った。 Lambdaは使ったこと無かったが、イベント起動でサーバレスでよしなに動かしてくれるものという風に概念的には知っていた。 AWSコンソールからコードを登録したり、コンパイル済み実行バイナリをアップロードしたり…という管理手段が気に食わない所だったが、Lambdaの構成管理が可能なツールが出ているらしかった。
Lambdaをコードで構成管理できるServerless
Lambdaの構成をコード管理するツールとしてServerlessを選定した。
- Lambdaと、そのトリガーを1つの
serverless.yml
で管理できる - [未検証]AWS以外にもGCPやAzureでもLambda的なFaaSを管理できる
同様のツールApexとの比較
インフラ強い方にはApexを勧められた。調べてみると、Lambdaの関数自体の開発や管理には強いようだが、関数のトリガー管理にTerraformが必須なのが気に掛かった。AWSにデプロイするためにApex経由でTerraformを実行する必要がある。できればツール単独である程度のことまでは出来て欲しい。
Serverlessをどこで実行するか?
ローカルPC上でServerlessを実行してAWSにLambdaを構築するには、対応する権限を持ったIAMユーザのアクセスキーが必要となる。 アクセスキーを特定の開発者のマシンに入れて管理したくないので、Serverlessの実行環境もAWS上で完結させたい。
これを実現させるために、AWS上でGitHub上のリポジトリからビルド・デプロイする構成を構築する必要がある。 適合する手段としてCodePipelineを使用する。
ちなみにCodePipelineの構成もコード管理したいので、これをTerraformで構成する。
CodePipelineを構成する
対応したコードをGitHubにPushした。
Terraformをapplyする前に、このGitHubリポジトリにアクセス可能なトークンを作成する必要がある。
GitHubトークンには以下の権限を設定する。
解決しなかった問題
AWS: aws_codepipeline - Terraform by HashiCorpによると、 aws_codepipeline
でGitHubをProviderとする場合は、環境変数 GITHUB_TOKEN
にトークンをセットする必要がある。
これに従って前項で生成したGitHubトークンを環境変数にセットしてTerraformをapplyすると、CodePipelineの生成まで上手く行った。
しかし、AWS側から設定されるはずのWebhookは、当該リポジトリに設定されなかった。なぜ?いろいろトライしたが実現できず。
AWSのドキュメントAWS CodePipeline が Webhook を通じて GitHub からプッシュイベントをサポートするには以下のように記述されているのに…。
GitHub からのプッシュイベントは、AWS CodePipeline コンソール、AWS コマンドラインインターフェイス、および CodePipeline API 経由で設定されます。
暫定対策?として、構築されたCodePipelineの設定をAWSコンソール上で編集してGitHubのリポジトリとブランチを再設定した。 これによりWebhookは作成され、対象ブランチへのPushイベントによりCodePipelineの起動が確認できた。
余談として、Terraformのほうの仕様として、他のトークン等のセット方法と異なる点に関しては Issue: Putting GITHUB_TOKEN in terraform config for aws_codepipeline #2796 が挙がっている。 確かに環境変数を渡すルートを他のトークンとは別で管理しなくてはならず、あまり良い設計には見えない。
GITHUB_TOKEN未設定で実行した場合
GITHUB_TOKEN
を設定せずに terraform apply
すると、以下のように OAuthToken
が無いというエラーメッセージが出るが、これはAWSからのメッセージ。
実際にはTerraformの設定上に OAuthToken
を設定しても効力が無いので注意が必要。
Error: Error applying plan:
1 error(s) occurred:
* aws_codepipeline.codepipeline: 1 error(s) occurred:
* aws_codepipeline.codepipeline: [ERROR] Error creating CodePipeline: InvalidActionDeclarationException: Action configuration for action 'Source' is missing required configuration 'OAuthToken'
status code: 400, request id: ********-****-****-****-************
Serverless
Makefile内でdepで依存モジュールの解決を行い、Linux用ビルドを行う。
Makefile
build:
dep ensure
env GOOS=linux go build -ldflags="-s -w" -o bin/tryo tryo/main.go
env GOOS=linux go build -ldflags="-s -w" -o bin/mendes mendes/main.go
出来上がったbin下のファイルが serverless.yml
内の以下の記述によりLambdaとトリガーに反映される。
functions:
tryo:
handler: bin/tryo
environment: ${self:custom.tryo.environment.${self:provider.stage}}
events:
- http:
path: tryo
method: post
mendes:
handler: bin/mendes
environment: ${self:custom.mendes.environment.${self:provider.stage}}
events:
# 10pm JST every weekday
- schedule: cron(*/10 0-10 ? * MON-FRI *)
tryoは上記の通り設定することで、/tryo
へのPOSTリクエストをトリガーとして実行されるようになる。
実装は API Gateway で実現され、HTTPSのEndpointは反映時にログに出力される。
mendesは上記の設定で、cron定義に従って時刻起動で実行されるようになる。 実装は CloudWatch Events で実現される。
認証情報 in SSM
認証情報や各種トークンなどはリポジトリにコミットしたくない。
しかしデプロイ時には必要になるものは、コード上では取得先を定義して、デプロイ時に注入したい。
ServerlessではSSMを参照先として指定できるため、serverless.yml
上で以下のように指定することで、SSM上の値を変数として取得して各設定に仕様できる。
custom:
defaultStage: staging
tryo:
environment:
staging:
SLACK_CLIENT_SECRET: ${ssm:/serverless-practice/staging/tryo/slack-client-secret}
SLACK_VERIFICATION_TOKEN: ${ssm:/serverless-practice/staging/slack-verification-token}
production:
SLACK_CLIENT_SECRET: ${ssm:/serverless-practice/production/slack-client-secret}
SLACK_VERIFICATION_TOKEN: ${ssm:/serverless-practice/production/slack-verification-token}
mendes:
environment:
staging:
SLACK_CLIENT_SECRET: ${ssm:/serverless-practice/staging/mendes/slack-client-secret}
production:
SLACK_CLIENT_SECRET: ${ssm:/serverless-practice/production/mendes/slack-client-secret}
/serverless-practice/staging/tryo/slack-client-secret
などの部分がキーになる。
このキーをAWS Systems Managerのパラメータストアで設定し、対応する値(認証情報やトークン)を入れることで設定の注入を行うことができる。
やりたいことはいくらでもあるが、今日はここまで。