この記事は、Pulumi dotnet Advent Calendar 2019の23日目です。
Terraformを使っていると、HCLで定義したリソースがHCLの定義とずれると差分として検出されました。
しかし、Pulumiではpulumi upをしただけでは検出されないことに気づきます。
こういったIaCに期待するのはConfiguration Driftの検出であり、Desired Stateへの収束です。 そのDesired StateはCodeでしょうか? それとも?
PulumiとTerraformの考え方の違いが垣間見えます。
- 概要
- 期待する挙動とは
- Pulumi のデフォルトの挙動
- Pulumi のデフォルトの挙動で困る状況
- pulumi refresh: Pulumi の State と実リソースの同期を行う
- デフォルト挙動は変更されないのか
概要
特にterraformで慣れていてPulumiを使う場合は、pulumi refreshやpulumi up --refreshを使うと違和感が少ないでしょう。
- Terraformは実行時にStateと実リソースの差を同期する (デフォルトの動作)
- Pulumiは実行時にStateと実リソースの差を同期しない (デフォルトの動作)
- Pulumiは意図的に同期しないことをデフォルトにしている
- PulumiでTerraform同様にStateと実リソースの差分を同期するには、
pulumi refreshやpulumi up --refreshを使う
期待する挙動とは
pulumiもterraformでも期待する挙動は同じです。
私が期待する挙動は、コードと実リソースの合致がされているか(Desired Stateにあるか) の保証です。
端的にいうと、コードの表現 = 実リソースを期待します。
terraformはこれがデフォルト挙動で提供されており、Pulumiでは明示的に指定します。
Pulumi のデフォルトの挙動
再現Issueを立ててあります。これに沿ってみてみます。
Pulumi could not detect Resource change without code change. · Issue #3664 · pulumi/pulumi
PulumiでS3 Bucketを作成します。
new Pulumi.Aws.S3.Bucket("hogemoge", new Pulumi.Aws.S3.BucketArgs { Tags = new Dictionary<string, object> { { "foo", "bar" }, }, });
適用すると、Bucketが作成されます。
$ pulumi up
Type Name Plan Info pulumi:pulumi:Stack aws-sandbox-master 'dotnet build -nologo .' completed successfully
pulumi:pulumi:Stack aws-sandbox-master 2 messages Type Name Plan Info + └─ aws:s3:Bucket hogemoge create
Resources:
+ 1 to create
Do you want to perform this update?
> yes
no
details
Resources:
+ 1 created
意図通り、Bucketが生成されています。

Bucketのタグを変更してみましょう。仮にキーをfooからfoo-1にしますが、Valueの変更でも同じ挙動です。

Pulumiのコードには変更を加えず、pulumi upをするとどうなるでしょうか?
結果は、「実リソースで生じた変更を検知しない」です。
$ pulumi up
Previewing update (master):
Type Name Plan Info pulumi:pulumi:Stack aws-sandbox-master 'dotnet build -nologo .' completed successfully
pulumi:pulumi:Stack aws-sandbox-master 2 messages
Resources:
1 unchanged
Terraform の挙動
terraformではデフォルトでコードと実リソースの差分が検出されていると書きましたが、どのような挙動をするかおさらいしておきます。
terraformでS3 Bucketを作成します。
resource "aws_s3_bucket" "foo" { bucket = "foo-1234sdfgb" tags = { foo = "bar" } }
applyします。
$ terraform apply �Terraform v0.12.18 ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_s3_bucket.foo will be created + resource "aws_s3_bucket" "foo" { + acceleration_status = (known after apply) + acl = "private" + arn = (known after apply) + bucket = "foo-1234sdfgb" + bucket_domain_name = (known after apply) + bucket_regional_domain_name = (known after apply) + force_destroy = false + hosted_zone_id = (known after apply) + id = (known after apply) + region = (known after apply) + request_payer = (known after apply) + tags = { + "foo" = "bar" } + website_domain = (known after apply) + website_endpoint = (known after apply) + versioning { + enabled = (known after apply) + mfa_delete = (known after apply) } } Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
期待通りリソースが作られます。Pulumiと一緒です。

Bucketのタグを変更してみましょう。仮にキーをfooからfoo-1にしますが、Valueの変更でも同じ挙動です。

コードに変更を加えずに、terraform planやterraform apply をすると、コードと実リソースの変更が検出されます。
$ terraform apply
�Terraform v0.12.18
Configuring remote state backend...
Initializing Terraform configuration...
-----------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_s3_bucket.foo will be updated in-place
~ resource "aws_s3_bucket" "foo" {
acl = "private"
arn = "arn:aws:s3:::foo-1234sdfgb"
bucket = "foo-1234sdfgb"
bucket_domain_name = "foo-1234sdfgb.s3.amazonaws.com"
bucket_regional_domain_name = "foo-1234sdfgb.s3.ap-northeast-1.amazonaws.com"
force_destroy = false
hosted_zone_id = "Z2M4EHUR26P7ZW"
id = "foo-1234sdfgb"
region = "ap-northeast-1"
request_payer = "BucketOwner"
~ tags = {
+ "foo" = "bar"
- "foo-1" = "bar" -> null
}
versioning {
enabled = false
mfa_delete = false
}
}
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Pulumi のデフォルトの挙動で困る状況
Pulumiは自分のStateと実リソースの差分を検知するのですが、pulumi upをしても自分のstateを実リソースのstateと同期しないため、コードと実リソースで差分が生じていることを検知できません。
Terraformを使っていると「コードと実リソースの差分を検知する」ことに慣れているため、Pulumiのデフォルトは検知しません。
これには違和感を感じます。Pulumiで作ったリソースの変更はPulumiが検知しない限り気づけません。いちいちPulumiが作ったリソースに変更があったことを、Webコンソールやaws cliで確認することはないので、Pulumiに気づいてほしいです。
pulumi refresh: Pulumi の State と実リソースの同期を行う
PulumiのStateと実リソースの同期を行うには、明示的にpulumi refreshかpulumi up --refresh (-r) を行う必要があります。
このコマンドで、Pulumi Stateが実リソースと同期されるので、次にpulumi previewやpulumi upをすると、コードと実リソースの差分が検出されます。
これを考慮すると、Pulumiコマンドは次の順で実行することになるでしょう。
$ pulumi refresh $ pulumi up
コマンド2つの実行は面倒です。pulumi up時にリフレッシュさせるのが多いでしょう。
$ pulumi up --refresh
実際に、これに気づいてpulumi previewをかけたところ、32 changesが生じて涙目になりました。
が、実際のところ、stateの差分は コードで表現していないプロパティ でも生じます。
そのため、pulumi refreshでstateの同期時に変更が検出されても、空がデフォルトである場合、次にpulumi upしたときは検出されないのがほとんどです。
例えば、Route53のレコードは多くのプロパティがありますが、これらはpulumi refreshで差分として検出されても、pulumi upでは差分にならないでしょう。

正直、コードと実リソースにDriftが生じるのあり得ないので、pulumi refreshもれなくかけていくのがいいでしょう。
デフォルト挙動は変更されないのか
Issue立っているものの、コメントを見ていると中の人としてはダウンサイドが目につくのでやりたくないらしいです。
Consider automatically refreshing on preview/update/destroy · Issue #2247 · pulumi/pulumi
まじかよと思いつつも、How Pulumi Worksを見ると、ProviderからStateへの向きがないので、Stateと実リソースのリフレッシュをデフォルト行わないのは理解できます。
