この記事は、Pulumi dotnet Advent Calendar 2019の13日目です。
Terraformでは、PRに連動させてどのようにPlan/Applyをするのかを一度は考え、すでに実践しているはずです。 私のお勧めはAtlantisですが、CircleCIでもいいでしょう。Terraform Cloudもいいですね。
Pulumiには残念ながらAtlantisはないので、PRに連動させてCircleCIとのContinuous Deliveryを組んでみましょう。
概要
公式のContinuous Deliveryにある中から、CircleCIを使ってPRベースでpulumi preview + Approve + pulumi upを 実行できるようにしてみましょう。
ここで目指すのは、PRマージまでにpulumi upができる仕組です。
Atlantisのように、PR内でその変更のエラーも含めて解決するコンセプトは、ビルドして実行すれば確定するわけではない開発フローにおいて大事です。
Why
PulumiはStateをWeb上に持っているとはいえ、Pulumiをローカルで実行しているとterraformをローカルで実行するのと同じ事故が起きます。
環境
Pulumi .NETCoreで書いたリソースをAWS環境に適用します。
- Pulumi 1.6.1
- Pulumi dotnet (C# を利用)
- AWS
完成イメージ
まずはCircleCIでPRの実行プレビュー、手動で許可、実行という流れでやります。
理想的にはAtlantis のような仕組みが欲しいのですが、まずはPRベースを目指します。
開発フローです。
- ブランチを作成して変更をcommit & push
- PRを作成
- PRをトリガーに、CircleCIのWorkflowでPulumi Preview Jobが実行
- GitHub Appsが、Pulumi Preview結果をPRにコメント通知
- GitHubのchecksからcircleciの
pulumi/approveを選択しWorkflowを開く - Workflowの
approveをするとpulumi_updateが実行される
実行イメージです。
GitHub PRでPulumiのGitHub Appsによる通知がされています。

GitHub PRのchecksを見ると、circleciのpulumi/approveがPendingなのでクリック。

CircleCI Workflow上でapproveするとpulumi upが実行される


すべてのジョブが実行されるとGitHubのChecksが通るので、PRをマージします。
pulumi upに失敗した場合、ここで検知して修正をcommitできます。

Pulumi が提供している Continuous Delivery
複数のCIを使ったCDを提供しています。
GitHub Actionsにもそうそうに対応されていて、フットワークの軽さがあります。

事前準備
事前に4つ準備をします。
- GitHub Appsのインストール
- dotnet core 3 + node + glibcなDocker Iamgeの準備
作成してあるのでご利用ください。
- PulumiのAccessTokenを取得
CircleCI での pulumi の実行に使います。
- AWSでPulumiがAWSで実行するユーザーのAccessKey、AccessSecretの準備
GitHub Apps について
Pulumiが公式に提供しているPulumi GitHub Appsをインストールします。
これを使うことで、GitHubのPRにPulumiのStack変更をコメントしたりChecks APIにサマリ表示できるます。
Pulumiは、GitリポジトリでPulumi cliを実行すると、ブランチ、コミットIDをトラッキングしており、アクティビティに表示されているのがわかります。(一部のCI環境ではPRの番号が自動トラックされます。対応していないCIでも手動で情報を与えることができます。)

GitHub AppsのPulumiは、この情報に沿ったPRに対してPulumiのStack変更をコメントします。
コメントには、リソースの変更サマリcreated, updated, deletedが含まれており、Pulumi Consoleへのリンクも含まれます。

また、Checks APIと統合されるので、どんな変更があったのかさくっとみることもできます。最高。

なお、Stackに変更がない場合、コメントはありません。Checks APIには変更がなくても書き込みされます。
Pulumi .NETCore 用の docker イメージの用意
guitarrapc/docker-dotnetsdk-node-awsを用意したので使ってください。
CircleCI上でDockerコンテナを使ってPulumi CLIを動かします。 ただ、Pulumi .NETCoreをAWS環境で実行するには、Pulumiが公式に提供しているnodeイメージでは足りません。
必要なリソースは次の通りです。
- .NET Core SDK 3.0: C# コードのビルド、実行
- Node: Pulumi実行のため
- glibc: Pulumi dotnetのgRPC通信のため
- aws cli: Pulumi-Aws環境をaws configureするため (configureのみなので、シェルで書いてもok)
Pulumi CLI自体は、CircleCI Orbによってインストールされるのでバージョン管理をしないためにもインストールしないでおきます。
AWS CLIも同様にバージョン管理したくないものの、結構大きいのでイメージに含めてしまいます。
これらを含めたDockerイメージはないので、Alpineをベースに作りました。
guitarrapc/docker-dotnetsdk-node-aws: Docker, AWS-CLI, .NET SDK, Node
余談
PulumiはAplineで動作する前提ではないもののhttps://github.com/pulumi/pulumi/issues/1986を見るとlibc6-compatを追加すれば動くとあります。入れると確かにpulumiコマンド自体は動きますが、 Pulumi .NETCoreなStackをpulumi upすると依存ライブラリが見つらずgRPC実行でこけます。
Error loading native library "/home/dotnet/app/runtimes/linux/native/libgrpc_csharp_ext.x64.so"
これを解消するには、https://github.com/grpc/grpc/issues/18428を参考にsgerrand/alpine-pkg-glibcを使います。
このDockerimageではそういう対策も入っています。
CircleCI の構成
Pulumi公式でCircleCIの構成が紹介されています。これに沿って組むといいでしょう。
GitHubへのPulumi Logsの結果通知はGitHub Appsに委託できるので、CircleCIはpulumi の preview 、適用前のapprove、pulumi の updateの3ステップに集中できます。
考えるべきこととして、pulumi の updateはどのタイミングで行えばいいでしょうか?
pulumi previewではビルドエラー、pulumiの仮実行でのエラー検知はできますが、リソース作成時におこるエラーは検知できません。となると、PRのmasterマージ時にpulumi upの適用は、PRで修正が完結させる保障ないので避けたいところです。今回はAtlantisに習い、「PRのマージ前に適用する」とPR内で修正コミットも行って完結もできるようにしましょう。
circleciのconfig.yaml全体です。
version: 2.1
# set following EnvironmentVariables
# - PULUMI_ACCESS_TOKEN
# - AWS_ACCESS_KEY_ID
# - AWS_SECRET_ACCESS_KEY
# - AWS_DEFAULT_REGION
orbs:
pulumi: pulumi/pulumi@1.2.0
aws-cli: circleci/aws-cli@0.1.18
executors:
dotnetcore3:
docker:
- image: guitarrapc/docker-dotnetsdk-node-aws:1.16.292
environment:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
NUGET_XMLDOC_MODE: skip
# PR will try CI/CD pulumi on any branch w/o master. (please apply pulumi change on PR)
# `preview` -> `approve` -> `up`
workflows:
pulumi:
jobs:
- pulumi_preview:
filters:
tags:
only: /.*/
- approve:
type: approval
requires:
- pulumi_preview
filters:
tags:
only: /.*/
branches:
ignore: master
- pulumi_update:
requires:
- approve
filters:
tags:
only: /.*/
branches:
ignore: master
jobs:
pulumi_preview:
executor: dotnetcore3
working_directory: ~/repo/
steps:
- checkout
- pulumi/login:
version: 1.7.1
- pulumi/refresh:
stack: STACK
- pulumi/preview:
stack: STACK
pulumi_update:
executor: dotnetcore3
working_directory: ~/repo/
steps:
- checkout
- aws-cli/setup
- pulumi/login:
version: 1.7.1
- pulumi/refresh:
stack: STACK
- pulumi/update:
stack: STACK
skip-preview: true
ポイントを順にみていきます。
環境変数
今回はAWS環境へのリソース展開なので、Jobの環境変数にPulumiのアクセストークンとAWSのアクセスキーを設定します。
- PULUMI_ACCESS_TOKEN
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_DEFAULT_REGION
Pulumiのアクセストークンは、Pulumi Web UIから自分のプロファイルのアクセストークンを利用しました。
Orb
pulumiのコマンド実行は、Pulumiから公式に提供されているpulumi orbを利用します。
主に、pulumi/login, pulumi/refresh、 pulumi/preview, pulumi/updateを使うでしょう。ほかにもテスト環境の構築、削除できます。
AWSの認証credentialファイルの作成は、aws cli orbを使っています。
aws-cli/setupを使うとaws configure相当の認証作成を行ってくれます。
Executors
用意したDocker Image guitarrapc/docker-dotnetsdk-node-awsを使います。
あとはWorkflowとJobsを書けば完成です。実行してみると、イメージ通りにコメントが飛び、CI/CDが回ります。
余談: GitHub Apps を使わない Pulumi の実行結果のPRコメント
GitHub Appsに気づかず、pulumi previewの結果をGitHubに投げつける仕組みを作っていたので一応張り付けておきます。
CircleCIでpuluimi previewを実行して、その結果をGitHub Comment APIで書き込むものです。
jobs: pulumi_preview: working_directory: ~/repo/ steps: - checkout - pulumi/login: version: 1.6.1 - run: pulumi preview --stack master --cwd ~/repo | tee pulumi.log - run: name: post github pr comment command: | # pick up pulumi log's Diagnostics line num, and escape " to ' for JSON validation. PULUMI_PERMALINK=$(tail -n 1 pulumi.log) PULUMI_DIAGNOSTICS_LINESTART=$(cat pulumi.log | grep -x "Diagnostics\:" -n | cut -f 1 -d ":") PULUMI_MESSAGE=$(echo $(tail -n "+$PULUMI_DIAGNOSTICS_LINESTART" pulumi.log | head -n $PULUMI_DIAGNOSTICS_LINESTART | sed -e 's/$/ \\n/g' | sed -e 's/"/'\''/g')) WORKFLOW_URL=https://circleci.com/workflow-run/${CIRCLE_WORKFLOW_ID} PULL_REQUEST_ID=$(echo $CIRCLE_PULL_REQUEST | grep -o -E '[0-9]+$' | head -1 | sed -e 's/^0\+//') GITHUB_PR_COMMENT_URL=https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${PULL_REQUEST_ID}/comments?access_token=${GITHUB_ACCESS_TOKEN} curl -f -X POST -H 'Content-Type:application/json' -d "{\"body\":\"### WORKFLOW\n\nclick [HERE]($WORKFLOW_URL) and approve pulumi up.\n\n### PULUMI LOG\n\n$PULUMI_MESSAGE\"}" ${GITHUB_PR_COMMENT_URL}
Pulumiの実行結果を取りたかったので、teeしています。 Pulumiの実行ログから、Diagnostics以下を取得しています。 WorkflowのApproveを押す前提なので、Workflowへのリンクも貼るようにしています。
これで、GitHub PRにWorkflowへのリンクとPulumiの実行ログが表示されます。

GitHub Appsがあれば無用です。何かに使いたいときにどうぞ。
FAQ
CircleCI でpulumi up が失敗しても具体的なエラーメッセージがPR上に表示されない
現状の仕様では、pulumi upがfailedしたことはわかりますが、そのエラーメッセージやCircleCIやpulumi consoleを見ないとわからないです。
GitHubのPRにコメント表示してもいいのではないかと提案しています。
Issue #3617 · pulumi/pulumi | GitHub