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で作成するシンプルなパイプラインの大まかな作成手順を記載します。

  1. CloudFormationテンプレートを格納するS3バケットを作成する。

  2. CodePiplineのパイプラインを作成する。

  3. 作成したパイプラインの中で、1.で作成したソースステージを作成する。

  4. ソースステージの後続タスクとして、デプロイステージを作成する。デプロイステージの中にアクションタイプとして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変更時に意図せず別のリソースの設定を変更してしまうリスクが発生します。設定変更の影響範囲、環境変更作業が複雑になってしまうため、ライフサイクルごとにテンプレートは分離しましょう。

  • 監査用のログを補完するS3やログ管理サーバ(EC2)

  • 一時的な性能不足を補うスポットインスタンス(EC2)

  • AWSアカウント内のNWハブとしての役割を担う Transit Gateway

クロススタックリファレンスを利用する

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 テンプレートのスタックにわたるリソースを参照する

Ref - AWS CloudFormation

Mappings - AWS CloudFormation

注意: OutputsはNoEcho属性を指定してもマスクされませんので、センシティブな情報を取り扱うことは避けましょう。 センシティブな情報を扱う場合は、AWS Secrets Managerを使用してください。

パラメータ - AWS CloudFormation

自動作成されるリソースも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テンプレートを実行することの醍醐味です。しかし、各テンプレート毎の依存関係を正しく把握し、無駄なく最適な解を見つけることは容易ではありません。そこで、ガントチャートを使用しましょう。実施すべきことは、シンプルに以下を実施するだけです。

  1. 全てのCloudFormationテンプレートをタスクとして入力する。

  2. 各テンプレートの前に実行されていなければならないテンプレートの番号を先行タスクとして入力する。

以下の図は投稿用にガントチャート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
アクション:

  • ソースS3バケットの参照権限/アーティファクト出力用S3バケットの更新権限(アクション:"s3:*")

  • 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をアップロードするひと手間で環境構築ができるようにすることで、開発時は環境を削除しておくことが可能となり、コスト削減も見込めます。