Terraform、Cloud Build、GitOps を使用してインフラストラクチャをコードとして管理する

このチュートリアルでは、一般的な GitOps 手法を使用して、TerraformCloud Build により、Infrastructure as Code を管理する方法について説明します。GitOps という用語は、最初に Weaveworks によって造られました。その主なコンセプトは、Git リポジトリを使用して必要な環境状態を保存することです。Terraform は、コードを使用してクラウド インフラストラクチャを予想どおりに作成、変更、改善できる HashiCorp ツールです。このチュートリアルでは、Google Cloud の継続的インテグレーション サービスである Cloud Build を使用して、Terraform マニフェストを環境に自動的に適用します。

このチュートリアルは、インフラストラクチャを予想どおりに変更するための洗練された戦略を探している開発者とオペレーターを対象としています。この記事は、Google Cloud、Linux、GitHub に精通していることを前提としています。

State of DevOps で、ソフトウェア デリバリーのパフォーマンスを向上させると認められた機能が報告されています。このチュートリアルでは、次の機能について説明します。

アーキテクチャ

このチュートリアルが Terraform の実行を管理するための GitOps プラクティスをどのように適用するのかという点について、次のアーキテクチャ図を検討してください。GitHub ブランチ(devprod)を使用して、実際の環境を表していることに留意してください。これらの環境は、Virtual Private Cloud(VPC)ネットワーク(それぞれ devprod)によって Google Cloud プロジェクトに定義されます。

開発環境と本番環境を備えたインフラストラクチャ。

Terraform コードを dev または prod ブランチに push すると、プロセスが開始されます。このシナリオでは、Cloud Build がトリガーし、Terraform マニフェストを適用して、それぞれの環境で必要な状態を実現します。一方、Terraform コードを他のブランチ(機能ブランチなど)に push すると、Cloud Build が実行され、terraform plan が実行されますが、どの環境にも適用されません。

理想的には、開発者またはオペレーターのいずれかが、保護されていないブランチに対してインフラストラクチャの提案を行い、pull リクエストを通じてそれらを提出する必要があります。このチュートリアルで後述する Cloud Build GitHub アプリは、ビルドジョブを自動的にトリガーし、terraform plan レポートをこれらの pull リクエストにリンクします。このようにして、潜在的な変更を共同編集者と話し合ってレビューし、変更がベースブランチにマージされる前にフォローアップ commit を追加できます。

懸念事項が発生しない場合は、最初に変更を dev ブランチにマージする必要があります。このマージにより、dev 環境へのインフラストラクチャ デプロイメントがトリガーされ、この環境をテストできます。テストを行い、デプロイした内容について確認した後、dev ブランチを prod ブランチにマージして、本番環境へのインフラストラクチャのインストールをトリガーする必要があります。

目標

  • GitHub リポジトリを設定します。
  • Cloud Storage バケットに状態を保存するように Terraform を構成します。
  • Cloud Build サービス アカウントに権限を付与します。
  • Cloud Build を GitHub リポジトリに接続します。
  • 機能ブランチで環境構成を変更します。
  • 開発環境への変更を促進します。
  • 本番環境への変更を促進します。

費用

このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

このドキュメントに記載されているタスクの完了後、作成したリソースを削除すると、それ以上の請求は発生しません。詳細については、クリーンアップをご覧ください。

前提条件

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  5. Make sure that billing is enabled for your Google Cloud project.

  6. In the Google Cloud console, activate Cloud Shell.

    Activate Cloud Shell

    At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

  7. Cloud Shell で、選択したプロジェクトの ID を取得します。
    gcloud config get-value project
    このコマンドでプロジェクト ID が返されない場合は、プロジェクトが使用されるように Cloud Shell を構成します。PROJECT_ID は、実際のプロジェクト ID に置き換えます。
    gcloud config set project PROJECT_ID
  8. 必要な API を有効にします。
    gcloud services enable cloudbuild.googleapis.com compute.googleapis.com
    このステップが完了するまでに数分かかる場合があります。
  9. Git を Cloud Shell で使用したことがない場合は、名前とメールアドレスを使用して Git を構成します。
    git config --global user.email "YOUR_EMAIL_ADDRESS"
    git config --global user.name "YOUR_NAME"
    
    Git はこの情報を使用して、Cloud Shell で作成する commit の作成者を識別します。

GitHub リポジトリを設定する

このチュートリアルでは、単一の Git リポジトリを使用してクラウド インフラストラクチャを定義します。異なる環境に対応するさまざまなブランチを持つことで、このインフラストラクチャをオーケストレートします。

  • dev ブランチには、開発環境に適用される最新の変更が含まれています。
  • prod ブランチには、本番環境に適用される最新の変更が含まれています。

このインフラストラクチャを使用すると、常にリポジトリを参照してそれぞれの環境で必要とされる構成の内容を把握し、最初に dev 環境にマージすることにより、新たな変更を提案できます。次に dev ブランチを後続の prod ブランチにマージして、変更をプロモートします。

開始するには、solutions-terraform-cloudbuild-gitops リポジトリをフォークします。

  1. GitHub で https://github.com/GoogleCloudPlatform/solutions-terraform-cloudbuild-gitops.git に移動します。
  2. ページの右上隅にある [Fork] をクリックします。

    リポジトリをフォークする。

    これで、ソースファイルを格納する solutions-terraform-cloudbuild-gitops リポジトリのコピーが作成されます。

  3. Cloud Shell で、このフォークされたリポジトリのクローンを作成し、YOUR_GITHUB_USERNAME を GitHub ユーザー名に置き換えます。

    cd ~
    git clone https://github.com/YOUR_GITHUB_USERNAME/solutions-terraform-cloudbuild-gitops.git
    cd ~/solutions-terraform-cloudbuild-gitops
    

このリポジトリのコードは次のように構成されています。

  • environments/ フォルダには、devprod などの環境を表すサブフォルダが含まれています。これらはそれぞれ、成熟、開発、および運用のさまざまな段階でワークロードを論理的に分離します。これらの環境はできる限り類似した構成にすることをおすすめしますが、各サブフォルダには独自の Terraform 構成があり、必要に応じて一意の設定ができるようになっています。

  • modules/ フォルダには、インライン Terraform モジュールが含まれています。これらのモジュールは、関連リソースの論理グループを表し、異なる環境でコードを共有するために使用されます。

  • cloudbuild.yaml ファイルは、一連の手順に基づいてタスクを実行する方法など、Cloud Build に関する手順を含むビルド構成ファイルです。このファイルは、Cloud Build がコードを取得するブランチに応じて、条件付き実行を指定します。たとえば、

    • devprod ブランチの場合は、次の手順が実行されます。

      1. terraform init
      2. terraform plan
      3. terraform apply
    • その他のブランチの場合は、次の手順が実行されます。

      1. terraform init(すべての environments サブフォルダ)
      2. terraform plan(すべての environments サブフォルダ)

提案された変更がすべての環境に適していることを確認するために、terraform initterraform plan はすべての environments サブフォルダで実行されます。たとえば、pull リクエストをマージする前にプランを確認することで、承認されていないエンティティにアクセス権が付与されていないことを保証できます。

Cloud Storage バケットに状態を保存するように Terraform を構成する

デフォルトでは、Terraform はローカルの terraform.tfstate という名前のファイルに状態を保存します。多くのユーザーが Terraform を同時に実行していて、各マシンが現在のインフラストラクチャを独自に理解している場合は特に、このデフォルト構成が原因でチームでの Terraform の使用が難しくなる場合があります。

このような問題を回避するために、このセクションでは、Cloud Storage バケットを指すリモート状態を構成します。リモート状態はバックエンドの機能であり、このチュートリアルでは、backend.tf ファイルで構成されます。次に例を示します。

# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


terraform {
  backend "gcs" {
    bucket = "PROJECT_ID-tfstate"
    prefix = "env/dev"
  }
}

次の手順では、Cloud Storage バケットを作成し、新しいバケットと Google Cloud プロジェクトを指すようにいくつかのファイルを変更します。

  1. Cloud Shell で、Cloud Storage バケットを作成します。

    PROJECT_ID=$(gcloud config get-value project)
    gsutil mb gs://${PROJECT_ID}-tfstate
    
  2. オブジェクトのバージョニングを有効にして、デプロイの履歴を保持します。

    gsutil versioning set on gs://${PROJECT_ID}-tfstate
    

    オブジェクトのバージョニングを有効にすると、ストレージ コストが増加します。これは、古い状態のバージョンを削除するようにオブジェクトのライフサイクル管理を構成することで軽減できます。

  3. terraform.tfvars ファイルと backend.tf ファイルの両方で PROJECT_ID プレースホルダをプロジェクト ID に置き換えます。

    cd ~/solutions-terraform-cloudbuild-gitops
    sed -i s/PROJECT_ID/$PROJECT_ID/g environments/*/terraform.tfvars
    sed -i s/PROJECT_ID/$PROJECT_ID/g environments/*/backend.tf
    

    OS X/MacOS では、次のように sed -i の後に 2 つの引用符("")の追加が必要な場合があります。

    cd ~/solutions-terraform-cloudbuild-gitops
    sed -i "" s/PROJECT_ID/$PROJECT_ID/g environments/*/terraform.tfvars
    sed -i "" s/PROJECT_ID/$PROJECT_ID/g environments/*/backend.tf
    
  4. すべてのファイルが更新されたかどうかを確認します。

    git status
    

    出力は次のようになります。

    On branch dev
    Your branch is up-to-date with 'origin/dev'.
    Changes not staged for commit:
     (use "git add <file>..." to update what will be committed)
     (use "git checkout -- <file>..." to discard changes in working directory)
           modified:   environments/dev/backend.tf
           modified:   environments/dev/terraform.tfvars
           modified:   environments/prod/backend.tf
           modified:   environments/prod/terraform.tfvars
    no changes added to commit (use "git add" and/or "git commit -a")
    
  5. 変更を commit して push します。

    git add --all
    git commit -m "Update project IDs and buckets"
    git push origin dev
    

    GitHub の構成に応じて、前述の変更を push するために認証する必要があります。

Cloud Build サービス アカウントに権限を付与する

Cloud Build サービス アカウントが Google Cloud リソースを管理する目的で Terraform スクリプトを実行できるようにするには、プロジェクトへの適切なアクセス権を付与する必要があります。説明をわかりやすくするため、このチュートリアルではプロジェクト エディタへのアクセス権が付与されています。ただし、プロジェクト編集者のロールに広範囲の権限がある場合、本番環境では、通常は最小限のアクセス権を付与する、会社の IT セキュリティに関するベスト プラクティスに従う必要があります。

  1. Cloud Shell で、プロジェクトの Cloud Build サービス アカウントのメールを取得します。

    CLOUDBUILD_SA="$(gcloud projects describe $PROJECT_ID \
        --format 'value(projectNumber)')@cloudbuild.gserviceaccount.com"
    
  2. Cloud Build サービス アカウントに必要なアクセス権を付与します。

    gcloud projects add-iam-policy-binding $PROJECT_ID \
        --member serviceAccount:$CLOUDBUILD_SA --role roles/editor
    

Cloud Build を GitHub リポジトリに直接接続する

このセクションでは、Cloud Build GitHub アプリのインストール方法について説明します。このインストールでは、GitHub リポジトリを Google Cloud プロジェクトに接続できるため、新しいブランチを作成するか、コードを GitHub に push するたびに Cloud Build が Terraform マニフェストを自動的に適用できます。

次の手順では、solutions-terraform-cloudbuild-gitops リポジトリにのみアプリをインストールする手順を示していますが、より多くのリポジトリまたはすべてのリポジトリにアプリをインストールすることもできます。

  1. Cloud Build アプリの GitHub Marketplace ページにアクセスします。

    Cloud Build アプリページを開く

    • GitHub でアプリを初めて構成する場合は、ページの下部にある [Setup with Google Cloud Build] をクリックして、[Grant this app access to your GitHub account] をクリックします。
    • GitHub でアプリを構成するのが初めてではない場合は、[Configure access] をクリックします。個人アカウントの [Applications] ページが開きます。
  2. Cloud Build の行で [Configure] をクリックします。

  3. [Only select repositories] を選択し、[solutions-terraform-cloudbuild-gitops] を選択してリポジトリに接続します。

  4. [Save] または [Install] をクリックします。ボタンのラベルはワークフローによって異なります。インストールを続行するために Google Cloud にリダイレクトされます。

  5. Google Cloud アカウントでログインします。プロンプトが表示されたら、Cloud Build と GitHub の統合を承認します。

  6. Cloud Build ページでプロジェクトを選択します。ウィザードが表示されます。

  7. [リポジトリを選択] セクションで、GitHub アカウントと solutions-terraform-cloudbuild-gitops リポジトリを選択します。

  8. 利用規約に同意する場合は、チェックボックスをオンにして、[接続] をクリックします。

  9. [トリガーを作成] セクションで、[トリガーを作成] をクリックします。

    1. トリガー名を追加します(push-to-branch など)。このトリガー名は、後で必要になるためメモしておきます。
    2. [イベント] セクションで、[ブランチに push する] を選択します。
    3. [ソース] セクションの [ブランチ] フィールドで .* を選択します。
    4. [作成] をクリックします。

Cloud Build GitHub アプリが構成され、GitHub リポジトリが Google Cloud プロジェクトにリンクされました。今後、GitHub リポジトリの変更により Cloud Build の実行がトリガーされ、GitHub Checks を使用して結果が GitHub に報告されます。

新機能ブランチで環境構成を変更する

これでほとんどの環境が構成されました。次に開発環境でコードを変更してみましょう。

  1. GitHub で、フォークされたリポジトリのメインページに移動します。

    https://github.com/YOUR_GITHUB_USERNAME/solutions-terraform-cloudbuild-gitops
    
  2. dev ブランチ内にいることを確認します。

  3. ファイルを編集用に開くには、modules/firewall/main.tf ファイルに移動し、鉛筆アイコンをクリックします。

  4. 30 行目の [target_tags] フィールドで "http-server2" のタイプミスを修正します。

    値には "http-server" を指定してください。

  5. 「Fixing http firewall target」などの commit メッセージをページの下部に追加し、[Create a new branch for this commit and start a pull request] を選択します。

  6. [Propose changes] をクリックします。

  7. 次のページで、[Create pull request] をクリックして、変更した新しい pull リクエストを開きます。

    pull リクエストが開くと、Cloud Build ジョブが自動的に開始されます。

  8. [Show all checks] をクリックし、チェックが緑色になるまで待ちます。

    pull リクエストのすべてのチェックを表示する。

  9. [Details] をクリックして、[View more details on Google Cloud Build] リンクで terraform plan の出力などの詳細情報を表示します。

pull リクエストはまだ統合しないでください。

Cloud Build ジョブが cloudbuild.yaml ファイルで定義されたパイプラインを実行したことに注意してください。前述のように、このパイプラインは取得されるブランチに応じて異なる動作をします。ビルドは、$BRANCH_NAME 変数が環境フォルダと一致するかどうかを確認します。一致する場合、Cloud Build はその環境の terraform plan を実行します。それ以外の場合、Cloud Build はすべての環境に対して terraform plan を実行し、提案された変更がすべての環境に適していることを確認します。これらの計画のいずれかが失敗すると、ビルドが失敗します。

- id: 'tf plan'
  name: 'hashicorp/terraform:1.0.0'
  entrypoint: 'sh'
  args: 
  - '-c'
  - | 
      if [ -d "environments/$BRANCH_NAME/" ]; then
        cd environments/$BRANCH_NAME
        terraform plan
      else
        for dir in environments/*/
        do 
          cd ${dir}   
          env=${dir%*/}
          env=${env#*/}  
          echo ""
          echo "*************** TERRAFORM PLAN ******************"
          echo "******* At environment: ${env} ********"
          echo "*************************************************"
          terraform plan || exit 1
          cd ../../
        done
      fi 

同様に、terraform apply コマンドは環境ブランチに対して実行されますが、それ以外の場合は完全に無視されます。このセクションでは、新しいブランチにコード変更を送信したため、Google Cloud プロジェクトにインフラストラクチャの展開は適用されませんでした。

- id: 'tf apply'
  name: 'hashicorp/terraform:1.0.0'
  entrypoint: 'sh'
  args: 
  - '-c'
  - | 
      if [ -d "environments/$BRANCH_NAME/" ]; then
        cd environments/$BRANCH_NAME      
        terraform apply -auto-approve
      else
        echo "***************************** SKIPPING APPLYING *******************************"
        echo "Branch '$BRANCH_NAME' does not represent an official environment."
        echo "*******************************************************************************"
      fi

ブランチをマージする前に Cloud Build の実行成功を必須とする

それぞれの Cloud Build の実行が成功した場合にのみマージを適用できるようにするには、次の手順に進みます。

  1. GitHub で、フォークされたリポジトリのメインページに移動します。

    https://github.com/YOUR_GITHUB_USERNAME/solutions-terraform-cloudbuild-gitops
    
  2. リポジトリ名の下にある [Settings] をクリックします。

  3. 左側のメニューで [Branches] をクリックします。

  4. [Branch protection rules] で、[Add rule] をクリックします。

  5. [Branch name pattern] に「dev」と入力します。

  6. [Protect matching branches] セクションで、[Require status checks to pass before merging] を選択します。

  7. 以前に作成した Cloud Build トリガー名を検索します。

  8. [Create] をクリックします。

  9. 手順 3~7 を繰り返し、[Branch name pattern] を prod に設定します。

この構成は、dev ブランチと prod ブランチの両方を保護するうえで重要です。つまり、commit を最初に別のブランチに push する必要があり、その後でのみ保護されたブランチにマージできます。このチュートリアルの保護では、マージを許可するために Cloud Build の実行が成功する必要があります。

開発環境の変更を促進する

マージされるのを待っている pull リクエストがあります。そこで、必要な状態を dev 環境に適用します。

  1. GitHub で、フォークされたリポジトリのメインページに移動します。

    https://github.com/YOUR_GITHUB_USERNAME/solutions-terraform-cloudbuild-gitops
    
  2. リポジトリ名の下で、[Pull requests] をクリックします。

  3. 作成した pull リクエストをクリックします。

  4. [Merge pull request]、[Confirm merge] の順にクリックします。

    統合を確定する。

  5. 新しい Cloud Build がトリガーされたことを確認します。

    Cloud Build ページに移動する

  6. ビルドを開き、ログを確認します。

    ビルドが完了すると、次のような内容が表示されます。

    Step #3 - "tf apply": external_ip = EXTERNAL_IP_VALUE
    Step #3 - "tf apply": firewall_rule = dev-allow-http
    Step #3 - "tf apply": instance_name = dev-apache2-instance
    Step #3 - "tf apply": network = dev
    Step #3 - "tf apply": subnet = dev-subnet-01
    
  7. EXTERNAL_IP_VALUE をコピーし、ウェブブラウザでアドレスを開きます。

    http://EXTERNAL_IP_VALUE
    

    このプロビジョニングで、VM を起動してファイアウォール ルールが伝播されるまでに数秒かかる場合があります。最終的には、ウェブブラウザに「Environment: dev」が表示されます。

  8. Cloud Storage バケット内の Terraform 状態ファイルに移動します。

    https://storage.cloud.google.com/PROJECT_ID-tfstate/env/dev/default.tfstate
    

本番環境の変更をプロモートする

開発環境をすべてテストしたため、インフラストラクチャ コードを本番環境にプロモートさせることができます。

  1. GitHub で、フォークされたリポジトリのメインページに移動します。

    https://github.com/YOUR_GITHUB_USERNAME/solutions-terraform-cloudbuild-gitops
    
  2. リポジトリ名の下で、[Pull requests] をクリックします。

  3. [New pull request] をクリックします。

  4. ベース リポジトリの場合は、フォークされたリポジトリを選択します。

  5. [base] に、独自のベース リポジトリから prod を選択します。[compare] に dev を選択します。

    変更内容を比較します。

  6. [Create pull request] をクリックします。

  7. [title] には「Promoting networking changes」などのタイトルを入力し、[Create pull request] をクリックします。

  8. terraform plan の詳細を含む Cloud Build から提案された変更を確認し、[Merge pull request] をクリックします。

  9. [Confirm merge] をクリックします。

  10. Google Cloud コンソールで [ビルド履歴] ページを開き、本番環境に適用されている変更を確認します。

    Cloud Build ページに移動する

  11. ビルドが完了するのを待ってから、ログを確認します。

    ログの最後に、次のような内容が表示されます。

    Step #3 - "tf apply": external_ip = EXTERNAL_IP_VALUE
    Step #3 - "tf apply": firewall_rule = prod-allow-http
    Step #3 - "tf apply": instance_name = prod-apache2-instance
    Step #3 - "tf apply": network = prod
    Step #3 - "tf apply": subnet = prod-subnet-01
    
  12. EXTERNAL_IP_VALUE をコピーし、ウェブブラウザでアドレスを開きます。

    http://EXTERNAL_IP_VALUE
    

    このプロビジョニングで、VM を起動してファイアウォール ルールが伝播されるまでに数秒かかる場合があります。最終的には、ウェブブラウザに「Environment: prod」が表示されます。

  13. Cloud Storage バケット内の Terraform 状態ファイルに移動します。

    https://storage.cloud.google.com/PROJECT_ID-tfstate/env/prod/default.tfstate
    

Cloud Build でサーバーレスのコード パイプラインとしてのインフラストラクチャの構成が正常に終了しました。今後、次のことをお試しください。

  • 個別のユースケースのデプロイメントを追加します。
  • ニーズを反映する追加の環境を作成します。
  • 環境ごとの VPC ではなく、環境ごとのプロジェクトを使用します。

クリーンアップ

チュートリアルが終了したら、Google Cloud で作成したリソースをクリーンアップして今後料金が発生しないようにします。

プロジェクトの削除

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

GitHub リポジトリの削除

GitHub リポジトリで新しい pull リクエストがブロックされないように、ブランチの保護ルールを削除できます。

  1. GitHub で、フォークされたリポジトリのメインページに移動します。
  2. リポジトリ名の下にある [Settings] をクリックします。
  3. 左側のメニューで [Branches] をクリックします。
  4. [Branch protection rules] セクションで、devprod の両方の行の [Delete] ボタンをクリックします。

必要に応じて、Cloud Build アプリは、GitHub から完全にアンインストールできます。

  1. GitHub の [Applications] 設定に移動します。

    GitHub の [Applications] ページに移動

  2. [Installed GitHub Apps] タブで、[Cloud Build] 行の [Configure] をクリックします。次に、[Danger zone] セクションで、[Uninstall Google Cloud Builder] 行の [Uninstall] ボタンをクリックします。

    ページの上部に「You're all set.A job has been queued to uninstall Google Cloud Build.」と表示されます。

  3. [Authorized GitHub Apps] タブで、[Google Cloud Build] 行の [Revoke] ボタンをクリックし、ポップアップで [I understand, revoke access] をクリックします。

GitHub リポジトリを保持しない場合は、次のようにします。

  1. GitHub で、フォークされたリポジトリのメインページに移動します。
  2. リポジトリ名の下にある [Settings] をクリックします。
  3. [Danger Zone] までスクロールします。
  4. [Delete this repository] をクリックし、確認手順に沿って操作します。

次のステップ