- OpenTofu言語
- モジュール
- モジュールの作成
- リファクタリング
リファクタリング
共有モジュールや長期にわたる構成では、初期のモジュール構造やリソース名が最終的に手に余るようになることがあります。たとえば、以前は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
アドレスの両方で、モジュール、リソース、および子モジュール内のリソースを選択できる特別なアドレッシング構文を使用します。以下では、いくつかのリファクタリングのユースケースと、各状況に適したアドレッシング構文について説明します。
- リソースの名前変更
- リソースに対する
count
またはfor_each
の有効化 - モジュールコールの名前変更
- モジュールコールに対する
count
またはfor_each
の有効化 - 1つのモジュールを複数のモジュールに分割する
moved
ブロックの削除
リソースの名前変更
リソース構成を持つ次のサンプルモジュールを考えてみましょう
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
を使用して、キーの切り替え、およびcount
、for_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
}
count
を以前は使用していなかった既存のリソースに追加すると、そのリソースを明示的に言及するmoved
ブロックを記述しない限り、OpenTofuは元のオブジェクトを自動的にインスタンスゼロに移動することを提案します。ただし、モジュールを将来読む人にとって変更をより明確にするために、対応するmoved
ブロックを明示的に記述することを引き続きお勧めします。
モジュールコールの名前変更
リソースの名前変更と同様の方法で、モジュールへのコール名を変更できます。次の元のモジュールバージョンを考えてみましょう
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
を使用してキーを切り替えたり、count
、for_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" に属します。
古いリソースアドレスにバインドされた既存のオブジェクトを置き換えることなく、このリファクタリングを実現するには、次のことを行う必要があります。
- モジュール "x" を記述し、含める必要がある2つのリソースをコピーします。
- モジュール "y" を記述し、含める必要がある1つのリソースをコピーします。
- 元のモジュールを編集して、これらのリソースを含めないようにし、代わりに既存のユーザーを移行するためのシム構成のみを含めます。
新しいモジュール "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
として作成されたかのように扱います。