SREチームの橋本です。SRE連載の7月号になります。
カヤック社内では弊社藤原のecspressoをAmazon ECSのデプロイツールとして活用していますが、AWS公式のデプロイツールAWS Copilot(現在v1.29)もそのオールインワン的な性質から、開発・運営リソースが限られるプロジェクトでは選択肢に入るようになってきました。
今回はそのAWS Copilot活用のため、背後にあるAWS CloudFormationテンプレートをカスタマイズする手法を紹介します。
AWS CopilotとCloudFormation
AWS CopilotはECSなどのデプロイを簡単にするCLIツールですが、実態としてはManifestと呼ばれるYAMLの設定ファイルからCloudFormationテンプレートを生成し、各種リソースを作成・管理するものです。
ECSでサービスを作るとき、典型的な構成としてはロードバランサーを前段に置くことになりますが、単純にこの2つを作成して構築完了とはなりません。
IAMロール、VPC、セキュリティグループなどなど多くの周辺リソースが必要となり、ロードバランサーに対してもターゲットグループやリスナーなどを設定します。
こうした手間をcopilot
コマンド一つに集約できるのがAWS Copilotの利点と言えます。
ただ設定が簡単な分ある程度は決め打ちな部分もあり、例えばCloudFrontは環境につき1つだけといった制約もあります。(具体的には環境のcdnで設定します。) こうした部分をカスタマイズするときに有用なのがAddonやオーバーライドといった機能です。
例えばcopilot init
でtechblog
アプリケーションを作成し、myservice
というLoad Balanced Web Serviceを作成、インストラクションに従ってtest
環境も作成すると、以下の4つのCloudFormationスタックが作成されます。
- techblog-infrastructure-roles
- StackSet-techblog-infrastructure-*** (末尾にハッシュ的な文字列が付くようです)
- techblog-test
- techblog-test-myservice
1つ目はCloudFormation自身が使うためのIAMロール、2つ目は環境間で共通のリソース(KMSキー、成果物を置くS3など)で特に触ることはないので、カスタマイズというと3つ目の環境、4つ目のサービスに関するスタックへ変更を加えることになります。
Addonでリソースを追加する
Addon機能では、単にCloudFormationテンプレートを書くことで追加のリソースを定義することができます。
環境のディレクトリ下のaddons/
に形式に従うテンプレートを置くと、環境のデプロイ時に一緒にAddonのデプロイが行われます。
例えばCloudFrontディストリビューションを追加で配置する場合、以下のようなテンプレートを書くことになります。
Parameters: App: Type: String Env: Type: String Resources: MyCloudFrontDistribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: DefaultCacheBehavior: AllowedMethods: - HEAD - GET CachedMethods: - HEAD - GET CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # CachingOptimized Compress: true OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3 # AllViewer TargetOriginId: s3 ViewerProtocolPolicy: redirect-to-https Enabled: true Origins: - Id: s3 DomainName: 'example-bucket.s3.ap-northeast-1.amazonaws.com' OriginPath: '' OriginAccessControlId: !GetAtt MyDistributionOriginAccessControl.Id S3OriginConfig: OriginAccessIdentity: '' ConnectionAttempts: 3 ConnectionTimeout: 10 MyDistributionOriginAccessControl: Type: AWS::CloudFront::OriginAccessControl Properties: OriginAccessControlConfig: Name: example-bucket OriginAccessControlOriginType: s3 SigningBehavior: always SigningProtocol: sigv4
これでcopilot env deploy
すると、内部的には環境のCloudFormationスタックの対してネストされたスタックが作成されます。
またv1.27からは差分のプレビュー機能が追加され、deploy --diff
によりCloudFormationテンプレートの差分が分かるようになりました。
$ copilot env deploy --diff Only found one environment, defaulting to: test ~ Resources: + AddonsStack: + Metadata: + 'aws:copilot:description': 'A CloudFormation nested stack for your additional AWS resources' + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + App: !Ref AppName + Env: !Ref EnvironmentName + TemplateURL: https://stackset-techblog-infras-pipelinebuiltartifactbuc-***.s3.ap-northeast-1.amazonaws.com/manual/addons/environments/***.yml Continue with the deployment? (y/N)
これは環境test
のスタックにネストされたスタックが追加されるという内容で、TemplateURL
のファイルを見るとネストされたスタック(Addonに当たる部分)の内容も確認できます。
この場合上のYAMLと同内容になります。
単に追加のリソースが作られるのが基本ですが、ワークロードのAddonの場合、Outputs:
に以下のようなリソースを記述するとECSタスクロールやECSサービスなどに付与することができます。(ドキュメント参照)
- IAMポリシー
- セキュリティグループ
- シークレット
- 環境変数
元々ワークロードに対してサポートされていたAddonは、v1.25で環境に対してもサポートされ、例えば複数のサービスで利用するS3といったリソースがより自然に扱えるようになりました。(またv1.29ではパイプラインにもサポートされました。)
なおcopilot storage
というコマンドがあり、これによりRDSやS3をワークロードのAddonであったり、環境のAddonとしても定義することができます。
オーバーライドで生成されるテンプレートを書き換える
例えばCloudFrontディストリビューションへのCloudFront Functions(あるいはLambda@Edge)の紐づけは今のところサポートされていません。
CloudFrontディストリビューションはManifestから生成されるリソースのため、Addonでは触れませんが、v1.27で追加されたoverride
コマンドによりこうしたケースにも対応が可能となりました。
オーバーライドはCDK(AWS Cloud Development Kit)とYAMLパッチの両方に対応しています。ここでは特に複雑なことをしないので、よりシンプルなYAMLパッチによるオーバーライドを試してみます。
まず再びAddonsで、CloudFront関数を用意します。(IPを返すだけのレスポンスに書き換えてしまうので実用性はありませんが、動作確認は簡単にできます)
Parameters: App: Type: String Env: Type: String Resources: RewriteFunction: Type: AWS::CloudFront::Function Properties: AutoPublish: true FunctionCode: !Sub | function handler(event) { var request = event.request; var clientIp = event.viewer.ip; return { statusCode: 200, statusDescription: '200 OK', body: clientIp }; } FunctionConfig: Comment: !Sub 'Respond with the client IP address' Runtime: cloudfront-js-1.0 Name: !Sub "${AWS::StackName}-RewriteFunction" Outputs: RewriteFunctionARN: Description: 'The ARN of the CloudFront function that rewrites the request' Value: !Ref RewriteFunction Export: Name: !Sub ${App}-${Env}-RewriteFunctionARN
最後のExportによってOutputsの値が他のスタックからも利用可能になります。なお名前空間はリージョン単位なので、${App}-${Env}-
という風にプレフィックスを付けることが推奨されています。
この関数をCloudFrontディストリビューションに紐づけます。対象となるディストリビューションは、例えばcopilot env package
で環境のCloudFormationテンプレートを出力すると、以下のように見つけることができます。
Resources: …… CloudFrontDistribution: Metadata: 'aws:copilot:description': 'A CloudFront distribution for global content delivery' Condition: CreateALB Type: AWS::CloudFront::Distribution Properties: DistributionConfig: DefaultCacheBehavior: AllowedMethods: ["GET", "HEAD", "OPTIONS", "PUT", "PATCH", "POST", "DELETE"] CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # See https://go.aws/3bJid3k TargetOriginId: !Sub 'copilot-${AppName}-${EnvironmentName}-origin' OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3 # See https://go.aws/3BIE8CP ViewerProtocolPolicy: allow-all ……
CloudFront関数を紐づけるには、DefaultCacheBehavior
下にFunctionAssociations
の項目を追加します。具体的には以下のようなYAMLパッチでオーバーライドします。
# copilot/environments/overrides/cfn.patches.yml - op: add path: /Resources/CloudFrontDistribution/Properties/DistributionConfig/DefaultCacheBehavior/FunctionAssociations value: - EventType: viewer-request FunctionARN: Fn::ImportValue: !Sub ${AppName}-${EnvironmentName}-RewriteFunctionARN
あとはデプロイするだけです。
$ copilot env deploy --diff Only found one environment, defaulting to: test ~ Description: CloudFormation environment template for infrastructure shared among Copilot workloads. -> CloudFormation environment template for infrastructure shared among Copilot workloads using AWS Copilot with YAML patches. ~ Resources/CloudFrontDistribution/Properties/DistributionConfig/DefaultCacheBehavior: + FunctionAssociations: + - EventType: viewer-request + FunctionARN: + Fn::ImportValue: !Sub ${AppName}-${EnvironmentName}-RewriteFunctionARN Continue with the deployment?
便利ですね!
終わりに
AWS CopilotはVPC周りなどの細かい手間が減るので個人的に好きなツールでしたが、最近のアップデートによって大きくカスタマイズ性が向上し実用性が高まりつつあると感じています。 たとえ今AWS Copilotを使っていない、という方にとってもこの記事でその高まりの片鱗を感じて頂ければ嬉しく思います。