メインコンテンツにスキップ

リファクタリング

共有モジュールや長期にわたる構成では、初期のモジュール構造やリソース名が最終的に手に余るようになることがあります。たとえば、以前は1つの子モジュールだったものが、2つの独立したモジュールとして構成した方がより理にかなっており、既存のリソースのサブセットを新しいモジュールに移動させることに決めるかもしれません。

OpenTofuは、モジュールまたはリソースの一意なアドレスによって関連付けを行いながら、以前の状態と新しい構成を比較します。したがって、デフォルトでは、OpenTofuはオブジェクトの移動や名前変更を、古いアドレスにあるオブジェクトを破棄し、新しいアドレスに新しいオブジェクトを作成する意図として認識します。

構成にmovedブロックを追加して、オブジェクトを過去に移動または名前変更した場所を記録すると、OpenTofuは古いアドレスにある既存のオブジェクトを、あたかも新しいアドレスに属しているかのように扱います。

movedブロックの構文

movedブロックはラベルを必要とせず、from引数とto引数のみを含みます

コードブロック
moved {
from = aws_instance.a
to = aws_instance.b
}

上記の例では、現在aws_instance.bとして認識されているリソースが、このモジュールの以前のバージョンではaws_instance.aとして認識されていたことを記録しています。

aws_instance.bの新しいプランを作成する前に、OpenTofuはまず、状態に記録されているaws_instance.aの既存のオブジェクトがあるかどうかを確認します。既存のオブジェクトがある場合、OpenTofuはそのオブジェクトの名前をaws_instance.bに変更してから、プランの作成に進みます。その結果、あたかもオブジェクトが元々aws_instance.bで作成されたかのようにプランが作成されるため、適用中にオブジェクトを破棄する必要がなくなります。

fromアドレスとtoアドレスの両方で、モジュール、リソース、および子モジュール内のリソースを選択できる特別なアドレッシング構文を使用します。以下では、いくつかのリファクタリングのユースケースと、各状況に適したアドレッシング構文について説明します。

リソースの名前変更

リソース構成を持つ次のサンプルモジュールを考えてみましょう

コードブロック
resource "aws_instance" "a" {
count = 2

# (resource-type-specific configuration)
}

この構成を初めて適用すると、OpenTofuはaws_instance.a[0]aws_instance.a[1]を作成します。

後でこのリソースに別の名前を選択する場合は、resourceブロックの名前ラベルを変更し、movedブロック内に古い名前を記録できます。

コードブロック
resource "aws_instance" "b" {
count = 2

# (resource-type-specific configuration)
}

moved {
from = aws_instance.a
to = aws_instance.b
}

このモジュールを使用する各構成の次のプランを作成すると、OpenTofuはaws_instance.aに属する既存のオブジェクトを、あたかもaws_instance.b用に作成されたかのように扱います。aws_instance.a[0]aws_instance.b[0]として扱われ、aws_instance.a[1]aws_instance.b[1]として扱われます。

モジュールの新しいインスタンス(aws_instance.a一度も持っていなかった)は、movedブロックを無視し、通常どおりにaws_instance.b[0]aws_instance.b[1]を作成することを提案します。

この例のアドレスはどちらもリソース全体を参照しているため、OpenTofuはリソースのすべてのインスタンスの移動を認識します。つまり、それぞれを個別に識別する必要なく、aws_instance.a[0]aws_instance.a[1]の両方をカバーします。

各リソースタイプには別々のスキーマがあるため、異なるタイプのオブジェクトには互換性がありません。したがって、movedを使用してリソースの名前を変更することはできますが、movedを使用して別のリソースタイプに変更したり、管理対象リソース(resourceブロック)をデータリソース(dataブロック)に変更したりすることはできません

リソースに対するcountまたはfor_eachの有効化

単一インスタンスのリソースを含む次のサンプルモジュールを考えてみましょう

コードブロック
resource "aws_instance" "a" {
# (resource-type-specific configuration)
}

この構成を適用すると、OpenTofuはアドレスaws_instance.aにバインドされたオブジェクトを作成します。

後で、このリソースでfor_eachを使用して、複数のインスタンスを体系的に宣言します。以前にaws_instance.aのみに関連付けられていたオブジェクトを保持するには、movedブロックを追加して、オブジェクトが新しい構成でどのインスタンスキーを使用するかを指定する必要があります

コードブロック
locals {
instances = tomap({
big = {
instance_type = "m3.large"
}
small = {
instance_type = "t2.medium"
}
})
}

resource "aws_instance" "a" {
for_each = local.instances

instance_type = each.value.instance_type
# (other resource-type-specific configuration)
}

moved {
from = aws_instance.a
to = aws_instance.a["small"]
}

上記により、OpenTofuはaws_instance.aにある既存のオブジェクトを破棄するプランニングを行わず、代わりにそのオブジェクトをあたかもaws_instance.a["small"]として元々作成されたかのように扱います。

上記のように、2つのアドレスの少なくとも1つにインスタンスキーが含まれている場合、OpenTofuは両方のアドレスをリソース全体ではなく、リソースの特定のインスタンスを参照しているものとして理解します。つまり、movedを使用して、キーの切り替え、およびcountfor_each、またはそのどちらでもない状態を切り替える際にキーを追加および削除できます。

以下は、同様の方法でリソースインスタンスキーへの変更を記録する有効なmovedブロックのその他の例です

コードブロック
# Both old and new configuration used "for_each", but the
# "small" element was renamed to "tiny".
moved {
from = aws_instance.b["small"]
to = aws_instance.b["tiny"]
}

# The old configuration used "count" and the new configuration
# uses "for_each", with the following mappings from
# index to key:
moved {
from = aws_instance.c[0]
to = aws_instance.c["small"]
}
moved {
from = aws_instance.c[1]
to = aws_instance.c["tiny"]
}

# The old configuration used "count", and the new configuration
# uses neither "count" nor "for_each", and you want to keep
# only the object at index 2.
moved {
from = aws_instance.d[2]
to = aws_instance.d
}

モジュールコールの名前変更

リソースの名前変更と同様の方法で、モジュールへのコール名を変更できます。次の元のモジュールバージョンを考えてみましょう

コードブロック
module "a" {
source = "../modules/example"

# (module arguments)
}

この構成を適用すると、OpenTofuは、このモジュールで宣言された任意のリソースのアドレスに、モジュールパスmodule.aをプレフィックスとして付与します。たとえば、リソースaws_instance.exampleは、完全なアドレスmodule.a.aws_instance.exampleを持ちます。

後でこのモジュール呼び出しにより良い名前を選択した場合は、moduleブロックの名前ラベルを変更し、古い名前をmovedブロック内に記録できます。

コードブロック
module "b" {
source = "../modules/example"

# (module arguments)
}

moved {
from = module.a
to = module.b
}

このモジュールを使用する各構成に対して次のプランを作成するとき、OpenTofuは、module.aで始まる既存のオブジェクトアドレスを、代わりにmodule.bで作成されたかのように扱います。module.a.aws_instance.exampleは、module.b.aws_instance.exampleとして扱われます。

この例のアドレスは両方とも、モジュール呼び出し全体を参照しており、OpenTofuはその呼び出しのすべてのインスタンスの移動を認識します。このモジュール呼び出しでcountまたはfor_eachを使用した場合、各インスタンスを個別に指定する必要なく、すべてのインスタンスに適用されます。

モジュール呼び出しでのcountまたはfor_eachの有効化

単一インスタンスモジュールのこの例を考えてみましょう

コードブロック
module "a" {
source = "../modules/example"

# (module arguments)
}

この構成を適用すると、OpenTofuはmodule.aで始まるアドレスを持つオブジェクトを作成します。

後のモジュールバージョンでは、複数のインスタンスを体系的に宣言するために、このリソースでcountを使用する必要があるかもしれません。以前にaws_instance.aのみに関連付けられていたオブジェクトを保持するには、movedブロックを追加して、そのオブジェクトが新しい構成でどのインスタンスキーを使用するかを指定できます。

コードブロック
module "a" {
source = "../modules/example"
count = 3

# (module arguments)
}

moved {
from = module.a
to = module.a[2]
}

上記の構成は、module.a内のすべてのオブジェクトを、module.a[2]で元々作成されたかのように扱うようOpenTofuに指示します。その結果、OpenTofuはmodule.a[0]module.a[1]に対してのみ新しいオブジェクトを作成する計画を立てます。

上記のように、2つのアドレスの少なくとも1つにインスタンスキー([2]など)が含まれている場合、OpenTofuは両方のアドレスを、モジュール呼び出し全体ではなく、モジュール呼び出しの特定のインスタンスを参照するものとして理解します。つまり、movedを使用してキーを切り替えたり、countfor_each、またはそのいずれでもないものを切り替える際にキーを追加および削除したりできます。

インスタンスに関連付けられた移動を記録するその他の例については、同様のセクションリソースでのcountおよびfor_eachの有効化を参照してください。

1つのモジュールを複数のモジュールに分割する

モジュールが新しい要件をサポートするように成長するにつれて、最終的に2つの個別のモジュールに分割する価値があるほど大きくなる可能性があります。

このモジュールの例を考えてみましょう

コードブロック
resource "aws_instance" "a" {
# (other resource-type-specific configuration)
}

resource "aws_instance" "b" {
# (other resource-type-specific configuration)
}

resource "aws_instance" "c" {
# (other resource-type-specific configuration)
}

これを次のように2つのモジュールに分割できます

  • aws_instance.aはモジュール "x" に属するようになりました。
  • aws_instance.bもモジュール "x" に属します。
  • aws_instance.cはモジュール "y" に属します。

古いリソースアドレスにバインドされた既存のオブジェクトを置き換えることなく、このリファクタリングを実現するには、次のことを行う必要があります。

  1. モジュール "x" を記述し、含める必要がある2つのリソースをコピーします。
  2. モジュール "y" を記述し、含める必要がある1つのリソースをコピーします。
  3. 元のモジュールを編集して、これらのリソースを含めないようにし、代わりに既存のユーザーを移行するためのシム構成のみを含めます。

新しいモジュール "x" および "y" には、resourceブロックのみを含める必要があります。

コードブロック
# module "x"

resource "aws_instance" "a" {
# (other resource-type-specific configuration)
}

resource "aws_instance" "b" {
# (other resource-type-specific configuration)
}
コードブロック
# module "y"

resource "aws_instance" "c" {
# (other resource-type-specific configuration)
}

元のモジュールは、現在後方互換性のためのシムのみであり、2つの新しいモジュールを呼び出し、リソースがそれらに移動したことを示します。

コードブロック
module "x" {
source = "../modules/x"

# ...
}

module "y" {
source = "../modules/y"

# ...
}

moved {
from = aws_instance.a
to = module.x.aws_instance.a
}

moved {
from = aws_instance.b
to = module.x.aws_instance.b
}

moved {
from = aws_instance.c
to = module.y.aws_instance.c
}

元のモジュールの既存のユーザーが新しい「シム」バージョンにアップグレードすると、OpenTofuはこれらの3つのmovedブロックに気づき、3つの古いリソースアドレスに関連付けられたオブジェクトが、元々2つの新しいモジュール内で作成されたかのように動作します。

このモジュールファミリの新しいユーザーは、結合されたシムモジュールまたは2つの新しいモジュールを個別に使用できます。既存のユーザーには、古いモジュールが非推奨になり、新しいニーズには2つの個別のモジュールを使用する必要があることを伝えることをお勧めします。

マルチモジュールリファクタリングの状況は、親モジュールが子モジュールを「閉じた箱」と見なし、その中に正確にどのリソースが宣言されているかを認識していないという典型的なルールに違反するという点で、珍しいものです。この妥協案は、これらの3つのモジュールすべてが同じ人々によって保守され、単一のモジュールパッケージで一緒に配布されることを前提としています。

OpenTofuは、movedブロック内のモジュール参照を、それらが定義されているモジュールインスタンスを基準に解決します。たとえば、上記の元のモジュールがすでにmodule.originalという名前の子モジュールである場合、module.x.aws_instance.aへの参照はmodule.original.module.x.aws_instance.aとして解決されます。モジュールは、独自のオブジェクトと子モジュールのオブジェクトに関するmovedステートメントのみを作成できます。

countまたはfor_eachメタ引数を使用して呼び出されたモジュール内のリソースを参照する必要がある場合は、リソース構成の新しい場所と一致させるために、使用する特定のインスタンスキーを指定する必要があります。

コードブロック
moved {
from = aws_instance.example
to = module.new[2].aws_instance.example
}

movedブロックの削除

時間の経過とともに、長期間使用されるモジュールは多くのmovedブロックを蓄積する可能性があります。

movedブロックを削除すると、古いアドレスを参照する構成は、既存のオブジェクトを移動するのではなく削除する計画を立てるため、一般的に破壊的な変更になります。以前のバージョンのモジュールのユーザーのアップグレードパスを維持するために、モジュールの以前のバージョンからすべての履歴movedブロックを保持することを強くお勧めします。

movedブロックを削除することを決定する場合は、注意して進めてください。組織内でプライベートモジュールを保守しており、すべてのユーザーが新しいモジュールバージョンでtofu applyを正常に実行したことを確信している場合は、movedブロックを削除しても安全な場合があります。

同じオブジェクトの名前を変更したり、2回移動する必要がある場合は、新しいブロックが既存のブロックを参照する連結されたmovedブロックを使用して、完全な履歴を文書化することをお勧めします。

コードブロック
moved {
from = aws_instance.a
to = aws_instance.b
}

moved {
from = aws_instance.b
to = aws_instance.c
}

この方法で移動のシーケンスを記録すると、aws_instance.aにオブジェクトがある構成と、aws_instance.bにオブジェクトがある構成の両方で、アップグレードを成功させることができます。どちらの場合も、OpenTofuは既存のオブジェクトを元々aws_instance.cとして作成されたかのように扱います。