フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

【Terraform】null_resourceでシーケンシャルなterraform リソース作成を実現する

f:id:lirlia:20220113143145p:plain

Terraform 内のリソースにおいて depends_on という順序制御の仕組みがあります。しかし同じ module を複数呼び出す際に、シーケンシャルに実行したいケースがあるともいます。(私はありました)

もちろん呼び出す module ごとに depends_on を書けば解決するのですが、新しくmodule を呼び出す処理を書く時に絶対 depends_on を忘れそうです。

できれば「呼び出された module 側で対処して欲しい」ですね。

方法

方法は単純で作成したいリソースの前後にnull_resourceを作成し、ロックの作成、削除を行ないます。

イメージとしては以下のとおりです。

  1. null_resource でロックファイルを作成
  2. リソースを作成
  3. null_resource でロックファイルを削除

このようにすることで上記のプロセスを複数箇所から呼び出しても、ロックファイルの有無によって並列実行を防ぐことが出来ます。

サンプルコード

今回は GCPでIPアドレスを作成するmoduleを3つ呼び出し、ロックファイルでこれらの実行をシーケンシャルにできるか試しました。(module名が node-pool になっているのは無視してください)

コードはこちら。

github.com

module.node-pool-111111.null_resource.get_lock: Creating...
module.node-pool-222222.null_resource.get_lock: Creating...
module.node-pool-333333.null_resource.get_lock: Creating...
module.node-pool-111111.null_resource.get_lock: Provisioning with 'local-exec'...
module.node-pool-222222.null_resource.get_lock: Provisioning with 'local-exec'...
module.node-pool-333333.null_resource.get_lock: Provisioning with 'local-exec'...
module.node-pool-111111.null_resource.get_lock (local-exec): Executing: ["/bin/sh" "-c" "modules/test/get-lock.sh lockfile1"]
module.node-pool-333333.null_resource.get_lock (local-exec): Executing: ["/bin/sh" "-c" "modules/test/get-lock.sh lockfile1"]
module.node-pool-222222.null_resource.get_lock (local-exec): Executing: ["/bin/sh" "-c" "modules/test/get-lock.sh lockfile1"]
module.node-pool-222222.null_resource.get_lock: Creation complete after 0s [id=6049866523593116765]
module.node-pool-111111.null_resource.get_lock (local-exec): waiting: /tmp/lockfile1
module.node-pool-333333.null_resource.get_lock (local-exec): waiting: /tmp/lockfile1

★ここで node-pool-222222 はロックが取れたので先に進んでいる
module.node-pool-222222.google_compute_address.this: Creating...
module.node-pool-111111.null_resource.get_lock: Still creating... [10s elapsed]
module.node-pool-333333.null_resource.get_lock: Still creating... [10s elapsed]
module.node-pool-222222.google_compute_address.this: Still creating... [10s elapsed]
module.node-pool-222222.google_compute_address.this: Creation complete after 12s [id=projects/sre-training-267108/regions/asia-northeast1/addresses/test-2-address]

★ここで node-pool-222222 はリソースができたのでロックを削除している
module.node-pool-222222.null_resource.release_lock: Creating...
module.node-pool-222222.null_resource.release_lock: Provisioning with 'local-exec'...
module.node-pool-222222.null_resource.release_lock (local-exec): Executing: ["/bin/sh" "-c" "modules/test/release-lock.sh lockfile1"]
module.node-pool-222222.null_resource.release_lock (local-exec): delete: /tmp/lockfile1
module.node-pool-222222.null_resource.release_lock: Creation complete after 0s [id=8917614109989523954]
module.node-pool-111111.null_resource.get_lock: Still creating... [20s elapsed]
module.node-pool-333333.null_resource.get_lock: Still creating... [20s elapsed]
module.node-pool-111111.null_resource.get_lock: Creation complete after 20s [id=7923255662165431221]
module.node-pool-333333.null_resource.get_lock (local-exec): waiting: /tmp/lockfile1

★ここで node-pool-111111 はロックが取れたので先に進んでいる
module.node-pool-111111.google_compute_address.this: Creating...
module.node-pool-333333.null_resource.get_lock: Still creating... [30s elapsed]
module.node-pool-111111.google_compute_address.this: Still creating... [10s elapsed]
module.node-pool-111111.google_compute_address.this: Creation complete after 12s [id=projects/sre-training-267108/regions/asia-northeast1/addresses/test-1-address]

★ここで node-pool-111111 はリソースができたのでロックを削除している
module.node-pool-111111.null_resource.release_lock: Creating...
module.node-pool-111111.null_resource.release_lock: Provisioning with 'local-exec'...
module.node-pool-111111.null_resource.release_lock (local-exec): Executing: ["/bin/sh" "-c" "modules/test/release-lock.sh lockfile1"]
module.node-pool-111111.null_resource.release_lock (local-exec): delete: /tmp/lockfile1
module.node-pool-111111.null_resource.release_lock: Creation complete after 0s [id=6082285058616397965]
module.node-pool-333333.null_resource.get_lock: Still creating... [40s elapsed]
module.node-pool-333333.null_resource.get_lock: Creation complete after 40s [id=8955755976657092243]

★ここで node-pool-333333 はロックが取れたので先に進んでいる
module.node-pool-333333.google_compute_address.this: Creating...
module.node-pool-333333.google_compute_address.this: Still creating... [10s elapsed]
module.node-pool-333333.google_compute_address.this: Creation complete after 12s [id=projects/sre-training-267108/regions/asia-northeast1/addresses/test-3-address]

★ここで node-pool-333333 はリソースができたのでロックを削除している
module.node-pool-333333.null_resource.release_lock: Creating...
module.node-pool-333333.null_resource.release_lock: Provisioning with 'local-exec'...
module.node-pool-333333.null_resource.release_lock (local-exec): Executing: ["/bin/sh" "-c" "modules/test/release-lock.sh lockfile1"]
module.node-pool-333333.null_resource.release_lock (local-exec): delete: /tmp/lockfile1
module.node-pool-333333.null_resource.release_lock: Creation complete after 0s [id=2383163452978889987]

以下コード

main.tf

module "node-pool-111111" {
  source = "./modules/test"
  name   = "test-1"
}

module "node-pool-222222" {
  source = "./modules/test"
  name   = "test-2"
}

module "node-pool-333333" {
  source = "./modules/test"
  name   = "test-3"
}

modules/test/main.tf

resource "null_resource" "get_lock" {
  triggers = {
    name = var.name
  }

  provisioner "local-exec" {
    command = "${path.module}/get-lock.sh lockfile1"
  }
}


resource "google_compute_address" "this" {
  name         = "${var.name}-address"
  address_type = "EXTERNAL"
  region       = "asia-northeast1"

  depends_on = [
    null_resource.get_lock
  ]
}

resource "null_resource" "release_lock" {
  triggers = {
    name = var.name
  }

  provisioner "local-exec" {
    command = "${path.module}/release-lock.sh lockfile1"
  }

  depends_on = [
    google_compute_address.this
  ]
}

modules/test/get-lock.sh

#!/bin/bash
set -o nounset

LOCK_FILE="/tmp/$1"
LOCK_TIMEOUT_SECONDS=${2:-1800}
SECONDS=0

# lock 用のディレクトリが作成できるか、タイムアウトを迎えるまでループする
# mkdir は atomic(同時に実行されても片方しか成功しない)なのでこれを利用してロックを取得する
# see: http://mywiki.wooledge.org/BashFAQ/045
until mkdir $LOCK_FILE 2> /dev/null
do
    [[ $SECONDS -gt $LOCK_TIMEOUT_SECONDS ]] \
        && echo "timeout: $LOCK_FILE" \
        && exit 1

    echo "waiting: $LOCK_FILE"
    sleep 20
done

exit 0

release-lock.sh

#!/bin/bash
set -o pipefail
set -o nounset
set -o errexit

LOCK_FILE="/tmp/$1"
rm -rf "$LOCK_FILE"
echo "delete: $LOCK_FILE"