Open6

CDK Pipelineを導入するときの手順メモ

pirosikickpirosikick

0. 前提や事前準備

  • マルチアカウントでのデプロイの話
    • CI/CD用のAWSアカウントから、アプリケーション用のAWSアカウントにデプロイ
  • AWSアカウントは事前作っておいて、Admin権限を持っておく
    • 自社の場合、ControlTowerを使って管理している。
    • aws-sso-utilsで設定までやっているとよい。

1. aws-cdkをインストール

$ npm install -g aws-cdk
pirosikickpirosikick

2. cdk init

アプリケーションと同じリポジトリにcdkというディレクトリを作成し、その中にCDKのコードを置くことを想定。

$ mkdir cdk
$ cd cdk
$ cdk init app --language typescript
pirosikickpirosikick

3. @aws-cdk/pipelinesのインストール

cdkディレクトリで@aws-cdk/pipelinesをインストール。

$ npm install @aws-cdk/pipelines

4. cdk.json"@aws-cdk/core:newStyleStackSynthesis": trueを追加

CDK Pipelinesを利用する場合に必要。

{
  "app": "…",
  "context": {,
    "@aws-cdk/core:newStyleStackSynthesis": true
  }
}
pirosikickpirosikick

5. 空のパイプラインをデプロイする

5.1 CodePipelineで接続を作る

レポジトリにプッシュしたコードをCodePipelineがプルするために、CodePipelineに接続を作る。

  1. CI/CD用のAWSアカウントでAWSコンソールに入る
  2. AWSコンソールでCodePipelineを開く
  3. 左メニュー → 設定 → 接続をクリック
  4. 「接続を作成」ボタンを押し、使うリポジトリサービス(ex: Github, BitBucket...)を選択し、認証する
  5. 接続のARNが発行されるので、それをコピーしておく

5.2 パイプラインのStackを書く

cdk/lib/pipeline-stack.ts

ファイル名やクラス名のベストプラクティス、わからん。

cdk/lib/pipeline-stack.ts
// cdk/lib/pipeline-stack.ts
import { Construct, Stack, StackProps } from "@aws-cdk/core";
import {
  CodePipeline,
  CodePipelineSource,
  ShellStep,
} from "@aws-cdk/pipelines";

export class PipelineStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    new CodePipeline(this, "Pipeline", {
      synth: new ShellStep("Synth", {
        input: CodePipelineSource.connection(
          "OWNER/REPO", // リポジトリがgithub.com/hoge/fugaなら、"hoge/fuga"
          "main", // ブランチ名
          {
            connectionArn: "接続のARN",
          }
        ),
        commands: [
          // アプリケーション側でビルドがあれば
          "npm ci",
          "npm run build",
          // CDKのビルド
          "cd cdk",
          "npm ci",
          "npm run build",
          "npx cdk synth",
        ],
        // 今回cdkディレクトリ以下にCDKのコードがあるので指定
        primaryOutputDirectory: "cdk/cdk.out",
      }),
      // マルチアカウントでデプロイする場合に必要
      crossAccountKeys: true,
    });
  }
}

cdk/bin/cdk.ts

cdk init後の状態だと、cdk/lib/cdk-stack.tsのStackをインスタンス化しているコードになっているはず。
それを↑で書いたPipelineStackのインスタンス化に変える。envにはCI/CD用のAWSアカウントのIDやリージョンを指定する。
最後にapp.synth()も書く。

cdk/bin/cdk.ts
import "source-map-support/register";
import * as cdk from "@aws-cdk/core";
import { PipelineStack } from "../lib/pipeline-stack";

const app = new cdk.App();

new PipelineStack(app, "HogeHogeAppPipelineStack", {
  env: { account: "CI/CDのAWSアカウントのID(数字のやつ)", region: "ap-northeast-1" },
});

app.synth();

この辺のコードは正直雰囲気でしか理解できておらず、いつか完全理解したい。

5.3 cdk bootstrap

マルチアカウントでデプロイする場合、CI/CD用のAWSアカウントとデプロイ先AWSアカウントで2回cdk bootstrapが必要。
デプロイ先は--trust CI/CD用AWSアカウントのIDを付けて実行する。
CloudFormationの実行権限をAdministratorAccessにしてしまっているが、いい方法がわからん!

$ cd cdk

# CI/CD用AWSアカウントのcdk bootstrap
$ npx cdk bootstrap \
    --profile CI/CD用AWSアカウントのAdmin権限を持ったprofile \
    --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \
    aws://CI/CD用AWSアカウントのID/ap-northeast-1

# デプロイ先AWSアカウントのcdk bootstrap
$ npx cdk bootstrap \
    --profile デプロイ先AWSアカウントのAdmin権限を持ったprofile \
    --trust CI/CD用AWSアカウントのID \
    --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \
    aws://デプロイ先AWSアカウントのID/ap-northeast-1

5.4 cdk deploy

最初の1回は手元でcdk deployが必要。その後はリポジトリにプッシュすれば、デプロイできるようになる。引数にcdk/bin/cdk.tsでインスタンス化したパイプラインのスタックのIDを渡す(上記例だとHogeHogeAppPipelineStack)。

$ npx cdk deploy \
    --profile CI/CD用AWSアカウントの管理者権限のprofile \
    HogeHogeAppPipelineStack

再度、cdk deployが必要なケース。

CodePipelineのオプションで渡している部分を変更するときは再度cdk deployが必要な場合があるっぽい。例えば、synthに渡しているShellStepcommandsを変更する場合は、コミットしてプッシュしても反映されなかった。この辺のルールはよくわかってないが、コードを変更してプッシュしても反映されない場合は試してみるとよさそう。

pirosikickpirosikick

6. アプリケーションのStackを書く

cdk/lib/hoge-hoge-app-stack.ts
import * as cdk from "@aws-cdk/core";

export class HogeHogeAppStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    
    …好きなように書いて…
  }
}

7. Stageを追加する

まず、Stageのサブクラスを作る。そのコンストラクタ内で↑で作ったアプリケーションのStackをインスタンス化する。この時に渡すIDのベストプラクティスがわからないので、とりあえずStackと同じ名前にしている。

cdk/lib/deploy-app-stage.ts
import * as cdk from "@aws-cdk/core";
import { HogeHogeAppStack } from "./hoge-hoge-app-stack";

export class DeployAppStage extends cdk.Stage {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StageProps) {
    super(scope, id, props);

    new HogeHogeAppStack(this, "HogeHogeApp");
  }
}

CodePipelieaddStageメソッドで↑のStageのサブクラスを追加する。

cdk/lib/pipeline-stack.ts
import * as cdk from "@aws-cdk/core";
import {
  CodePipeline,
  CodePipelineSource,
  ShellStep,
} from "@aws-cdk/pipelines";
import { DeployAppStage } from "./deploy-app-stage";

export class PipelineStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const pipeline = new CodePipeline(this, "Pipeline", { …割愛… });

    // 追記
    pipeline.addStage(
      new DeployAppStage(this, "Prod", {
        env: { account: "デプロイ先AWSアカウントのID", region: "ap-northeast-1" },
      })
    );
  }
}

コードをコミットしてプッシュすれば、CodePipelieがデプロイを実行する。

上記の例では本番環境のみを追加しているが、テスト環境などある場合はその分addStageを増やせばよい。また、本番環境前に手動での承認を挟む、なども可能。