こんばんはやみん、クラウドエースの光の戦士です。
Pulumiが注目を浴びていますが、TerraformとPulumiを両方使ったことのある立場から比較をします。
Terraform歴は 約170個のリソースを定義したプロジェクトを3つ、
Pulumi歴は約20個のリソースを定義したプロジェクト1つです。
Pulumiについては特に歴が浅いため、その記述については正確さを欠く場合があります。
この記事内の全ての記述は、Google Cloud Platform(GCP)というパブリッククラウドサービスでの使用経験に基づくものです。
目次
Stateファイルの所在について
両者に良いところがあります。
Terraform
Terraformの場合は、Terraformによってapply または import された状態を任意のファイルに保存することができます。このファイルはローカルマシンのファイルシステムに保存しても良いですし、
以下のように記述をしてGoogleCloudStorageのようなクラウドストレージに保存しても良いです。
terraform {
backend "gcs" {
bucket = "ca-tominaga-test"
prefix = "terraform/state"
}
}
ある程度複雑なTerraformの定義を書いたり、 terraform import
によって大量のリソースをimportし始めると、Stateファイルを編集する裏技を実行するTerraformersの方もいると思いますが、そのような小細工が通用する点で、TerraformのStateファイルが目に見えるファイルとして存在していることは有益になり得ます。
Pulumi
一方で、PulumiのStateはPulumi Cloud Consoleという場所で管理されます。
画面イメージはこのようなものです。
Recent Activity
の詳細を見にいくと、その変更によりどのリソースにどのような影響があったのかを確認することができます。
この例では、BigQueryにDatasetとTableが作成されたことを確認できます。
Terraformの場合でもクラウドストレージサービスのファイルバージョニング機能を用いて差分を確認することは不可能ではありませんが、Pulumiの方が見通しが良さそうです。
(3/22)追記
PulumiのStateファイルもローカルストレージに保存することができました。
ただしクラウドのストレージには依然として執筆時点では対応していませんが、PRがありました。
https://github.com/pulumi/pulumi/pull/2455
こちらのPRがmergeされれば、terraformと同様にStateを管理することができるようになるでしょう。
料金について
チームでInfrastructure as Code
を推進しようとするのであれば、チーム全員でStateを管理する必要があります。
State管理については、料金の面から両者には明確な差異があります。
Terraform
TerraformのStateをチームで管理する手法は、クラウドストレージへStateファイルを保存するような方法が考えられます。これは非常に安価に済みます。
Terraform Enterpriseを使用する方法も考えられますが、こちらについては明確な料金プランが記載されていませんでした。
Terraform Enterpriseの機能を調べてみると、PulumiのようなState管理も可能になりそうですので、Terraformを利用しながらPulumiの良いところも取れるでしょう。
Pulumi
一方で、Pulumiについてはチームでの運用を考えると料金を考慮する必要があります。
個人でStateを管理する場合には無料で使用することのできるPulumiですが、チームでの運用やGitHub, Slackとの連携には公式記載の料金がかかります。
有料プランのPulumiは、GitHub や GitLabなどのOrganizationと連携することができて、レポジトリとそのレポジトリ内におけるnamespaceのようなものである Stack
という単位でStateを管理することができます。
既存の組織リソースと連携することができるのは非常にありがたいことですが、組織メンバー1人につき、年間契約で $50/user/month
の金額が必要になります。
GSuite Enterpriseが $25/user/month
であることを考えると、会社組織に属する1人の技術者として見ても高く感じます。
依存関係の解決について
例えば、GCPのサービスアカウントを作成し、そのサービスアカウントに対してProject Viewerの役割を紐付ける場合を考えて両者を比較します。
この例のポイントは、役割を紐付けるためにはサービスアカウントが作成されている必要がある、という依存関係です。
Terraform
resource "google_service_account" "warrior-of-light" {
account_id = "warrior-of-light"
display_name = "漆黒のヴィランズ楽しみ!!!"
}
resource "google_project_iam_member" "warrior-of-light" {
role = "roles/viewer"
member = "serviceAccount:${google_service_account.warrior-of-light.email}"
}
Terraformが非常に優れているところは、このような依存関係が存在する場合のほとんどで、自動的にその依存関係を解決してリソースを作成してくれるところにあると思っています。
上記の記述は何も問題がなく terraform apply
することができます。
Pulumi
TerraformのノリでPulumiのコードを書くと、こんな感じでしょうか。
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
// Create ServiceAccount named `warrior-of-light`
const serviceAccountConfig: gcp.serviceAccount.AccountArgs = {
accountId: "warrior-of-light",
};
const serviceAccount = new gcp.serviceAccount.Account(
"warrior-of-light",serviceAccountConfig);
// Attach IAM role to warrior-of-light service account
const iamMemberConfig: gcp.projects.IAMMemberArgs = {
member: `serviceAccount:${serviceAccount.email}`,
role: "roles/viewer",
};
const iamMember = new gcp.projects.IAMMember(
"warrior-of-light", iamMemberConfig);
このファイルを pulumi up
(≒ terraform apply)した結果は以下のようにエラーになります。
$ pulumi up
Previewing update (dev):
Type Name Plan
+ pulumi:pulumi:Stack ca-tominaga-test-dev create
+ ├─ gcp:projects:IAMMember warrior-of-light create
+ └─ gcp:serviceAccount:Account warrior-of-light create
Resources:
+ 3 to create
Do you want to perform this update? yes
Updating (dev):
Type Name Status Info
+ pulumi:pulumi:Stack ca-tominaga-test-dev created
+ ├─ gcp:serviceAccount:Account warrior-of-light created
+ └─ gcp:projects:IAMMember warrior-of-light **creating failed** 1 error
Pulumiは依存関係を勝手に解決してくれません。
リソース同士が依存関係を持つ場合には、以下のように記述する必要があります。
(一部省略)
...
const serviceAccount = new gcp.serviceAccount.Account(
"warrior-of-light",serviceAccountConfig);
// Attach IAM role to warrior-of-light service account
serviceAccount.email.apply(email => {
const iamMemberConfig: gcp.projects.IAMMemberArgs = {
member: `serviceAccount:${email}`,
role: "roles/viewer",
};
new gcp.projects.IAMMember("warrior-of-light", iamMemberConfig);
});
JavaScriptやTypeScriptでプログラムを書いたことのある方であれば、非同期処理の解決をしている想像ができると思います。
このPulumiのプログラミングモデルは、Pulumiの公式ドキュメントに詳細な記述があります。
この性質がデメリットであると書いているわけではありません。
依存関係の明示的な解決はメリットになる場面があると考えていますが、プログラミングの経験が浅い方にとっては、Terraformに比べて理解し難い可能性があると考えています。
既存リソースのimportについて
既にマニュアル操作により作成されたプロジェクトをInfrastructure as Code
として表現したい、という場面は多いと思います。
Terraform
Terraformの場合は terraform import
という便利な機能があり、AWSの場合はプロジェクト(というのかはわかりませんが)内のリソースを全てチェックしてTerraformの設定ファイル化までしてくれるそうです。めちゃくちゃ羨ましい
GCPの場合はそこまで便利なわけではありませんが、起動済みのリソースの定義をStateファイルに記述してくれて、それを元に設定ファイルを書くことができてとても便利です。
Pulumi
一方Pulumiには、記事執筆時点では terraform import
に該当する機能はありません。
余談ですが、GCP には Cloud Asset Inventory というものが存在し、プロジェクト内の全てのリソースをJSON形式でクラウドストレージへ出力するAPIがあります。
もしPulumiを使って既存のプロジェクト内リソースをコード化しようと思ったら必ず役に立つでしょう。
静的型付けの力について
最後に、これはTerraformとPulumiの差異というよりは、両者が採用している言語の差異になります。
Terraform
TerraformはHCLを利用して記述をします。
yamlに比べて直感的でわかりやすく、Terraformが習得しやすい技術である理由の1つであると思っています。
Pulumi
Pulumiを記述することのできる言語は JavaScript
, Python
, Go
, TypeScript
と豊富ですが、ここではTypeScript
(TS)について書きます。
TSは静的型付けができる言語です。この事実は、Infrastructure as Code
を実現する際に非常に大きな力になります。
私の場合、Terraformで記述する際には、Terraformのドキュメントとにらめっこしながら、どんなKeyでどのようなValueを記述すればいいのかを探す時間がとても長いです。
覚えてしまえば大した手間ではないのだと思いますが、なかなかそのレベルに到達するのは大変です。
PulumiをTSで書いた場合、Pulumiの型定義ファイルを利用することができます。型定義ファイルというのは、TSの世界でクラスやメソッドなどが持つ引数とその型などが定義されているファイルです。
下記の画像の例では、 gcp.projects.IAMMember
のコンストラクタとして、string型のname
と、
IAMMemberArgs型のargs
が必須であることがわかります。
そして、 IAMMemberArgs
にどのような値を渡すべきなのかも、すべて右側の型定義ファイルに書いてあります。
このことは、Infrastructure as Code
としてコードを書く際に非常に大きな力になります。
所感
以上のような差異があると理解したうえで、私はPulumiの方が好みです。
これはPulumiを使うべきと言っているわけではなく、私がAngularというWebアプリケーションフレームワーク、及びその開発言語であるTypeScriptを好んでいるという事実がそうさせています。
Pulumiの可能性は、現代でエンジニアに以下の区別が当てはまるのかどうかは無視するとして、
“アプリケーションエンジニア”と呼ばれる人たちが、”インフラエンジニア”と呼ばれる人たちが主に行っていたであろうSREの一貫である、Infrastracure as Codeを普段自分たちが書きまくっているプログラミング言語で記述できる
という点にあると思います。
HCLやyamlが特別難しいわけではありませんが、慣れ親しんだプログラミング言語と慣れ親しんだツールで記述できるというのは、明確なメリットになり得るでしょう。
Pulumiについて問題を述べるならば、プログラミング言語で記述できるが故に、記述者によってコードの書き方に大きな違いが出そうなところです。
KubernetesClusterというinterfaceを定義して、KubernetesClusterを実装する GKEClusterクラスやEKSClusterクラスを書くと依存性を注入できて…
みたいなことを考え始めてしまうのが悩みです。