目の前に僕らの道がある

勉強会とか、技術的にはまったことのメモ

Terraform使いがPulumiに入門しました

この記事は3-shake Advent Calendar 2023の16日目の記事です。

qiita.com

この内容はSRETT #8で発表した内容に補足しています。

3-shake.connpass.com

前提

筆者は以下の背景を持っています。

  • 普段はAWSをメインに触っている
  • 普段はTerraformをメインで使ってる
  • Pulumiはプロダクションでは使ったことがない
    • ちゃんとは把握できてない

語らないこと

以下のようなPulumi以外の基本的なことは語りません

  • IaCとは
    • 概要、特徴、メリット・デメリット
  • Terraformとは
    • 概要、特徴、メリット・デメリット、操作方法

モチベーション

なんでPulumiを今回調べようかと思った動機について書こうと思います。

Terraformの記述力に限界を感じていたというところが大きいです。以下の点がつらいかなと思っていたところです。

  • 足りない関数
  • 二重ループのためのModule使用
  • 分岐処理のためのcountと三項演算子

とはいえ、記述力が低いからこそ複雑なことを抑制できて可読性が上がっている面もあると思います。 冗長でも、可読性が高いというのはメリットではあります。

他の選択肢としては以下のものがあるかと思います。

  • CDK
    • AWSに限定される
  • CDKTF(CDK for Terraform)
    • 結局terraformのJSONコードに変換されるので、terraformに依存します
    • それ自体は悪くないが、どうせならTerraformから離れたものを学びたい

そこでなにか良いものがないかと思い当たったところにPulumiがあったので調べてみようとなりました。

Pulumiとは

Pulumiはプログラミング言語でインフラを構築可能なプロビジョニングツールです。

Terraformと同じようにProviderを通して複数のクラウドに対応しています。

TerraformはHCLという宣言的言語を使用するのに対し、Pulumiは汎用的なプログラミング言語を使用してインフラリソースを定義します。

Pulumi - Infrastructure as Code in Any Programming Language

対応言語

参考: Pulumi Languages & SDKs | Pulumi Docs

Pulumiのアーキテクチャ

以下のようの構成になっています。

参考: How Pulumi Works | Pulumi Docs

  • Language host
    • インフラリソースの定義を Program (後述)として好きな言語で定義します。
  • Deployment Engine
    • 希望する状態に変更するための操作セットを実行する役割を果たします。
  • Resource Provider
    • クラウドサービスとの通信を処理して、Programで定義したリソースの変更処理を行います。

上記の例だと、Programにリソースの定義がある場合、Stateと比較して、管理されているリソースであるかを確認します。

存在すれば、プロバイダーを通して実際のクラウドのリソースの状態と比較して差分があれば適用。

存在しない場合、プロバイダーを通してリソースを作成。

Pulumiのコンポーネント

What is Pulumi? | Pulumi Docs

Pulumiのコンポーネントは以下のようになっています。

  • Project
  • Program
    • インフラのあるべき姿を定義したもの
  • Resource
    • インフラを構成するオブジェクト。ResourceのプロバティはOutputとして他のResourceのInputに使用することができます
  • Stack
    • Programを実行すると作成されるインスタンス。同一のProgramから開発、ステージング、本番環境のStackを個別に作成することができます。

Pulumi Cloud

Terraform Cloudのようなものと考えていただいて良いです。 デプロイの状態、履歴やシークレットを管理して、CI/CDやGitHubと連携してデプロイを実行することもできます。 Pulumi CLIはバックエンドを明示的に指定しない限りはでデフォルトでPulumi Cloudを使用します。

Terraformはデフォルトでlocalバックエンドを使用します。

以下はPulumi Cloudの画面です。

Pulumi Cloud 料金

個人で使う限りは無料で使用することができます。

※2023/12/18現在

Pulumi操作方法

ここからPulumiの操作方法を見て行きたいと思います

Pulumiインストール

個人的にはバージョン管理したいのでasdfでインストールします。brewでもインストールできます。

# .tool-versions
pulumi 3.97.0 
asdf install

Pulumi Cloudへログイン

デフォルトではPulumi Cloudへログインします。以下のコマンドを実行するとブラウザが起動するので、ログイン処理をします。

pulumi login

Pulumi Cloudを使わず、ローカルにstateを保存したい場合は以下のとおりです。

pulumi logout
pulumi loign --local

Projectの作成

pulumi new コマンドで新しいProjectを作成できます。同時にStackも作成されます。引数にテンプレートを指定できます。 ウィザード形式で設定をすることができます。

以下の例は awsプロバイダーを使用して、言語はTypeScriptを使用するテンプレートとなります。

ディレクトリ内にはPulumi実行に必要な各種ファイルが生成されます。 ここで見るべきは以下の3ファイルです。

  • Pulumi.yaml
    • プロジェクト設定
  • Pulumi.dev.yaml
    • Stack(dev)設定
  • index.ts
    • リソース定義
# Pulumi.yaml
name: sample
runtime: nodejs
description: A minimal AWS TypeScript Pulumi program
# Pulumi.dev.yaml
config:
aws:region: us-east-1
// index.ts
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

// Create an AWS resource (S3 Bucket)
const bucket = new aws.s3.Bucket("my-bucket");

// Export the name of the bucket
export const bucketName = bucket.id;

変更を確認

plumi preview コマンドでStackの変更差分を確認できます。 terraform plan を似ていますが、こちらは差分の詳細は表示されません。

Stackデプロイ

pulumi up コマンドでStackをデプロイできます。 terraform planterraform apply を組み合わせた挙動になります。

実行すると選択肢が出ます。

details を選択すると変更差分の詳細が表示されます。

yesを選択すると、変更が適用されます。

リソース削除

pulumi destroy でStackを削除できます。 pulumi up と同じようにdetailsで詳細表示、 yes で削除実行ができます

state操作

PulumiではStackごとにStateが保存されています。 Stateを操作するコマンドは以下のとおりです。

  • state出力(terraform state pull 相当 )
    • pulumi stack export
  • state インポート(terraform import相当)
    • pululmi import <TYPE> <NAME> <ID>
  • state 削除(terraform state rm 相当)
    • pulumi state delete <URN>

Terraformからの移行

Terraformからの移行オプションは以下の通りとなります。

  1. terraformとPulumiを共存する
    • Pulumiからtfstateを参照する
  2. tfstateからリソースをPulumiへインポートする
  3. TerraformのコードをPulumiのコードに変換する

参考: Adopting Pulumi | Pulumi Docs

参考: Migrating from Terraform | Pulumi Docs

TerraformとPulumiを共存する(tfstateを参照)

networkリソースに関しては既存のterraformを使いつつ、そのoutputをPulumiで使うイメージになります。 以下のようなコードでlocalのtfstateが参照できるので、値を参照して利用することができます。

import * as aws from "@pulumi/aws";
import * as terraform from "@pulumi/terraform";

// Reference the Terraform state file:
const networkState = new terraform.state.RemoteStateReference("network", {
    backendType: "local",
    path: "/path/to/terraform.tfstate",
});

// Read the VPC and subnet IDs into variables:
const vpcId = networkState.getOutput("vpc_id");
const publicSubnetIds = networkState.getOutput("public_subnet_ids");

// Now spin up servers in the first two subnets:
for (let i = 0; i < 2; i++) {
    new aws.ec2.Instance(`instance-${i}`, {
        ami: "ami-7172b611",
        instanceType: "t2.medium",
        subnetId: publicSubnetIds[i],
    });
}

tfstateからインポート

pulumi import --from terraform ./terraform.tfstate のようにすることによってtfstateからリソースをインポートすることができます。

terraformからコード変換

pulumi convert --from terraform コマンドを使用することで、既存のTerraformのコードをPulumiのコードに変換することができます。

ただし、変換できないコードはTODOコメントが付く。90%~95%は変換が対応しているとのこと。

pulumi convert --from terraform --language typescript

まとめ

Pulumiの概要と基本操作をTerraformと対比しながら説明してきました。

  • 新規プロジェクトである程度複雑な処理をしたい。
  • プログラミング言語に精通している人がメンバーにいる。

そういった場合にはPulumiは良さそうに思えます。

しかしながら、ある程度Terraformで出来上がっているプロジェクトをPulumiに移行するのはそれなりに大変なので、プロジェクトの規模感とコストに見合うかを考えて導入するか考えると良いでしょう。

また、複雑なことをしたいというのは、本当に必要とされていることなのでしょうか?冗長でも簡易的な書き方をした方が望ましい場合もあるかと思います。そのあたりの目利きをちゃんと考えたいところです。

自分自身まだまだ使いこなせていないですし、追いきれてないPulumiのトピックもあるので、今後も選択肢の一つとして調べていきたいところです。