こんにちは。てるい@さっぽろです。まだ積もってはいませんが雪が降る日が少しずつ増えてきました。
久しぶりにHubotをEC2上にインストールする必要が出てきて、せっかくなのでモダンで快適な感じに作ってみましたので共有したいと思います。
経緯など
弊社では各種メディアで取り上げられて有名なブリを初めSlack上にBotが住んでいますが、それらの多くはHeroku上にデプロイされています。
今回は特定のプロジェクトで利用するBotであり、そのプロジェクト用のAWSアカウントで請求をまとめたかったことと、そのアカウントのAWSリソースを操作するのにIAM Roleで権限を管理できた方が便利なためEC2上に構築しました。しかし、EC2上に構築したからといってHeroku環境よりもデプロイなどの使い勝手が落ちるのはよろしくないので、それなりに良い感じのHubot環境を作ってみたという形です。
概要
ポイント
- デプロイ対象となるリソースはCloudFormationで一元管理
- ソースコードはGitHubにホスト
- masterの更新を契機にCodeDeployでHubotのカスタムスクリプトを更新
- Privateリポジトリとはいえ、GitHubにSlack Tokenを生で保存するのはよろしくないのでKMSで暗号化
また、構成図には記載していませんが、HubotのカスタムスクリプトはES6で書けるようにしています。
手順など
今回のソースコードなどはアカウントIDなどをマスクして公開しておきました。
https://github.com/marcy-terui/babel-hubot-codedeploy
ちなみに特にRegionやAMI IDをParameter化したりはしていないので、もし流用される際は適宜書き換えてくださいw
①ベースとなる環境をデプロイ
aws cloudformation create-stack \
--stack-name $YOUR_STACK_NAME \
--template-body file://cfn-abel-hubot-codedeploy.yml \
--capabilities CAPABILITY_NAMED_IAM
これで、CodeDeployやNode.jsランタイムのインストールされたEC2とSlack Tokenを暗号化するためのKMSキー、およびそれらを扱うためのIAM UserやRoleが作成されます。
cfnテンプレート上でのポイントとしてはこの辺りで、KMS Keyのアクセス制御はIAMで行わずにKMSのポリシー側に寄せることです。
BabelHubotKey:
Type: "AWS::KMS::Key"
Properties:
Description: "Hubot Encription key"
KeyPolicy:
Version: "2012-10-17"
Id: "orthros-bot-admin"
Statement:
- Sid: "Allow administration of the key from GUI"
Effect: "Allow"
Principal:
AWS: "arn:aws:iam::123456789:role/gui-role"
Action:
- "kms:*"
Resource: "*"
- Sid: "Allow administration of the key from CLI"
Effect: "Allow"
Principal:
AWS: "arn:aws:iam::123456789:user/cli-user"
Action:
- "kms:*"
Resource: "*"
- Sid: "Allow use of the key"
Effect: "Allow"
Principal:
AWS: !GetAtt BabelHubotRole.Arn
Action:
- "kms:Decrypt"
- "kms:ReEncrypt*"
- "kms:GenerateDataKey*"
- "kms:DescribeKey"
Resource: "*"
これは、KMSを他の用途で使う場合にも有効なテクニックで、IAM側で制御すると鍵の交換をしたくなった際にポリシーの書き換えが必要になりますが、IAM側では Resource: *
で許可しておき、KMS側のポリシーで権限を制御することで、KMSのAPIはキーが指定されなかった際に自分が許可されてる中で復号できるキーがあれば自動的にそれを選択して復号化してくれるため、鍵交換をKMS内で完結させることができます。
②CodeDeployの設定
基本的にナビゲーションの通りに作成するだけです。デプロイ種別やデプロイのタイミングも選択する必要がありますが、今回は1台だけで同じインスタンスを使い続けるため、デフォルトのIn-place Deployment
(既存のインスタンスにデプロイして交換をしない) と OneAtAllTime
(一台ずつデプロイする) で構いません。デプロイ対象の選択だけ、 aws:cloudformation:stack-name
タグを選択し、Stack名を入力します。
③CodePipelineの設定
適当な名前を入力し、SourceにGitHubを選択してリポジトリとブランチを選択します(この前にGitHubとのOAuth信頼関係を結ぶ画面が出ます)
先程作成したCodeDeployのApplicationとDeployment Groupを選択します。
Let's Deploy!
以上で環境は完成です。さあ、デプロイ!と行きたい所ですが、Slack Tokenを暗号化してリポジトリに含める必要があるので下記のコマンドを実行します。
aws kms encrypt --key-id $YOUR_KMS_KEY_ARN \
--plaintext $YOUR_SLACK_TOKEN \
--query CiphertextBlob \
--output text \
| base64 --decode > encrypted-slack-token.txt
これで作成した暗号化されたSlack Tokenが書かれている encrypted-slack-token.txt
をリポジトリへpushすると、CodeDeployがデプロイ時に実行するHubotの起動スクリプトに含まれている下記のコマンドによって、Slack Tokenが復号化してセットされるという仕組みです。
export HUBOT_SLACK_TOKEN=$(aws kms decrypt --region us-west-2 --ciphertext-blob fileb://encrypted-slack-token.txt --query Plaintext --output text | base64 --decode)
最後に
記事内で触れなかったES6で書かれたカスタムスクリプトを動かすために必要なnpmモジュールや起動方法についてはpackage.jsonや.babelrc、起動スクリプトあたりをお読みいただければと思います。
ちなみにこれを実際にデプロイしたプロジェクト環境なんですが、Bot以外は完全なServerless構成となっていて、BotのためだけにEC2が居るという可笑しな状況になってるんですが、Lambdaで動く良い感じのBotをご存知の方がいれば是非教えていただきたい気持ちでいっぱいです!よろしくお願いします!!!