tech.guitarrapc.cóm

Technical updates

Pulumi の .NET Coreの Preview リリースとGetting Started

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

qiita.com

Pulumi の .NET Core 対応版が Preview リリースされました。

Getting Startedから、実際に環境を構築したり疑問点の解決を回を重ねながらみていきましょう。

目次

TL;DR

実際にPulumi x .NET Core 3.0 (C#) で使ってみていますがそのまとめをざっくり。

  • まだ Preview なので、TypeScript や Python と比べてできない機能も数多い
  • 動作もいくつかバグが残っていたりして、NuGet のPreview パッケージが週一でどんどん改善されている
  • 現状は Visual Studio で実行まで完結するわけではないので注意
  • 型は偉大、terraformのようにドキュメントを見る手間が格段に減った
  • 型でわからないときでも terraform のドキュメントをみればok
  • ドキュメントのメソッドやクラスは結構古かったりする

Getting Started はいい出来にはなっているので見てみるといいでしょう。

Pulumi Getting Started

これに基づいて進めていく。

https://www.pulumi.com/docs/get-started/

Login

まずは何はともあれアカウントを作る。

https://app.pulumi.com/signin

f:id:guitarrapc_tech:20191201235949p:plain

アクセストークンを作ることで、pulumi cli とアカウントが紐づいて操作できるようになる。

https://app.pulumi.com/guitarrapc/settings/tokens

事前準備

install .NET Core 3.0 SDK

Pulumi の実行は、コンパイルしてから実行されます。 このコンパイル、実行のために .NET Core 3.0 SDK が必要です。

https://dotnet.microsoft.com/download

Install Pulumi cli

windows

scoopに対応したので、これでpulumi cliを入れましょう。main bucket で利用できます。(scoop インストールしたらすぐにつかえるということです)

scoop install pulumi

cli でログインします。これでCLIが Webと紐づいて、ログが記録されたりリソースが可視化されます。

pulumi login

macOS

Homebrew でどうぞ。

brew install pulumi

更新は upgrade で。

brew upgrade pulumi

linux

curl で拾ってきて、実行するだけです。

curl -fsSL https://get.pulumi.com | sh

アクセストークン の生成方法

3つあります。アクセストークンを管理したくないので、自動生成がお勧めです。

  • (recommended) pulumi login でログインしたら、何も入力せずに Enterします。ブラウザが立ち上がるので、ログインすると自動的にアクセストークンが生成されます。

f:id:guitarrapc_tech:20191202002004p:plain

  • アクセストークンを pulumi loginで入れる
  • アクセストークンを PULUMI_ACCESS_TOKEN 環境変数に入れる。

Language Support

  • GA: TYPESCRIPT, JAVASCRIPT, PYTHON
  • PREVIEW: C# (.NET Core 3.0 SDK or later.), Golang

Configure AWS

AWS を操作する前に、認証だけ解決しておくこと。 default プロファイルを使わない場合、権限のある User を以下の環境変数に入れておくのが手っ取り早い。

  • AWS_PROFILE

Visual Studio の Launch Profile で 環境変数に入れてもok です。 pulumi up でdotnet build されるので、それが使われます。

Create a New Project

AWS C# project を作成します。

mkdir pulumi
cd pulumi
pulumi new aws-csharp

デフォルトだと次のようになる。

> pulumi new aws-csharp

This command will walk you through creating a new Pulumi project.

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

project name: (pulumi)
project description: (A minimal AWS C# Pulumi program)
Created project 'pulumi'

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

aws:region: The AWS region to deploy into: (us-east-1) ap-northeast-1
Saved config

Your new project is ready to go!

To perform an initial deployment, run 'pulumi up'

これで自動的に csproj や Program.cs、 yaml が生成される。

$ ls -la
drw-rw-rw-  6 S07671 0 4096 2019-11-13 10:21 .
drw-rw-rw-  6 S07671 0 4096 2019-11-12 18:33 ..
-rw-rw-rw-  1 S07671 0 6401 2019-11-12 18:41 .gitignore
-rw-rw-rw-  1 S07671 0  318 2019-11-12 18:41 Infra.csproj
-rw-rw-rw-  1 S07671 0 1115 2019-11-12 19:00 Infra.sln
-rw-rw-rw-  1 S07671 0 1357 2019-11-13 10:21 Program.cs
-rw-rw-rw-  1 S07671 0   37 2019-11-12 18:41 Pulumi.dev.yaml
-rw-rw-rw-  1 S07671 0   74 2019-11-12 18:41 Pulumi.yaml

f:id:guitarrapc_tech:20191202000011p:plain

ファイルは次の通り。

  • Pulumi.yaml defines the project.
  • Pulumi.dev.yaml contains configuration values for the stack we initialized.
  • Program.cs is the Pulumi program that defines our stack resources. Let’s examine it.
name: pulumi
runtime: dotnet
description: A minimal AWS C# Pulumi program
config:
  aws:region: ap-northeast-1
using System.Collections.Generic;
using System.Threading.Tasks;

using Pulumi;
using Pulumi.Aws.S3;

class Program
{
    static Task<int> Main()
    {
        return Deployment.RunAsync(() => {

            // Create an AWS resource (S3 Bucket)
            var bucket = new Bucket("my-bucket");

            // Export the name of the bucket
            return new Dictionary<string, object>
            {
                { "bucketName", bucket.Id },
            };
        });
    }
}
1.8.0

仮実行とエラー解消

この状態で pulumi up とするとスタックがどう生成されるか表示される。

Config に aws:region の設定が抜けているとエラーが発生する

と思いきや、エラー。

$ pulumi up
Previewing update (sandbox):
     Type                 Name            Plan     Info
     pulumi:pulumi:Stack  pulumi-sandbox           'dotnet build -nologo .' completed successfully
 ms
 +   pulumi:pulumi:Stack          pulumi-sandbox  create     1 error; 2 messages
����ł����B                Name            Plan       Info
 +   └─ pkg:component:ekscluster  sandbox         create

Diagnostics:
  pulumi:pulumi:Stack (pulumi-sandbox):
    �N���v���t�@�C�� "(����)" ���K�p�ł��܂����ł����B
    �g�p�\�ȋN���v���t�@�C�����������܂����ł����B

    error: Running program 'C:\git\infra\pulumi\sandbox\bin\Debug\netcoreapp3.0\Infra.dll' failed with an unhandled exception:
    System.NullReferenceException: Object reference not set to an instance of an object.
       at Pulumi.Deployment.InvokeAsync[T](String token, ResourceArgs args, InvokeOptions options, Boolean convertResult)
       at Infra.EksClusterComponent.CreateAsync() in C:\git\infra\pulumi\sandbox\EksClusterComponent.cs:line 25
       at Program.<>c.<<Main>b__0_0>d.MoveNext() in C:\git\infra\pulumi\sandbox\Program.cs:line 39
    --- End of stack trace from previous location where exception was thrown ---
       at Pulumi.Stack.RunInitAsync(Func`1 init)
       at Pulumi.Output`1.GetValueAsync()
       at Pulumi.Deployment.RegisterResourceOutputsAsync(Resource resource, Output`1 outputs)
       at Pulumi.Deployment.Runner.WhileRunningAsync()

エラーは、System.NullReferenceException: Object reference not set to an instance of an object.。リソースの読み取りを使用とする時点で怒られる。

原因は、pulumi で AWS の Region情報 aws:region を config に設定していないため。 なので、Config設定する。

$ pulumi config set aws:region ap-northeast-1

AWS Profile の指定が抜けているとエラーが発生する

config にaws:region の設定後、再実行するとまた失敗する。

$ pulumi up
Previewing update (dev):
     Type                 Name        Plan     Info
     pulumi:pulumi:Stack  pulumi-dev           .NET Core ���� Microsoft (R) Build Engine �o�[�W���� 16.3.0+0f     pulumi:pulumi:Stack  pulumi-dev             C:\git\xxx\infra\pulumi\Infra.csproj �̕����� 45.39 ms �Ŋ��     pulumi:pulumi:Stack  pulumi-dev           'dotnet build .' completed successfully
[resource plugin aws-1.8.0] installing
Downloading plugin: 61.24 MiB / 61.24 MiB [=========================] 100.00% 5s
Moving plugin... done.

     Type                 Name        Plan       Info
 +   pulumi:pulumi:Stack  pulumi-dev  create
     └─ aws:s3:Bucket     my-bucket              1 error

Diagnostics:
  aws:s3:Bucket (my-bucket):
    error: unable to discover AWS AccessKeyID and/or SecretAccessKey - see https://pulumi.io/install/aws.html for details on configuration

エラーは error: unable to discover AWS AccessKeyID and/or SecretAccessKey - see https://pulumi.io/install/aws.html for details on configurationなのでAWSプロファイルがないことを示す。

ということで、aws configure でdefault profile の認証を設定する。

https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/

もし複数プロファイルがあり、任意のプロファイルを参照したい場合は、AWS_PROFILE 環境変数にプロファイルを指定する。

リソースを作成してみる

これで pulumi up すると、yes / no が選択できる。 yes でリソースが作成、削除される。

$ pulumi up

Previewing update (dev):
     Type                 Name        Plan     Info
     pulumi:pulumi:Stack  pulumi-dev           .NET Core ���� Microsoft (R) Build Engine �o�[�W���� 16.3.0+0f     pulumi:pulumi:Stack  pulumi-dev             C:\git\infra\pulumi\Infra.csproj �̕����� 34.9 ms �Ŋ��      pulumi:pulumi:Stack  pulumi-dev           'dotnet build .' completed successfully

     Type                 Name        Plan
 +   pulumi:pulumi:Stack  pulumi-dev  create
 +   └─ aws:s3:Bucket     my-bucket   create

Resources:
    + 2 to create

Do you want to perform this update? yes
Updating (dev):
     Type                 Name        Status     Info
     pulumi:pulumi:Stack  pulumi-dev             .NET Core ���� Microsoft (R) Build Engine �o�[�W���� 16.3.0+     pulumi:pulumi:Stack  pulumi-dev               C:\git\infra\pulumi\Infra.csproj �̕����� 32.92 ms �Ŋ�     pulumi:pulumi:Stack  pulumi-dev             'dotnet build .' completed successfully

     Type                 Name        Status
 +   pulumi:pulumi:Stack  pulumi-dev  created
 +   └─ aws:s3:Bucket     my-bucket   created

Outputs:
    bucketName: "my-bucket-626a03e"

Resources:
    + 2 created

Duration: 9s

Permalink: https://app.pulumi.com/guitarrapc/pulumi/dev/updates/1

リソースを見てみると、後ろにハッシュがつくことで名前の重複が防がれていることがわかる。

f:id:guitarrapc_tech:20191202000032p:plain

リソースを削除してみる

消すと、pulumi up で実行したときに消える

            // Create an AWS resource (S3 Bucket)
            //var bucket = new Bucket("my-bucket");
> pulumi up
Previewing update (dev):
     Type                 Name        Plan     Info
     pulumi:pulumi:Stack  pulumi-dev           'dotnet build .' completed successfully
����܂
     Type                 Name        Plan
     pulumi:pulumi:Stack  pulumi-dev
 -   └─ aws:s3:Bucket     my-bucket   delete

Outputs:
  - bucketName: "my-bucket-626a03e"

Resources:
    - 1 to delete
    1 unchanged

Do you want to perform this update? yes
Updating (dev):
     Type                 Name        Status     Info
     pulumi:pulumi:Stack  pulumi-dev             'dotnet build .' completed successfully
�����
     Type                 Name        Status
     pulumi:pulumi:Stack  pulumi-dev
 -   └─ aws:s3:Bucket     my-bucket   deleted

Outputs:
  - bucketName: "my-bucket-626a03e"
    hogemoge  : "piyopiyo"

Resources:
    - 1 deleted
    1 unchanged

Duration: 5s

Permalink: https://app.pulumi.com/guitarrapc/pulumi/dev/updates/3

余談: VS での F5 実行

F5 で実行はまだ無理げ。

Complete Support for .NET · Issue #3470 · pulumi/pulumi https://github.com/pulumi/pulumi/issues/3470

VS上のPulumi_Monitor とかも環境変数仕込むと gRPC のchannelがフックされるのでいったんなしがよさそう。

new Pulumi.Aws.Provider() で指定できる? 例えば、pulumi-sandbox profile の ap-northeast-1 region ならこう。-> profile 参照されてないときと同じエラーでダメだった。

        new Pulumi.Aws.Provider("sandbox", new Pulumi.Aws.ProviderArgs()
        {
            Profile = "pulumi-sandbox",
            Region = "ap-northeast-1",
        });

さらにリソースを追加

KMS を追加してみる。

    static Task<int> Main()
    {
        return Deployment.RunAsync(() => {

            // Create a KMS Key for S3 server-side encryption
            var key = new Pulumi.Aws.Kms.Key("my-key");

            // Create an AWS resource (S3 Bucket)
            var bucket = new Pulumi.Aws.S3.Bucket("my-bucket", new Pulumi.Aws.S3.BucketArgs
            {
                ServerSideEncryptionConfiguration = new Pulumi.Aws.S3.Inputs.BucketServerSideEncryptionConfigurationArgs
                {
                    Rule = new Pulumi.Aws.S3.Inputs.BucketServerSideEncryptionConfigurationRuleArgs
                    {
                        ApplyServerSideEncryptionByDefault = new Pulumi.Aws.S3.Inputs.BucketServerSideEncryptionConfigurationRuleApplyServerSideEncryptionByDefaultArgs
                        {
                            SseAlgorithm = "aws:kms",
                            KmsMasterKeyId = key.Id,
                        },
                    },
                },
            });

            // Export the name of the bucket
            return new Dictionary<string, object> {
                { "bucket_name", bucket.Id },
            };
        });
    }

追加される。

pulumi up
Previewing update (dev):
     Type                 Name        Plan     Info
     pulumi:pulumi:Stack  pulumi-dev           'dotnet build .' completed successfully
����܂
     Type                 Name        Plan
     pulumi:pulumi:Stack  pulumi-dev
 +   ├─ aws:kms:Key       my-key      create
 +   └─ aws:s3:Bucket     my-bucket   create

Outputs:
  + bucket_name: output<string>
  - hogemoge   : "piyopiyo"

Resources:
    + 2 to create
    1 unchanged

Do you want to perform this update?

kms を見ると、name に指定した文字列がどこにもないのが気になる。

f:id:guitarrapc_tech:20191202004455p:plain

stack destroy

$ pulumi destroy

Previewing destroy (dev):

     Type                 Name        Plan
 -   pulumi:pulumi:Stack  pulumi-dev  delete
 -   ├─ aws:s3:Bucket     my-bucket   delete
 -   └─ aws:kms:Key       my-key      delete

Outputs:
  - bucket_name: "my-bucket-c3f5c37"

Resources:
    - 3 to delete

Do you want to perform this destroy?
  yes
> no
  details
Duration: 24s

Permalink: https://app.pulumi.com/guitarrapc/pulumi/dev/updates/5
The resources in the stack have been deleted, but the history and configuration associated with the stack are still maintained.
If you want to remove the stack completely, run 'pulumi stack rm dev'.

TODO

Getting Started の中で幾つもわからないことが出てきました。 次回から順にみていきましょう。

  • pulumi up 時の文字化け対策
  • Pulumi Web UI でできること
  • 後ろのハッシュはずすのどうするの?
  • IAM Document Policy のような JSON をいい感じで型から生成できるの?
  • リソース作成後の型が Output<string> だけどいい感じに変換できるの?
  • Terraform Module 的な仕組みどうするの?
  • 既存リソースの import どうするの?
  • よく使うコマンドは?
  • state ってどうなってるの?
  • 他のstate 参照どうするの?
  • VS 上 で F5実行のステータスどうなってる?