😊

Terraform構築におけるAzure Pipelinesの利用

2023/12/31に公開

はじめに

Azure Pipelinesを使ったTerraform構築の自動化を実践してみた。Pipelinesの構成は以下の通り。今回はMicrosoftホステッドをメインに構築した。Selfホステッドの構築はAppendixを参照。

やること

  1. サンプルコード作成
    1-1. Azure PortalのCloud Shell上での実装が一番早い。ビルドサーバの構築が不要で済むためである。

  2. DevOps組織の作成
    2-1. Terraformを使う場合は、extensionsを追加したほうが効率的
    azure-pipelines-tasks-Terraform
    azure-Terraform

  3. Azure Repos作成とサンプルコードのpush

  4. Pipelines作成

    • アシスタントが使えてGUIベースでのyaml作成ができるので利用すべし
    • サービスコネクションの作成が必要になる。サービスコネクションとは、Pipelinesに付与するプリンシパル(権限)と考えればよい。
  5. (余力)リリース効率化のパイプラインへの組み込み

    • plan結果のBlobへのjson出力
      • Azure CLIを利用する。
    • 承認者によるチェックを挟む。
      • ゲート承認によって実現が可能である。管理者によるチェックという意味であれば、承認を使った方がよい気がする。
    • Terraform testの実施
    • パイプライン上でのTriggerの検討
      • noneとすることで、Gitとの連携は行いつつも、push後の自動デプロイは避けることができる。

つまったところ

やることの番号に合わせて記載。

4-1. Pipeline実行時のエラー

No hosted parallelism has been purchased or granted. To request a free parallelism grant, please fill out the following form https://aka.ms/azpipelines-parallelism-request

→ Formから無料枠を申請するか、並列ジョブを購入して実行できるようにする。

4-2. Terraform Installのtask名は、アシスタントの指示に従うと実行エラーが発生した。

アシスタントではtask名の末尾に@0と入れられたが、@1が正しいようである。@1はReadmeにある通りで、このExtensionならではの書き方ではある。
https://marketplace.visualstudio.com/items?itemName=JasonBJohnson.azure-pipelines-tasks-Terraform

4-3. サービスプリンシパルの作成方法(サービスコネクションの前提として必要)

Pipelineを作成するときに自動作成も可能ではある。ただし、プロジェクトベースの場合は管理上パラシとかに残した方が良いと思うため、サービスプリンシパルの作成を先に実施してもよいと思う。
https://learn.microsoft.com/ja-jp/entra/identity-platform/howto-create-service-principal-portal

  • PortalのMicrosoft Entraでの登録
  • 必要な権限のプリンシパルへの付与
  • プリンシパル内でのシークレットの作成
  • パイプライン作成時に必要情報を登録する
    といった流れになる。

4-4. サービスプリンシパルキーの作成方法

Pipelinesのtask作成時に設定が求められた。
これは、Azure Portalのサービスプリンシパルの画面から、シークレットを作成して登録することで払い出しが可能である。

4-5. This pipeline needs permission to access a resource before this run can continue to Terraform の対処。

サービスコネクションの設定後に上記のメッセージが出てくる。どうやら権限が足りないようなので、画面の案内に従ってアクセスを許可した。
ただし、どこかどう変わったのかが不明だったため、調べてみたら以下のようにサービスコネクションのPipeline permissionsにパイプラインへの許可設定が追加されていた。

この画面上での1つ1つの追加はできないようである。右側の三点リーダーから、他の全てのパイプラインに対して当該サービスコネクションが利用できるようになる。Reposと連携する時にも同様のメッセージとともに権限追加が必要になる。

5-1. jsonファイルへのplan結果の出力方法

アシスタントのplanでは実施できなかった。Readmeを見ると、customを使わないとoutputfileパスが使えないようである。
https://github.com/microsoft/azure-pipelines-Terraform/blob/main/Tasks/TerraformTask/TerraformTaskV4/README.md

5-2. jsonファイル名の変更方法

ビルド名にすることも可能である。その際には、$(Build.BuildNumber)でビルド名が呼び出し可能。また、ビルド名自体を変更することも可能なようである。
https://learn.microsoft.com/ja-jp/azure/devops/pipelines/process/run-number?view=azure-devops&tabs=yaml

5-3. MSホステッド×Blob側でのNW制限への対応方法

Pipelineの中で、NW制限をかけているBlobに出力する場合の許可設定は何を開ければいいのか。
https://learn.microsoft.com/ja-jp/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#to-identify-the-possible-ip-ranges-for-microsoft-hosted-agents
→ DevOpsのプロジェクトのリージョンの穴あけをするしかなさそうである。

対処法をMSサポートに確認をした。要点は以下の通り。
MSホステッドのIPはDevOpsプロジェクトのリージョンに依存し、AzureCloud.<リージョン>のパブリックIPになる。膨大なIPがあり、かつ、Storage Account側でのNW制限は200IPまでしか実施できない。そのため、もし上記の構成でNW制限を掛ける場合には、Pipeline実行時にMSホステッドのIPを取得したうえで通信許可設定を行うような流れにする必要がある。実行時のIPは固定(実行ごとに毎回変わる)なので、膨大な数を開ける必要はなくなる。ただし、設定反映を待つ処理を入れる必要がある。
個人的に思いつく案としては、以下がある。

  • Blobのパブリック公開
  • TeamsやSlackへの連携(Blobへの出力をやめる)
  • セルフホステッドの利用とエンドポイントでのアクセス制御
  • 出力自体やめる。(Pipelineの中で見れるし…)

5-4. 承認後のTerraform plan/applyでのエラー

承認前後のステージで、Terraformのインストール・実行ディレクトリが変化してしまう。

  • 承認前のステージ

/opt/hostedtoolcache/Terraform/1.6.6/x64/Terraform plan -no-color

  • 承認後のステージ

/usr/local/bin/Terraform providers

→ MSホステッドを利用する場合は、jobが分かれると、実行されるエージェントが変わる。今回は承認後のjobではTerraformをインストールするtaskが無かったため、実行に失敗した。jobの中で処理をまとめる or 前提処理をjob内で繰り返し実施が必要である。
1つのjobにする場合はステージ分割の概念がなくなるため、承認は利用できない。ゲートを使った承認方法に切り替える必要が出てくる。どちらががいいのかは不明…視覚的にわかりやすいのはEnvironmentを使ったいわゆるな承認である。
https://stackoverflow.com/questions/68032778/azure-devops-pipeline-Terraform-init-fail

→ ステージ分割した実行の場合は、作業ディレクトリ内にTerraformファイルが無いことでエラーが出る。
そのため、checkoutオプションによって、Reposを作業場所に指定することも必要である。Pipelineのyamlファイルと同じ場所に.tfファイルがある場合は以下を追加する。

checkout: self

https://stackoverflow.com/questions/68032778/azure-devops-pipeline-Terraform-init-fail

5-5. 承認ゲートとして利用できる「ManualIntervention」は、クラシックパイプラインでの利用が前提となっている。

https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/manual-intervention-v8?view=azure-pipelines

しかし、クラシックパイプラインは利用としては禁止される方向である。
https://zenn.dev/microsoft/articles/azure-devops-disable-creation-of-classic-pipelines
代わりにプレビューである「ManualValidation」が利用できる。
https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/manual-validation-v0?view=azure-pipelines

5-6. tfsateファイルが出力できていない。

本来差分が無いはずなのに、毎回のplan時にリソースがaddと認識されてしまう。
yamlの以下の箇所にて、tfstateファイルを指定できていなかった。backendAzureRmKeyというキーのため、てっきりStorage Accountのアクセスキーと誤認してしまった。

backendAzureRmKey: 'terraform.tfstate'

気になったところ

直接Pipelineの構築エラーではないが、知識として調べたこと。

  1. MS hostedを選択するか、SelfHostedを選択するか。
比較 メリット デメリット
MSホステッド IaaSレイヤの管理は不要 カスタムソフトウェアは導入不可。金額は高め(1つのジョブにつき$40/月)。VMのスペックが少し弱い、都度ライブラリのインストールから必要になるため実行時間はかかる。
セルフホステッド 実行時以外はダウンさせることができる。IaaSカスタム性は高い。金額は安め(1つのジョブにつき$15/月)。都度ライブラリダウンロードは不要なので性能面への改善は叶う。 NW制限への対応が面倒。VMとしての料金は別途必要になる。

比較は以下も参考になる。上記以外にもVMSSを使った方法もある。
https://tsuna-can.hateblo.jp/entry/2021/03/18/090000

  1. 並列ジョブってなんだろう。
    名前の通り、パイプラインのジョブを並列で実行する。
    ビルドやデプロイの各ステージを複数のジョブに分割し、それらのジョブを同時に実行することができる。これにより、複数の処理が同時進行でき、全体の処理時間が短縮される。Azure Pipelineの場合は、この並列ジョブをジョブの単位として、Microsoftホステッドもしくはセルフホステッドで実行する。並列ジョブが"1"の場場合は、ジョブが2つではなく1つという考え方になる。

  2. メール通知について
    利用している組織・プロジェクトは、アカウントのメールアドレスのボックスは覗けなかった。そのため、メールの通知先を変更する必要があった。その方法としては以下を参照すること。また、通知の種類のON/OFFの切り替えも可能である。その場合は「Project Settings」→「notifications」から実施できる。

https://learn.microsoft.com/ja-jp/azure/devops/organizations/notifications/change-email-address?view=azure-devops

※ ManualInterventionの場合はなぜかメールが飛んでこない…
※ 「Delivery Settings」からも通知全体としての宛先メールアドレスが指定できそうであった。試したがうまく機能せず…。1つ1つのサブスクリプションで設定されている通知先の方が縛りとしては強いのだろうか。制御したい場合は、デフォルトで設定されているサブスクリプションではなく独自に追加した方がよさそうである。(未検証)

  1. ブランチの開発環境毎の制御について
    開発環境にmasterブランチ以外にプッシュしている資材をデプロイしたい場合はどうしたらいいのか。
    → pipelineの実行時に取得元のReposのリポジトリを選択し制御できる。

参照資料

https://xkenshirou.hatenablog.com/entry/2023/07/19/202156
https://github.com/Azure/Terraform/tree/master/samples/integration-testing

https://azuredevopslabs.com/labs/vstsextend/Terraform/
→ ラボもあるぽっい。

Appendix1(セルフホステッド)

  • Microsoftホステッドを使った手順を主に書いてきたが、セルフホステッド利用したPipeline実行も行った。(長くなれば別記事に切り出す予定)

構築の流れ

  • セルフホステッドVMの作成
  • DevOps組織上でのエージェントプールの作成
  • PATの作成
    権限はお好みで。
  • セルフホステッドエージェントのダウンロード
    • DevOps組織の「Agent Pool」の登録画面から、専用のスクリプトが確認できる。
  • エージェントのAgent Poolへの登録
    • 登録が完了すると反映が確認できる。画像はOfflineの状態になっているが、後述の対応を行うことで解消可能。
      設定完了後
  • Pipeline(yaml)の編集
    nameはエージェント名ではないことに注意する。
pool:
  name: <エージェントプール名>

初回実行時は、yamlのエージェントプールが変更になっているため、パイプラインからリソースへのアクセスに権限付与が必要になる。

実行時に詰まったところ

セルフホステッドVM内でエージェント実行時に「Process terminated. Couldn't find a valid ICU package installed on the system.Set the configuration flag System.Globalization.Invariant to true if you want to run with no globalization support. 」のエラーが出るとき

依存関係がインストールされていない可能性があるため、事前に、エージェントのアンインストールを実施した上で、以下のコマンドを実行した。

sudo ./bin/installdependencies.sh

「export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1」を環境変数に入れることでも対応が可能。ただし公式から案内されている対処法ではない。

PATの設定時の権限エラー(VS30063: You are not authorized to access https://dev.azure.com

PAT作成時に「Agent Pools」を正しく設定していなかったことにより、エージェントからDevOps(エージェントプール)へのアクセス権限が設定できていなかった。漏れなく設定するように。

エージェントのOffline解消

VM内で明示的にrun.shを実行することで解消した。

gitのインストールが必要

Azure Reposを扱うため必要になる。検証で作成したRHEL7系は素直にyumでgitをインストールすると、v1.80であり要件にマッチしない。(最低でもv2.9.0以上)
そのため、ソースからのインストールが必要になる。以下の手順が参考になる。
ベースとなる手順(作業場所は、cd /usr/local/src)

https://git-scm.com/book/ja/v2/使い始める-Gitのインストール

gitのソースコードをダウンロードするときには、wgetを使う。
最新バージョンは公式を参照。v2.43.0が現時点は最新。
docbook2X パッケージのインストールには、EPELリポジトリの有効化が必要がある。

Azure CLIのインストール

plan結果のjsonファイルをBlobに出力する場合には必要。個別要件なので必要であればというレベル。

参考手順

https://techblog.ap-com.co.jp/entry/2022/11/30/184807

Appendix2(Slack通知)

Pipelineの実行通知をSlackに連携した。
承認までSlack上で実行できるのは便利であるが、/azpipelines signinにて、承認権限を持つアカウントでAzureにサインインすると、通知を流したチャネルに参加している人は誰でも承認できてしまう(いわゆる代理承認)。
そのため、承認者だけのプライベートチャネルに通知を流すなどの一工夫は必要になる。

/azpipelines subscriptionsにて、どういった通知を受け取るのか細かく設定も可能である。

Discussion