【AWS CDK (Python)】 クロスアカウントでのデプロイ方法

## はじめに
本記事は、私自身の備忘録を兼ねてAWS CDKをこれから始める方の一助になればと思い、AWS CDKの使い方等をまとめたものです。
今回は、別アカウントにAWS CDKでリソースをデプロイする方法を確認します。
なお、本記事は私自身の経験を基に記載していますが、間違いがあったらすみません。

## 別アカウントにAWS CDKでリソースをデプロイしたい！
具体的には以下のような構成になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/01812436-c653-629b-e9e6-1166bfaab543.png)

概要としては、
1. Account Bでcdk bootstrapを実行し、必要なRoleを作成
2. Account Aでcdk deployを実行し、Account Bにリソースを作成（Account Bで適切にcdk bootstrapができていればcdkで勝手にAssumeRoleしてくれる）

です。
図にすると単純ですね。。実際、やってること自体は難しくないです。

## 環境
本記事は以下の環境を使用して記載しています。
* AWS Cloud9
* AWS CDK：2.142.1
* Python： 3.12.3
* Node.js： 20.13.1

## クロスアカウントでのCDKデプロイ
以下の流れでクロスアカウントでのCDKデプロイ確認をしていきます。

[準備1. （Account A & B）Cloud9の作成](#準備1-account-a--bcloud9の作成)
[準備2. （Account A & B）AWS CDKのインストール・初期化](#準備2-account-a--baws-cdkのインストール初期化)
[1. （Account B）cdk bootstrapの実行](#1-account-bcdk-bootstrapの実行)
[2. （Account A）Cloud9にアタッチするインスタンスプロファイルの作成](#2-account-acloud9にアタッチするインスタンスプロファイルの作成)
[3. （Account A）CDKプログラムの作成](#3-account-acdkプログラムの作成)
[4. （Account A）cdk deployの実行](#4-account-acdk-deployの実行)
***

#### 準備1. （Account A & B）Cloud9の作成
まず、作業場所になるCloud9を作成します。
Cloud9の作成は、以下の記事を参考にしてください。
* [【AWS CDK/Python】 AWS CDKの始め方](https://qiita.com/takataka987/items/4448dbf06d8c70173305#1-cloud9%E3%81%AE%E4%BD%9C%E6%88%90)

#### 準備2. （Account A & B）AWS CDKのインストール・初期化
次に、作成したCloud9にAWS CDKをインストールし初期化します。
AWS CDKのインストール・初期化は、以下の記事を参考にしてください。
* [【AWS CDK/Python】 AWS CDKの始め方](https://qiita.com/takataka987/items/4448dbf06d8c70173305#3-aws-cdk%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E5%88%9D%E6%9C%9F%E5%8C%96)

#### 1. （Account B）cdk bootstrapの実行
ここから本題のクロスアカウントでのCDKデプロイに必要な設定を行っていきます。
まず、デプロイされる側のアカウントでcdk bootstrapを実行します。
このbootstrapでデプロイに必要なRoleが作成されます。
ただし、今回はクロスアカウントでのデプロイになるため、ここで作成されるRoleにデプロイする側のアカウントからAssumeRoleする必要があるため、必要な設定を行っていきます。（と言ってもbootstrapにオプションを追加するだけですが。。）
肝は`--trust <Account A>`です。このオプションを付けることによって、bootstrapで作成されるRoleの信頼関係にAccount Aが追加され、Account AからのAssumeRoleが可能になります。
※必要なリージョンについて全部実行してください。
```console
$ cd cdk-app/
$ source .venv/bin/activate
$ cdk bootstrap aws://<Account B>/ap-northeast-1 --trust <Account A> --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess
 ⏳  Bootstrapping environment aws://<Account B>/ap-northeast-1...
Trusted accounts for deployment: <Account A>
Trusted accounts for lookup: (none)
Execution policies: arn:aws:iam::aws:policy/AdministratorAccess
CDKToolkit: creating CloudFormation changeset...
 ✅  Environment aws://<Account B>/ap-northeast-1 bootstrapped.

$ 
```

Account BのCloud9の使用は、このbootstrapだけなので、必要に応じて削除してください。

#### 2. （Account A）Cloud9にアタッチするインスタンスプロファイルの作成
次にCloud9にアタッチするインスタンスプロファイルを作成します。
Cloud9には、標準でAWSCloud9SSMInstanceProfileというインスタンスプロファイルが付いているのですが、このインスタンスプロファイルのままでは、先ほどbootstrapで作成したRoleにAssumeRoleできないので、インスタンスプロファイルを個別に作成します。
IAMのコンソール画面からRoleを作成します。今回は、Cloud9用のRoleなので、信頼されたエンティティタイプはAWSのサービスを選択し、サービスでEC2を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/22e3c3a3-fc94-720e-121f-c788346652de.png)

ポリシーには、AWSCloud9SSMInstanceProfileを選択し、適当な名前を付けてRoleを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/ffb3c450-2aab-073f-53ff-a4e3c20f1ab0.png)

Roleを作成したらRoleの編集画面を開き、以下のポリシーを追加作成します。
このポリシーは、Account Bに作成したRoleへのAssumeRoleを許可するポリシーになります。

```json
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "Statement1",
			"Effect": "Allow",
			"Action": [
				"sts:AssumeRole"
			],
			"Resource": [
				"arn:aws:iam::<Account A>:role/cdk-hnb659fds-*-<Account A>-*"
			]
		}
	]
}
```
ポリシーに適当な名前を付けて変更を保存してください。
これでインスタンスプロファイルの準備が出来ましたので、EC2のコンソール画面からCloud9のインスタンスプロファイルを変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/fe929952-78e0-cc7f-e917-c17526f1fc2d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/d9a81c45-4679-02db-a02b-4b5f1bc16a21.png)

Cloud9は、標準でAWS Managed Temporary Credentialsという一時クレデンシャルで動いていて、このままだとインスタンスプロファイルが無効化されているので、AWS Managed Temporary Credentialsを無効化します。
Cloud9を起動し、メニューからPreferencesを開き、AWS SettingsのAWS managed temporary credentialsをOFFにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/83669a1b-6846-93b4-9363-7eb04ccaaeba.png)

#### 3. （Account A）CDKプログラムの作成
ここまでで、クロスアカウントでのデプロイに必要な設定自体はほぼ終わりです。
後は、実際にデプロイするCDKのプログラムを作成してデプロイするだけです。
ので、プログラムを作っていきます。
まず、`cdk_app_stack.py`ですが、今回は、クロスアカウントでのデプロイが確認したいだけなので、S3バケットを作成するだけの簡単なプログラムを作成します。
```python:cdk_app/cdk_app_stack.py
from aws_cdk import (
    Stack,
    aws_s3 as s3,
    RemovalPolicy
)
from constructs import Construct

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        s3bucket = s3.Bucket(
            self,
            "MyS3Bucket",
            bucket_name = "cdk-test-bucket-2024mmdd",
            removal_policy=RemovalPolicy.DESTROY
        )
```

次に、`app.py`を作成します。
ここで、`env`パラメータの`account`に`<Account B>`を指定することで、Account Bにデプロイすることができるようになります。
```python:app.py
#!/usr/bin/env python3
import os
import aws_cdk as cdk
from cdk_app.cdk_app_stack import CdkAppStack

app = cdk.App()
CdkAppStack(
        app,
        "CdkAppStack",
        env={'account': '<Account B>', 'region': 'ap-northeast-1'}
    )

app.synth()
```

プログラムの作成は、以上です。

#### 4. （Account A）cdk deployの実行
では、実際にデプロイしてみます。
デプロイは普通にcdk deployで問題ありません。クロスアカウントでデプロイするために特別なオプションは不要です。
ちなみにbootstrapもAccount Aでは必要ありません。ただ、Account Bだけでなく、Account Aに対しても何等かのリソースをデプロイする場合は、bootstrapが必要になるので、注意ください。

```console
$ cd cdk-app/
$ source .venv/bin/activate
$ cdk deploy

✨  Synthesis time: 15.16s

CdkAppStack:  start: Building 6143164220b2f020963562e3fb73eaebf7092b89549bbdc020f64eaaeffeb57a:<Account B>-ap-northeast-1
CdkAppStack:  success: Built 6143164220b2f020963562e3fb73eaebf7092b89549bbdc020f64eaaeffeb57a:<Account B>-ap-northeast-1
CdkAppStack:  start: Publishing 6143164220b2f020963562e3fb73eaebf7092b89549bbdc020f64eaaeffeb57a:<Account B>-ap-northeast-1
CdkAppStack:  success: Published 6143164220b2f020963562e3fb73eaebf7092b89549bbdc020f64eaaeffeb57a:<Account B>-ap-northeast-1
CdkAppStack: deploying... [1/1]
CdkAppStack: creating CloudFormation changeset...

 ✅  CdkAppStack

✨  Deployment time: 36.44s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:<Account B>:stack/CdkAppStack/0430ad70-1903-11ef-81c1-0a444c6e54b1

✨  Total time: 51.59s


$ 
```

cdk deployした結果を確認してみます。
まずは、Account BのCloudFormationのコンソールで確認してみます。
ちゃんとCREATE_COMPLETEが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/4a9ab564-e0ec-3bb0-a4c1-18793d75092b.png)

続いて、S3バケットを確認すると、こちらもきちんと出来てることが確認できました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/2934f74e-2301-4112-a2ea-e3b1e891648e.png)

## まとめ
クロスアカウントでのCDKデプロイを確認しました。複数のアカウントを管理するシステムでは、共通のデプロイ環境からインフラを管理・デプロイすることも多いのではないかと思いますので、そういった場合に役立つかなと思います。
最後まで読んでいただいてありがとうございます。
少しでも参考になれば幸いです。
#### 参考文献
* [ブートストラッピング](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/bootstrapping.html)