fbpx

Pulumi リソース管理の仕組み / architecture ついて調査してみました #Pulumi #GCP #Go

この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。

terraformやAnsible、AWS CloudFormationなど、クラウドリソースオーケストレーションツール(または構成管理ツール)の類似ツールとなるPulumiのarchitecture概要や、どのようにしてインフラストラクチャを管理しているか、少し深掘りしていきます。本記事の後半では既にクラウド環境にデプロイされているリソースのPulumi管理方法について検証していきます。初めてPulumiを触る方は、Pulumiのチュートリアル実施blogを前回の記事でまとめていますので、ご参考ください。

目次

Pulumi architectureとインフラストラクチャ管理について

Pulumiの主なコンポーネント/StackとState (参考)

前回blogで触れたPulumiの主なコンポーネントについて以下に再掲します

1_pulumi-concept

Program → インフラストラクチャのあるべき姿を定義したもの
Resource → インフラストラクチャを構成するオブジェクト(実際のインフラリソース)。オブジェクトのプロパティ(設定値)は他のオブジェクトに共有することが可能(図のInputs/Outputsの部分)
Project → Programのソースコードとメタデータ(どのようにProgramを動かすかの情報)を格納するディレクトリ
Stack → Projectディレクトリ内でProgramを「pulumi up」(Pulumi CLI) した後のインスタンス。同一のProgramから本番環境/ステージング環境など、用途に応じて複数の環境用にインスタンスを作成可能

Stackは本番環境用やテスト環境用など、様々な用途でStackを作成することができます。このStackごとに、現在のインフラストラクチャに関する情報がjsonで記録されていて、これを「State」と呼びます。

[stateの例]

{
    "version": 3,
    "deployment": {
        "manifest": {
            "time": "2022-09-13T17:36:36.814054+09:00",
            "magic": "c57d2cc6e4ddc1ad908bcf42d81d39304d5e92a1de5a6e1d5a5c485a70ff807b",
            "version": "v3.39.1"
        },
        "secrets_providers": {
            "type": "service",
            "state": {
                "url": "https://api.pulumi.com",
                "owner": "CL_Kenneth",
                "project": "quickstart2",
                "stack": "dev"
            }
        },
        "resources": [
            {
                "urn": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev",
                "custom": false,
                "type": "pulumi:pulumi:Stack",
                "outputs": {
                    "bucketEndpoint": "http://storage.googleapis.com/my-bucket-b5f5f89/index.html-*****",
                    "bucketName": "gs://my-bucket-b5f5f89"
                }
            },

PulumiはStateから、そのインフラストラクチャがいつどのように作成/削除/読み取り/更新されたかなどの情報を読み取り、「pulumi up」コマンド実行時のアクション(次はリソースを作成/更新または削除すべきかどうか)を決定します

ちなみに、現在のStateは以下の手順で実際に確認することが可能です

[前提]
・ macOS v11.6.4
・ go1.18.3 darwin/amd64
・ Pulumi v3.39.1
前回のblogなどであらかじめStackが作成された状態
  • 現在アクティブなStackを確認します
  • $ pulumi stack ls
    NAME  LAST UPDATE  RESOURCE COUNT  URL
    dev   1 hour ago   5               https://app.pulumi.com/****/quickstart2/dev
    stg*  n/a          n/a             https://app.pulumi.com/****/quickstart2/stg
    
  • 今回は「dev」StackのStateを取得するので、「dev」Stackを選択します。選択後は再度stack lsで「dev」stackがアクティブであることを確認します
  • $ pulumi stack select dev
    
    $ pulumi stack ls
    NAME  LAST UPDATE  RESOURCE COUNT  URL
    dev*  1 hour ago   5               https://app.pulumi.com/****/quickstart2/dev
    stg   n/a          n/a             https://app.pulumi.com/****/quickstart2/stg
    
  • 「pulumi stack export」コマンドを実行して現在のStackでStateを標準出力します
  • $ pulumi stack export
    {
        "version": 3,
        "deployment": {
            "manifest": {
                "time": "2022-09-13T17:36:36.814054+09:00",
                "magic": "c57d2cc6e4ddc1ad908bcf42d81d39304d5e92a1de5a6e1d5a5c485a70ff807b",
                "version": "v3.39.1"
            },
            "secrets_providers": {
                "type": "service",
                "state": {
                    "url": "https://api.pulumi.com",
                    "owner": "CL_Kenneth",
                    "project": "quickstart2",
                    "stack": "dev"
                }
            },
            "resources": [
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev",
                    "custom": false,
                    "type": "pulumi:pulumi:Stack",
                    "outputs": {
                        "bucketEndpoint": "http://storage.googleapis.com/my-bucket-b5f5f89/index.html-12e5385",
                        "bucketName": "gs://my-bucket-b5f5f89"
                    }
                },
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default",
                    "custom": true,
                    "id": "80738ccf-25df-4478-8b0a-f6c4c3a15dba",
                    "type": "pulumi:providers:gcp",
                    "inputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "outputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "parent": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev"
                },
    ~~~~~~(略)~~~~~~
    

    また、Stateの内容についてPulumi service (GUI) でも「いつどのように作成/削除/読み取り/更新されたか」が視覚的に分かりやすい形で確認することができます。

  • 以下コマンドを実行するとPulumi service画面が表示されます。実行前にログインが済んでいない場合は、ここで一度ログイン処理を実施します。
  • $ pulumi console
    Manage your Pulumi stacks by logging in.
    Run `pulumi login --help` for alternative login options.
    Enter your access token from https://app.pulumi.com/account/tokens
        or hit  to log in using your browser                   :
    
  • pulumi service画面の「Stack」タブに移動し、リソース変更履歴欄右側の[Details]リンクをクリックします
  • 一番直近のpulumi実行履歴(activity)が表示されます

Pulumiインフラストラクチャ更新の流れ (参考)

PulumiにおけるStateを用いたインフラストラクチャ管理の具体的なフローについて、公式サイトを参考に説明用の図を作成しましたので以下掲載します。

大まかな流れとしては、「Language Host」で「Program」を実行し、「Deployment Engine」が「Program(あるべき状態)」と「State(現在の状態)」を比較したのち、必要なアクション(リソースの作成/更新/削除)を決定します。実際のリソース作成/更新/削除は「Deployment Engine」からの情報を元に、AWSやGCPなどの各サービスに対応した「Provider」で行われます。

[フローに登場する各コンポーネント]

Language Host → 「Language executor」と「Language runtime」の2つのバイナリで構成されます。「Language executor」はPulumi Programの実行を担い、Pulumi CLIインストール時にplugin(binary name: pulumi-language-<language-name>)として同時にインストールされます。「Language runtime」は「pulumi preview」および「pulumi up」実行時に、Program内でリソースが更新されたかどうか確認し、リソースの更新を検知したら、後述の「Deployment Engine」にリソース更新のリクエストを投げます。「Language runtime」は開発言語(python,go,node.js等)毎にパッケージとして配布されます(例:Node runtimeはnpm、Python runtimeはPyPI、Go runtimeはgo moduleなど)

Deployment Engine → Language Hostからのリソース更新リクエストを元に、現在の「State」から次のアクション(リソースの作成/更新/削除)を決定します。決定後は後述の「Resource Provider」に実際にリソースを作成/更新/削除を依頼します。ちなみに、Deployment Engine自体は「Pulumi CLI」に組み込まれています

Resource Providers → AWSやAzureなどの実際のインフラ環境上のリソース管理をDeployment Engine経由で実施するためのプラグインと、リソースのタイプ毎に用意されるSDKで構成されます。

Pulumi architecture(参考)

PulumiはこのStateを「Backend」と呼ばれるストレージに格納します。Pulumiのデフォルト設定ではオンライン上の「Pulumi Service」のbackendが利用されます。

「Pulumi Service」のarchitectureは以下になります(公式サイトより引用)

Pulumi Serviceはインターネット上で2つのエンドポイント(web application用/REST API用、どちらも「app.pulumi.com」)が提供されます。Pulumi Serviceは管理対象のインフラストラクチャと直接通信することは無いため、Pulumi Service側でインフラストラクチャのクレデンシャル情報を要求することはありません。代わりに、クライアントCLI側でPulumi Serviceとインフラストラクチャの双方と通信する形となります。Pulumi Service自体はSOC2やpen-testingなど複数の監査を経てセキュリティが担保され、安全に管理されています。

ちなみに、以下のようにPulumi Service自体をお好みのプライベートクラウド環境(AWS/Azure/GCP/Kubernetesなど)に自前でホストすることも可能で、インターネットアクセスを発生させないようなPulumi利用環境の構築が出来ます。

既存リソースをPulumiで管理する

state importについて (参考)

既に存在しているクラウド環境のインフラストラクチャ(リソース)をPulumiで管理したい場合は、以下のようなimport処理を実施して、PulumiのStateに対して「既に存在しているリソースの情報」を記録することが出来ます。Stateに「既に存在しているリソースの情報」が記録されていない状態で、かつ、Programにはリソースの情報が記載されている状態で「Pulumi up」を実施してしまうと、同じリソースが重複して作成されてしまいますので(詳細は後述)、既存リソースのimport処理は必ず実施しておく必要があります。

[既存リソースのPulumi管理方法]
・ Pulumiで管理したいリソースのリソースIDを調べる
・ import option(リソースID)をProgramに記載する
・ 管理したいリソースと同様の設定値をProgramに記載する

実際にやってみる (参考)

今回はGCP環境に既に作成されているGCS Bucketを、Pulumiで管理させるケースを想定して検証実施します。事前準備として、新しくPulumi ProjectとStackを作成します。

[import対象リソース]
・ GCS bucket
・ 名前:my-bucket-0927
・ ロケーションタイプ:Region
・ 場所:asia-northeast1
・ GCPプロジェクト:pulumi-test
[環境]
・ macOS v11.6.4
・ go1.18.3 darwin/amd64
・ Pulumi v3.39.1

Pulumi Projectを新規で作成します。新規ディレクトリを作成して、pulumi newコマンドを実行します。project nameは「quickstart2」、stack nameはデフォルト「dev」に設定します。その他の設定もデフォルトにします。「gcp:project:」には、今回の対象リソースがデプロイされているGCPプロジェクト「pulumi-test」を設定します。

$ mkdir quickstart2
$ cd quickstart2
$ pulumi new gcp-go
This command will walk you through creating a new Pulumi project.

Enter a value or leave blank to accept the (default), and press .
Press ^C at any time to quit.

project name: (quickstart2)
project description: (A minimal Google Cloud Go Pulumi program)
Created project 'quickstart2'

Please enter your desired stack name.
To create a stack in an organization, use the format / (e.g. 'acmecorp/dev').
stack name: (dev)
Created stack 'dev'

gcp:project: The Google Cloud project to deploy into: pulumi-test
Saved config

Installing dependencies...

Finished installing dependencies

Your new project is ready to go!

To perform an initial deployment, run 'pulumi up'

Stack作成直後はPulumiでリソースのデプロイは実施していないので、リソースに関する情報は記載されていません。

$ pulumi stack export
{
    "version": 3,
    "deployment": {
        "manifest": {
            "time": "2022-09-27T14:26:05.35656+09:00",
            "magic": "c57d2cc6e4ddc1ad908bcf42d81d39304d5e92a1de5a6e1d5a5c485a70f*****",
            "version": "v3.39.1"
        },
        "secrets_providers": {
            "type": "service",
            "state": {
                "url": "https://api.pulumi.com",
                "owner": "CL_Kenneth",
                "project": "quickstart2",
                "stack": "dev"
            }
        }
    }
}

import option(リソースID)を付与しない場合のPulumi up

既にGCP環境に作成されているリソースについて、stateに情報が記載されていない状態でpulumi upを実施するとどうなるか確認します。

  • 既存リソースと設定値(名前、ロケーションなど)を合わせて、以下Program(main.go)を作成します
  • package main
    
    import (
        "github.com/pulumi/pulumi-gcp/sdk/v6/go/gcp/storage"
        "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
        pulumi.Run(func(ctx *pulumi.Context) error {
            // Create a GCP resource (Storage Bucket)
            _, err := storage.NewBucket(ctx, "my-bucket-0927", &storage.BucketArgs{
                Location: pulumi.String("ASIA-NORTHEAST1"),
                StorageClass: pulumi.String("STANDARD"),
                UniformBucketLevelAccess: pulumi.Bool(true),
                ForceDestroy:             pulumi.Bool(true),
            })
            if err != nil {
                return err
            }
    
            return nil
        })
    }
    
  • Programが準備でき次第、pulumi upを実施します
  • $ pulumi up
    Previewing update (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/previews/3db8d890-d3f4-4e5c-ba7f-*****
    
         Type                   Name             Plan
     +   pulumi:pulumi:Stack    quickstart2-dev  create
     +   └─ gcp:storage:Bucket  my-bucket-0927   create
    
    Resources:
        + 2 to create
    
    Do you want to perform this update? yes
    Updating (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/updates/*
    
         Type                   Name             Status
     +   pulumi:pulumi:Stack    quickstart2-dev  created
     +   └─ gcp:storage:Bucket  my-bucket-0927   created
    
    Resources:
        + 2 created
    
    Duration: 6s
    
  • 上記の通り、stateにリソースの情報が無いため、pulumiでは新しくリソースが作成(create)されてしまいます。pulumi up後のstateは以下のようになります。
  • $ pulumi stack export
    {
        "version": 3,
        "deployment": {
            "manifest": {
                "time": "2022-09-27T22:28:10.351319+09:00",
                "magic": "c57d2cc6e4ddc1ad908bcf42d81d39304d5e92a1de5a6e1d5a5c485a70ff807b",
                "version": "v3.39.1"
            },
            "secrets_providers": {
                "type": "service",
                "state": {
                    "url": "https://api.pulumi.com",
                    "owner": "CL_Kenneth",
                    "project": "quickstart2",
                    "stack": "dev"
                }
            },
            "resources": [
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev",
                    "custom": false,
                    "type": "pulumi:pulumi:Stack"
                },
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default",
                    "custom": true,
                    "id": "3f0d2c97-9218-4ff9-bb2f-73ff82141b67",
                    "type": "pulumi:providers:gcp",
                    "inputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "outputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "parent": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev"
                },
                {
                    "urn": "urn:pulumi:dev::quickstart2::gcp:storage/bucket:Bucket::my-bucket-0927",
                    "custom": true,
                    "id": "my-bucket-0927-dd72df8",
                    "type": "gcp:storage/bucket:Bucket",
                    "inputs": {
                        "__defaults": [
                            "name"
                        ],
                        "forceDestroy": true,
                        "location": "ASIA-NORTHEAST1",
                        "name": "my-bucket-0927-dd72df8",
                        "storageClass": "STANDARD",
                        "uniformBucketLevelAccess": true
                    },
                    "outputs": {
                        "__meta": "{\"e2bfb730-ecaa-11e6-8f88-34363bc7c4c0\":{\"create\":240000000000,\"read\":240000000000,\"update\":240000000000}}",
                        "cors": [],
                        "defaultEventBasedHold": false,
                        "encryption": null,
                        "forceDestroy": true,
                        "id": "my-bucket-0927-dd72df8",
                        "labels": {},
                        "lifecycleRules": [],
                        "location": "ASIA-NORTHEAST1",
                        "logging": null,
                        "name": "my-bucket-0927-dd72df8",
                        "project": "pulumi-test",
                        "publicAccessPrevention": "inherited",
                        "requesterPays": false,
                        "retentionPolicy": null,
                        "selfLink": "https://www.googleapis.com/storage/v1/b/my-bucket-0927-******",
                        "storageClass": "STANDARD",
                        "uniformBucketLevelAccess": true,
                        "url": "gs://my-bucket-0927-dd72df8",
                        "versioning": null,
                        "website": null
                    },
                    "parent": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev",
                    "provider": "urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default::3f0d2c97-9218-4ff9-bb2f-73ff82141b67",
                    "propertyDependencies": {
                        "forceDestroy": [],
                        "location": [],
                        "storageClass": [],
                        "uniformBucketLevelAccess": []
                    }
                }
            ]
        }
    }
    
  • ここで一度作成されたbucketを削除して、元に状態に戻します
  • $ pulumi destroy
    Previewing destroy (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/previews/d5fcd57a-7498-494d-aee3-********
    
         Type                   Name             Plan
     -   pulumi:pulumi:Stack    quickstart2-dev  delete
     -   └─ gcp:storage:Bucket  my-bucket-0927   delete
    
    Resources:
        - 2 to delete
    
    Do you want to perform this destroy? yes
    Destroying (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/updates/**
    
         Type                   Name             Status
     -   pulumi:pulumi:Stack    quickstart2-dev  deleted
     -   └─ gcp:storage:Bucket  my-bucket-0927   deleted
    
    Resources:
        - 2 deleted
    
    Duration: 3s
    
  • リソースを削除すると、再度stateの中身からリソースの情報が削除されます
  • $ pulumi stack export
    {
        "version": 3,
        "deployment": {
            "manifest": {
                "time": "2022-09-27T22:37:04.390227+09:00",
                "magic": "c57d2cc6e4ddc1ad908bcf42d81d39304d5e92a1de5a6e1d5a5c485a70ff807b",
                "version": "v3.39.1"
            },
            "secrets_providers": {
                "type": "service",
                "state": {
                    "url": "https://api.pulumi.com",
                    "owner": "CL_Kenneth",
                    "project": "quickstart2",
                    "stack": "dev"
                }
            }
        }
    }
    

import option(リソースID)を付与した場合のPulumi up (参考)

次に、import option(リソースID)を付与した場合のPulumi upの挙動を確認していきます。

  • APIリファレンスによると、bucketの場合「name」がリソースIDとなるため、以下のように「NewBucket functionの引数」にimport option(リソースID)を追記します
  • pulumi.Run(func(ctx *pulumi.Context) error {
        // Create a GCP resource (Storage Bucket)
        _, err := storage.NewBucket(ctx, "my-bucket-0927", &storage.BucketArgs{
            Location:                 pulumi.String("ASIA-NORTHEAST1"),
            StorageClass:             pulumi.String("STANDARD"),
            UniformBucketLevelAccess: pulumi.Bool(true),
        }, pulumi.Import(pulumi.ID("my-bucket-0927")))
    
  • この状態で一度pulumi upを実行します
  • $ pulumi up
    Previewing update (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/previews/774dfb50-a363-451d-845b-********
    
         Type                   Name             Plan       Info
     +   pulumi:pulumi:Stack    quickstart2-dev  create
     =   └─ gcp:storage:Bucket  my-bucket-0927   import     [diff: ~name]; 1 warning
    
    Diagnostics:
      gcp:storage:Bucket (my-bucket-0927):
        warning: inputs to import do not match the existing resource; importing this resource will fail
    
    
    Do you want to perform this update?  [Use arrows to move, enter to select, type to filter]
      yes
    > no
      details
    
  • bucketのplan内容が「create」ではなく「import」になっていることが分かります。これはbucketについて実際にリソースを作成(create)せずに、stateに指定したIDのリソース情報を追加するplan内容となります
  • ただし、上記bucketのplanで「warning: inputs to import do not match the existing resource; importing this resource will fail」が表示されています。「details」を選択して詳細を確認します
  • Do you want to perform this update? details
    + pulumi:pulumi:Stack: (create)
        [urn=urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev]
        = gcp:storage/bucket:Bucket: (import)
            [id=my-bucket-0927]
            [urn=urn:pulumi:dev::quickstart2::gcp:storage/bucket:Bucket::my-bucket-0927]
          ~ name: "my-bucket-0927" => "my-bucket-0927-ad559e3"
    

    「name」が既存のリソース「my-bucket-0927」から、「my-bucket-0927-ad559e3」に変更されようとしています。importを実行する際は、nameなどの設定値がProgramとGCP環境の既存リソースとで一致させる必要がありますが、このままだと「name」が異なっているので、plan内容では「importが失敗します」との警告が表示されていることになります。

    ここで変更後のbucket name「my-bucket-0927-ad559e3」のsuffixに付与されている文字列ですが、これはpulumiの「Auto-naming」処理により自動的に付与されているランダムの16進数になります。「Auto-naming」の目的は主に以下2つです

  • 同じPulumi Projectで複数Stackを作成する際の、リソース名の衝突(重複)を防ぐため
  • 一部のクラウドプロバイダーにおけるPulumiのゼロダウンタイムでのリソース更新を行うため。一部のクラウドプロバイダーではリソース更新時に、「更新後の設定値でリソースを新規作成し、作成後に参照先を新規作成したリソースに変更し、その後古いリソースを削除する」プロセスでリソース更新が行われる
  • 上記の通り、複数Stackを作成する際のリソース名重複によるエラーがたびたび発生し得るので、公式ドキュメントではProject名とStack名をリソースの名前に含めるか(例:test-bucet-<project_name>-<stack_name>)、またはリソースのオプションに「deleteBeforeReplace: true」を追加して、リソース更新時にリソースが新規作成される際に古いリソースが削除されるようにするよう、推奨されています

  • 今回の検証では複数Stackを作成して同一リソースを作成するケースでは無いため、Auto-namingの処理を実施しない固定の「name」を指定するオプション「Name:」を以下のように追加します
  • _, err := storage.NewBucket(ctx, "my-bucket-0927", &storage.BucketArgs{
        Name:                     pulumi.String("my-bucket-0927"),
        Location:                 pulumi.String("ASIA-NORTHEAST1"),
        StorageClass:             pulumi.String("STANDARD"),
        UniformBucketLevelAccess: pulumi.Bool(true),
    }, pulumi.Import(pulumi.ID("my-bucket-0927")))
    
  • この状態で再度pulumi upを実行します。その後「warning」が消えていることを確認し、「yes」を選択します
  • $ pulumi up
    Previewing update (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/previews/ef046ad6-7345-4ce7-b423-*********
    
         Type                   Name             Plan
     +   pulumi:pulumi:Stack    quickstart2-dev  create
     =   └─ gcp:storage:Bucket  my-bucket-0927   import
    
    Resources:
        + 1 to create
        = 1 to import
        2 changes
    
    Do you want to perform this update? details
    + pulumi:pulumi:Stack: (create)
        [urn=urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev]
        = gcp:storage/bucket:Bucket: (import)
            [id=my-bucket-0927]
            [urn=urn:pulumi:dev::quickstart2::gcp:storage/bucket:Bucket::my-bucket-0927]
            location                : "ASIA-NORTHEAST1"
            name                    : "my-bucket-0927"
            project                 : "pulumi-test"
            publicAccessPrevention  : "enforced"
            uniformBucketLevelAccess: true
    
    Do you want to perform this update? yes
    Updating (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/updates/**
    
         Type                   Name             Status
     +   pulumi:pulumi:Stack    quickstart2-dev  created
     =   └─ gcp:storage:Bucket  my-bucket-0927   imported
    
    Resources:
        + 1 created
        = 1 imported
        2 changes
    
    Duration: 5s
    
  • importが無事完了しました。GCP Consoleでは「リソースIDを付与しない場合のPulumi up」のケースのように、新たにリソースが作成されず、かつ以下のようにstateにリソースの情報が追加されていることが確認できます
  • $ pulumi stack export
    {
        "version": 3,
        "deployment": {
            "manifest": {
                "time": "2022-09-28T00:30:29.247685+09:00",
                "magic": "c57d2cc6e4ddc1ad908bcf42d81d39304d5e92a1de5a6e1d5a5c485a70ff807b",
                "version": "v3.39.1"
            },
            "secrets_providers": {
                "type": "service",
                "state": {
                    "url": "https://api.pulumi.com",
                    "owner": "CL_Kenneth",
                    "project": "quickstart2",
                    "stack": "dev"
                }
            },
            "resources": [
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev",
                    "custom": false,
                    "type": "pulumi:pulumi:Stack"
                },
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default",
                    "custom": true,
                    "id": "45fdfb64-6f42-4a8e-b6c5-29eceae70e6f",
                    "type": "pulumi:providers:gcp",
                    "inputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "outputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "parent": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev"
                },
                {
                    "urn": "urn:pulumi:dev::quickstart2::gcp:storage/bucket:Bucket::my-bucket-0927",
                    "custom": true,
                    "id": "my-bucket-0927",
                    "type": "gcp:storage/bucket:Bucket",
                    "inputs": {
                        "__defaults": [
                            "forceDestroy"
                        ],
                        "forceDestroy": false,
                        "location": "ASIA-NORTHEAST1",
                        "name": "my-bucket-0927",
                        "storageClass": "STANDARD",
                        "uniformBucketLevelAccess": true
                    },
                    "outputs": {
                        "__meta": "{\"e2bfb730-ecaa-11e6-8f88-34363bc7c4c0\":{\"create\":240000000000,\"read\":240000000000,\"update\":240000000000}}",
                        "cors": [],
                        "defaultEventBasedHold": false,
                        "encryption": null,
                        "forceDestroy": false,
                        "id": "my-bucket-0927",
                        "labels": {},
                        "lifecycleRules": [],
                        "location": "ASIA-NORTHEAST1",
                        "logging": null,
                        "name": "my-bucket-0927",
                        "project": "pulumi-test",
                        "publicAccessPrevention": "enforced",
                        "requesterPays": false,
                        "retentionPolicy": null,
                        "selfLink": "https://www.googleapis.com/storage/v1/**/my-bucket-0927",
                        "storageClass": "STANDARD",
                        "uniformBucketLevelAccess": true,
                        "url": "gs://my-bucket-0927",
                        "versioning": null,
                        "website": null
                    },
                    "parent": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev",
                    "provider": "urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default::45fdfb64-6f42-4a8e-b6c5-29eceae70e6f",
                    "propertyDependencies": {
                        "location": [],
                        "name": [],
                        "storageClass": [],
                        "uniformBucketLevelAccess": []
                    },
                    "importID": "my-bucket-0927"
                }
            ]
        }
    }
    
  • 最後にProgramの対象リソースからリソースID(import option)を削除します
  • _, err := storage.NewBucket(ctx, "my-bucket-0927", &storage.BucketArgs{
        Name:                     pulumi.String("my-bucket-0927"),
        Location:                 pulumi.String("ASIA-NORTHEAST1"),
        StorageClass:             pulumi.String("STANDARD"),
        UniformBucketLevelAccess: pulumi.Bool(true),
    })
    
  • 念のため再度pulumi upを実行して差分が無いことを確認できたら完了です
  • $ pulumi up
    Previewing update (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/previews/802ed26c-03e1-4fa3-91fe-********
    
         Type                 Name             Plan
         pulumi:pulumi:Stack  quickstart2-dev
    
    Resources:
        2 unchanged
    

Pulumiコマンドによるimport(参考)

これまではProgramにimport option(リソースID)を追記してimport実施しましたが、pulumiコマンドによるimportも実施出来ます。import方法については概ね各リソースのAPIリファレンスに記載されています。今回はGCS Bucketのimportを実施します。

  • stateは空(リソースの情報が無い)の状態で、GCP Consoleには未だPulumiで管理されていないbucketが存在している状態です
  • $ pulumi stack export
    {
        "version": 3,
        "deployment": {
            "manifest": {
                "time": "2022-09-28T00:53:02.915989+09:00",
                "magic": "c57d2cc6e4ddc1ad908bcf42d81d39304d5e92a1de5a6e1d5a5c485a70ff807b",
                "version": "v3.39.1"
            },
            "secrets_providers": {
                "type": "service",
                "state": {
                    "url": "https://api.pulumi.com",
                    "owner": "CL_Kenneth",
                    "project": "quickstart2",
                    "stack": "dev"
                }
            }
        }
    }
    
  • 以下pulumi importコマンドを実行します。文法は「pulumi import [type] [name] [id] [option]」になります
  • $ pulumi import gcp:storage/bucket:Bucket my-bucket-0927 my-bucket-0927
    Previewing import (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/previews/f34c0f49-cb16-4bfb-889a-*******
    
         Type                   Name             Plan
     +   pulumi:pulumi:Stack    quickstart2-dev  create
     =   └─ gcp:storage:Bucket  my-bucket-0927   import
    
    Resources:
        + 1 to create
        = 1 to import
        2 changes
    
    Do you want to perform this import? details
    + pulumi:pulumi:Stack: (create)
        [urn=urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev]
        = gcp:storage/bucket:Bucket: (import) 🔒
            [id=my-bucket-0927]
            [urn=urn:pulumi:dev::quickstart2::gcp:storage/bucket:Bucket::my-bucket-0927]
            [provider=urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default_6_29_0::04da6b54-80e4-46f7-96ec-b56ff0331ba9]
            location                : "ASIA-NORTHEAST1"
            name                    : "my-bucket-0927"
            project                 : "pulumi-test"
            publicAccessPrevention  : "enforced"
            uniformBucketLevelAccess: true
    
    Do you want to perform this import? yes
    Importing (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/updates/**
    
         Type                   Name             Status
     +   pulumi:pulumi:Stack    quickstart2-dev  created
     =   └─ gcp:storage:Bucket  my-bucket-0927   imported
    
    Resources:
        + 1 created
        = 1 imported
        2 changes
    
    Duration: 2s
    
  • import完了後、stateにリソースの情報が追加されていることが確認できます
  • $ pulumi stack export
    {
        "version": 3,
        "deployment": {
            "manifest": {
                "time": "2022-09-28T01:03:49.870847+09:00",
                "magic": "c57d2cc6e4ddc1ad908bcf42d81d39304d5e92a1de5a6e1d5a5c485a70ff807b",
                "version": "v3.39.1"
            },
            "secrets_providers": {
                "type": "service",
                "state": {
                    "url": "https://api.pulumi.com",
                    "owner": "CL_Kenneth",
                    "project": "quickstart2",
                    "stack": "dev"
                }
            },
            "resources": [
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev",
                    "custom": false,
                    "type": "pulumi:pulumi:Stack"
                },
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default_6_29_0",
                    "custom": true,
                    "id": "597778d7-cd2d-4070-96ff-7c244c0dc3ff",
                    "type": "pulumi:providers:gcp",
                    "inputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "outputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    }
                },
                {
                    "urn": "urn:pulumi:dev::quickstart2::gcp:storage/bucket:Bucket::my-bucket-0927",
                    "custom": true,
                    "id": "my-bucket-0927",
                    "type": "gcp:storage/bucket:Bucket",
                    "inputs": {
                        "__defaults": [],
                        "location": "ASIA-NORTHEAST1",
                        "name": "my-bucket-0927",
                        "project": "pulumi-test",
                        "publicAccessPrevention": "enforced",
                        "uniformBucketLevelAccess": true
                    },
                    "outputs": {
                        "__meta": "{\"e2bfb730-ecaa-11e6-8f88-34363bc7c4c0\":{\"create\":240000000000,\"read\":240000000000,\"update\":240000000000}}",
                        "cors": [],
                        "defaultEventBasedHold": false,
                        "encryption": null,
                        "forceDestroy": false,
                        "id": "my-bucket-0927",
                        "labels": {},
                        "lifecycleRules": [],
                        "location": "ASIA-NORTHEAST1",
                        "logging": null,
                        "name": "my-bucket-0927",
                        "project": "pulumi-test",
                        "publicAccessPrevention": "enforced",
                        "requesterPays": false,
                        "retentionPolicy": null,
                        "selfLink": "https://www.googleapis.com/storage/v1/**/my-bucket-0927",
                        "storageClass": "STANDARD",
                        "uniformBucketLevelAccess": true,
                        "url": "gs://my-bucket-0927",
                        "versioning": null,
                        "website": null
                    },
                    "parent": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev",
                    "protect": true,
                    "provider": "urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default_6_29_0::597778d7-cd2d-4070-96ff-7c244c0dc3ff"
                }
            ]
        }
    }
    

    ちなみにimportコマンド実行直後に、importしたリソースの以下ProgramがCLIに表示されます。これをそのままProgramにコピーして利用できます。「実際にimportは実行したくないけど、リソースのAPIリファレンス参照前に、Programのコードだけ確認したい」時などは、pulumi importコマンドを一度実行して、plan内容確認時(Do you want to perform this import?)で「no」を選択すれば、importを実行せずにコードが出力されます

    Please copy the following code into your Pulumi application. Not doing so
    will cause Pulumi to report that an update will happen on the next update command.
    
    Please note that the imported resources are marked as protected. To destroy them
    you will need to remove the `protect` option and run `pulumi update` *before*
    the destroy will take effect.
    
    package main
    
    import (
        "github.com/pulumi/pulumi-gcp/sdk/v6/go/gcp/storage"
        "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
        pulumi.Run(func(ctx *pulumi.Context) error {
            _, err := storage.NewBucket(ctx, "my-bucket-0927", &storage.BucketArgs{
                Location:                 pulumi.String("ASIA-NORTHEAST1"),
                Name:                     pulumi.String("my-bucket-0927"),
                Project:                  pulumi.String("pulumi-test"),
                PublicAccessPrevention:   pulumi.String("enforced"),
                UniformBucketLevelAccess: pulumi.Bool(true),
            }, pulumi.Protect(true))
            if err != nil {
                return err
            }
            return nil
        })
    }
    

その他state関連操作について

その他state関連のPulumiコマンドについて一部紹介します

pulumi state delete [URN] (参考)

特定リソースのURN(Uniform Resource Name)を用いて、stateからリソースの情報を削除することができます(実際にクラウド環境にデプロイされているリソース自体は削除されません)

「実際にリソース自体は削除したくないが、Pulumiの管理からは外したい」場合などに利用します

  • 削除したいリソースのURNを調べます(今回はbucketのstate情報を削除します)。stateから該当リソースのURNを確認することができます
  • $ pulumi stack export
    {
    ~~(略)~~
    "resources": [
    ~~(略)~~
    {
            "urn": "urn:pulumi:dev::quickstart2::gcp:storage/bucket:Bucket::my-bucket-0927",
            "custom": true,
            "id": "my-bucket-0927",
            "type": "gcp:storage/bucket:Bucket",
            "inputs": {
                "__defaults": [
                    "forceDestroy"
                ],
                "forceDestroy": false,
                "location": "ASIA-NORTHEAST1",
                "name": "my-bucket-0927",
                "storageClass": "STANDARD",
                "uniformBucketLevelAccess": true
            },
    ~~(略)~~
    }
    
  • または以下pulumiコマンドでURNを表示させることが可能です
  • $ pulumi stack --show-urns
    Current stack is dev:
        Owner: CL_Kenneth
        Last updated: 23 minutes ago (2022-09-28 01:03:49.870847 +0900 JST)
        Pulumi version: v3.39.1
    Current stack resources (3):
        TYPE                          NAME
        pulumi:pulumi:Stack           quickstart2-dev
        │  URN: urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev
        ├─ gcp:storage/bucket:Bucket  my-bucket-0927
        │     URN: urn:pulumi:dev::quickstart2::gcp:storage/bucket:Bucket::my-bucket-0927
        └─ pulumi:providers:gcp       default_6_29_0
              URN: urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default_6_29_0
    
    Current stack outputs (0):
        No output values currently in this stack
    
    More information at: https://app.pulumi.com/CL_Kenneth/quickstart2/dev
    
    Use `pulumi stack select` to change stack; `pulumi stack ls` lists known ones
    
  • URNを指定して、以下コマンドを実行します
  • $ pulumi state delete urn:pulumi:dev::quickstart2::gcp:storage/bucket:Bucket::my-bucket-0927
     warning: This command will edit your stack's state directly. Confirm? Yes
    Resource deleted
    
  • stateから対象のリソース情報(bucket)が削除されました
  • $ pulumi stack export
    {
        "version": 3,
        "deployment": {
            "manifest": {
                "time": "2022-09-28T01:40:56.95378+09:00",
                "magic": "c57d2cc6e4ddc1ad908bcf42d81d39304d5e92a1de5a6e1d5a5c485a70ff807b",
                "version": "v3.39.1"
            },
            "secrets_providers": {
                "type": "service",
                "state": {
                    "url": "https://api.pulumi.com",
                    "owner": "CL_Kenneth",
                    "project": "quickstart2",
                    "stack": "dev"
                }
            },
            "resources": [
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev",
                    "custom": false,
                    "type": "pulumi:pulumi:Stack"
                },
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default",
                    "custom": true,
                    "id": "f8f41b6d-87d4-4cd2-93b8-ebdbf766398c",
                    "type": "pulumi:providers:gcp",
                    "inputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "outputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "parent": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev"
                }
            ]
        }
    }
    

pulumi preview (参考)

stateの情報とProgramのコードの差分を確認することができます。pulumi upコマンドでも差分の確認は出来ますが、間違えて実際のデプロイを実行してしまう場合もありますので、pulumi previewで差分を確認する方が無難かと思います

  • 現在のProgramのコード
  • $ cat main.go
    package main
    
    import (
        "github.com/pulumi/pulumi-gcp/sdk/v6/go/gcp/storage"
        "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
        pulumi.Run(func(ctx *pulumi.Context) error {
            // Create a GCP resource (Storage Bucket)
            _, err := storage.NewBucket(ctx, "my-bucket-0927", &storage.BucketArgs{
                Name:                     pulumi.String("my-bucket-0927"),
                Location:                 pulumi.String("ASIA-NORTHEAST1"),
                StorageClass:             pulumi.String("STANDARD"),
                UniformBucketLevelAccess: pulumi.Bool(true),
            })
            if err != nil {
                return err
            }
    
            return nil
        })
    }
    
  • 現在のstate情報。stateにはProgramに記載のbucketの情報がありません
  • $ pulumi stack export
    {
        "version": 3,
        "deployment": {
            "manifest": {
                "time": "2022-09-28T01:40:56.95378+09:00",
                "magic": "c57d2cc6e4ddc1ad908bcf42d81d39304d5e92a1de5a6e1d5a5c485a70ff807b",
                "version": "v3.39.1"
            },
            "secrets_providers": {
                "type": "service",
                "state": {
                    "url": "https://api.pulumi.com",
                    "owner": "CL_Kenneth",
                    "project": "quickstart2",
                    "stack": "dev"
                }
            },
            "resources": [
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev",
                    "custom": false,
                    "type": "pulumi:pulumi:Stack"
                },
                {
                    "urn": "urn:pulumi:dev::quickstart2::pulumi:providers:gcp::default",
                    "custom": true,
                    "id": "f8f41b6d-87d4-4cd2-93b8-ebdbf766398c",
                    "type": "pulumi:providers:gcp",
                    "inputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "outputs": {
                        "project": "pulumi-test",
                        "version": "6.29.0"
                    },
                    "parent": "urn:pulumi:dev::quickstart2::pulumi:pulumi:Stack::quickstart2-dev"
                }
            ]
        }
    }
    
  • 以下コマンドで差分を確認します。bucket新規作成の差分が表示されています
  • $ pulumi preview
    Previewing update (dev)
    
    View Live: https://app.pulumi.com/CL_Kenneth/quickstart2/dev/previews/f8807e3a-5045-4e63-ae14-******
    
         Type                   Name             Plan
         pulumi:pulumi:Stack    quickstart2-dev
     +   └─ gcp:storage:Bucket  my-bucket-0927   create
    
    Resources:
        + 1 to create
        1 unchanged
    

まとめ

Pulumi architectureとstateの挙動について少し理解を深めることができました。Pulumiを運用していく上で便利だと感じたState関連の操作やPulumi Serviceに関する情報について、今後も機会があれば紹介していきたいと思います。

新規CTA