Terraform、Jenkins、GitOps でのコードとしてのインフラストラクチャの管理

このチュートリアルでは、一般的な GitOps 手法を使用して、TerraformJenkins によりインフラストラクチャをコードとして管理する方法について説明します。このチュートリアルは、ソフトウェア アプリケーションを管理する方法でインフラストラクチャを管理するためのベスト プラクティスを探しているデベロッパーとオペレーターを対象としています。この記事は、Terraform、Jenkins、GitHub、Google Kubernetes Engine(GKE)、Google Cloud を理解していることを前提としています。

アーキテクチャ

このチュートリアルで使用されているアーキテクチャは、GitHub ブランチ(devprod)を使用して、実際の開発環境と本番環境を表します。これらの環境は、Google Cloud プロジェクトの Virtual Private Cloud(VPC)ネットワーク(devprod)によって定義されます。

インフラストラクチャの提案

次のアーキテクチャ図に示すように、デベロッパーまたはオペレーターが、GitHub の保護されていないブランチ(通常は機能ブランチ)にインフラストラクチャ プロポーザルを行うと、プロセスが開始されます。該当する場合、dev ブランチへの pull リクエスト(PR)を介して開発環境にプロモーションを行うことができます。その後、Jenkins によってジョブが自動的にトリガーされ、検証パイプラインが実行されます。このジョブは terraform plan コマンドを実行して、検証結果を GitHub に報告し、詳細なインフラストラクチャ変更レポートへのリンクを含めます。潜在的な変更を共同編集者と話し合ってレビューし、変更が dev ブランチにマージされる前にフォローアップ commit を追加できるため、この手順は重要です。

Terraform の実行を管理するための GitOps プラクティスを示すアーキテクチャ。

Dev デプロイ

検証プロセスが正常に完了し、提案されたインフラストラクチャの変更を承認すると、pull リクエストを dev ブランチにマージできます。次の図は、このプロセスを示しています。

pull リクエストの「dev」ブランチへのマージ。

マージが完了すると、Jenkins によって別のジョブがトリガーされ、デプロイ パイプラインが実行されます。このシナリオでは、開発環境の Terraform マニフェストが開発環境で適用されます。Terraform コードを本番環境にプロモートする前にテストできるため、この手順は重要です。

Prod デプロイ

テストを行い、変更を本番環境に昇格する準備が整ったら、dev ブランチを prod ブランチにマージして、本番環境へのインフラストラクチャのインストールをトリガーする必要があります。次の図は、このプロセスを示しています。

「dev」ブランチを「prod」ブランチにマージして、本番環境へのインフラストラクチャのインストールをトリガーする。

pull リクエストを作成する場合も、同じ検証プロセスが実行されます。このプロセスにより、運用チームは本番環境で提案された変更を確認して承認できます。

目標

  • GitHub リポジトリを設定します。
  • Terraform リモート状態ストレージを作成します。
  • GKE クラスタを作成して Jenkins をインストールします。
  • 機能ブランチで環境構成を変更します。
  • 開発環境への変更を促進します。
  • 本番環境への変更を促進します。

料金

このチュートリアルでは、課金対象である次の Google Cloud コンポーネントを使用します。

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

このチュートリアルを終了した後、作成したリソースを削除すると、それ以上の請求は発生しません。詳しくは、クリーンアップをご覧ください。

始める前に

  1. Google アカウントにログインします。

    Google アカウントをまだお持ちでない場合は、新しいアカウントを登録します。

  2. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    [プロジェクトの選択] ページに移動

  3. Cloud プロジェクトに対して課金が有効になっていることを確認します。プロジェクトに対して課金が有効になっていることを確認する方法を学習する

  4. Cloud Console で、Cloud Shell をアクティブにします。

    Cloud Shell をアクティブにする

    Cloud Console の下部にある Cloud Shell セッションが開始し、コマンドライン プロンプトが表示されます。Cloud Shell はシェル環境です。gcloud コマンドライン ツールなどの Cloud SDK がすでにインストールされており、現在のプロジェクトの値もすでに設定されています。セッションが初期化されるまで数秒かかることがあります。

  5. Cloud Shell で、プロジェクト ID を構成し、GitHub ユーザー名とメールアドレスを設定します。
    PROJECT_ID=PROJECT_ID
    GITHUB_USER=YOUR_GITHUB_USER
    GITHUB_EMAIL=YOUR_EMAIL_ADDRESS
    gcloud config set project $PROJECT_ID
    

    Cloud Shell から GitHub にまだアクセスしたことがない場合、次のように、ユーザー名とメールアドレスで GitHub を構成できます。

    git config --global user.email "$GITHUB_EMAIL"
    git config --global user.name "$GITHUB_USER"
    

    GitHub はこの情報を使用して、Cloud Shell で作成する commit の作成者を識別します。

GitHub リポジトリを設定する

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

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

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

開始するには、solutions-terraform-jenkins-gitops リポジトリをフォークする必要があります。

  1. GitHub で solutions-terraform-jenkins-gitops リポジトリに移動します。
  2. [フォーク] をクリックします。

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

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

  3. Cloud Shell で、このフォークされたリポジトリのクローンを作成します。

    cd ~
    git clone https://github.com/$GITHUB_USER/solutions-terraform-jenkins-gitops.git
    cd ~/solutions-terraform-jenkins-gitops
    

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

    • example-pipelines/ フォルダ: このチュートリアルで使用するパイプラインの例が含まれるサブフォルダがあります。
    • example-create/: 環境で仮想マシンを作成するための Terraform コードが含まれています。
    • environments/: バックエンド構成で dev 環境フォルダと prod 環境フォルダが含まれ、example-create/ フォルダのファイルへのリンクが含まれています。
    • jenkins-gke/ フォルダ: 新しい GKE クラスタに Jenkins をデプロイするために必要なスクリプトが含まれています。
    • tf-gke/: GKE にデプロイし、Jenkins とその依存リソースをインストールするための Terraform コードが含まれています。

Terraform リモート状態ストレージを作成する

Terraform の状態はデフォルトでローカルに保存されます。ただし、リモート セントラル ストレージに状態を保存して、どのシステムからもアクセスできるようにすることをおすすめします。このアプローチは、異なるシステム上で複数のコピーを作成し、インフラストラクチャの構成と状態の不一致を引き起こす可能性を回避するのに役立ちます。

このセクションでは、Terraform のリモート状態を保存する Cloud Storage バケットを構成します。

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

    gsutil mb gs://${PROJECT_ID}-tfstate
    
  2. オブジェクトのバージョニングを有効にして、状態の履歴を保持します。

    gsutil versioning set on gs://${PROJECT_ID}-tfstate
    
  3. terraform.tfvars ファイルと backend.tf ファイルの両方で PROJECT_ID プレースホルダをプロジェクト ID に置き換えます。

    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./example-pipelines/environments/*/terraform.tfvars
    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./example-pipelines/environments/*/backend.tf
    
    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./jenkins-gke/tf-gke/terraform.tfvars
    sed -i.bak "s/PROJECT_ID/${PROJECT_ID}/g" ./jenkins-gke/tf-gke/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:   example-pipelines/environments/dev/backend.tf
            modified:   example-pipelines/environments/dev/terraform.tfvars
            modified:   example-pipelines/environments/prod/backend.tf
            modified:   example-pipelines/environments/prod/terraform.tfvars
            modified:   jenkins-gke/tf-gke/backend.tf
            modified:   jenkins-gke/tf-gke/terraform.tfvars
    
  5. 変更を commit して push します。

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

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

GKE クラスタの作成と Jenkins のインストール

このセクションでは、Terraform と Helm を使用して、コードとしてのインフラストラクチャを管理する環境を設定します。まず、Terraform と Cloud Foundations Toolkit を使用して、Virtual Private Cloud、GKE クラスタ、Workload Identity を構成します。次に、helm を使用して、この環境に Jenkins をインストールします。

Terraform コマンドの実行を開始する前に、GitHub の個人アクセス トークンを作成する必要があります。このトークンは、フォークされたリポジトリに Jenkins からアクセスできるようにするために必要です。

GitHub 個人アクセス トークンを作成する

  1. GitHub にログインします。
  2. プロフィール写真をクリックし、[設定] をクリックします。
  3. [デベロッパー設定]、[個人用アクセス トークン] の順にクリックします。
  4. [新しいトークンを生成] をクリックし、[メモ] フィールドでトークンの説明を入力して、repo スコープを選択します。
  5. [トークンを生成] をクリックし、新しく作成したトークンをクリップボードにコピーします。

    トークンを生成してクリップボードにコピーする。

  6. Cloud Shell で、GITHUB_TOKEN 変数にトークンを保存します。この変数の内容は、後で GKE クラスタの Secret として保存されます。

    GITHUB_TOKEN="NEWLY_CREATED_TOKEN"
    

GKE クラスタを作成して Jenkins をインストールします。

GKE クラスタを作成します。このクラスタには、Workload Identity jenkins-wi-jenkins@PROJECT_ID.iam.gserviceaccount.com)が含まれ、これにより Jenkins が Cloud Console の [サービス アカウント] ページで必要とする権限をJenkins に付与できます。セキュリティのプロパティと管理性の向上により、Workload Identity を使用して GKE 内から Google Cloud サービスにアクセスすることをおすすめします。

Google Cloud インフラストラクチャをコードとして管理するには、Jenkins で Google Cloud API を使用するための認証を行う必要があります。以下の手順により、Terraform を使用して Jenkinsが Google サービス アカウント(GSA)として機能するために使用する Kubernetes サービス アカウント(KSA)を構成します。この構成により、Google Cloud API にアクセスすると、Jenkins が自動的に GSA として認証されます。

説明をわかりやすくするため、このチュートリアルではプロジェクト エディタへのアクセス権を付与します。ただし、このロールにはさまざまな権限があるため、本番環境では、通常は最小限のアクセス権を付与する、会社の IT セキュリティに関するベスト プラクティスに従う必要があります。

  1. Cloud Shell で、Terraform をインストールします。

    wget https://releases.hashicorp.com/terraform/0.12.24/terraform_0.12.24_linux_amd64.zip
    unzip terraform_0.12.24_linux_amd64.zip
    sudo mv terraform /usr/local/bin/
    rm terraform_0.12.24_linux_amd64.zip
    
  2. GKE クラスタを作成して Jenkins をインストールします。

    cd jenkins-gke/tf-gke/
    terraform init
    terraform plan --var "github_username=$GITHUB_USER" --var "github_token=$GITHUB_TOKEN"
    terraform apply --auto-approve --var "github_username=$GITHUB_USER" --var "github_token=$GITHUB_TOKEN"
    

    この処理が完了するまでに数分かかる場合があります。出力は次のようになります。

    Apply complete! Resources: 28 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    ca_certificate = LS0tLS1CRU..
    client_token = <sensitive>
    cluster_name = jenkins
    gcp_service_account_email = jenkins-wi-jenkins@PROJECT_ID.iam.gserviceaccount.com
    jenkins_k8s_config_secrets = jenkins-k8s-config
    jenkins_project_id = PROJECT_ID
    k8s_service_account_name = jenkins-wi-jenkins
    kubernetes_endpoint = <sensitive>
    service_account = tf-gke-jenkins-k253@PROJECT_ID.iam.gserviceaccount.com
    zone = us-east4-a
    

    新しく作成された GKE クラスタに Jenkins がデプロイされると、Helm チャートのドキュメントに従って、Jenkins ホーム ディレクトリが永続ボリュームに保存されます。このデプロイには、example-pipelines/environments/Jenkinsfile を使用して事前構成されたマルチブランチ パイプラインも付属します。これは pull リクエストでトリガーされ、dev ブランチと prod ブランチにマージされます。

  3. メインフォルダに戻ります。

    cd ../..
    
  4. 先ほど作成したクラスタ認証情報を取得します。

    gcloud container clusters get-credentials jenkins --zone=us-east4-a --project=${PROJECT_ID}
    
  5. Jenkins の URL と認証情報を取得します。

    JENKINS_IP=$(kubectl get service jenkins -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    JENKINS_PASSWORD=$(kubectl get secret jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
    printf "Jenkins url: http://$JENKINS_IP\nJenkins user: admin\nJenkins password: $JENKINS_PASSWORD\n"
    
  6. 前の手順の出力情報を使用して、Jenkins にログインします。

  7. GitHub でビルドに直接アクセスするリンクを作成できるように、Jenkins のロケーションを構成します。[Manage Jenkins] > [Configure System] をクリックし、[Jenkins URL] フィールドで Jenkins URL を設定します。

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

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

  1. Cloud Shell で、チームの他のメンバーに影響を与えることなく作業できる新機能ブランチを作成します。

    git checkout -b change-vm-name
    
  2. 仮想マシン名を変更します。

    cd example-pipelines/example-create
    sed -i.bak "s/\${var.environment}-001/\${var.environment}-new/g" main.tf
    

    example-create フォルダにある main.tf ファイルを変更します。このファイルは dev 環境フォルダと prod 環境フォルダによってリンクされています。つまり、変更が両方の環境に伝播されます。

  3. コードの変更を GitHub 機能ブランチに push します。

    git commit -am "change vm name"
    git push --set-upstream origin change-vm-name
    
  4. GitHub で、フォークされたリポジトリのメインページに移動します。

  5. リポジトリの [Pull requests] タブをクリックし、[New pull request] をクリックします。

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

    ベース リポジトリに対する pull リクエストを作成する。

  7. ベースには dev を選択し、比較には change-vm-name を選択します。

    ベースを選択し、フォークを比較します。

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

  9. pull リクエストが開くと、Jenkins ジョブが自動的に開始されます(Jenkins で新しい pull リクエストが確認されるまで数分かかる場合があります)。[Show all checks] をクリックし、チェックが緑色になるまで待ちます。

    チェックが緑色になるまで待ちます。

  10. [Details] をクリックして、terraform plan コマンドの出力などの詳細情報を表示します。

    「terraform plan」の出力など、結果に関する詳細。

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

提案されている変更がすべての環境に確実に適用されるよう、terraform plan がすべての example-pipelines/environments サブフォルダで実行されます。この方法により、pull リクエストをマージする前にプランを確認して、許可されていないエンティティへのアクセスを許可しないなどの保証ができます。

JENKINSFILEterraform plan コードは次のとおりです。

stage('TF plan') {
  when { anyOf {branch "prod";branch "dev";changeRequest() } }
  steps {
    container('terraform') {
      sh '''
      if [[ $CHANGE_TARGET ]]; then
        TARGET_ENV=$CHANGE_TARGET
      else
        TARGET_ENV=$BRANCH_NAME
      fi

      if [ -d "example-pipelines/environments/${TARGET_ENV}/" ]; then
        cd example-pipelines/environments/${TARGET_ENV}
        terraform plan
      else
        for dir in example-pipelines/environments/*/
        do
          cd ${dir}
          env=${dir%*/}
          env=${env#*/}
          echo ""
          echo "*************** TERRAFOM PLAN ******************"
          echo "******* At environment: ${env} ********"
          echo "*************************************************"
          terraform plan || exit 1
          cd ../../../
        done
      fi'''
    }
  }
}

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

JENKINSFILEterraform apply コードは次のとおりです。

stage('TF Apply') {
  when { anyOf {branch "prod";branch "dev" } }
  steps {
    container('terraform') {
      sh '''
      TARGET_ENV=$BRANCH_NAME

      if [ -d "example-pipelines/environments/${TARGET_ENV}/" ]; then
        cd example-pipelines/environments/${TARGET_ENV}
        terraform apply -input=false -auto-approve
      else
        echo "*************** SKIPPING APPLY ******************"
        echo "Branch '$TARGET_ENV' does not represent an official environment."
        echo "*************************************************"
      fi'''
    }
  }
}

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

Jenkins ジョブが成功した場合にのみ、マージが適用されるようにできます。

  1. GitHub で、フォークされたリポジトリのメインページに移動します。
  2. リポジトリ名の下にある [Settings] をクリックします。
  3. [STRONGBranches] をクリックします。
  4. [Branch protection rules] で、[Add rule] をクリックします。
  5. [Branch name pattern] に「dev」と入力します。
  6. [Protect matching branches] で [Require status checks to pass before merging] を選択し、[continuous-integration/jenkins/pr-merge] を選択します。

    (省略可)未審査と未承認の pull リクエストが本番環境にマージされないように、[Require pull request reviews before merging] オプションと [Include administrators] オプションを有効にすることを検討してください。

  7. [作成] をクリックします。

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

この構成は、dev ブランチと prod ブランチを保護するうえで重要です。つまり、最初に commit を別のブランチに push してから、保護されたブランチにマージする必要があります。このチュートリアルでは、Jenkins ジョブの実行が成功してはじめてマージが許可されることで保護が有効になります。新しく作成された pull リクエストで構成が適用されたかどうかを確認できます。緑のチェックマークを探します。

構成が適用されたことを確認する。

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

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

  1. GitHub で、フォークされたリポジトリのメインページに移動します。
  2. リポジトリ名の下で、[Pull requests] をクリックします。
  3. 作成した pull リクエストをクリックします。
  4. [Merge pull request]、[Confirm merge] の順にクリックします。

    pull リクエストをマージして確認します。

  5. Jenkins で [Blue Ocean を開く] をクリックします。次に、terraform-jenkins-create-demo マルチブランチ プロジェクトの [Branchs] タブで、ステータス アイコンを調べて、新しい dev ジョブがトリガーされたかどうかを確認します。起動には 1 分程度かかる場合があります。

    新しい **dev** ジョブがトリガーされたことを確認します。

  6. Cloud Console で [VM インスタンス] ページに移動し、新しい名前の VM があるかどうかを確認します。

    [VM インスタンス] に移動

    新しい名前の VM があるかどうかを確認します。

本番環境の変更の促進

開発環境をテストしたので、インフラストラクチャ コードを本番環境に昇格させることが可能です。

  1. GitHub で、フォークされたリポジトリのメインページに移動します。
  2. [New pull request] をクリックします。
  3. ベース リポジトリの場合は、フォークしたリポジトリを選択します。

    フォークしたリポジトリを選択する。

  4. ベースには prod を選択し、比較には dev を選択します。

    ベースと比較のフォーク リポジトリ。

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

  6. タイトルには「Promoting vm name change」などのタイトルを入力し、[Create pull request] をクリックします。

  7. チェッカーが緑色になるまで待ち(1 ~ 2 分かかる場合があります)、[continuous-integration/jenkins/pr-merge] の横にある [Details] リンクをクリックします。

    チェッカーが緑色に変わるまで待ちます。

  8. Jenkins で TF Plan を選択し、ログで提案された変更を確認します。

    ログ内の提案された変更を確認する。

  9. 提案された変更が正しく表示されている場合は、GitHub で [Merge pull request] をクリックしてから [Confirm merge] をクリックします。

  10. Cloud Console で [VM インスタンス] ページを開き、本番環境 VM がデプロイされていることを確認します。

    VM インスタンス

    本番環境 VM がデプロイされているかどうかを確認します。

Jenkins で Infrastructure as Code パイプラインを構成しました。今後、次のことをお試しください。

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

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud Platform アカウントに課金されないようにする手順は次のとおりです。

プロジェクトの削除

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ