fukasawah.github.io

PulumiでAzureのリソースを構成する

PulumiはIndrastracture as Codeを実現するソフトウェア。 Azure Resource Manager(Template)やTerraformで出来ることと同じだが、特定の言語(JavaScript,TypeScript,Python)で記述できるのが強み。

Azure Resource Manager Templateに嫌気がさしつつ、terraformでやろうかな、と思っていたらPulumi見かけたのでクイックスタートを走ってみた。

Pulumiのインストール

以下でOS毎のインストール方法が書かれている。

https://pulumi.io/reference/install/

Azureを使う場合はAzure CLI 2.0.x以上を入れておく。

https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest

ログイン

Pulumiは状態管理などをPulumiが提供しているサーバ上で行う。 terraformのようにS3ストレージにtfstateを上げて共有する代わりにこれらを使うが、ログインのためのアカウント作成が面倒なのと、状態ファイルを握られるのが何か嫌なので、今回は使わない事にする。

ローカルに用意することもできるので、今回はこれを使う。

mkdir ~/quickstart
cd ~/quickstart
pulumi login "file://~/quickstart"

プロジェクトを作る

AzureのQuickStartを読みながら進める

https://pulumi.io/quickstart/azure/install-pulumi/

プロバイダはAzure、言語はTypeScriptでプロジェクトを作成する。 ローカルにログインしての作成なので、少し表示が違う。

$ pulumi new azure-typescript
Created project 'quickstart'

Enter your passphrase to protect config/secrets: ********
Re-enter your passphrase to confirm: ********
Created stack 'dev'

Enter your passphrase to unlock config/secrets
    (set PULUMI_CONFIG_PASSPHRASE to remember): ********
Saved config

Installing dependencies...

...

added 163 packages from 190 contributors and audited 510 packages in 35.953s
found 0 vulnerabilities

Finished installing dependencies

Your new project is ready to go!

To perform an initial deployment, run 'pulumi up'

ローカルの場合はパスフレーズを使い暗号化を行う。これはプロジェクト毎に設定するようだ。 次に、プロジェクトを作成した後、devというstackが作られる。stackというのは環境みたいなもので、しばしば開発者が確認で使う「Development(開発環境)」や、お客様が遊んで使う「staging(検証環境)」ユーザが実際に使う「production(本番環境)」と呼んだりするが、Pulumiではこれらを1つ1つをstackと呼んで分けている。 なので、プロジェクトの中には複数のstackがあり、stack毎に反映していくことになる。今回はデフォルトのstackとしてdevが作られたが、stackの操作はサブコマンドのpulumi stackで一通り操作できる。(doc)

Pulumiのプログラムを書く

index.tsが実際のコードになる。デフォルトでリソースグループとストレージアカウントを作成するコードが用意されている。

少し改変する。

import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure";

const PROJECT_NAME = 'pulumi'

// Create an Azure Resource Group
const resourceGroup = new azure.core.ResourceGroup(`rg-${PROJECT_NAME}`, {
    location: "JapanEast",
});

// Create an Azure resource (Storage Account)
const account = new azure.storage.Account(`storage${PROJECT_NAME}`, {
    resourceGroupName: resourceGroup.name,
    location: resourceGroup.location,
    accountTier: "Standard",
    accountReplicationType: "LRS",
});

// Export the connection string for the storage account
export const connectionString = account.primaryConnectionString;

Pulumi upで反映する

$ pulumi up
Enter your passphrase to unlock config/secrets
    (set PULUMI_CONFIG_PASSPHRASE to remember): ********
Previewing update (dev):

 +  pulumi:pulumi:Stack quickstart-dev create
 +  azure:core:ResourceGroup rg-pulumi create
 +  azure:storage:Account storagepulumi create

Resources:
    + 3 to create

Updating (dev):

 +  pulumi:pulumi:Stack quickstart-dev creating
 +  azure:core:ResourceGroup rg-pulumi creating
 +  azure:core:ResourceGroup rg-pulumi created
 +  azure:storage:Account storagepulumi creating
@ updating....
 +  azure:storage:Account storagepulumi created

Outputs:
    connectionString: "DefaultEndpointsProtocol=https;AccountName=storagepulumi516909b7;AccountKey=********************/********************==;EndpointSuffix=core.windows.net"

Resources:
    + 3 created

Duration: 29s

Permalink: file:///C:/Users/fukasawah/quickstart/.pulumi/stacks/dev.json

出来上がったが、実際に作られたリソース名はrg-pulumided110d7storagepulumi516909b7という感じで後ろに8文字のサフィックスがくっつく形となった。これはPulumiが勝手につけているようだ。

storageはともかく、リソースグループは付けたくないと思うので、明示的に指定する方法を探る。

リソース名を明示して変更を反映する

用意されているリソースには大抵nameプロパティがあり、これを明示的に設定すればよいらしい。

// Create an Azure Resource Group
const resourceGroup = new azure.core.ResourceGroup(`rg-${PROJECT_NAME}`, {
    name: `rg-${PROJECT_NAME}`, // この行を追記
    location: "JapanEast",
});

Azureのリソースグループは通常名前変更ができないし、そもそも前のリソースの名前(rg-pulumided110d7)がわからなくなる。

しかしpulumiは前回実行後の状態を(現在は)ローカルで管理している。

今回は、使われなくなったリソースは削除し、再度新規作成する形で置き換わる。(この挙動は「IaCあるある」で既存のデータが吹っ飛ぶので注意が必要。) また、リソースグループに紐づくストレージアカウントも作り直しになっている。(リソースグループの移動は出来るはずだが、Pulumiはそこまで面倒見てないらしい。)

$ pulumi up
Previewing update (dev):

     Type                         Name            Plan        Info
     pulumi:pulumi:Stack          quickstart-dev
 +-   azure:core:ResourceGroup  rg-pulumi       replace     [diff: ~name]
 +-   azure:storage:Account     storagepulumi   replace     [diff: ~location,name,resourceGroupName]

Resources:
    +-2 to replace
    1 unchanged

Do you want to perform this update? yes
Updating (dev):

     Type                         Name            Status       Info
     pulumi:pulumi:Stack          quickstart-dev
 +-   azure:core:ResourceGroup  rg-pulumi       replaced     [diff: ~name]
 +-   azure:storage:Account     storagepulumi   replaced     [diff: ~name,resourceGroupName]

Outputs:
  ~ connectionString: "DefaultEndpointsProtocol=https;AccountName=storagepulumi516909b7;AccountKey=********************/********************==;EndpointSuffix=core.windows.net" => "DefaultEndpointsProtocol=https;AccountName=storagepulumi77efe7b5;AccountKey=********************/********************==;EndpointSuffix=core.windows.net"

Resources:
    +-2 replaced
    1 unchanged

Duration: 1m14s

Permalink: file:///C:/Users/fukasawah/quickstart/.pulumi/stacks/dev.json

ACIでnginxコンテナを立てる

チュートリアルでは、ストレージアカウントを削除し、nginxのACI(Azure Container Instances)を立てる例に書き換えているので習う。

https://pulumi.io/quickstart/azure/modify-program/

import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure";

const PROJECT_NAME = 'pulumi'

// Create an Azure Resource Group
const resourceGroup = new azure.core.ResourceGroup(`rg-${PROJECT_NAME}`, {
    name: `rg-${PROJECT_NAME}`,
    location: "JapanEast",
});

// Create an Azure Container Group
const container = new azure.containerservice.Group("aci-nginx", {
    containers: [{
        name: "nginx",
        image: "nginx",
        memory: 1,
        cpu: 1,
        ports: [{
            port: 80,
            protocol: "TCP"
        }],
    }],
    osType: "Linux",
    resourceGroupName: resourceGroup.name,
    location: resourceGroup.location,
});

// Export the public IP of the container
export const ip = container.ipAddress;

その後、pulumi upすると、ストレージアカウントが消えて、ACIが作成されたことが分かる。

作成後、nginxにアクセスする。接続先のIPは以下のようにして取れる。

curl $(pulumi stack output ip)

pulumi stack output ipipはindex.tsでexportしている変数ipのことで、これを参照することが出来るらしい。便利。

リソースの削除

pulumi destroy

次のステップ

ドキュメントにはgithubのサンプルコードを案内している。

https://pulumi.io/quickstart/azure/next-steps/

https://github.com/pulumi/examples

他のクラウドと比べると少し少ないが、Azureのサンプルがいくつか載っている。書き方の参考になるかもしれない。

感想

TypeScriptとVSCodeの書き味が最高だった。TypeScriptのモジュールシステムを使えるはずなので、共通化もしやすそう。

欠点は後発なのでまだまだプラクティスが少ないこと。公式がサポートに入っていないのでリソースの追加・修正があった時に追従するのが遅いだろう、という所かな。

ただ後者はTemplateDeploymentリソースがあるので、部分的にARM Templateでカバーできそうだ。ちゃんとoutputも扱える。

その他

バックエンドは他にないのか?

ローカルは何かイマイチ(git反映忘れそう)だし、PulumiのWebサーバに依存したくないなぁ、と思っていた。 そこで他のバックエンドを使えないか調べたところ、一応AWS,GCP,Azureをサポートしているようだ。

内部的にはgo-cloudで抽象化しているので、go-cloudのopenBucketのドキュメントを見ると、どのような環境変数が必要かは分かる。

細かいところは「Qiita - Pulumiの状態管理にクラウドストレージバックエンドを使う」に書いた。