やりたいこと

プロダクトの開発に関わる各開発者が気軽に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トークンには以下の権限を設定する。 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のパラメータストアで設定し、対応する値(認証情報やトークン)を入れることで設定の注入を行うことができる。

やりたいことはいくらでもあるが、今日はここまで。

参考リスト