前置き
AWSアカウント単位でのスケールアウトが必要になった場合「開発用AWSアカウント」「本番用AWSアカウント1」「本番用AWSアカウント2」のような使い分けをすることがあります。
その場合のCI/CDが意外と面倒だったので、覚書をしておきます。
なお、サンプルのアプリはlambdaとします。
やりたいことのイメージ1
解決方法
そもそも何が問題か
lambdaをターゲットとするので、AWSの構築とアプリのデプロイはCloudFormationで可能です。
また、CloudFormationのテンプレートをCloudFormation Stacksetsで複数AWSアカウントに配布することも可能です。
ですが、CloudFormation StackSetsは、CodePipelineに対応していないので、ソースやCloudFormationのテンプレートファイルの変更を検知して、CI/CDすることがそのままではできません。
どのように解決するか
CloudFormation StackSetsをCloudFormationで作成し、そのCloudFormationをCodePipelineで実行することで解決します。
ただし、CloudFormationでは、CloudFormation StackSetsの作成をサポートしていないので、CloudFormationのカスタムリソースの機能を利用して、CloudFormation StackSetsを作成します。
参考)
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-custom-resources.html
やりたいことのイメージ2
実際の手順
カスタムリソースのバックエンドのlambdaを作る
このlambdaはAWSの人たちが作って公開しているので
https://github.com/awslabs/aws-cloudformation-templates/tree/master/aws/solutions/StackSetsResource
のリポジトリにあるのでダウンロードします。
build.sh修正
# ! /bin/sh
# Figure out AWS profile to use
[[ -z "${AWS_PROFILE}" ]] && profile='default' || profile="${AWS_PROFILE}"
[[ -z "${2}" ]] && profile=$profile || profile="${2}"
# Figure out install region
[[ -z "${AWS_DEFAULT_REGION}" ]] && region='us-east-1' || region="${AWS_DEFAULT_REGION}"
〜略〜
※プロファイル(default)とリージョン(us-east-1)を、自分の環境に合わせて変更します。
その後build.shを実行すると、カスタムリソースのバックエンドのlambdaを作るためのCloudFormationのスタックが作成され
StackSetCustomResource-StackSetResourceFunction-XXXXXXXXXXXというlambdaがデプロイされます。
これがカスタムリソースのバックエンドのlambdaです。
各AWSアカウントに配布したいCloudFormationテンプレートファイルを作る
app.yaml作成
Resources:
StackSetsSampleLambda:
Type: AWS::Lambda::Function
Properties:
Code: functions/
Handler: main.lambda_handler
Runtime: python3.6
Role: !GetAtt StackSetsSampleIamRole.Arn
Timeout: 30
StackSetsSampleIamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
Policies:
-
PolicyName: "stacksets_sample_lambda_policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action: "cloudwatch:*"
Resource: "*"
functions/main.py作成
def lambda_handler(event, context):
return "hello"
各AWSアカウントに配布するソースを置くS3バケットの作成
適当にバケットを作り、バケットポリシーで、配布先のAWSアカウントからの参照を許可する。↓例
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "GetObject",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::{配布先AWSID}:root"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::{作成したバケット名}/*"
},
{
"Sid": "ListBucket",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::{配布先AWSID}:root"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::{作成したバケット名}"
}
]
}
デプロイする環境の数だけ"Principal"を書く
パッケージする
aws cloudformation package --template-file app.yaml --s3-bucket {作成したバケット名} --output-template-file packaged-app.yaml
パッケージした後のyamlファイルを確認して、S3にアップロードする
packaged-app.yaml確認
Resources:
StackSetsSampleLambda:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket: {作成したバケット名}
S3Key: {自動的にキーが入っています}
Handler: main.lambda_handler
Runtime: python3.6
Role:
Fn::GetAtt:
- StackSetsSampleIamRole
- Arn
Timeout: 30
StackSetsSampleIamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: stacksets_sample_lambda_policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: cloudwatch:*
Resource: '*'
先程作ったS3のバケットにアップロードしておきます。
※このテンプレートファイルは、後述のCloudFormationの設定ファイルから利用します。
httpでアクセスできれば、アップロード先はどこでも良いですが、ここでは
https://{作成したバケット名}/packaged-app.yaml
にアップロードしたものとします。
CloudFormation StackSetsを動かせるようにする
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacksets-prereqs.html
を読んで、適切に権限設定をしてください。
今回の例で言うと
開発AWSアカウントが管理者アカウントなので、AWSCloudFormationStackSetAdministrationRoleを作る必要があり
疑似本番AWSアカウントや本番AWSアカウントがターゲットアカウントなので、AWSCloudFormationStackSetExecutionRoleを作る必要があります。
なお、開発用AWSアカウント自身にもデプロイしたい場合は、自分自身がターゲットアカウントになるのでAWSCloudFormationStackSetExecutionRoleを作っておく必要があります。
CloudFormation StackSetsを管理するCloudFormationテンプレートファイルを作る
これが提示されているサンプルなのですが、間違っているのと、お試しするには冗長なので、以下を使います。
stacksets.yaml作成
AWSTemplateFormatVersion: '2010-09-09'
Resources:
StackSet:
Type: Custom::StackSet
Properties:
ServiceToken:
Fn::ImportValue: StackSetCustomResource
StackSetName: StackSetsTest
StackSetDescription: Deploy Lambda
TemplateURL: https://{作成したバケット名}/packaged-app.yaml
Capabilities:
- CAPABILITY_IAM
AdministrationRoleARN: "arn:aws:iam::{管理アカウントのAWS ID}:role/AWSCloudFormationStackSetAdministrationRole"
ExecutionRoleName: "AWSCloudFormationStackSetExecutionRole"
OperationPreferences: {
"RegionOrder": [ "ap-northeast-1" ],
"FailureToleranceCount": 0,
"MaxConcurrentCount": 3
}
Tags:
- Environment: Testing
- Creator: Chuck
- DeployDate: "20190918"
StackInstances:
# Production
- Regions:
- ap-northeast-1
Accounts:
- "{配布先AWSID1}"
- "{配布先AWSID2}"
- "{配布先AWSID3}"
Cloudformation StackSetsを作る
stacksets.yamlを使って、CloudFormationを実行します。
成功すると、カスタムリソースのバックエンドのlambdaが起動し、StackSetsが作られます。
StackSetsが作られたことによって、各ターゲットのAWSアカウントにCloudFormationのスタックが作成され
今回の例だと、IAMとLambdaが作成され、Lambdaにアプリがデプロイされます。
配布先AWSアカウントやソース変更時の配布
今回の構成は、いくらソースや配布するCloudFormationのテンプレートが変わっても、CloudFormation StackSetsのテンプレートファイルが変更されない限り、最初のCloudFormationが変更されなければ、配布されません。
なので、ソースなどを修正して
aws cloudformation package --template-file app.yaml 〜
を行った後は必ず
stacksets.yaml修正
AWSTemplateFormatVersion: '2010-09-09'
Resources:
StackSet:
Type: Custom::StackSet
Properties:
ServiceToken:
Fn::ImportValue: StackSetCustomResource
StackSetName: StackSetsTest
StackSetDescription: Deploy Lambda
TemplateURL: https://{アップロードしたpackaged-app.yamlのURL}/packaged-app.yaml
Capabilities:
- CAPABILITY_IAM
AdministrationRoleARN: "arn:aws:iam::{管理アカウントのAWS ID}:role/AWSCloudFormationStackSetAdministrationRole"
ExecutionRoleName: "AWSCloudFormationStackSetExecutionRole"
OperationPreferences: {
"RegionOrder": [ "ap-northeast-1" ],
"FailureToleranceCount": 0,
"MaxConcurrentCount": 3
}
Tags:
- Environment: Testing
- Creator: Chuck
- DeployDate: "20190918" ← 絶対変更する
StackInstances:
# Production
- Regions:
- ap-northeast-1
Accounts:
- "{配布先AWSID1}"
- "{配布先AWSID2}"
- "{配布先AWSID3}" ← 必要に応じて変更する
して、stacksets.yamlを元にCloudFormationを動かす必要があります。
※タグだけ変えて無理やりCloudformation StackSetsに変更を検知させています。
CodePipelineとの連携について
リポジトリの各ブランチへのプッシュに合わせて
aws cloudformation package --template-file app.yaml 〜
を行い、stacksets.yamlをCodePipelineで適宜動かすだけなので割愛します。