TerraformからPulumiへの移行方法
序論
先日、Terraform開発元であるHashiCorp社から今後リリースするすべての製品においてライセンスを変更するアナウンスが発表されました。
ライセンス変更による影響はこちらの記事などをご覧頂ければわかりやすいと思います。
このライセンス変更はHashiCorp社と競合する企業に対して競合プロダクト内にコミュニティ版HashiCorp製品を無償で利用できなくなるもので、Terraformなどを利用するユーザー自身には影響はありません。Terraform Cloudと競合するサービスを提供するSpacelift[1]やenv0[2]がこのライセンス変更に対して自身らのプロダクトの今後について述べております。2社ともこのライセンス変更に対して賛成の立場をとり、今後自分たちのプロダクトについてどうするか弁護士と相談すると記載しています。[3][4]
一方で同じプロビジョニングツールとして知られるPulumiへの関心が高まっています。
私が以前書いたPulumiの記事[5]に急にイイねが増えており、閲覧数も急上昇していました。
8/7~8/16の記事表示回数推移
先ほどのライセンス変更の詳細を紹介した記事が公開されて以降日本でもPulumiについて検索する回数が増えているようです。
GoogleTrendsより
しかしすでに多く企業、個人でインフラのコード化を実現する場合ツールにTerraformを採用している人は多いと思います。
Pulumiは他のプロビジョニングツールからの移行を提供する手順について紹介[6]していますので、TerraformからPulumiに移行する方法について紹介していきます。
対象読者
- Pulumiについて知りたい人
- TerraformからPulumiへの移行に興味がある人
Pulumiとは
先にPulumiについて簡単に紹介しますとプログラミング言語でインフラ開発できるプロビジョニングツールとなります。以前私が書いた記事と重複している部分もありますのでよろしければそちらもご覧ください。
Terraformで利用されるHCLのような独自言語とは異なり、TypeScriptやPython、Goなど開発者が慣れ親しんだ言語でインフラをコーディングできます。
[7]
対応言語- Node.js(JavaScript,TypeScript)
- Python
- Go
- .NET(C#,F#,Visual Basic)
- Java
- Pulumi YAML
プログラミング言語以外にもYAMLでも書けますので、プログラミングが不得手なインフラエンジニアでも利用できると思います。
AWSやGoogle Cloud、Azureといった主要なパブリックラウド以外にもDatadogやAuth0などのクラウドSaaSといった100以上ものパッケージを提供しています。
ステート管理方法
プロビジョニングツールで構築された実際のインフラ情報を記録したメタデータのことをステート情報と呼び、ステート情報には秘文情報も含まれるため取り扱いには注意が必要です。Terraformはデフォルトではデプロイを実行したローカルマシン内にステートファイルが生成されます。チームでインフラコード開発する際はステートファイルをAmazon S3やCloud Storageなどの外部ストレージに保存するかTerraform Cloudにおいてチームで管理できるようにします。
Pulumiは標準でPulumi Cloudと呼ばれるステート管理やマネージドなCI/CDを提供してくれるSaaSを提供してくれます。
コンソール画面
他ツールからの移行
冒頭でも紹介したとおりPulumiは他のプロビジョニングツールのコードをPulumiに移行するための手段が公式で紹介されています。
以前はtf2pulumiと呼ばれるコンバーターツールを公式が提供していましたが、2023年6月に新たにpulumi convert
サブコマンドの実装を発表しました。[8]
このコマンドは2023年8月現在でTypeScript、Python、Go、C#に対応しています。pulumi convert
を使ってTerraformで書いたコードをPulumiに移行してます。
サンプルコード
サンプル用にS3バケット作成するシンプルなTerraformコードを用意しました。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
random = {
source = "hashicorp/random"
version = "3.5.1"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "ap-northeast-1"
}
provider "random" {}
# 一意のバケット名となるSuffix
resource "random_string" "s3_bucket_name" {
length = 8
special = false
upper = false
}
# Create S3
resource "aws_s3_bucket" "migration_S3" {
bucket = "yuta-${random_string.s3_bucket_name.id}"
}
terraform apply
コマンドでデプロイするとS3が作成されます。
このTerraformファイルが配置されているディレクトリ上でPulumiのコンバーターコマンドを実行します。
pulumi convert --from terraform --language typescript
上記コマンドを実行するとディレクトリ直下にPulumiで使われるプロジェクト一式が生成されます。
$ tree -L 1
.
├── Pulumi.yaml
├── index.ts
├── main.tf
├── node_modules
├── package-lock.json
├── package.json
├── terraform.tfstate
└── tsconfig.json
1 directory, 9 files
Terraform関係のファイルと一緒の場所に置きたくない場合、--out <ディレクトリ名>
で違う場所にプロジェクトが生成されます。
$ pulumi convert --from terraform --language typescript --out typescript-convert
Converting from terraform...
Converting to nodejs...
Installing dependencies...
added 215 packages, and audited 216 packages in 45s
68 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Finished installing dependencies
$ ls .\typescript-convert\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2023/08/16 22:38 node_modules
-a--- 2023/08/16 22:38 20 .gitignore
-a--- 2023/08/16 22:38 366 index.ts
-a--- 2023/08/16 22:38 83688 package-lock.json
-a--- 2023/08/16 22:38 203 package.json
-a--- 2023/08/16 22:38 75 Pulumi.yaml
-a--- 2023/08/16 22:38 354 tsconfig.json
index.ts
ファイルにインフラの構成情報が記載されます。
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as random from "@pulumi/random";
const s3BucketName = new random.RandomString("s3BucketName", {
length: 8,
special: false,
upper: false,
});
// Create S3
const migrationS3 = new aws.s3.BucketV2("migrationS3", {bucket: pulumi.interpolate`yuta-${s3BucketName.id}`});
せっかくなので他の言語でS3を作成する構成情報ファイルをまとめました。本題とはそれるのでトグルで丸めてますが興味ありましたらご覧ください。
おまけ
Python
import pulumi
import pulumi_aws as aws
import pulumi_random as random
s3_bucket_name = random.RandomString("s3BucketName",
length=8,
special=False,
upper=False)
# Create S3
migration_s3 = aws.s3.BucketV2("migrationS3", bucket=s3_bucket_name.id.apply(lambda id: f"yuta-{id}"))
Go
package main
import (
"fmt"
"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/s3"
"github.com/pulumi/pulumi-random/sdk/v4/go/random"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
s3BucketName, err := random.NewRandomString(ctx, "s3BucketName", &random.RandomStringArgs{
Length: pulumi.Int(8),
Special: pulumi.Bool(false),
Upper: pulumi.Bool(false),
})
if err != nil {
return err
}
_, err = s3.NewBucketV2(ctx, "migrationS3", &s3.BucketV2Args{
Bucket: s3BucketName.ID().ApplyT(func(id string) (string, error) {
return fmt.Sprintf("yuta-%v", id), nil
}).(pulumi.StringOutput),
})
if err != nil {
return err
}
return nil
})
}
C#
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
using Random = Pulumi.Random;
return await Deployment.RunAsync(() =>
{
var s3BucketName = new Random.RandomString("s3BucketName", new()
{
Length = 8,
Special = false,
Upper = false,
});
// Create S3
var migrationS3 = new Aws.S3.BucketV2("migrationS3", new()
{
Bucket = s3BucketName.Id.Apply(id => $"yuta-{id}"),
});
});
pulumi up
コマンドでS3を作成します。
$ pulumi up
Please choose a stack, or create a new one: <create a new stack>
Please enter your desired stack name.
To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`): Yuhta28/migration-terraform
Created stack 'migration-terraform'
Previewing update (migration-terraform)
Type Name Plan
+ pulumi:pulumi:Stack temp-migration-terraform create
+ ├─ random:index:RandomString s3BucketName create
+ └─ aws:s3:BucketV2 migrationS3 create
Resources:
+ 3 to create
Do you want to perform this update? yes
Updating (migration-terraform)
Type Name Status
+ pulumi:pulumi:Stack temp-migration-terraform created (1s)
+ ├─ random:index:RandomString s3BucketName created (0.23s)
+ └─ aws:s3:BucketV2 migrationS3 created (1s)
Resources:
+ 3 created
Duration: 8s
デプロイが完了するとS3が新規に作成されます。
既存リソースの移行
生成されたコードをデプロイすると新規にリソースが作成されます。ただ実際はTerraformで作成した既存リソースをPulumiに移したいと考える人も多いと思います。
Pulumiにはステートファイルに保存されている既存リソース情報をPulumiのステートファイルにインポートする手順が用意されています。
まず始めにTerraformのステートファイルをローカルに用意します。外部ストレージに保存している場合terraform state pull
コマンドでローカルにステートファイルをダウンロードできます。
下記のimport.ts
ファイルをステートファイルが存在するディレクトリにコピーします。
index.ts
ファイルの先頭に次のコードを追加します。
import "./import"
次にTerraformのステート情報をimport.ts
のimportFromStatefile
変数にセットすることで既存リソースをインポートできます。
$ pulumi config set importFromStatefile ./terraform.tfstate
pulumi up
コマンドでインポートしたリソースをPulumiで管理できるようになります。
と、ドキュメントにはそう書いていますが実際のところ以下のようなエラーが発生し、インポートができませんでした。
エラー内容を確認するとステートファイルのバージョンが3でないため失敗したと出ています。現在のTerraformのステートファイルはバージョン4ですが、バージョン3はTerraformのバージョンが0.13以下のもので現在のバージョンとは互換性の保証がありません。ドキュメントで参照しているimport.ts
はアーカイブされたリポジトリにあるものでメンテナンスされていないファイルです。
個人的にこれはドキュメントの記載ミスではないかと思いましたのでissueを立てて問い合わせている最中です。
続報がありましたら更新いたします。
2023/8/24追記
回答がありましてどうやらこの方法はもうサポートしないということでした。ドキュメントも更新されまして上記のimport.ts
ファイルへの導線もドキュメントからなくなりました。
This is the same issue that had been tracked in pulumi/tf2pulumi#241. However, we've since moved to a new model for conversion from Terraform as part of https://www.pulumi.com/blog/converting-full-terraform-programs-to-pulumi/. That new model does not yet have a replacement for the state conversion. We are tracking adding that in pulumi/pulumi#5953 (comment) and pulumi/pulumi-converter-terraform#23.
In the meantime, we will want to update the docs here to not point at the archived (and not currently working for v4 state) import scripts.
PulumiにはTerraformと同じようにpulumi import
コマンドも提供されており、このコマンドを実行すれば既存リソースのインポートができます。
Terraformで作成したS3をpulumi import
でPulumi配下にインポートしてみます。
pulumi import手順
インポート対象S3
pulumi import
コマンドを実行する場合最初にスタックを作成する必要があります。
スタックはpulumi stack init <スタック名>
で作成できます。
$ pulumi stack init import-s3
Created stack 'import-s3'
スタックを作成したディレクトリ上でインポートしたいリソースのタイプ、リソース名、任意のPulumi IDを引数に指定します。先ほどのS3をインポートしたい場合以下のコマンドを実行します。
$ pulumi import aws:s3/bucket:Bucket import-s3 yuta-fmajk8l4
Previewing import (import-s3)
Type Name Plan
+ pulumi:pulumi:Stack temp-import-s3 create
= └─ aws:s3:Bucket import-s3 import
Resources:
+ 1 to create
= 1 to import
2 changes
Do you want to perform this import? yes
Importing (import-s3)
Type Name Status
+ pulumi:pulumi:Stack temp-import-s3 created (3s)
= └─ aws:s3:Bucket import-s3 imported (1s)
Resources:
+ 1 created
= 1 imported
2 changes
Duration: 4s
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.
インポートが成功しますとターミナル上にPulumiのソースコードが表示されます。
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const imoprt_s3 = new aws.s3.Bucket("import-s3", {
arn: "arn:aws:s3:::yuta-fmajk8l4",
bucket: "yuta-fmajk8l4",
hostedZoneId: "Z2M4EHUR26P7ZW",
requestPayer: "BucketOwner",
serverSideEncryptionConfiguration: {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: "AES256",
},
},
},
}, {
protect: true,
});
Pulumi Cloud上にも作成したスタックとインポートしたリソース情報が記録されています。
ただTerraformでのインポート作業を経験した人ならご存じだと思いますがこのインポートコマンドは1リソース毎にコマンド実行しなければならないため手間がかかります。別ツールからの移行は労力のかかる作業なのでPulumi社も移行支援するサービスを提供しているようです。
さすがに日本語対応はしていないと思いますが、もし興味ありましたら問い合わせた感想などお待ちしております。
所感
TerraformからPulumiへの移行方法について説明しました。
Pulumiは機能開発が活発で昨年あたりから多くの機能アップデートが発表されています。Terraformのライセンス変更自体はユーザーに影響はないものと考えていますが、Pulumiも面白いツールだと思いますのでぜひとも手に取ってみてください。
LT資料
LTのスライド資料です。
参考文献
-
https://www.pulumi.com/docs/using-pulumi/adopting-pulumi/migrating-to-pulumi/ ↩︎
-
これにともないtf2pulumiの使用は非推奨になりました ↩︎
Discussion