pulumi を試してみた

pulumi

terraform と似たような IaC ツールの一種。terraform と異なる最大の特徴は javascript, python など一般的なプログラミング言語で記述できる点。 これによって IDE サポート、言語の拡張性、モジュール化、変数定義 などの恩恵を最大限に受けている。

https://github.com/pulumi/pulumi

パット見て他の記事も参照した感じだと、state 管理と、サポートしているプロバイダーの種類を克服すれば十分 terraform を刺せそう。

install

$ curl -fsSL https://get.pulumi.com/ | sh
=== Installing Pulumi v3.9.1 ===
+ Downloading https://get.pulumi.com/releases/sdk/pulumi-v3.9.1-darwin-x64.tar.gz...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 61.6M  100 61.6M    0     0  14.8M      0  0:00:04  0:00:04 --:--:-- 14.8M
+ Extracting to /Users/k-jun/.pulumi/bin
+ Adding $HOME/.pulumi/bin to $PATH in /Users/k-jun/.zshrc

=== Pulumi is now installed! 🍹 ===
+ Please restart your shell or add /Users/k-jun/.pulumi/bin to your $PATH
+ Get started with Pulumi: https://www.pulumi.com/docs/quickstart

~/ghq/github.com/k-jun/playground-aws master 6s
$ which pulumi
/Users/k-jun/.pulumi/bin/pulumi

setup

pulumi new コマンドを実行すると諸々の設定がインタラクティブに走る。pulumi にログインさせられた際には、思わず嫌悪感が走ったがまだ大丈夫...。

$ pulumi new
...
...
Please choose a template: aws-typescript               A minimal AWS TypeScript Pulumi program
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: (pulumi-demo)
project description: (A minimal AWS TypeScript Pulumi program)
Created project 'pulumi-demo'

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'

aws:region: The AWS region to deploy into: (us-east-1)
Saved config

Installing dependencies...


added 122 packages, and audited 123 packages in 19s

28 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
npm notice
npm notice New minor version of npm available! 7.19.1 -> 7.20.5
npm notice Changelog: https://github.com/npm/cli/releases/tag/v7.20.5
npm notice Run npm install -g npm@7.20.5 to update!
npm notice
Finished installing dependencies

Your new project is ready to go! ✨

To perform an initial deployment, run 'pulumi up'

こういうファイルが生成されている。これを変更することでリソースを操作する。

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;

差分の詳細と確認がインタラクティブに行われ、変更を適用する。terraform の差分が全部かってに出力されて、これでいい...? と比べると雲泥の差。 こっちのほうがずっといい。そもそも terraform の差分は分かりづらい。

$ pulumi up
...
Do you want to perform this update? details
+ pulumi:pulumi:Stack: (create)
    [urn=urn:pulumi:dev::pulumi-demo::pulumi:pulumi:Stack::pulumi-demo-dev]
    + aws:s3/bucket:Bucket: (create)
        [urn=urn:pulumi:dev::pulumi-demo::aws:s3/bucket:Bucket::my-bucket]
        [provider=urn:pulumi:dev::pulumi-demo::pulumi:providers:aws::default_4_14_0::04da6b54-80e4-46f7-96ec-b56ff0331ba9]
        acl         : "private"
        bucket      : "my-bucket-a7424d4"
        forceDestroy: false

Do you want to perform this update? yes
...

run

ひとまず以下の箇所が気になったので、これを見ていく。

  • ec2 の作成
  • state 管理 & 競合防止
  • github との連携
  • changeIgnoreの設定

ec2 instance を作成するところまで。いずれ Terraform を超える気がする...。 以下の点が Terraform と比べて圧倒的に勝っている。

  • Type Script で記述できるのがめちゃくちゃ良い。型がそのまま ドキュメントになるので全てコードで完結する。
  • 言語としての柔軟性の高さ。モジュール化、変数化、繰り返しなど煩雑な問題が全て "プログラミング言語" で記述するという一撃で解決している。。圧倒的。
  • state を意識せずとも使える勝手の良さ。console に全てかってに格納してくれる。何なら管理しているリソースの視覚化も勝手にやってくれる。
  • Github との相性の良さ。pulumi を実行した際に、勝手に github の head を読み取ってリンクしてくれる。

が、差分検知は少し怪しいかもしれない。作成した インスタンスの Tag 差分は流石に pulumi で検知してくれなかった..。

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

const pulumi_vpc = new aws.ec2.Vpc("pulumi_vpc", {
  cidrBlock: "10.1.0.0/16",
  enableDnsSupport: true,
  enableDnsHostnames: true,
  tags: { "Name": "pulumi-vpc" }
})

const pulumi_subnet_private = new aws.ec2.Subnet("pulumi_subnet_private", {
  vpcId: pulumi_vpc.id,
  cidrBlock: "10.1.1.0/24",
  mapPublicIpOnLaunch: false,
  tags: { "Name": "pulumi-subnet-private" }
})

const pulumi_subnet_public = new aws.ec2.Subnet("pulumi_subnet_public", {
  vpcId: pulumi_vpc.id,
  cidrBlock: "10.1.101.0/24",
  mapPublicIpOnLaunch: true,
  tags: { "Name": "pulumi-subnet-public" }
})

const pulumi_internet_gateway = new aws.ec2.InternetGateway("pulumi_internet_gateway", {
  vpcId: pulumi_vpc.id,
  tags: { "Name": "pulumi-internet-gateway" }
})

const pulumi_route_table = new aws.ec2.RouteTable("pulumi_route_table", {
  vpcId: pulumi_vpc.id,
  routes: [
    {
      cidrBlock: "0.0.0.0/0",
      gatewayId: pulumi_internet_gateway.id,
    },
  ],
  tags: { "Name": "pulumi-route-table" }
})

const pulumi_security_group = new aws.ec2.SecurityGroup("pulumi_security_group", {
  vpcId: pulumi_vpc.id,
  ingress: [
    {
      fromPort: 22,
      toPort: 22,
      protocol: "tcp",
      cidrBlocks: ["0.0.0.0/0"],
    },
    {
      fromPort: 80,
      toPort: 80,
      protocol: "tcp",
      cidrBlocks: ["0.0.0.0/0"],
      self: true,
    },
    {
      fromPort: 443,
      toPort: 443,
      protocol: "tcp",
      cidrBlocks: ["0.0.0.0/0"],
      self: true,
    },
  ],
})

const ubuntu1804 = "ami-0cfa3caed4b487e77"

const pulumi_ec2 = new aws.ec2.Instance("pulumi_ec2", {
  subnetId: pulumi_subnet_private.id,
  ami: ubuntu1804,
  instanceType: aws.ec2.InstanceType.C5_Large,
  keyName: "k-jun",
  vpcSecurityGroupIds: [pulumi_security_group.id],
  tags: { "Name": "pulumi-ec2" }
})

差分の無視も大丈夫そう。きちんと差分として表示されなくなった。

const pulumi_security_group = new aws.ec2.SecurityGroup("pulumi_security_group", {
  vpcId: pulumi_vpc.id,
  ingress: [
    {
      fromPort: 22,
      toPort: 22,
      protocol: "tcp",
      cidrBlocks: ["0.0.0.0/0"],
    },
    {
      fromPort: 80,
      toPort: 80,
      protocol: "tcp",
      cidrBlocks: ["0.0.0.0/0"],
      self: true,
    },
    {
      fromPort: 443,
      toPort: 443,
      protocol: "tcp",
      cidrBlocks: ["0.0.0.0/0"],
      self: true,
    },
  ],
}, {
  ignoreChanges: ["ingress[0]"]
})