tech.guitarrapc.cóm

Technical updates

Pulumi Workaround

この記事は、Pulumi dotnet Advent Calendar 2019 の25日目です。

qiita.com

最終日は、Pulumi でこんなときどうするのかをまとめていきます。

目次

TL;DR

Pulumi でトラブルに遭遇したときにどのように回避するかのワークアラウドです。

随時更新予定。

Pulumi.Aws.Iam.Invokes の Input<T>Output<T> の依存解決を行えない

Pulumi の DataSourceは、値が決定的に定まっていることを前提にしているため、 Output<T> を受けることを想定していません。

つまり以下のコードは失敗します。

    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Pulumi;
    using Pulumi.Aws.Iam;
    using Pulumi.Aws.Iam.Inputs;
    
    class Program
    {
        static Task<int> Main()
        {
            return Deployment.RunAsync(async () =>
            {
                var policy = await Pulumi.Aws.Iam.Invokes.GetPolicyDocument(new GetPolicyDocumentArgs
                {
                    Statements = new[] {
                        new GetPolicyDocumentStatementsArgs
                        {
                            Actions = "sts:AssumeRole",
                            Effect = "Allow",
                            Principals = new GetPolicyDocumentStatementsPrincipalsArgs
                            {
                                Type = "Service",
                                Identifiers = "ec2.amazonaws.com",
                            }
                        },
                    },
                });
                var role = new Pulumi.Aws.Iam.Role($"role", new RoleArgs
                {
                    AssumeRolePolicy = policy.Json,
                });
    
                var assumepolicy = await Pulumi.Aws.Iam.Invokes.GetPolicyDocument(new GetPolicyDocumentArgs
                {
                    Statements = new[] {
                        new GetPolicyDocumentStatementsArgs
                        {
                            Actions = "sts:AssumeRole",
                            Effect = "Allow",
                            Principals = new GetPolicyDocumentStatementsPrincipalsArgs
                            {
                                Type = "Service",
                                Identifiers = "ec2.amazonaws.com",
                            }
                        },
                        new GetPolicyDocumentStatementsArgs
                        {
                            Actions = "sts:AssumeRole",
                            Effect = "Allow",
                            Principals = new GetPolicyDocumentStatementsPrincipalsArgs
                            {
                                Type = "AWS",
                                // throws exception here!
                                Identifiers = role.Arn,
                            }
                        }
                    }
                });
    
                return new Dictionary<string, object>
                {
                    { "arn", role.Arn },
                    { "assumepolicy", assumepolicy.Json },
                };
            });
        }
    }

もし Output<T> な値を受けて、Data Source の取得を行いたい場合は、Output<T>.Apply でDataSource を Output の結果を受けて実行されるようにし、出力も Output<T> に変換します。

    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Pulumi;
    using Pulumi.Aws.Iam;
    using Pulumi.Aws.Iam.Inputs;
    
    class Program
    {
        static Task<int> Main()
        {
            return Deployment.RunAsync(async () =>
            {
                var policy = await Pulumi.Aws.Iam.Invokes.GetPolicyDocument(new GetPolicyDocumentArgs
                {
                    Statements = new[] {
                        new GetPolicyDocumentStatementsArgs
                        {
                            Actions = "sts:AssumeRole",
                            Effect = "Allow",
                            Principals = new GetPolicyDocumentStatementsPrincipalsArgs
                            {
                                Type = "Service",
                                Identifiers = "ec2.amazonaws.com",
                            }
                        },
                    },
                });
                var role = new Pulumi.Aws.Iam.Role($"role", new RoleArgs
                {
                    AssumeRolePolicy = policy.Json,
                });
    
                var assumepolicy = role.Arn.Apply(roleArn => Pulumi.Aws.Iam.Invokes.GetPolicyDocument(new GetPolicyDocumentArgs
                {
                    Statements = new[] {
                        new GetPolicyDocumentStatementsArgs
                        {
                            Actions = "sts:AssumeRole",
                            Effect = "Allow",
                            Principals = new GetPolicyDocumentStatementsPrincipalsArgs
                            {
                                Type = "Service",
                                Identifiers = "ec2.amazonaws.com",
                            }
                        },
                        new GetPolicyDocumentStatementsArgs
                        {
                            Actions = "sts:AssumeRole",
                            Effect = "Allow",
                            Principals = new GetPolicyDocumentStatementsPrincipalsArgs
                            {
                                Type = "AWS",
                                Identifiers = roleArn,
                            }
                        }
                    }
                }));
    
                // you can pass assumepolicy to other Resource.
    
                return new Dictionary<string, object>
                {
                    { "arn", role.Arn },
                    { "assumepolicy", assumepolicy.Json },
                };
            });
        }
    }

Pulumi-aws の destroy で vpc 削除が何度やっても失敗する

pulumi destory をしたときに、vpc の削除がどうしても失敗することがあります。

    error: Plan apply failed: deleting urn:pulumi:ekscluster::sandbox::pkg:component:ekscluster$pkg:component:network$aws:ec2/vpc:Vpc::sandbox-network-vpc: Error deleting VPC: DependencyViolation: The vpc 'vpc-052ade5d4667965e8' has dependencies and cannot be deleted.

これは Secutiry Group で、自身の Security Group を参照しているときにおこります。 特に、EKS Cluster などが自動的に作成した Security Groupで、自分自身の Security Group を参照しているときに起こりま す。 Pulumi が知っていれば自分で解消するので、知らない状況を作らないようにするのがいいでしょう。

と思っても、Kubernetes 1.14 + eks.3 以降は、EKS デフォルトの挙動として PulumiなどAPIで指定したセキュリティグループは Addtional Security Groupになり、EKS のデフォルト Security Groupに自身を許可するSGを自分で作るようになった。ので、破綻する...。

pulumi up の preview と実行でそれぞれ dotnet build がかかる

pulumi up を実行すると、Preview と Up が実行されます。

  • preview が実行されプロンプトが表示
  • yes を選択するとpreview の内容が適用されます

yes を選択したときの適用は、一見すると preview の内容ですが、実際はC# コードが再度評価されて、再度 dotnet build が実行されます。

そのため、pulumi up をして yes を選択する前に Visual Studio 上で Pulumi dotnet のコードを変更すると Preview と違う結果が実行されます。

注意しましょう。

pulumi で作成したリソースに直接変更を加えられてもpulumi が検出しない

pulumi は、デフォルトでは実リソースと自分のStateを同期しません。 そのため、該当リソースにコード変更をしない限り気づけません。

terraform のように、コード変更なしに気づくためには、pulumi refreshpulumi up --refresh を行います。

これによって、Pulumi の State と実リソースの状態が同期されて、コードに変更がなくても差分として検出されるようになります。

pulumi のリソースをプロジェクト間で移動させたい

残念ながらサポートされてません。

Support moving resources between projects · Issue #3389 · pulumi/pulumi

他プロジェクトのリソースを参照したい

terraform の remote state 的なものは、pulumi では StackReference といいますが、C# ではサポートされていません。

TypeScript ではサポートされています。

import * as k8s from "@pulumi/kubernetes";
import * as pulumi from "@pulumi/pulumi";
const env = pulumi.getStack();
const infra = new pulumi.StackReference(`acmecorp/infra/${env}`);
const provider = new k8s.Provider("k8s", { kubeconfig: infra.getOutput("kubeConfig") });
const service = new k8s.core.v1.Service(..., { provider: provider });

https://www.pulumi.com/docs/intro/concepts/organizing-stacks-projects/#inter-stack-dependencies

teraform から pulumi に移行する

やる意義があるのならやるのは選択がでるでしょう。

terraform から TypeScript への移行プログラムはあります。

pulumi/tf2pulumi: A tool to convert Terraform projects to Pulumi TypeScript programs

他の言語バインディングは今のところ見つからない。

どのみちImport 祭りになるのはちょっと辛いですが。

Adopting Existing Cloud Resources into Pulumi | Pulumi

Pulumi でよく使うコマンド

この記事は、Pulumi dotnet Advent Calendar 2019 の24日目です。

qiita.com

そろそろ終わりです。pulumi でよく使うコマンドを見てみましょう。

目次

TL;DR

普段の実行はCI/CD でかけるはずなので、state を操作したりといった特殊操作で使うことになるでしょう。

ドキュメント

コマンドリファレンスは結構丁寧なのでドキュメントにあたるのがよいです。

Pulumi CLI | Pulumi

操作

普段の操作系です。 だいたいCIで実行するので案外使わないやつ。

コンパイルついでに preview までが手元でやるパターン。

pulumi up

圧倒的再頻出。リソース作成のドライラン、実行を行う。

pulumi up --refresh にしないと、state が実リソースとずれていることに気づけない罠。

主にCIでしか適用しないが、手元でIDEからどうしてもpreview 見たいときに pulumi up --refresh をIDEの Debug Runに仕込む。

pulumi config

REF: Pulumi のコンセプト - プログラミングモデル - Config 参照 | kinoco Kibela

pulumi config set Key Value で値をセット pulumi config get Key で値を取得

pulumi destroy

リソースを全部ころす。 あるいは特定のだけ --target Array でころす。

pulumi preview

リソースの dry run。なんで君には、--refresh がないんだい?

と思っていたら、1.8.0 (2019/12/19 リリース) で、pulumi preview --refresh がサポートされました。 desuyone。

https://github.com/pulumi/pulumi/blob/master/CHANGELOG.md#180-2019-12-19

pulumi refresh

主にCI でしか実行しない。

pulumi refreshpulumi preview の代わりに使うのは違うんだなぁ.... この辺り、terraform的な利用とpulumi の想定に違いを感じる。

State

state 直接いじるのはしたくないものの、terraform 同様あるはある。

pulumi state delete URN

ステートから 実リソースのURN参照を削除する。 つまり、実リソースを Pulumi の管理外にする。

stack

stack は最初とかしか触らないので案の定頻繁にはやらない。

pulumi stack --show-urns

スタックのリソースを URN 付きで表示します。 URN把握するのに便利。

pulumi stack init STACK

スタック を作成、あるいは紐づけします。

pulumi stack list

スタック一覧の出力

pulumi stack output

スタックのOutput の出力

pulumi stack select STACK

スタックの切り替え

バージョン確認

Issue 報告のおともに

pulumi version

バージョンの確認

pulumi plugin ls

プラグイン一覧の出力

Pulumi のState と実リソースの差分を同期する

この記事は、Pulumi dotnet Advent Calendar 2019 の23日目です。

qiita.com

Terraform を使っていると、HCLで定義したリソースがHCLの定義とずれると差分として検出されました。 しかし、Pulumi では pulumi up をしただけでは検出されないことに気づきます。

こういった IaC に期待するのは Configuration Drift の検出であり、Desired State への収束です。 そのDesired State は Code でしょうか? それとも?

Pulumi と Terraform の考え方の違いが垣間見えます。

目次

TL;DR

特に terraform で慣れていて Pulumi を使う場合は、pulumi refreshpulumi up --refresh を使うと違和感が少ないでしょう。

  • Terraform は実行時にState と実リソースの差を同期する (デフォルトの動作)
  • Pulumi は実行時に State と 実リソースの差を同期しない (デフォルトの動作)
  • Pulumi は意図的に同期しないことをデフォルトにしている
  • Pulumi でTerraform 同様にState と実リソースの差分を同期するには、 pulumi refreshpulumi 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 planterraform 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 に気づいて教えてほしいと思います。いちいちPulumiが作ったリソースに変更があったことを、Webコンソールやaws cli で確認することはないでしょう。

pulumi refresh: Pulumi の State と実リソースの同期を行う

Pulumi の Stateと実リソースの同期を行うには、明示的に pulumi refreshpulumi up --refresh (-r) を行う必要があります。

このコマンドで、Pulumi State が実リソースと同期されるので、次に pulumi previewpulumi 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 への向きがないのでなるほどと思わなくもない。

How Pulumi Works | Pulumi

ローカルPulumi ProjectとアカウントPulumi Project の紐づけ

この記事は、Pulumi dotnet Advent Calendar 2019 の22日目です。

qiita.com

あまりないのですが、時にすでにある Project と ローカルのpulumi を紐づけたいときがあります。

そんなときにどうやればいいのか見ておきます。

目次

TL;DR

  • アカウントの Projectとの紐づけは、Pulumi.yaml で行っている
  • pulumi up したディレクトリと同一名のstackがあればそれをつかい、なければ聞かれるのでそこで紐づける

Summary

Pulumi は、 Project - Stack という構造で組まれています。 アカウントは複数の Project を持つことができ、Project は複数の Stack を持つことができます。

そのため、手元のプロジェクトを、他のプロジェクトやStack と紐づけたい場合には、そのプロジェクトで明示したり、 pulumi up 時にstackの選択/stackの作成をする必要があります。

Pulumi Project の紐づけ

ローカルのPulumi 定義を見てみると、Pulumi.yamlPulumi.STACKNAME.yaml があることがわかります。

このうち、Pulumi Project と現在のPulumi定義の紐づけを行っているのが、Pulumi.yaml です。

例えば、Project名にaws-sandbox、ランタイムに C#、概要を AWS サンドボックスのプロジェクトであることを明示するなら次のようになります。

name: aws-sandbox
runtime: dotnet
description: AWS Sandbox Project

このYAMLが定義の元であるため、Web上の表示は YAML の内容で表示されます。 表示の更新タイミングは、pulumi up 時です。

Pulumi Stack の紐づけ

Stack は、具体的な定義ファイルで紐づいていません。 Web上に State を持っているので、手元のプロジェクトを pulumi uppulumi stack select で紐づけるだけです。

ローカルのPulumi定義とWebを紐づけるのは、config ファイルで、Pulumi.STACKNAME.yaml というルールで存在します。

例えば、awsのregionがap-northeast-1 であるStack ekscluster があるなら次のようなYAMLファイルがあるでしょう。

config:
  aws:region: ap-northeast-1

Pulumi で特定のリソースのみUpdateやDestroyする

この記事は、Pulumi dotnet Advent Calendar 2019 の21日目です。

qiita.com

Pulumi で terraform target のような操作をどうやるか見てみます。

目次

TL;DR

Pulumi CLI 1.3.0 から --target によって可能になった。

合わせて replace により入れ替えや、明示的な依存指示もサポートされている

--replace stringArray          Specify resources to replace. Multiple resources can be specified using --replace run1 --replace urn2
-t, --target stringArray           Specify a single resource URN to update. Other resources will not be updated. Multiple resources can be specified using --target urn1 --target urn2
--target-dependents            Allows updating of dependent targets discovered but not specified in --target list
--target-replace stringArray   Specify a single resource URN to replace. Other resources will not be updated. Shorthand for --target urn --replace urn.

実際に操作する

URN を調べる。

pulumi stack --show-urns

あとはURN を指定する。

pulumi up --target URN