PulumiのGet Started with Google Cloudを試してみた
こんにちは!DA(データアナリティクス)事業本部 サービスソリューション部の大高です。
先日、マルチクラウドに対応している Infrastructure as Code のツール「Pulumi」のGet Started with AWSを試してみました。
今回、こちらのエントリと同様にGoogle Cloudも試してみたいと思います。
やりたいこと
以下の公式ドキュメントに記載されている「Get Started with Pulumi」の「Get Started with Google Cloud」に沿って進めていきたいと思います。
こちらでは簡単なリソースの作成から、変更、削除までの流れを試すことができます。
前提条件
実施する環境として、OSはMacOSを利用し、Nodeも事前に導入済みです。
% node -v v16.14.2
また、Google Cloudのアカウントは作成済みで、以下に従って設定済みです。
なお、2022/04/20時点ではGoogle Cloudのパッケージは過渡期になっており、今後は以下がメインになっていくと思われます。(現在はPublic Previewでした)
今回はチュートリアルに従って「Google Cloud (GCP) Classic」を利用します。
Get Started with Pulumi
では、実際にドキュメントに従って始めていきます。
Before You Begin
Install Pulumi
まずはPulumiのインストールです。Homwbrew経由でのインストールなので特に問題ありません。
% brew install pulumi % pulumi version v3.29.1
Install Language Runtime
今回はTypeScript
で試したいと思いますので、Node.jsのインストールが必要となります。
こちらは「前提条件」に記載のとおり事前に導入済みなので詳細はスキップしますが、私はanyenvを利用して導入しています。
% node -v v16.14.2
Configure Pulumi to access your Google Cloud account
このGet Startedでは roles/storage.admin
または roles/storage.legacyBucketOwner
の権限が付与されたIAM Userが必要となります。こちらは「前提条件」に記載のとおり事前に作成済みです。
Pulumiのアカウント作成とトークンの発行
ドキュメントには記載がありませんが、後で利用するので「Pulumiのアカウント作成」と「トークンの発行」がまだの場合、対応を実施しておきます。
下記のページの「Create an account」からアカウントを作成します。
2022/04/18現在では、GitHub、GitLab、Atlassian、Emailのいずれかからアカウント作成が可能でした。
ユーザーを作成したら、下記のアクセストークン発行ページのURLにアクセスし「Create token」からトークンを発行します。
- {USERNAME} - Settings | Pulumi
Create a New Project
では、事前準備ができたのでプロジェクトを作成していきます。
以下のとおりディレクトリを作成して、プロジェクトの初期化を行います。なお、pulumiの初回利用時には初期化時に先程作成したアクセストークンを聞かれるので、入力をして進めます。
% mkdir quickstart && cd quickstart % pulumi new gcp-typescript 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 <ENTER> to log in using your browser : Welcome to Pulumi! Pulumi helps you create, deploy, and manage infrastructure on any cloud using your favorite language. You can get started today with Pulumi at: https://www.pulumi.com/docs/get-started/ Tip of the day: Resources you create with Pulumi are given unique names (a randomly generated suffix) by default. To learn more about auto-naming or customizing resource names see https://www.pulumi.com/docs/intro/concepts/resources/#autonaming.
続けて、プロジェクトの設定を入力していきます。基本的にそのままENTERで進めますが、プロジェクト名は自身のGoogle Cloudのプロジェクト名を指定します。
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: (quickstart) project description: (A minimal Google Cloud TypeScript Pulumi program) Created project 'quickstart' 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' gcp:project: The Google Cloud project to deploy into: foo-bar Saved config
あとはインストールが進むのを待ちます。
Installing dependencies... added 115 packages, and audited 116 packages in 16s 33 packages are looking for funding run `npm fund` for details found 0 vulnerabilities npm notice npm notice New minor version of npm available! 8.5.0 -> 8.7.0 npm notice Changelog: https://github.com/npm/cli/releases/tag/v8.7.0 npm notice Run npm install -g npm@8.7.0 to update! npm notice Finished installing dependencies Your new project is ready to go! ✨ To perform an initial deployment, run 'pulumi up'
完了しました。
Review the New Project
以下の3つのファイルが作成され、yamlファイルは想定どおりの設定になっていることを確認します。
- Pulumi.yaml
- Pulumi.dev.yaml
- index.ts
設定ファイルは以下のようになっていました。
name: quickstart runtime: nodejs description: A minimal Google Cloud TypeScript Pulumi program
config: gcp:project: "foo-bar"
また、リソース構築用のスクリプトは以下のようになっています。
import * as pulumi from "@pulumi/pulumi"; import * as gcp from "@pulumi/gcp"; // Create a GCP resource (Storage Bucket) const bucket = new gcp.storage.Bucket("my-bucket", { location: "US" }); // Export the DNS name of the bucket export const bucketName = bucket.url;
今回は、バケット名とロケーションを変更してみます。
import * as pulumi from "@pulumi/pulumi"; import * as gcp from "@pulumi/gcp"; // Create a GCP resource (Storage Bucket) const bucket = new gcp.storage.Bucket("cm-ootaka-get-started-with-pulumi", { location: "ASIA" }); // Export the DNS name of the bucket export const bucketName = bucket.url;
Deploy the Stack
準備ができたのでデプロイに進みます。
以下のpulumi up
コマンドを実行することで、何が作成されるかを表示されます。
% pulumi up Previewing update (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/previews/5fb5d79e-604b-4eca-8afe-957b82c8f912 Type Name Plan + pulumi:pulumi:Stack quickstart-dev create + └─ gcp:storage:Bucket cm-ootaka-get-started-with-pulumi create Resources: + 2 to create Do you want to perform this update? [Use arrows to move, enter to select, type to filter] yes > no details
問題がなければカーソルをyes
に合わせて続けます。
Do you want to perform this update? yes Updating (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/updates/2 Type Name Status pulumi:pulumi:Stack quickstart-dev + └─ gcp:storage:Bucket cm-ootaka-get-started-with-pulumi created Outputs: + bucketName: "gs://cm-ootaka-get-started-with-pulumi-b23262a" Resources: + 1 created 1 unchanged Duration: 5s
GCSバケットが作成されました!なお、作成したリソースはOutputs
に表示されており、以下のコマンドでも確認できます。
% pulumi stack output bucketName gs://cm-ootaka-get-started-with-pulumi-b23262a
Modify the Program
次に、プログラムを修正して静的サイトホスティングができるようにしていきます。
まずは以下のようなindex.html
ファイルを追加します。
<html> <body> <h1>Hello, Pulumi!</h1> </body> </html>
ファイルを追加したので、プログラムもこのファイルをGCSバケットに配置するように修正します。
import * as pulumi from "@pulumi/pulumi"; import * as gcp from "@pulumi/gcp"; // Create a GCP resource (Storage Bucket) const bucket = new gcp.storage.Bucket("cm-ootaka-get-started-with-pulumi", { location: "ASIA" }); const bucketObject = new gcp.storage.BucketObject("index.html", { bucket: bucket.name, source: new pulumi.asset.FileAsset("index.html") }); // Export the DNS name of the bucket export const bucketName = bucket.url;
Deploy the Changes
では、もう一度デプロイします。
index.htmlをデプロイする
% pulumi up Previewing update (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/previews/5096fe6b-e004-4a06-9a24-1073f3640036 Type Name Plan pulumi:pulumi:Stack quickstart-dev + └─ gcp:storage:BucketObject index.html create Resources: + 1 to create 2 unchanged Do you want to perform this update? [Use arrows to move, enter to select, type to filter] yes > no details
yesを選択して適用します。
Do you want to perform this update? yes Updating (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/updates/3 Type Name Status pulumi:pulumi:Stack quickstart-dev + └─ gcp:storage:BucketObject index.html created Outputs: bucketName: "gs://cm-ootaka-get-started-with-pulumi-b23262a" Resources: + 1 created 2 unchanged Duration: 3s
実際にgsutilコマンドで確認もできます。
% gsutil ls $(pulumi stack output bucketName) gs://cm-ootaka-get-started-with-pulumi-b23262a/index.html-a277eb1
ちゃんとファイルが置かれていますね。
静的サイトホスティングをする
続けて、改めて静的サイトホスティングがされるようにコードを修正していきます。
import * as pulumi from "@pulumi/pulumi"; import * as gcp from "@pulumi/gcp"; // Create a GCP resource (Storage Bucket) const bucket = new gcp.storage.Bucket("cm-ootaka-get-started-with-pulumi", { location: "ASIA", website: { mainPageSuffix: "index.html", }, uniformBucketLevelAccess: true, }); const bucketIAMBinding = new gcp.storage.BucketIAMBinding( "cm-ootaka-get-started-with-pulumi-IAMBinding", { bucket: bucket.name, role: "roles/storage.objectViewer", members: ["allUsers"], } ); const bucketObject = new gcp.storage.BucketObject("index.html", { bucket: bucket.name, contentType: "text/html", source: new pulumi.asset.FileAsset("index.html"), }); // Export the DNS name of the bucket export const bucketName = bucket.url; export const bucketEndpoint = pulumi.concat( "http://storage.googleapis.com/", bucket.name, "/", bucketObject.name );
バケットへのパブリック・アクセスを許可し、index.html
のコンテツタイプ指定を行っています。
また、bucketEndpoint
としてウェブサイトにアクセスするためのエンドポイントURLを設定しています。
この状態で改めてデプロイします。
% pulumi up Previewing update (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/previews/fcfc13e3-e30d-40b7-a0c5-b966cfeeabc2 Type Name Plan Info pulumi:pulumi:Stack quickstart-dev ~ ├─ gcp:storage:Bucket cm-ootaka-get-started-with-pulumi update [diff: +website~uniformBucket + ├─ gcp:storage:BucketIAMBinding cm-ootaka-get-started-with-pulumi-IAMBinding create +- └─ gcp:storage:BucketObject index.html replace [diff: ~contentType] Outputs: + bucketEndpoint: "http://storage.googleapis.com/cm-ootaka-get-started-with-pulumi-b23262a/index.html-990cebf" Resources: + 1 to create ~ 1 to update +-1 to replace 3 changes. 1 unchanged Do you want to perform this update? [Use arrows to move, enter to select, type to filter] yes > no details
yes
を選択します。
Do you want to perform this update? yes Updating (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/updates/4 Type Name Status Info pulumi:pulumi:Stack quickstart-dev **failed** 1 error ~ ├─ gcp:storage:Bucket cm-ootaka-get-started-with-pulumi updated [diff: +website~uniformBucketLevelAccess] + ├─ gcp:storage:BucketIAMBinding cm-ootaka-get-started-with-pulumi-IAMBinding created +- └─ gcp:storage:BucketObject index.html **replacing failed** [diff: ~contentType]; 1 error Diagnostics: pulumi:pulumi:Stack (quickstart-dev): error: update failed gcp:storage:BucketObject (index.html): error: 1 error occurred: * Error when reading or editing Storage Bucket Object "index.html-3dd354f": googleapi: Error 403: xxxxx does not have storage.objects.get access to the Google Cloud Storage object., forbidden Outputs: - bucketName: "gs://cm-ootaka-get-started-with-pulumi-b23262a" Resources: + 1 created ~ 1 updated 2 changes. 1 unchanged Duration: 10s
失敗してしまいました。ストレージ権限を設定していたつもりでしたが不足していたようなので、改めて権限を付与し直してリトライします。
% pulumi up Previewing update (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/previews/9efcbcd4-9175-40d3-8c6c-873f693566ec Type Name Plan Info pulumi:pulumi:Stack quickstart-dev +- └─ gcp:storage:BucketObject index.html replace [diff: ~contentType] Outputs: + bucketEndpoint: "http://storage.googleapis.com/cm-ootaka-get-started-with-pulumi-b23262a/index.html-8d16e39" Resources: +-1 to replace 3 unchanged Do you want to perform this update? yes Updating (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/updates/5 Type Name Status Info pulumi:pulumi:Stack quickstart-dev +- └─ gcp:storage:BucketObject index.html replaced [diff: ~contentType] Outputs: + bucketEndpoint: "http://storage.googleapis.com/cm-ootaka-get-started-with-pulumi-b23262a/index.html-d3b904b" bucketName : "gs://cm-ootaka-get-started-with-pulumi-b23262a" Resources: +-1 replaced 3 unchanged Duration: 3s
今度は成功です!curlコマンドで確認してみましょう。
% curl $(pulumi stack output bucketEndpoint) <html> <body> <h1>Hello, Pulumi!</h1> </body> </html>
想定どおり、ホスティングされました!
Destroy the Stack
最後に後片付けをします。
以下のコマンドで作成したリソースを削除します。
% pulumi destroy Previewing destroy (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/previews/7ee572e9-1c8f-476b-8362-9347ee75f12a Type Name Plan - pulumi:pulumi:Stack quickstart-dev delete - ├─ gcp:storage:BucketIAMBinding cm-ootaka-get-started-with-pulumi-IAMBinding delete - ├─ gcp:storage:BucketObject index.html delete - └─ gcp:storage:Bucket cm-ootaka-get-started-with-pulumi delete Outputs: - bucketEndpoint: "http://storage.googleapis.com/cm-ootaka-get-started-with-pulumi-b23262a/index.html-d3b904b" - bucketName : "gs://cm-ootaka-get-started-with-pulumi-b23262a" Resources: - 4 to delete Do you want to perform this destroy? [Use arrows to move, enter to select, type to filter] yes > no details
yes
を選択します。
Do you want to perform this destroy? yes Destroying (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/updates/6 Type Name Status Info pulumi:pulumi:Stack quickstart-dev **failed** 1 error - ├─ gcp:storage:BucketIAMBinding cm-ootaka-get-started-with-pulumi-IAMBinding deleted - ├─ gcp:storage:BucketObject index.html deleted - └─ gcp:storage:Bucket cm-ootaka-get-started-with-pulumi **deleting failed** 1 error Diagnostics: pulumi:pulumi:Stack (quickstart-dev): error: update failed gcp:storage:Bucket (cm-ootaka-get-started-with-pulumi): error: deleting urn:pulumi:dev::quickstart::gcp:storage/bucket:Bucket::cm-ootaka-get-started-with-pulumi: 1 error occurred: * Error trying to delete bucket cm-ootaka-get-started-with-pulumi-b23262a containing objects without `force_destroy` set to true Resources: - 2 deleted Duration: 8s
また失敗してしまったようです。
エラーメッセージに記載のあるとおり、バケットのforceDestroy
オプションをtrue
に設定します。
import * as pulumi from "@pulumi/pulumi"; import * as gcp from "@pulumi/gcp"; // Create a GCP resource (Storage Bucket) const bucket = new gcp.storage.Bucket("cm-ootaka-get-started-with-pulumi", { location: "ASIA", website: { mainPageSuffix: "index.html", }, uniformBucketLevelAccess: true, forceDestroy: true }); (...snip...)
次に、pulumi up
で適用してからpulumi destroy
を実行しました。
Do you want to perform this destroy? yes Destroying (dev) View Live: https://app.pulumi.com/ootaka-daisuke/quickstart/dev/updates/9 Type Name Status - pulumi:pulumi:Stack quickstart-dev deleted - ├─ gcp:storage:BucketObject index.html deleted - ├─ gcp:storage:BucketIAMBinding cm-ootaka-get-started-with-pulumi-IAMBinding deleted - └─ gcp:storage:Bucket cm-ootaka-get-started-with-pulumi deleted Outputs: - bucketEndpoint: "http://storage.googleapis.com/cm-ootaka-get-started-with-pulumi-b23262a/index.html-147943a" - bucketName : "gs://cm-ootaka-get-started-with-pulumi-b23262a" Resources: - 4 deleted Duration: 10s 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'.
今度は想定どおり削除されましたね!
念の為、以下のコマンドで削除されているか確かめます。
% gsutil ls gs://cm-ootaka-get-started-with-pulumi-b23262a BucketNotFoundException: 404 gs://cm-ootaka-get-started-with-pulumi-b23262a bucket does not exist.
ちゃんと削除されていますね。
まとめ
以上、PulumiのGet Started with Google Cloudを試してみました。
Get Started with AWSと同様に、違和感なく試すことができました。注意点として、S3とは違いGCSのバケットオプションとしてforceDestroy
を設定しておかないとバケット削除時にエラーになるようです。ここは、安全装置の側面もあるので適切なオプション設定が必要ですね。
どなたかのお役に立てば幸いです。それでは!