以下の内容はhttps://engineer.crowdworks.jp/entry/delete-tfstate-blocksより取得しました。


Terraform / OpenTofuでtfstate関連のブロックの削除を便利に行う

こんにちは。クラウドワークス SRE チームの田中(@kangaechu)です。

12月はアドベントカレンダーで重めの記事を書いたので、今回は軽いネタです。

手動での削除が手間だったTerraform/OpenTofuの import/moved/removed ブロックをGitHub Actionsを活用して効率化しました。その具体的な仕組みをご紹介します。

背景と課題

Terraform/OpenTofuでは、tfstateに対する処理を行うために、import/moved/removed ブロックを使用します。 例えば、以下は AWSで作成済みのEC2インスタンス i-abcd1234aws_instance.example リソースとしてインポートするための記述です。

# TODO: apply後に削除する
import {
  to = aws_instance.example
  id = "i-abcd1234"
}

resource "aws_instance" "example" {
  name = "hashi"
  # (other resource arguments...)
}

このような記述を行うことで、terraform plan でimportの状態を確認し、 terraform apply でtfstateにリソースを追加することができます。

import以外にも、movedブロックを使用してリソースの移動、removedブロックを使用してリソースの削除を行うことができます。

各ブロックのドキュメントです。

import moved removed
Terraform import moved removed
OpenTofu import moved removed

以前は terraform state import/mv/rm コマンドを使用してtfstateにリソースを追加する必要がありましたが、 これらのブロックを使用することで、tfstateの変更時にCIやレビューのプロセスを通して変更を行うことができるようになりました。

クラウドワークスでは、TerraformのCI/CDにAtlantisを使用しています。

engineer.crowdworks.jp

AtlantisをCI/CDで使用すると、GitHubにPull Requestを作成したりpushしたタイミングで terraform plan を実行し、plan結果をPull RequestのコメントにPostしてくれます。また、Pull Requestに atlantis apply とコメントすると、Atlantisは terraform apply を実行し、成功したらPull Requestをマージします。 import/moved/removed ブロックも同様に行われるため、マージ後はこれらのブロックは不要となります。 ただ、わざわざ手動で削除するのは面倒ですよね。

解決策

そこで、GitHub Actionを使用してこれらのブロックを自動で削除するGitHub Actionを作成しました。

name: cleanup-state-block
permissions:
  contents: write
  pull-requests: write

on:
  schedule:
    - cron: '0 22 * * 0' # JST 07:00 on Monday
  workflow_dispatch:

jobs:
  cleanup-state-block:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Download HCLEdit
        run: |
          hcledit_version=0.2.15
          case $(uname -m) in
              x86_64) arch=amd64 ;;
              aarch64) arch=arm64 ;;
          esac
          url="https://github.com/minamijoyo/hcledit/releases/download/v${hcledit_version}/hcledit_${hcledit_version}_linux_${arch}.tar.gz"
          echo "Downloading HCLEdit from $URL"
          curl -sSL $url | tar -xvzf - -C /usr/local/bin 
          hcledit version
        shell: bash

      - name: remove state blocks
        run: |
          # HCLEditでstate blockを削除
          target_block_names=(import removed moved)
          for block_name in "${target_block_names[@]}"; do
            find . -type f -name "*.tf" -execdir hcledit block rm "$block_name" -f {} -u \;
          done
          # 変更がなかったら終了
          change_file_count=$(git diff --name-only --ignore-blank-lines | wc -l)
          if [ $change_file_count -eq 0 ]; then
            echo "No changes"
            exit 0
          fi
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"
          
          # HCLEdit block rmは空行も修正してしまうため、空行のみの変更を無視してステージング
          branch_name=cleanup-state-blocks
          git switch -c $branch_name
          git diff --ignore-blank-lines | git apply --cached
          git commit -m "Remove state block" -m "This PR is generated by GitHub Actions"
          git push -f origin $branch_name
          echo 'CREATE_PR=true' >> $GITHUB_ENV
        shell: bash

      - uses: actions/create-github-app-token@v1
        if: env.CREATE_PR == 'true'
        id: app-token
        with:
          app-id: ${{ secrets.GHA_APP_ID }}
          private-key: ${{ secrets.GHA_PRIVATE_KEY }}

      - name: Create a pull request
        if: env.CREATE_PR == 'true'
        run: |
          echo -e "apply済みで不要なimport/removed/movedブロックを削除します。\nこのPRはcleanup-state-blockワークフローから実行されています。" > body.txt
          gh pr create --title "[cleanup] 不要なimport/removed/movedブロックを削除" --body-file body.txt --base ${{ github.ref_name }} --head cleanup-state-blocks
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
        shell: bash

実装

hcledit

hcleditは、HCLファイルを編集するためのツールです。

qiita.com

今回は、hcledit block rm コマンドを使用して、指定したブロックを削除しています。 hcleditはHCLファイルの構造も見ているため、ブロックの前の行にブロックに対するコメントが書かれている場合、# TODO: apply後に削除する のようなコメントもあわせて削除されます。 また、ブロック間の空行も削除します。とても便利ですね。

git diff --ignore-blank-lines

hcledit block rm は修正対象のファイルの連続した空行も削除してしまうため、空行のみの変更を無視してステージングするために git diff --ignore-blank-lines を使用しています。 これにより、空行のみの変更を無視してステージングすることができます。

Pull Requestの作成

Pull Requestの作成にはGitHub Appを使用しています。 GitHub Appの作成時には、以下の権限を付与します。

  • Contents: read
  • Pull requests: read and write
  • Metadata: read

また、GitHub AppのApp IDとPrivate KeyをリポジトリのActions secrets and variablesに指定します。

動作結果とメリット

GitHub Actions Workflowで指定した時刻(例だと毎週月曜日 7:00 JST)に削除のPull Requestが作成されます。 また、GitHub Actionsの画面からも手動で実行が可能です。

GitHub Actionsによって作成されたPull Requestはこのようになります。

GitHub Actionsによって作成されたPull Request

GitHub Actionsによって削除されたimport/removedブロック

Pull Requestが作成されたらレビューしてマージするだけ。かんたんな作業でリポジトリが綺麗な状態を保てて最高ですね。

まとめ

hcleditとGitHub Actionsを使用して、Terraform/OpenTofuの import/moved/removed ブロックの削除を自動化するGitHub Actionを作成しました。

クラウドワークスではSREを含むエンジニアを募集しています。興味のある方は以下のリンクからご応募ください。

crowdworks.co.jp




以上の内容はhttps://engineer.crowdworks.jp/entry/delete-tfstate-blocksより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14