しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Terraform と Azure Pipelines を使って複数の環境を管理する

以前に Terraform と Azure Pipelines を使ってシンプルに App Service を作るのを試しましたが、現実的にはあんな単純な定義で済むはずはなく、開発環境や本番環境といった複数の環境への対応が必要になってきます。

実際に Terraform でインフラ周りの管理を行っているので、採用した定義をメモとして残します。Azure Pipelines での Terraform 利用については前回のエントリを見てください。

今回の方針は以下の通りになります。最初は 1 つの Pipeline で全部やらせようかと思ってましたが、YAML が複雑になってきたので放棄して分離する方向にもっていきました。

今なら Terraform Workspace を使った方が良いと言われそうですが、一応調べたところ管理が複雑になりそうだったのでシンプルに tfstate を別に管理する方法を選びました。

  • Pipeline は Dev / Prd で分ける
    • YAML も Dev 向けと Prd 向けで 2 つに分ける
    • ただしある程度の共通化はする
  • PR ではそれぞれの環境向けの tfstate を使って plan を実行
    • Branch Policy で PR 作成時に実行する Pipeline を定義する
  • マージされたタイミングで適切な tfstate を使って apply を実行
    • YAML の Trigger でビルドするブランチを指定
    • 今回はとりあえず master / release の 2 つで行う

といろいろと書いてますが、単純に環境ごとに Pipeline と tfstate を分離して混ざらないようにしつつ、かつ実行の条件を少なくしてシンプルな定義にするということです。

Azure Pipelines は stage / job / step で複数定義して condition で複雑な条件を作れますが、結局のところ一本道にした方が管理コストが低いです。

先に今回作成した Terraform と Azure Pipelines の定義を公開しておきます。GitHub にディレクトリ構造そのまま上げているので、Terraform で複数の環境に対応する際の参考にしてください。

ディレクトリ構造はまあ良く見るタイプのやつです。envs 以下には環境の定義と依存する変数や設定を、modules には実際の Terraform 定義を入れていくという構造です。

f:id:shiba-yan:20200120191920p:plain

環境毎に固有のリソースを持ちたい場合は module としてディレクトリを追加してあげればよいですね。あまり環境に固有のリソースを作りたくないので、今回は定義してません。

GitHub にも上げてありますが、一部分だけ Azure Pipelines の定義を引っ張ってきました。Terraform CLI のインストールと初期化は全ての Job で同じなので Template を使って共通化してあります。

trigger:
- master

variables:
  terraformVersion: '0.12.19'
  azureSubscription: 'Azure Sponsorship'
  workingDirectory: '$(Build.SourcesDirectory)/envs/dev'
  tfstateName: 'develop.tfstate'
  vmImage: 'ubuntu-latest'

stages:
- stage: Terraform_Plan
  condition: ne(variables['Build.SourceBranch'], 'refs/heads/master')
  jobs:
  - job: Develop
    pool:
      vmImage: $(vmImage)
    steps:
    - template: templates/terraform-init.yml

    - task: TerraformTaskV1@0
      inputs:
        provider: 'azurerm'
        command: 'plan'
        environmentServiceNameAzureRM: $(azureSubscription)
        workingDirectory: $(workingDirectory)
      displayName: terraform plan

重要なのは workingDirectorytfstateName ぐらいで、環境に依存する値が入るので、環境を追加する際は修正する必要があります。あとは condition で plan と apply の実行を分けているぐらいです。

定義が出来れば Azure Pipelines で 2 つ Pipeline を作成していくのですが、先に Git に YAML を置いておくと Pipeline の作成時にどの定義を使うか選択できるようになるので便利です。

f:id:shiba-yan:20200120200648p:plain

Pipeline 定義の YAML は今回 .azure とか適当にディレクトリを切って入れるようにしました。ルートに YAML がずらずらと並んでいるのは見にくいので。

今回は Dev / Prd の 2 環境なので Pipeline も 2 つ用意しておきます。さらに Stg 環境を追加する場合は、YAML と Pipeline を増やしていく形です。

f:id:shiba-yan:20200120192044p:plain

Pipeline を作成したら、Branch Policy から Build Validation を追加しておきます。今回は master / release という 2 つのブランチ*1を使うので、それぞれの環境向けの Pipeline を選んで追加すればよいです。

f:id:shiba-yan:20200120192410p:plain

これで Pull Request が作成されると、自動で terraform plan が実行されるようになります。もちろんビルドが通るまでは Pull Request のマージがブロックされるので安全です。

f:id:shiba-yan:20200120192559p:plain

Pull Request をマージすると YAML の trigger に従ってビルドが走るので、そのブランチに紐づいた環境向けの terraform apply が実行されます。暫くするとリソースが作成されるはずです。

f:id:shiba-yan:20200120194927p:plain

開発環境で作ってきたリソースを本番環境へ適用するには release への Pull Request を作成します。release への Pull Request を作成すると、同様に本番向けの terraform plan が走ります。

f:id:shiba-yan:20200120195116p:plain

そして Pull Request をマージすると、本番向けの terraform apply が実行されてリソースが作成されます。

f:id:shiba-yan:20200120195425p:plain

アプリケーションのデプロイと同じように、Pull Request と CI ベースでの運用になるので簡単ですね。

Service connection の Approval を使えば承認必須にできるかなと思いましたが、terraform plan の実行でも要求されてしまうので微妙です。一応は Branch Policy を使って特定ユーザーのレビューを強制させるぐらいは出来るので、そっちで妥協かなと考えています。

*1:develop / master という構成のが一般的かもしれない