tech.guitarrapc.cóm

Technical updates

PulumiをCircleCIでContinuous Deliveryする

この記事は、Pulumi dotnet Advent Calendar 2019の13日目です。

https://qiita.com/advent-calendar/2019/pulumi-dotnet

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を提供しています。

Continuous Delivery | Pulumi

GitHub Actionsにもそうそうに対応されていて、フットワークの軽さがあります。

事前準備

事前に4つ準備をします。

  • GitHub Appsのインストール

apps/pulumi | GitHub

  • dotnet core 3 + node + glibcなDocker Iamgeの準備

作成してあるのでご利用ください。

DockerHub - guitarrapc/docker-dotnetsdk-node-aws

  • PulumiのAccessTokenを取得

CircleCI での pulumi の実行に使います。

  • AWSでPulumiがAWSで実行するユーザーのAccessKey、AccessSecretの準備

GitHub Apps について

Pulumiが公式に提供しているPulumi GitHub Appsをインストールします。

Pulumi GitHub App

これを使うことで、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を使います。

https://github.com/sgerrand/alpine-pkg-glibc

このDockerimageではそういう対策も入っています。

CircleCI の構成

Pulumi公式でCircleCIの構成が紹介されています。これに沿って組むといいでしょう。

CircleCI

GitHubへのPulumi Logsの結果通知はGitHub Appsに委託できるので、CircleCIはpulumi の preview適用前のapprovepulumi の 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/refreshpulumi/preview, pulumi/updateを使うでしょう。ほかにもテスト環境の構築、削除できます。

CircleCI Orb Registry - pulumi/pulumi

AWSの認証credentialファイルの作成は、aws cli orbを使っています。 aws-cli/setupを使うとaws configure相当の認証作成を行ってくれます。

CircleCI Orb Registry - circleci/aws-cli

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 upfailedしたことはわかりますが、そのエラーメッセージやCircleCIやpulumi consoleを見ないとわからないです。

GitHubのPRにコメント表示してもいいのではないかと提案しています。

Issue #3617 · pulumi/pulumi | GitHub

REF

Continuous Delivery | Pulumi