この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
CircleCIを使って、AWS SAMのデプロイを自動化してみました。
やったことをまとめた結果、ハンズオンみたいな手順書になりました。
なお、デプロイのみを対象とします。(テストは扱いません)
おすすめの方
- AWS SAMを使っている
- AWS SAMを自動デプロイしたい
- CircleCIで自動デプロイしたい
目次
- 環境
- リポジトリとブランチ運用
- AWSにサーバーレスアプリケーションを作成する
- AWS SAMデプロイの準備
- CircleCIの準備
- CircleCIの設定
- CircleCIでデプロイさせる!(開発環境)
- CircleCIでデプロイさせる!(本番環境)
- さいごに
- 参考
環境
項目 | バージョン |
---|---|
macOS | High Mojave 10.14.5 |
AWS CLI | aws-cli/1.16.179 Python/3.6.8 Darwin/18.6.0 botocore/1.12.169 |
AWS SAM CLI | 0.17.0 |
Python | 3.6 |
リポジトリ | GitHub |
リポジトリとブランチ運用
Gitリポジトリは、GitHubフローのような運用とし、Pushされたタグを本番環境にデプロイします。
自動デプロイの対象は、下記とします。デプロイ先は、同じAWSアカウントです。
種類 | 名前の例 | 環境 | AWSスタック名 |
---|---|---|---|
ブランチ | master | 開発環境 | CircleCIDeploySample-dev |
ブランチ | issues/123 | 開発環境 | CircleCIDeploySample-dev |
タグ | v1.2.3 | 本番環境 | CircleCIDeploySample-prod |
AWSにサーバーレスアプリケーションを作成する
作るものは単純なWebAPIです
次のWebAPIを作成します。
Path | Method |
---|---|
/message/{id} | GET |
下記のようなJSONを返します。
{
"message": "Pathで指定したid"
}
AWS SAMプロジェクトの準備
下記コマンドでプロジェクト一式を作成します。
$ sam init --runtime python3.6 --name CircleCIDeploySample
Python仮想環境の構築
pyenvとpipenvの導入
導入済みの場合は、次へどうぞ!
pyenvを導入します。
$ brew install pyenv
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
$ exec $SHELL -l
pipenvを導入します。
$ brew install pipenv
$ echo 'eval "$(pipenv --completion)"' >> ~/.bash_profile
$ exec $SHELL -l
Python仮想環境の作成
Pythonをインストールします。すでにある場合は次へ。
$ pyenv install 3.6.8
zipimport.ZipImportError: can't decompress data; zlib not available
なエラーが出た場合は、下記をご覧ください。
続いて、仮想環境を作成します。
$ pipenv install --python 3.6.8
仮想環境に入ります。
$ pipenv shell
なお、仮想環境はexit
で終了できます。
必要なライブラリを仮想環境に導入
仮想環境に入った状態で、ライブラリを導入します。
$ pipenv install awscli
$ pipenv install aws-sam-cli
SwaggerでAPI Gatewayを定義
swagger.yaml
を作成し、下記とします。
swagger.yaml
swagger: "2.0"
info:
description: SwaggerとAPI Gatewayのサンプルです。(CircleCIデプロイ用)
version: 1.0.0
title: Swagger Sample for CircleCI deploy
tags:
- name: Message
schemes:
- https
paths:
/message/{id}:
get:
tags:
- Message
summary: メッセージ取得
description: メッセージを取得します
consumes:
- application/json
produces:
- application/json
parameters:
- name: id
in: path
description: 任意のID
required: true
type: string
responses:
200:
description: successful operation
schema:
$ref: "#/definitions/MessageResponse"
x-amazon-apigateway-integration:
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction.Arn}/invocations
passthroughBehavior: when_no_templates
httpMethod: POST
type: aws_proxy
definitions:
MessageResponse:
type: object
required:
- message
properties:
message:
type: string
Lambda関数の作成
下記とします。
app.py
import json
def lambda_handler(event, context):
request_id = event['pathParameters']['id']
return {
'statusCode': 200,
'body': json.dumps({
'message': request_id
}),
}
requirements.txt
の内容は、空っぽにします。
AWS SAMテンプレートファイルの更新
下記とします。
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CircleCIDeploySample
Globals:
Function:
Timeout: 3
Parameters:
SystemEnv:
Type: String
AllowedValues:
- prod
- dev
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.6
Events:
HelloWorld:
Type: Api
Properties:
Path: /message/{id}
Method: get
RestApiId: !Ref HelloWorldApi
HelloWorldApi:
Type: AWS::Serverless::Api
Properties:
Name: !Sub CircleCIDeploySample-Api-${SystemEnv}
StageName: !Sub ${SystemEnv}
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: !Sub s3://cm-fujii-circleci-deploy-sample-bucket${SystemEnv}/swagger.yaml
Outputs:
HelloWorldApiUrl:
Value: !Sub https://${HelloWorldApi}.execute-api.${AWS::Region}.amazonaws.com/${SystemEnv}/message/{id}
AWS SAMデプロイの準備
Makefileの作成
Makefileを作成し、デプロイで使用するコマンドをまとめます。あとで使うコマンド達も記載しています。
Makefile
BUCKET_NAME := cm-fujii-circleci-deploy-sample-bucket-${SYSTEM_ENV}
STACK_NAME := CircleCIDeploySample-${SYSTEM_ENV}
create-bucket:
aws s3 mb s3://$(BUCKET_NAME)
copy-swagger:
aws s3 cp swagger.yaml s3://$(BUCKET_NAME)/swagger.yaml
build:
sam build
deploy:
sam package \
--output-template-file packaged.yaml \
--s3-bucket $(BUCKET_NAME)
sam deploy \
--template-file packaged.yaml \
--stack-name $(STACK_NAME) \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset \
--parameter-overrides SystemEnv=${SYSTEM_ENV}
get-apigateway-endpoint:
aws cloudformation describe-stacks \
--stack-name $(STACK_NAME) \
--query 'Stacks[].Outputs'
create-iam-user-for-circleci:
aws cloudformation deploy \
--template-file circleci-iam-user.yaml \
--stack-name ${STACK_NAME}-for-CircleCI-User \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides SystemEnv=${SYSTEM_ENV}
create-iam-user-access-key:
aws iam create-access-key \
--user-name CircleCIDeploySample-${SYSTEM_ENV}-for-CircleCI-User
delete-app:
aws cloudformation delete-stack \
--stack-name $(STACK_NAME)
delete-iam-user-for-ciecleci:
aws cloudformation delete-stack \
--stack-name ${STACK_NAME}-for-CircleCI-User
CircleCIの準備
CircleCI用の設定ファイルを作成
.circleci
ディレクトリを作成し、その中にconfig.yml
を作成します。
$ mkdir .circleci
$ touch .circleci/config.yml
続いて、config.yml
ファイルの中身を記述します。
.circleci/config.yml
version: 2.1
executors:
my-executor:
docker:
- image: circleci/python:3.6.8
environment:
PIPENV_VENV_IN_PROJECT: true
working_directory: ~/CircleCIDeploySample
commands:
restore:
steps:
- restore_cache:
key: CircleCIDeploySample-v3-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
save:
steps:
- save_cache:
paths:
- ".venv"
key: CircleCIDeploySample-v3-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
jobs:
setup:
executor: my-executor
steps:
- checkout
- restore
- run:
name: install
command: |
sudo pip install pipenv
pipenv install
- save
build:
executor: my-executor
parameters:
env:
type: enum
enum: ["prod", "dev"]
steps:
- checkout
- restore
- run:
name: sam-build
command: |
source .venv/bin/activate
aws --version
sam --version
echo << parameters.env >>
if [ << parameters.env >> = "dev" ]; then
export SYSTEM_ENV=<< parameters.env >>
export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID_DEV
export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY_DEV
else
export SYSTEM_ENV=<< parameters.env >>
export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID_PROD
export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY_PROD
fi
make copy-swagger
make build
deploy:
executor: my-executor
parameters:
env:
type: enum
enum: ["prod", "dev"]
steps:
- checkout
- restore
- run:
name: sam-deploy
command: |
source .venv/bin/activate
aws --version
sam --version
echo << parameters.env >>
if [ << parameters.env >> = "dev" ]; then
export SYSTEM_ENV=<< parameters.env >>
export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID_DEV
export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY_DEV
else
export SYSTEM_ENV=<< parameters.env >>
export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID_PROD
export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY_PROD
fi
make deploy
workflows:
version: 2.1
release-dev-workflow:
jobs:
- setup:
filters:
branches:
only:
- master
- /^issues\/\d+$/
- build:
env: dev
requires:
- setup
filters:
branches:
only:
- master
- /^issues\/\d+$/
- deploy:
env: dev
requires:
- build
filters:
branches:
only:
- master
- /^issues\/\d+$/
release-prod-workflow:
jobs:
- setup:
filters:
branches:
ignore: /.*/
tags:
only:
- /^v\d+\.\d+\.\d+$/
- build:
env: prod
requires:
- setup
filters:
branches:
ignore: /.*/
tags:
only:
- /^v\d+\.\d+\.\d+$/
- deploy:
env: prod
requires:
- build
filters:
branches:
ignore: /.*/
tags:
only:
- /^v\d+\.\d+\.\d+$/
CircleCI用のIAMユーザを作成
Web画面から作成してもよいのですが、せっかくなのでCloudFormationを利用して作成します。
circleci-iam-user.yaml
を作成し、下記とします。
circleci-iam-user.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Create IAM User and Role for CircleCI
Parameters:
SystemEnv:
Type: String
Description: SystemEnv
AllowedValues:
- prod
- dev
Resources:
CircleCIUser:
Type: AWS::IAM::User
Properties:
UserName: !Sub CircleCIDeploySample-${SystemEnv}-for-CircleCI-User
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonS3FullAccess
- arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator
- arn:aws:iam::aws:policy/AWSLambdaFullAccess
- arn:aws:iam::aws:policy/IAMFullAccess
CircleCIPoricy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub lCircleCIDeploySample-${SystemEnv}-for-CircleCI-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- "cloudformation:*"
Resource: "*"
Users:
- !Ref CircleCIUser
CircleCI用のIAMユーザ作成を実行します。
$ SYSTEM_ENV=dev make create-iam-user-for-circleci
$ SYSTEM_ENV=prod make create-iam-user-for-circleci
アクセスキーIDとシークレットキーの取得
下記コマンドで取得し、メモしておきます。
$ SYSTEM_ENV=dev make create-iam-user-access-key
$ SYSTEM_ENV=prod make create-iam-user-access-key
流出しないよう、取扱にご注意ください!
S3バケットの作成
ソースコードデプロイ先のバケットを作成します。
$ SYSTEM_ENV=dev make create-bucket
$ SYSTEM_ENV=prod make create-bucket
コミット
ここまでの内容をmasterブランチ
にコミットします。コミット済みの場合は次へ!
$ git add .
$ git commit -m "create sample"
リポジトリのPush
リポジトリをGitHubにPushします。
$ git push origin master
CircleCIの設定
ここから先は、Web画面で行います。
ログイン
CircleCIにログインします。
CircleCIのプロジェクトを作成
「ADD PROJECT」を選択し、さきほどGitHubにPushしたリポジトリを選択します。
続いて、「Start building」を選択します。
初めてのジョブが走りますが、環境変数が未設定なので失敗します。
環境変数の設定
プロジェクト一覧の設定マークを押し、設定画面に移ります。
Environment Variables
を選択します。
次の環境変数を追加します。
Name | Value |
---|---|
AWS_ACCESS_KEY_ID_DEV | 取得したAccessKeyId(開発用) |
AWS_ACCESS_KEY_ID_PROD | 取得したAccessKeyId(本番用) |
AWS_SECRET_ACCESS_KEY_DEV | 取得したSecretAccessKey(開発用) |
AWS_SECRET_ACCESS_KEY_PROD | 取得したSecretAccessKey(本番用) |
AWS_DEFAULT_REGION | ap-northeast-1 |
AWS_DEFAULT_OUTPUT | json |
CircleCIでデプロイさせる!(開発環境)
開発環境にデプロイ
さきほど失敗したWorkflowsの「Return」を選択し、そこの「Return from failed」を選択します。
すると、再びデプロイが始まります。
しばらくすると成功します!!! (ブラウザによっては自動更新してくれないため、手動更新してください)
API Gatewayのアドレスを取得する(開発環境)
$ SYSTEM_ENV=dev make get-apigateway-endpoint
[
[
{
"OutputKey": "HelloWorldApiUrl",
"OutputValue": "https://yyyyyy.execute-api.ap-northeast-1.amazonaws.com/dev/message/{id}"
}
]
]
APIを叩いてみる(開発環境)
適当に叩きます。
$ curl https://yyyyyy.execute-api.ap-northeast-1.amazonaws.com/dev/message/dev-test-0000
{"message": "dev-test-0000"}
できました!!!
CircleCIでデプロイさせる!(本番環境)
本番用Lambdaを少し修正する
せっかくなので、少しだけLambdaのコードを変えてみます。
app.py
import json
def lambda_handler(event, context):
request_id = event['pathParameters']['id']
return {
'statusCode': 200,
'body': json.dumps({
'env': 'prod',
'message': request_id
}),
}
特に意味もないですが、prod
と固定文字列を追加してみました。
本番環境にデプロイ
まずはコミットします。
$ git add .
$ git commit -m "modify app.py for prod"
続いて、タグを付けて、そのタグをPushします!
$ git tag v1.0.0
$ git push origin v1.0.0
すると、CircleCIによる本番環境へのデプロイが始まります。
しばらくすると、完了しました!
API Gatewayのアドレスを取得する(本番環境)
$ SYSTEM_ENV=prod make get-apigateway-endpoint
[
[
{
"OutputKey": "HelloWorldApiUrl",
"OutputValue": "https://zzzzzz.execute-api.ap-northeast-1.amazonaws.com/prod/message/{id}"
}
]
]
APIを叩いてみる(本番環境)
適当に叩きます。
$ curl https://zzzzzz.execute-api.ap-northeast-1.amazonaws.com/prod/message/hello-world
{"env": "prod", "message": "hello-world"}
できました!!!
さいごに
こういった作業は初めてだったので、とても時間が掛かりましたが、良い勉強になりました。
どんどん活用していきます!
参考
- AWS::IAM::User | AWS
- AWS Identity and Access Management によるアクセスの制御 | AWS
- IAM ユーザーのアクセスキーを作成する | AWS
- AWS CLIの環境変数 | AWS
- Welcome to CircleCI Documentation | CircleCI
- CircleCI での Python アプリケーションの設定 | CircleCI
- Pre-Built CircleCI Docker Images | CircleCI
- Configuring CircleCI | CircleCI
- circleci/aws-cli@0.1.13 | CircleCI
- Mac上のPython仮想環境をpipenv+pyenvへ移行してみた | Developers.IO
- Pipenvを用いたPythonの環境構築 | Qiita
- CircleCI 2.1 の新機能を使って冗長な config.yml をすっきりさせよう!
- いまさらだけどCircleCIに入門したので分かりやすくまとめてみた | Qiita