SPECIALIST

多様な専門性を持つNRIデジタル社員のコラム、インタビューやインサイトをご紹介します。

BACK

AWS CDK Pipelines modern APIでCodeBuild群からなるパイプラインを作ってみる

パイプライン自動構築までの旅路

みなさんこんにちは。NRIデジタルの松村です。他の私の投稿を読んでくださった方、お久しぶりです。今回は、私が最近中心的に取り組んでいるプロダクトである API Atelier の開発現場より、パイプライン自動構築に関するトピックスを紹介したいと思います。少し話が長くなりますが、お付き合いください。
なお、概要部分ではあまりCDKを知らない方でもイメージして頂けるように、CDKの用語(コンストラクトなど)は極力使用しない形で記載しています。一方で、後半は具体的な実装の話になりますので、そのあたりを解禁しています。そのため、途中で使用している用語感がガラッと変わっているようにみえるかもしれませんがご容赦ください。

みなさん、CI/CDしていますでしょうか? CI/CDを行うためにはパイプラインが欠かせませんよね。素早く柔軟な変更ができるように、というコンセプトで構築している API Atelier でも非常に重要な要素であり、パイプラインを作ってきました。基本構成はこんな感じです。

ところで、AWSサービスなどを個別で構築している場合、構成の変更がパイプラインと独立してしまいます。すると、リリースする時の順序を非常に気にしながら進める必要が出てきます。さらに、開発環境/本番環境で環境が分かれる場合、開発環境でその手順を確認・組み立て、本番環境用の手順化して慎重にリリースする必要が出てきます。この構成変更も含めてパイプラインでできるとよりリリースがしやすくなりますよね! そこで、AWS CDK という IaC の仕組みを利用し、AWSサービスの構築もアプリケーションと同じリポジトリに入れてコードベースで管理することとしました。これで、アプリだけではなくAWSサービスも含めたテストとリリースの仕組みができることになります。

しかし、この構成になった時に、デプロイが自動化されていない要素が1つ残っています。何か? そう、パイプライン自身です。でも、それをするには自分で自分自身を更新するという必要があります。どう考えてもクレイジーですよね。まるで自分の尻尾を噛む某蛇のようです。でも、そんなクレイジーを実現するサービスが作られました。それが、AWS CDK Pipelines です。2020/10に開発者プレビューで公開されました。今までのパイプラインをこちらのライブラリを使って構築するように再構成することで、パイプライン自身の更新もCI/CDに組み込むことができるようになりました。

そして、長らく開発者プレビューだったAWS CDK Pipelinesですが、2021/12 に開催されたAWSの年次最大イベントである re:Invent で CDKv2 の正式リリースが発表されたことを契機に、全体的な構成を大幅に見直して正式リリースをする運びとなりました。開発者プレビュー時代に利用していたモジュール群は original API、一方で正式リリースされたモジュール群は modern API と呼ばれています。主な利用方法に関して記載されたブログや、original API から modern API への移行方法について記載されたドキュメントも合わせて公開されています。なお、これにあわせ、v2では original API は廃止されています。最新のライブラリを使いたい場合は modern API を使用する必要があります。

さて、ここまでは AWS CDK Pipelines を「パイプライン更新自体をCI/CDに組み込めるツール」という側面からご説明してきましたが、実はもう1つ、主要な機能、というか考え方が含まれています。それは、「CodeBuild / buildspec を使わずに直接CDKの要素をパイプラインに組み込める」ということです(上記はこのイメージ図です)。元々は、パイプライン内でCDKを実行する方法として、buildspecという「実行手順書」を作成し、その中でコマンドを実行する形で実現していました。ただ、この方法は、せっかくPipelineもCDKで記述していることを考えると、無駄な手間を挟んでいる形になります。

そういうわけで、 modern API では、従来のような CodeBuild を繋げて実行するようなパイプライン構成ではなく、CDKの要素を直接定義してパイプライン内で構築するという方法が主流で考えられるようになっています。そのため、上記のような流れで既存のパイプライン構成を元に更新の自動化というメリットを享受したい、という使い方は主流ではなく、色々苦労する状態になりました。本記事では、このあたりの苦労ポイントや解決アプローチについてご紹介していきます。

なお、それなら modern API の考え方に寄せればよいのでは? と普通に考えると思うわけですが、実は1つ制約があります。それは、当該要素をコマンドラインからは構築できなくなる、ということです。パイプラインの要素として定義した場合、コマンドラインから実行できるスタックとしては定義できなくなります。1)両方で無理に定義すると jsii.errors.JSIIError: You cannot add a dependency from ‘パイプライン内スタック名’ (in Stage ‘パイプラインステージ名’) to ‘スタック名’ (in the App): dependency cannot cross stage boundaries のようなエラーが発生します。 (例はPythonの場合)

こういった理由もあり、従来の、CDKとしてコマンドラインで実行できる形で定義した上で、 CodeBuild 内でコマンドを叩くという方式を採用しています。

original API における構成

ここからCDKに関する用語が解禁となります。事前にCDKに関する学習をしたい方は、AWSから提供されている以下のコンテンツなどをご利用ください。

  • Blackbelt:CDKの概観を理解できます
  • CDK Workshop:実際にステップバイステップで構築する練習ができます

original APIで実現していた構成は以下のようになります。

ポイントは3つです。

  • 冒頭の Source, Build, UpdatePipeline は仕組みを使う上で固定。それぞれ、ソースコード取得、CDK synth、パイプラインの更新、を行います。パイプラインを更新した場合は、UpdatePipeline後に一度パイプラインが破棄され、再度初めから実行し直します。
  • 本番リリースを想定し、SIerではよくやるリリース確認ができるようなプロセスを挟んでいます。CDK実行前の確認、およびリリース前の確認を Manual Approval として挟み込んでいます。
  • 一部、Blue/Greenデプロイ方式を取ることで、環境上でのリリース前のテスト(連結テスト)を可能にしています(prd01-pipelines-stage-integration-env 部分)。ここは、開発環境などの別環境でリリース後にテストをすればよい、などいくつかの考え方がありますが、ここでは環境差異リスクも考えて、最低限のデプロイ後テストを含めてパイプライン内でのCIとして実行できる構成を取ります。

では、次に、この構成を modern API で実現するためにはどのようなポイントで問題になるかを見ていきましょう。

modern API で実現する際の問題点

ここから先の記事は、2022/4時点でのモジュールに依存します。そのため、今後の開発によっては内容が適切ではない状態となる可能性があること、予めご承知おきください。

大きく2つの問題点があります。

  • パイプラインのステージに対してCodeBuildを組み込むことが困難になった
  • パラメータの一部が隠蔽化され、利用できなくなった

以下でそれぞれについて説明します。

パイプラインのステージに対してCodeBuildを組み込むことが困難になった

最も大きい問題点は、パイプラインのステージに対してCodeBuildを普通に入れることができなくなった点です。これはgithub Issueにも上げられているのですが、2)2021/8 に起票されており、2022/4 現在でも解決の見通しが立っていません。かなりパイプライン構築構造の根幹に関わる部分の問題であるため、解決には時間がかかるのではないかと思われます。original API では pipeline.add_stage() 関数およびstage.add_actions() 関数 を使用することで追加することができました。ところが、 modern API では 独自に CDK Stack を含むような Stage クラスを定義することが必須化されました。 pre / post 処理として CodeBuildStep(modern API では CodeBuild 処理を追加する場合はこの形になります)を追加することはできるのですが、 Stage は「何も作らない空っぽのもの」も許容されないため、導入するとしてもダミー処理を入れるようなことが必要になります。

パラメータの一部が隠蔽化され、利用できなくなった

パイプラインの arn など、元々取得できていたいくつかのパラメータが隠蔽化され、取得できなくなっていることも問題となります。しかし、こちらについては、予め aws_codepipeline.Pipeline コンストラクトを作成しておき、これを pipelines.CodePipeline イニシャライザに引き渡すことで、大本のPipelineで取得できるパラメータであれば Pipeline から取得することができるようになります。例えば、パイプラインの実行状態を AWS Chatbot / Codestar Notification を通じて Slack へ連携する、などの際には arn が必要になりますが、これで実現できます。

modern API で CodeBuild 処理を並べる方法

結論から言うと、執筆時点においてベストな方法はありません。一長一短となるため、考えられるアプローチと、その時のやり方、欠点についてそれぞれ述べたいと思います。

wave を使用する

modern API には、 add_stage に似た関数として、add_wave というものがあります。これは、複数の stage を並行で実行するような構造を作ることができるもので、 wave を定義した後でこの wave に複数の stage を追加する形で利用します。ここでポイントなのが、先に wave を作るというところです。先に作るため、 stage を追加しない状態でも利用できます。また、 stage と同様に pre / post 処理を追加することもできます。そのため、例えば pre に対して CodeBuildStep を追加することで、CodeBuild だけを実行するステージを作ることができます。

ではこれで事足りるように見えますが、1点問題があります。 pre / post は Step[] 型であり、リストで渡すことができます。この時、リストで渡した Steps は全て並列で実行されます。そのため、stage 内で順番に実行したい(これは比較的シンプルでよくある要件です)、ということが実現できません。上記パイプラインで言うと prd01-pipelines-stage-integration-env 部分になります。そのため、 wave を使用する場合は、全ての CodeBuild ごとに1つずつ stage を用意する必要があります。

original API を使用し続ける

もちろん、可能です。 pipelines は開発が活発なモジュールであり、 CDKv1 もまだ開発は続いています。そのため、使いやすい状態で使用し続けて対応を待つ、というのは一つの選択肢です。
ただし、この場合は CDK v1 を使い続け、 v2 にバージョンアップできない、ということになります。一方でAWSのブログによると、CDK v1 は、 2022/6/1 にメンテナンスモードに入り、 2023/6/1 にはサポートを終了するとのこと。今後の機能拡充がされなくなることを考えると、残り2ヶ月程度で対応されるという期待をするのは少々難しそうに見えます。

aws_codepipeline などを使用して自力で構築する

これは、そもそも original API も作られる以前の状態に戻る、ということを意味します。実際、 UpdatePipeline の仕組みをリバース・エンジニアリングすることで再現することは、できなくはないでしょう。こちらの利点は、CDK v2 へのバージョンアップは問題なく出来るようになる、ということです。
デメリットは、当然ながら、車輪の再発明をすることになりますので不要な手間がかかること、今後 pipelines が発展しても独立したままという、エコシステムの恩恵を受けられないこと、です。書いててなんですが、あまり選ぶメリットはないでしょう。

CodeBuild を作る Stage / Stack を作成する

最後が、ある意味最も王道かもしれない、サンプルにあるように独自の Stage / Stack クラスを作成し、その中で CodeBuild を作る、という方法です。

これは最も良さそうに見えるのですが、残念ながら現時点では動かせていません。理由は、 CodeBuild では当然ながらパイプラインの冒頭で取得してきたソースコードを使いたいわけですが、そのためにはパイプラインと CodeBuild でアーティファクトを共有する必要があります。しかし、
 

class PipelineStack(Stack):
  def __init__(self, scope, id, env, **kwargs):
    super().__init__(scope, id, **kwargs)
    pipeline_base = aws_codepipeline.Pipeline(self, "PipelineBase")
    pipeline = pipelines.CodePipeline(self, "Pipeline", code_pipeline=pipeline_base, synth= #省略
      )
    pipeline.add_stage(MyStage(self, "MyStage", artifact_bucket=pipeline_base.artifact_bucket, env=env))

class MyStage(Stage):
  def __init__(self, scope, id, artifact_bucket, env):
    super().__init__(scope, id, env=env)
    MyStack(self, "MyStack", artifact_bucket=artifact_bucket, env=env)

class MyStack(Stack):
  def __init__(self, scope, id, artifact_bucket, env):
    super().__init__(scope, id, env=env)
    aws_codebuild.Project(self, "MyCodeBuildProject",
      build_spec=aws_codebuild.BuildSpec.from_object("{"version": "0.2", "phases": {"build": {"commands": ["echo 'Hello'"]}}}")),
      artifacts=aws_codebuild.Artifacts.s3(bucket=artifact_bucket))

 
のような形でアーティファクトを共有すると、 pipeline_base と CodeBuild の間に依存関係ができるらしく、前述の「AppとStageでリソースを共有できない」問題に抵触して作ることができなくなりました。
そのため、現時点ではこの方法は使用できません。(もし今後の調査で状況が進展しましたら記事を更新したいと思います)

まとめ

このように、従来型のパイプラインを CDK Pipelines modern API で実現することは一筋縄ではいきませんが、それでもパイプライン自身を含めてCI/CD化できることは非常に魅力的です。今後のIssue動向にも注目しつつ、チャレンジしてみてもよいのではないでしょうか。
NRIデジタルではお客様のDXをより進めていくために、このような DevOps / IaC の仕組みづくりをはじめとした先進技術の調査と適用、そしてビジネス価値への転換を継続的に実施しております。自社での取り組み方にお悩みがある方はご相談に乗ることができるかもしれませんので、お気軽にお声がけください。
また、技術力を用いて様々なお客様のDXを促進していくような仕事の仕方にご興味がある方も歓迎しています。カジュアル面談なども可能ですので、ぜひご検討ください。

それでは、今回は本テーマが発表された re:Invent での、Werner Vogels 氏のいつもの言葉で締めたいと思います。 Go Build !!!

References   [ + ]

1. 両方で無理に定義すると jsii.errors.JSIIError: You cannot add a dependency from ‘パイプライン内スタック名’ (in Stage ‘パイプラインステージ名’) to ‘スタック名’ (in the App): dependency cannot cross stage boundaries のようなエラーが発生します。 (例はPythonの場合)
2. 2021/8 に起票されており、2022/4 現在でも解決の見通しが立っていません。かなりパイプライン構築構造の根幹に関わる部分の問題であるため、解決には時間がかかるのではないかと思われます。