こんにちは。てるい@今年は雪が多い札幌です。
Serverless Frameworkで作ったPythonアプリのデプロイを効率化するために、CodeBuildによるデプロイ環境とそれをHubot(Slack)からの呼び出され方によって環境を切り替えつつデプロイできる仕組みを作ったのでご紹介したいと思います。
全体イメージ
こんなイメージで、Hubotへのコマンドの内容からデプロイ対象の環境を特定し、それに応じた環境変数などを与えながらPython3.6なServerlessアプリケーションをデプロイする仕組みです。
前提
- ソースコードのホスト先はGitHub(Private)
- Hubot環境はEC2上に構築したものを使用(ちなみにコチラの方法で作ったもの)
- Serverless Frameworkのバージョンは
1.25
、Pythonは3.6
を使用 - リージョンは今回は
us-west-2
を使用しています(他でも普通にできるはずです)
手順
①ECR(Elastic Container Registory)にCodeBuildで使用するDocker Imageをホストする
今回はNode.jsで動くServerless FrameworkとPythonで書かれたアプリケーションの組み合わせなので、CodeBuildが標準で用意しているNode.jsやPython向けのイメージでは片手落ちでデプロイすることが出来ません。また、CodeBuildの標準イメージはUbuntuベースでOSの設定がブラックボックスなので、Amazon Linux上で動いているLambdaではPythonのC拡張のライブラリなどを使用した場合に問題が起きる可能性があります。
そのため、今回はDocker Hubで公開されているAmazon Linuxの公式イメージからCodeBuildで使用するためのDockerイメージを作成してECRにホストさせ、そのイメージを使用してデプロイを行うようにします。
まずはECSの Repositories
メニューからECRのリポジトリを作成します。ここは名前を決めてポチッとするだけです。
出来上がったリポジトリにDocker ImageをPushします。
まず、このような Dockerfile
を用意します。
FROM amazonlinux:latest
RUN curl --silent --location https://rpm.nodesource.com/setup_8.x | bash -
RUN yum install nodejs python36 -y
これをBuildしてPushします。
$ `aws ecr get-login --no-include-email --region us-west-2`
$ docker build -t $YOUR_IMAGE_NAME .
$ docker tag $YOUR_IMAGE_NAME:latest $YOUR_ACCOUNT_NUMBER.dkr.ecr.us-west-2.amazonaws.com/$YOUR_IMAGE_NAME:latest
$ docker push $YOUR_ACCOUNT_NUMBER.dkr.ecr.us-west-2.amazonaws.com/$YOUR_IMAGE_NAME:latest
②ECRへのCodeBuildからのアクセスを許可する
CodeBuildからECRのイメージを取得させるにはそれを許可する設定が必要です。先程作成したECRリポジトリの Permissions
タブから許可設定を行います。
CodeBuildのサービスドメインである codebuild.amazonaws.com
を対象に許可設定を行います。
権限は Pull only actions
を選択します。
③CodeBuildプロジェクトの作成
Managed Console上では最後の操作となる、CodeBuildのプロジェクトを作成を行います。基本的にはナビゲーションどおりに設定していきます。
ソースリポジトリはGitHubの対象となるリポジトリを指定します。
ビルド環境は先程作成したECRにホストしているDocker Imageを使用するよう指定します。
また、Serverless FrameworkはIAM Roleの管理も行うため基本的に Administrator Access
のRoleを必要とするため、実行時のRoleにはそれを割り当てたRoleを指定します。もしくは、予め作成されたRoleを指定することもできるので、権限を絞りたい場合はそのようにすると良いでしょう。
④CodeBuildのビルド設定と実行スクリプトの準備
このような buildspec.yml
を準備します。ポイントは必要な環境変数をデプロイ対象となるproduction,stagingなどの環境毎に予めSSM Parameter Storeに入れておくことです。これを、CodeBuild実行時に指定する環境変数を見て切り替えさせることで環境毎に異なる環境変数を動的に設定させるようにします。
version: 0.2
env:
parameter-store:
FOO_PROD: foo-prod
FOO_STG: foo-stg
phases:
install:
commands:
- npm install
pre_build:
commands:
- chmod +x ./deploy.sh
build:
commands:
- ./deploy.sh
これをこのようなデプロイスクリプトを利用することで環境変数切り替えさせつつデプロイを行います。ちなみに、 serverless-python-requirementsというPluginを使っているのでPythonライブラリのインストールも sls deploy
だけでまとめて行えます。
#!/bin/bash -eu
export PATH=$PATH:$PWD/node_modules/.bin/
if [ $FOO_ENV = "production" ]; then
export FOO=$FOO_PROD
elif [ $FOO_ENV = "staging" ]; then
export FOO=$FOO_STG
fi
sls deploy
上記のようにすることで、 serverless.yml
に以下のように書いておくことでLambdaの環境変数を環境毎に切り替えさせるという形です。
plugins:
- serverless-python-requirements
custom:
pythonRequirements:
dockerizePip: false
pythonBin: ${opt:pythonBin, self:provider.runtime}
provider:
name: aws
runtime: python3.6
cfLogs: true
stage: ${env:FOO_ENV}
environment:
FOO: ${env:FOO}
⑤Hubot Scriptの作成
最後に、作成したCodeBuildのプロジェクトを呼び出すHubot Scriptを作成します。実際のスクリプトを全部載せると長すぎるのでポイントのCodeBuild呼び出し部分だけ載せるとこんな感じです。ポイントは、先程の環境毎の環境変数を切り替えるための環境変数を environmentVariablesOverride
の引数で指定することと、環境にマッピングされたGitHubのブランチ名を sourceVersion
引数で指定することです(まあこの辺は各位ポリシーあるかと思うので良い感じにやってください)
export default function(robot){
robot.respond(/deploy (production|staging)/, (res) => {
const environment = res.match[1];
const codebuild = new AWS.CodeBuild({region: 'us-west-2'});
const params = {
projectName: `foo-deploy`,
environmentVariablesOverride: [
{
name: 'FOO_ENV',
value: environment,
type: 'PLAINTEXT'
},
],
sourceVersion: environment
};
codebuild.startBuild(params, function(err, data) { /* snip */ });
});
}
⑥Let's Deploy!!
これで準備は完了です!Slackからdeployコマンドを実行した結果を一応貼っておきますね。
Botの発言がウザい感じですが、これは「そういうキャラだから仕方がない!」としか言いようがないです・・・!(塗りつぶしてる部分を外すと私と同世代の方には納得してもらえると思うんですけど、一応社内の開発コードネームが元になっているので伏せさせてもらってます)
最後に
気持ちの問題かもですが、Serverless FrameworkはIAMの強い権限を求めるので、Circle CIとかだとCredentialをCircle CIに持たせるのが少し躊躇われますが、CodeBuildでRole baseで権限を持たせられると認証情報がテンポラリになるので少し安心できます。今回は都合上Chatからですが、CodePipelineと組み合わせることで特定のブランチの更新をトリガーに自動的にデプロイさせることも可能かと思います(その場合はたぶん環境変数の切り替えに一工夫いりそうです。たぶんプロジェクトを分けて環境変数を予め仕込んでおく感じになるかなと)
Serverlessでもデプロイは効率化して良いServerlessライフを送りましょう!!