CDK Pipelines(Python)を使ってマルチアカウントへデプロイできるパイプラインを作成する
0. パイプラインをつくって試行回数を増やしたい
さて、マルチアカウントでSSOを設定する方法について前回の記事でまとめました。
今度はマルチアカウントにデプロイするパイプラインを作りたいと思います。
数パターンを考えましたが、
- devアカウント、stgアカウント、prodアカウントそれぞれにパイプラインを作成する
- manageアカウントにパイプラインを作成して各アカウントへクロスアカウントデプロイ
stgにデプロイして、確認してからApprovalするというパターンを取りたかったので、以下の構成を目指しました。
マネコンぽちぽちで試しにつくっていましたが、CodePipelineのDeployステージのCloudFormationで苦戦して断念。
そこでこちらのAWS公式ブログ を参考にCDK pipelinesを試しました。
ブログはTypescriptですが今回はpythonで試してみます。
サンプルのプロジェクトはこちらにて公開しています。
以下の手順で進めていきます
- 0. パイプラインをつくって試行回数を増やしたい
- 1. account idを調べる
- 2. secrets managerにgithub-tokenを登録する
- 3. cdk init
- 4. remote repositoryとしてgithubを登録する
- 5. .envの設定等
- 6. クロスアカウントの信頼関係を登録
- 7. lambdaのstackの追加
- 8. pipelineのstackを追加
- 9. app.pyを修正
- 10. pipelineのstackをcdk deploy
- 11. githubにpush
- まとめ
1. account idを調べる
マネコンからならこちらを確認。
2. secrets managerにgithub-tokenを登録する
AWSのmanageアカウントのSecretManagerにtokenを登録
3. cdk init
ひとまず今回はHelloAppという名前のプロジェクトということで作成します。
$ mkdir HelloApp && cd HelloApp/ $ cdk init --language=python $ source .venv/bin/activate $ pip install -r requirements.txt
4. remote repositoryとしてgithubを登録する
$ git remote add origin <git-hubのhttps url>
5. .envの設定等
今回の例では影響が少ないですが、アカウントIDやバケット名などの情報は環境変数で管理していきます。
$ pip install python-dotenv $ touch .env $ echo 'DEV_ACCOUNT_ID="<AWSアカウントID>"' >> .env $ echo 'STG_ACCOUNT_ID="<AWSアカウントID>"' >> .env $ echo 'PROD_ACCOUNT_ID="<AWSアカウントID>"' >> .env $ echo 'MANAGE_ACCOUNT_ID="<AWSアカウントID>"' >> .env
また、cdk独自の設定項目も追加
{ "app": "python3 app.py", "context": { "@aws-cdk/core:newStyleStackSynthesis": "true" <= これを追加 } }
また、今回使用するcdkのモジュール類をpip installする setup.pyのsetuptools.setup()のinstall_requiresに以下を追加。 バージョンは適宜修正してください。
[ "aws-cdk.core==1.88.0", "aws-cdk.aws-codepipeline==1.88.0", "aws-cdk.aws-codepipeline-actions==1.88.0", "aws-cdk.aws-codecommit==1.88.0", "aws-cdk.aws-codebuild==1.88.0", "aws-cdk.pipelines==1.88.0", "aws_cdk.aws_lambda==1.88.0", "aws-cdk.aws_logs==1.88.0" ]
$ pip install -r requirements.txt
6. クロスアカウントの信頼関係を登録
クロスアカウントでCloudFormationデプロイをする際に、このコマンドで信頼関係を定義することができる
$ cdk bootstrap --profile <manageアカウントのprofile> --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess aws://<manageアカウントのID>/ap-northeast-1 $ cdk bootstrap --profile <devアカウントのprofile> --trust <manageアカウントのID> --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess aws://<devアカウントのID>/ap-northeast-1 $ cdk bootstrap --profile <stgアカウントのprofile> --trust <manageアカウントのID> --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess aws://<stgアカウントのID>/ap-northeast-1 $ cdk bootstrap --profile <prodアカウントのprofile> --trust <manageアカウントのID> --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess aws://<prodアカウントのID>/ap-northeast-1
7. lambdaのstackの追加
今回は簡単なLambdaをサンプルでデプロイします。
mkdir -p functions/hello touch functions/hello/index.py
lambda自体は簡単なreturnをするだけ
# functions/hello/index.py def lambda_handler(event, context): return { "statusCode": 200, "body": "hello from lambda" }
そして、これらのコードをデプロイするためのCDKを記載します。
$ touch hello_app/hello_app_stack.py
# hello_app/hello_app_stack.py from aws_cdk import ( core, aws_lambda as lambda_, aws_logs as logs, ) class HelloAppStack(core.Stack): def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) hello_app_func = lambda_.Function(self, "HelloAppFunction", code=lambda_.Code.from_asset('functions/hello'), handler="index.lambda_handler", runtime=lambda_.Runtime.PYTHON_3_8, tracing=lambda_.Tracing.ACTIVE, timeout=core.Duration.seconds(29), memory_size=128, ) logs.LogGroup(self, 'HelloAppFunctionLogGroup', log_group_name='/aws/lambda/' + hello_app_func.function_name, retention=logs.RetentionDays.TWO_WEEKS )
8. pipelineのstackを追加
(1)PipelineStageのStackでlambdaのStackを読み込み
PipelineStageクラスの中で7.で作成したLambdaStackのクラスを初期化します。
# pipeline_lib/pipeline_stage.py from aws_cdk import ( core ) from hello_app.hello_app_stack import HelloAppStack class PipelineStage(core.Stage): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) HelloAppStack(self, self.node.try_get_context('service_name') + '-stack')
つづいて、必要に応じでステージに応じたpipelineのスタックを追加します。 Approvalつきのprodのpipelineをいかに例示します。
# pipeline_lib/pipeline_master_stack.py from aws_cdk import ( aws_codepipeline as codepipeline, aws_codepipeline_actions as actions, aws_codecommit as codecommit, aws_codebuild as codebuild, pipelines, core ) from pipeline_lib.pipeline_stage import PipelineStage import os STAGE = "stg" STAGE2 = "prod" class PipelineMasterStack(core.Stack): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) sourceArtifact = codepipeline.Artifact() cloudAssemblyArtifact = codepipeline.Artifact() pipeline = pipelines.CdkPipeline(self, 'Pipeline', pipeline_name=self.node.try_get_context( 'repository_name') + "-{}-pipeline".format(STAGE), cloud_assembly_artifact=cloudAssemblyArtifact, source_action=actions.GitHubSourceAction( action_name='GitHub', output=sourceArtifact, oauth_token=core.SecretValue.secrets_manager('github-token'), owner=self.node.try_get_context( 'owner'), repo=self.node.try_get_context( 'repository_name'), branch=STAGE2 ), synth_action=pipelines.SimpleSynthAction( synth_command="cdk synth", install_commands=[ "pip install --upgrade pip", "npm i -g aws-cdk", "pip install -r requirements.txt" ], source_artifact=sourceArtifact, cloud_assembly_artifact=cloudAssemblyArtifact, environment={ 'privileged': True }, environment_variables={ 'DEV_ACCOUNT_ID': codebuild.BuildEnvironmentVariable(value=os.environ['DEV_ACCOUNT_ID']), 'STG_ACCOUNT_ID': codebuild.BuildEnvironmentVariable(value=os.environ['STG_ACCOUNT_ID']), 'PROD_ACCOUNT_ID': codebuild.BuildEnvironmentVariable(value=os.environ['PROD_ACCOUNT_ID']), 'MANAGE_ACCOUNT_ID': codebuild.BuildEnvironmentVariable(value=os.environ['MANAGE_ACCOUNT_ID']) } ) ) stg = PipelineStage(self, self.node.try_get_context('repository_name') + "-{}".format(STAGE), env={ 'region': "ap-northeast-1", 'account': os.environ['STG_ACCOUNT_ID']} ) stg_stage = pipeline.add_application_stage(stg) stg_stage.add_actions(actions.ManualApprovalAction( action_name="Approval", run_order=stg_stage.next_sequential_run_order() )) prod = PipelineStage(self, self.node.try_get_context('repository_name') + "-{}".format(STAGE2), env={ 'region': "ap-northeast-1", 'account': os.environ['PROD_ACCOUNT_ID']} ) pipeline.add_application_stage(app_stage=prod)
一連の処理の中でcdkのcontextから値をとっている部分があるので、 sampleを動かすためには以下の設定も必要
$ touch cdk.context.json
{ "owner": "<github repositoryの組織>", "repository_name": "<repository名>", "service_name": "hello-app" }
9. app.pyを修正
cdkのエントリーポイントを修正して、pipelinesのstackを構成するように指定する
# app.py from aws_cdk import core from pipeline_lib.pipeline_master_stack import PipelineMasterStack from dotenv import load_dotenv import os load_dotenv() app = core.App() PipelineMasterStack( app, "{}-master-pipeline".format(app.node.try_get_context('service_name')), env={ 'region': "ap-northeast-1", 'account': app.account } ) app.synth()
10. pipelineのstackをcdk deploy
ここまでてきていればいいので
$ cdk list --profile <manageアカウントのprofile>
でデプロイ可能なapp名を確認して、
$ cdk deploy --profile <maageアカウントのprofile> <デプロイするapp名>
11. githubにpush
プロジェクト全体をgithubにpushしてpipelineが動作するのを確認する。
まとめ
cdk pipelinesを使用してかなり簡単にパイプラインを作成することができました。 これを参考に雛形のプロジェクトをつくっておけばマイクロサービス * ステージごとなど、たくさんのパイプラインも手間なく作れますね。
以上です。