CloudFormationによる環境構築をCodePipelineで自動化することを考えている方にお伝えしたいTips
1. 概要
本投稿は、CodePipelineを使ったCloudFormationによる環境構築について、実際の開発現場でも使えるTipsを紹介しています。
CloudFromationはテンプレートファイルに設定情報を記載しておくことで、何度でも同じ環境を再現できる非常に便利なサービスで、AWSの環境構築に利用している方も多いと思います。 しかし、実際の開発の現場では、VPC,EC2,S3・・・とサービス毎にテンプレートファイルが分かれているケースが多く、沢山のテンプレートを正しい順序で正確に実行するという作業に煩わしさを覚えている開発者の方も多いのではないでしょうか。
私自身、上述の課題を解決したいという思いを理由の一つに、CloudFormationによるデプロイをCodePipelineにより自動化しました。 しかし、自動化の恩恵を得るためには、テンプレートファイルの記載方法やCodePipelineの定義方法などに工夫が必要でしたので、Tipsとしてそれらを紹介します。
[対象読者]
CloudFromationの利用経験がある方。
CloudFromationのデプロイを効率化したいと思っている方。
IaCツールは沢山あるが、やっぱり純正ツールが良いよねと思っている方。
開発環境やDR環境の費用削減をしたい方。
※CloudFromationの利用経験がない方はまずは以下を参照することを推奨します。
20200826 AWS Black Belt Online Seminar AWS CloudFormation
[目次]
1. 概要
2. AWS CloudFormationとは
3. AWS CodePipelineとは
シンプルな使い方
CloudFormationテンプレートによるデプロイをCodePiplelineで繰り返し実行する際のTips
4. CodePipeline x CloudFormationを検討している方に是非実施していただきたいTips
CloudFormationテンプレートに関するTips
CloudFormationテンプレートはリソースのライフサイクルに合わせて分割する
クロススタックリファレンスを利用する
自動作成されるリソースもCloudFormation管理下に置くことを意識する
VPCフローログとCloudWatchLogsのロググループのテンプレートは分離する
CodePipelineに関するTips
CodePipelineのアクションはCloudFormationテンプレートで定義する
CodePipelineのアクションの順序(RunOrder)はガントチャートで整理する
パイプラインのアクション名は短い名前にする
CloudFormationアクションとCloudFormationスタックセットアクションの機能差分
CodePiplelineとデプロイ先環境のリージョンについて
【IAM】 CodePipelineのCloudFormationスタックセットアクションを使用する際のTips
自動作成されるCodePipeline用のIAMロールはさらに権限を絞る
CodePipelineのCloudFormationスタックセットアクションを使用する場合IAMロールの名称に指定がある
5. まとめ
2. AWS CloudFormationとは
引用:20200826 AWS Black Belt Online Seminar AWS CloudFormation
3. AWS CodePipelineとは
AWS CodePipeline は、ソフトウェアをリリースするために必要なステップのモデル化、視覚化、および自動化に使用できる継続的な配信サービスです。ソフトウェアリリースプロセスのさまざまなステージをすばやくモデル化して設定できます。CodePipeline はソフトウェアの変更を継続的にリリースするために必要なステップを自動化します。
引用:AWS CodePipeline とは。 - AWS CodePipeline
引用:20201111 AWS Black Belt Online Seminar AWS CodeStar & AWS CodePipeline
シンプルな使い方
CodePiplineには多様な機能がありますが、ソースステージとデプロイステージの二つだけのシンプルなパイプラインでも十分に自動化の恩恵を受けることができます。
以下にS3で作成するシンプルなパイプラインの大まかな作成手順を記載します。
CloudFormationテンプレートを格納するS3バケットを作成する。
CodePiplineのパイプラインを作成する。
作成したパイプラインの中で、1.で作成したソースステージを作成する。
ソースステージの後続タスクとして、デプロイステージを作成する。デプロイステージの中にアクションタイプとしてCloudFormationまたはCloudFormation StackSetsを指定してアクションを作成する。アクションはCloudFormationテンプレートの数だけ作成する必要がある。
※詳細はAWS公式ドキュメントのチュートリアルを参照の事。
CodePipeline チュートリアル - AWS CodePipeline
このようなPipelineを作成しておくことで、Zipに固めたCloudFormationテンプレート群をS3にアップロードするという一回の操作だけで、事前に定義した順序通りに全てのCloudFormationテンプレートを実行してくれます。ZipをS3にアップロードし、全てのステージが緑に変わったらデプロイ完了です。
CloudFormationテンプレートによるデプロイをCodePiplelineで繰り返し実行する際のTips
CodePiplineを再実行した際(ZipをS3に再アップロード)にテンプレートに変更がない場合はCloudFormationの機能(CodePipelineの機能ではない)でスキップされます。具体的にはCodePipelineのアクションは成功するが、CloudFormationの各スタックの"スタックインスタンス"タブの"状況の理由"欄を確認すると「No updates are to be perfomed」と記載されています。つまり、環境構築時、全てのCloudFormationテンプレートのうち、一部だけを更新したい場合も、全てのテンプレートを固めたZipをアップロードしても問題ありません。
環境構築時、全てのCloudFormationテンプレートのうち、一部だけを更新したい場合も、全てのテンプレートを固めたZipをアップロードしてください。作成済みアクションに対応するCloudFormationテンプレートがZipに含まれていない場合、当該アクションがエラーとなります。なお、エラーとなったアクション以降のアクショングループは実行されません。
エラーとなった場合も、完了済みのアクションがロールバックされることはありません。そのため、エラーは放っておくと割り切ってしまえばできたテンプレートから順次Zipに追加して繰り返し実行しても問題ありません。
補足説明1: ソースアクションに使用するS3のバージョニングを有効にする
ソースバケットを作成するときは、バケットでバージョニングを有効にしてください。既存の Amazon S3 バケットを使用する場合は、「バージョニングの使用」を参照して、既存のバケットでバージョニングを有効にします。
(https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/action-reference-S3.html)
補足説明2: ポーリングパイプラインを作成する際に必要となるCloudWatchEventsはCodePipeline作成時に合わせて作成するのが楽
下記の通り、 ポーリングパイプラインを作成する際の検出方法においてS3を使う場合はCloudWatchEventsの利用が推奨されています。
ポーリングパイプラインを推奨される変更検出方法に更新する - AWS CodePipeline
この時、下記を実行できるIAM権限が必要となるのですが、AWSマネジメントコンソールでCodePipeline作成時に新規IAMロールを作成可能(ラジオボタンで選択するだけ)ですので、それを利用するのが楽です。ちなみに、当該IAMロールに必要な権限は以下の通りです。
サービス:CloudWatchEvents
アクション:codepipeline:startPipelineExecution
4. CodePipeline x CloudFormationを検討している方に是非実施していただきたいTips
冒頭でも述べましたが、パイプラインによる自動化の恩恵を得るためには、テンプレートファイルの記載方法やCodePipelineの定義方法などに工夫が必要です。ここではそれら工夫点を紹介します。
CloudFormationテンプレートに関するTips
CloudFormationテンプレートはリソースのライフサイクルに合わせて分割する
以下のように、AWS環境内のリソースは用途によって作成から削除までのライフサイクルが異なります。ライフサイクルが異なるリソースを同じCloudFormationテンプレートに記載してしまうと、スタックの変更・削除が容易には行えなくります。例えば頻繁に設定変更されるEC2と監査用のログを補完するS3やログ管理サーバ(EC2)を同じCloudFormationテンプレートに記載していると、EC2変更時に意図せず別のリソースの設定を変更してしまうリスクが発生します。設定変更の影響範囲、環境変更作業が複雑になってしまうため、ライフサイクルごとにテンプレートは分離しましょう。
クロススタックリファレンスを利用する
CloudFormationテンプレートを複数に分割した場合、VPC ID・サブネットID・ELBのARN等の自動生成されるIDを後続のテンプレートでも利用できるようにする工夫が必要です。
クロススタックリファレンスを利用することで、先行テンプレートで作成された値を後続テンプレートで参照可能となります。
また、Mappingセクションを使ってクロススタックリファレンスの可読性を高めましょう。
例:
KMSを作成するCloudFormationテンプレート
~~略~~ Outputs: KMSKeyID: Value: !Ref KMSKey <- KMSのKeyIDを組み込み関数Refで取得 Export: Name: KMSKeyID <- 後続テンプレートで利用可能となるようKMSKeyIDをエクスポート
KMSを使用する後続のCloudFormationテンプレート
Mappings: CrossStackReference: KMSStack: KeyID: KMSKeyID <-KMSを使用するテンプレートのKeyIDにエクスポートしていたKMSKeyID を受け渡す。
参考:
AWS CloudFormation テンプレートのスタックにわたるリソースを参照する
注意: OutputsはNoEcho属性を指定してもマスクされませんので、センシティブな情報を取り扱うことは避けましょう。 センシティブな情報を扱う場合は、AWS Secrets Managerを使用してください。
自動作成されるリソースもCloudFormation管理下に置くことを意識する
AWSリソースの中には、明示的に作成をしていなくてもバックエンドで自動的に作成されるものがあります。
例: RDSをCloudFormationで作成する場合、EnableCloudwatchLogsExportsの設定によりCloudWatchLogsに自動的にロググループが作成されます。
AWS::RDS::DBInstance - AWS CloudFormation
EnableCloudwatchLogsExportsによりバックエンドで自動作成されるロググループには以下の注意点があります。
当該CloudFormationテンプレートに紐づくスタックを削除した場合、RDSは削除されるがロググループは削除されない。
自動作成されたロググループと同名のロググループの作成ができないため、スタック削除→再作成を行う場合エラーとなる。
バックエンドで自動作成されたロググループの設定はCloudFormationテンプレートに記載されていないため、設定変更(例えば保持期間など)をCloudFormationで変更できない。実施したい場合は、一度既存リソースのインポートを行う手間が発生する。
-> 解決策:ロググループを作成する別のCloudFormationテンプレートを用意し、RDSを作成するCloudFormationテンプレートを実行する前に、予めロググループを作成しておくことを推奨します。
また、 CloudFormationテンプレートで環境構築する場合には、原則明示的にテンプレートにリソースを定義することを心掛け、バックエンドで自動作成されることを避けましょう。自動作成されたリソースはいつどのタイミングで作成されたのかが分かりづらく、開発や運用に支障が出る可能性があります。また、バックエンドで自動作成されたものは、別途インポートなどの作業をしない限り、CloudFormationのスタック作成/削除の操作での管理や設定変更ができないため、環境変更に複数のインターフェース(CouldFormationとマネジメントコンソール)が必要となり運用作業が煩雑になります。
VPCフローログとCloudWatchLogsのロググループのテンプレートは分離する
以下のようにVPCフローログとCloudWatchLogsのロググループは一つのテンプレートに定義し、同じスタック内で作成することが可能です。 しかし、このスタックを削除するとCloudWatchLogsのロググループが削除されないという事象が発生します。
VPCFlowLog: DependsOn: LogGroup Type: AWS::EC2::FlowLog Properties: LogGroupName: VPCFlowLogName ~~略~~ LogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: LogGroupName ~~略~~
CloudWatchLogsのロググループが削除されない原因
スタック削除時にはVPCフローログとCloudWatchLogsのロググループともに直ぐに削除されますが、実はVPCフローログのデータ収集が中止されるまでに数分を要します。そのため、裏側では削除後もVPCフローログをCloudWatchLogsに送信する処理が動いており、結果として一度削除されたCloudWatchLogsのロググループ自動作成されるという挙動となります。
対処方法
VPCフローログとCloudWatchLogsのロググループのテンプレートは分離する。
CodePipelineでアクションを定義する際は、CloudWatchLogsのロググループのテンプレートの後にVPCフローログのテンプレートがデプロイされるようにRunOrderを調整する。
削除時はVPCフローログとCloudWatchLogsのロググループの削除の間に数分の間隔を設ける。
CodePipelineに関するTips
CodePipelineのアクションはCloudFormationテンプレートで定義する
CodePipelineアクションの設定を全てAWSマネジメントコンソールで行おうとすると、数十個あるアクションを何度も何度も手作業で行う必要があります。しかし、アクションをCloudFormationでデプロイしてしまえば、この煩わしい作業が軽減できます。
CodePipelineのCloudFormationテンプレートは以下のような構造となっています。
デプロイステージのActionsはCloudFormationテンプレートの数の分だけ定義が必要となりますが、以下のコメントで示した一部のパラメータを変更する必要こそあるものの、基本的にはCopy&Pasteで簡単に複数のActionを定義することができます。基本的にはName,StackName,Namespace,RunOrderの4つを変更すればOKです。AWSマネジメントコンソールで行うよりも効率的にミスなく定義できるのではないでしょうか。
Type: AWS::CodePipeline::Pipeline Properties: ArtifactStore: ArtifactStore ArtifactStores: - ArtifactStoreMap DisableInboundStageTransitions: - StageTransition Name: String RestartExecutionOnUpdate: Boolean RoleArn: String Stages: # ソースステージ Name: String Actions: Name: String ActionTypeId: ActionTypeId configuration: Json InputArtifacts: - InputArtifact Namespace: String OutputArtifacts: - OutputArtifact Region: String RoleArn: String RunOrder: Integer Blockers: - BlockerDeclaration # デプロイステージ Name: String Actions: # デプロイステージのActions部分をCloudFormationテンプレートの数だけCopy&Paste Name: String # わかりやすさのため一意になるように定義 ActionTypeId: ActionTypeId configuration: ActionMode: CHANGE_SET_REPLACE Capabilities: CAPABILITY_IAM,CAPABILITY_AUTO_EXPAND ChangeSetName: pipeline-changeset RoleArn: CloudFormation_Role_ARN StackName: my-pipeline-stack # 一意になるように定義 TemplateConfiguration: 'my-pipeline-stack::template-configuration.json' TemplatePath: 'my-pipeline-stack::template-export.yml' InputArtifacts: - InputArtifact Namespace: String # 必要に応じてAction毎に変更。私は普段CloudFormationテンプレートと一対一で対応づくように定義している。 OutputArtifacts: - OutputArtifact Region: String RoleArn: String RunOrder: Integer # CloudFormationテンプレートの実行順序を定義 Blockers: - BlockerDeclaration Tags: - Tag (参考:[https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codepipeline-pipeline.html])
CodePipelineのアクションの順序(RunOrder)はガントチャートで整理する
CodePipelineのアクションの順序の定義こそ、CodePipelineでCloudFormationテンプレートを実行することの醍醐味です。しかし、各テンプレート毎の依存関係を正しく把握し、無駄なく最適な解を見つけることは容易ではありません。そこで、ガントチャートを使用しましょう。実施すべきことは、シンプルに以下を実施するだけです。
全てのCloudFormationテンプレートをタスクとして入力する。
各テンプレートの前に実行されていなければならないテンプレートの番号を先行タスクとして入力する。
以下の図は投稿用にガントチャートをExcelで模したものですが、MSプロジェクトなどを使えば簡単に実行順序の最適化が可能です。このように、各テンプレートのRunOrderに設定すべき値(※)がガントチャートから一目瞭然でわかります。※以下の図では1~7の数字がRunOrderに設定する値
パイプラインのアクション名は短い名前にする
CodePipelineのアクション名はマネジメントコンソールで表示した場合、15文字程度しか表示されません。そのため、数の左側のような命名規則であれば問題ないのですが、右のように「システム名-環境名-リージョン名-アクション名-XX」の様な長い名称にしてしまうと、画面上から何のアクションが実行されたのかが判別できなくなってしまいます。アクション名は短くするか、海外の住所表記と日本の住所表記の違いの様によくある命名規約を逆順にするなどの工夫が必要です。 元画像の引用元:https://aws.amazon.com/jp/blogs/aws/codepipeline-update-build-continuous-delivery-workflows-for-cloudformation-stacks/
CloudFormationアクションとCloudFormationスタックセットアクションの機能差分
CodePipelineでCloudFormationスタックをデプロイする際、CloudFormationアクションとCloudFormationスタックセットアクションの二つのオプションを使用可能です。ここでは二つのアクションの主な差分を紹介します。CloudFormationスタックセットについて詳しくない方は、まずは以下の参考をご参照ください。 参考:AWS CloudFormation StackSets の操作 - AWS CloudFormation
CloudFormationアクション
指定可能なアクションモード:"スタックを作成または更新する","スタックを削除する","故障したスタックを取り換える","変更セットを作成または交換する","変更セットを実行する"
別リージョンへのスタック作成:CodePipelineでリージョンを指定可能。1アクションにつき1リージョン指定可能(デプロイ対象リージョンが複数ある場合はその分アクションを用意する)。
別アカウントへのスタック作成:不可
CloudFormationスタックセットアクション
指定可能なアクションモード:特になし(単純にスタックを作成または更新する事しかできない。)
別リージョンへのスタック作成:アクションを動作させるリージョンとデプロイ先のリージョンを指定可能。1アクションで複数リージョン指定可能。
別アカウントへのスタック作成:指定可能。1アクションで複数アカウント指定可能。
二種類のアクションの選び方
豊富なアクションモードの利用が必要な場合:CloudFormationアクション
複数アカウントにデプロイが必要な場合:CloudFormationスタックセットアクション
複数リージョンへのデプロイが必要な場合:どちらでも可能。ただし、CodePipelineの配置リージョン、CloudFormationスタックのデプロイ先リージョン、アクションプロバイダの組み合わせを考えて、構築自動化のためのアーキテクチャを検討する必要があります。
例:
パターン1. アクションプロバイダにCloudFormationアクションを使用して、リージョン毎にCodePipelineを構築する。
パターン2. アクションプロバイダにCloudFormationアクションを使用して、あるリージョンのCodePipelineから複数のリージョン上の環境を構築する。
パターン3. アクションプロバイダにCloudFormationスタックセットアクションを使用して、あるリージョンのCodePipelineから複数のリージョン上の環境を構築する。
ただし、一部Codepipelineが未対応のリージョン(大阪リージョン等)あるため、その場合はCloudFormationスタックセットアクションを使う必要があります。例えば、コスト削減などを目的に通常時は大阪リージョンのDR環境/開発環境を停止しておいて、必要な時だけ環境を自動構築するといった事をしたい場合、東京リージョンのCodePipelineからCloudFormationスタックセットアクションを使って大阪リージョンの環境を構築するといったことをご検討ください。
【IAM】 CodePipelineのCloudFormationスタックセットアクションを使用する際のTips
自動作成されるCodePipeline用のIAMロールはさらに権限を絞る
ここでは、ソースをS3とするCodePipelineを作成する際のCodePipeline用のIAMロール(CodePipelineのRoleArnに設定)を紹介します。
CodePipeline用のIAMロールは、AWSマネジメントコンソールでCodePipelineを作成する際に新規作成可能(ラジオボタンで選択するだけ)ですが、ここで紹介しているIAMポリシーは権限を最小にするためより厳しいポリシーにしていますので、参考にしてください。
サービス:CodePipeline
アクション:
CloudFormationスタックセットの作成権限(アクション:"cloudformation:*")
CloudFormationにAWSCloudFormationStackSetAdministrationRoleをパスするための権限(アクション:"iam:PassRole")
KMSのDecrypt権限(アクション:"kms:Decrypt")※KMSでS3を暗号化している場合のみ
KMSのGenerateDataKey権限(アクション:"KMS:GenerateDataKey")※KMSでS3を暗号化している場合のみ
CodePipelineのCloudFormationスタックセットアクションを使用する場合IAMロールの名称に指定がある
以下の通り、指定の名前でCodePipelineの各アクションのAdministrationRoleArnとExexutionRoleNameにロールを作成する必要があります。
管理者アカウントのロールは AWSCloudFormationStackSetAdministrationRole という名前にする必要があります。各ターゲットアカウントのロールは AWSCloudFormationStackSetExecutionRole という名前にする必要があります。(https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html)
5. まとめ
CodePipelineによりCloudFormationテンプレートのデプロイを自動化する際のTipsを紹介しました。
CloudFormationテンプレートの数だけ手作業でスタックを手動作成する苦労と比較すると、大きく手間が削減されるはずです。また、CodePipeline画面で成功/失敗が一目でわかるようになるメリットもあります。
さらに、S3にZipをアップロードするひと手間で環境構築ができるようにすることで、開発時は環境を削除しておくことが可能となり、コスト削減も見込めます。