IaC は DevOps の中で重要な立ち位置に居て、インフラ専門部隊だけではなく、バックエンドの開発者も柔軟に構成が変更できるためには
今までの Terraform や Ansible では敷居が高かった(ツール独自のループの書き方とか色々)。だけど Pulumi や aws cdk の登場により好きな言語でリソース管理ができるならその敷居はもっと下がって無駄なコストを減らせると思う(リソースの変更を願いせずとも PR 出せばいいとか)

Pulumi の良いところ

Terraform や Ansible といった独自言語(HCL)、もしくはツールに対しての知識は不要で好きな言語で IaC を実現できるのでインフラ専属じゃない人でも気軽にリソースを弄ることができる。(Terraform のバージョン追従みたいので辛い思いしなくていいし、loop の書き方をいちいち調べなくても良い)
また、Pulumi は Go にも対応してるので aws-cdk にはないメリット。

www.pulumi.com

そして、Pulumi の provider は Terraform の provider から生成されてるので tf2pulumi とかいうマイグレツールもある。
github.com

Aurora Cluster を作ってみる

CLI のインストールや、state ファイルの指定などは公式ドキュメントへ。

全貌

func createAuroraSecurityGroup(ctx *pulumi.Context) (*ec2.SecurityGroup, error) {
name := fmt.Sprintf("aurora-%s-%s", env, prj)
args := &ec2.SecurityGroupArgs{
VpcId: pulumi.String("vpc-0ccbc720526afbcff"),
Ingress: ec2.SecurityGroupIngressArray{
ec2.SecurityGroupIngressArgs{
CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
FromPort:   pulumi.Int(3306),
ToPort:     pulumi.Int(3306),
Protocol:   pulumi.String("TCP"),
},
},
Egress: ec2.SecurityGroupEgressArray{
ec2.SecurityGroupEgressArgs{
CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
FromPort:   pulumi.Int(0),
ToPort:     pulumi.Int(0),
Protocol:   pulumi.String("-1"),
},
},
Name: pulumi.String(name),
Tags: tags,
}
return ec2.NewSecurityGroup(ctx, name, args)
}
func createAuroraSubnetGroup(ctx *pulumi.Context) (*rds.SubnetGroup, error) {
args := &rds.SubnetGroupArgs{
Name:      pulumi.String(fmt.Sprintf("%s", prj)),
SubnetIds: pulumi.StringArray{pulumi.String("subnet-082199a2239986516"), pulumi.String("subnet-00e5e8eda3f4cdef1")},
Tags:      tags,
}
return rds.NewSubnetGroup(ctx, fmt.Sprintf("%s", prj), args)
}
func createAuroraClusterParameterGroup(ctx *pulumi.Context) (*rds.ClusterParameterGroup, error) {
timeZone := rds.ClusterParameterGroupParameterArgs{
Name:  pulumi.String("time_zone"),
Value: pulumi.String("Asia/Tokyo"),
}
args := &rds.ClusterParameterGroupArgs{
Family:     pulumi.String("aurora-mysql5.7"),
Parameters: rds.ClusterParameterGroupParameterArray{timeZone},
Tags:       tags,
}
return rds.NewClusterParameterGroup(ctx, fmt.Sprintf("%s-%s", env, prj), args)
}
func createAuroraCluster(ctx *pulumi.Context, clusterParameterGroupName, subnetGroupName pulumi.StringPtrInput, securityGroupID pulumi.IDOutput) (*rds.Cluster, error) {
name := fmt.Sprintf("%s-%s-cluster", env, prj)
args := &rds.ClusterArgs{
ApplyImmediately:            pulumi.Bool(true),
ClusterIdentifier:           pulumi.String(name),
DbClusterParameterGroupName: clusterParameterGroupName,
DbSubnetGroupName:           subnetGroupName,
Engine:                      pulumi.String("aurora-mysql"),
EngineMode:                  pulumi.String("provisioned"),
EngineVersion:               pulumi.String("5.7.mysql_aurora.2.07.2"),
MasterPassword:              pulumi.String(c.Require("aurora_master_password")),
MasterUsername:              pulumi.String(c.Require("aurora_master_username")),
Tags:                        tags,
VpcSecurityGroupIds:         pulumi.StringArray{securityGroupID},
}
return rds.NewCluster(ctx, name, args)
}
func createAuroraClusterInstance(ctx *pulumi.Context, cluster *rds.Cluster) error {
count := c.RequireInt("aurora_instances")
for i := 0; i < count; i++ {
name := fmt.Sprintf("%s-%s-instance-%d", env, prj, i)
args := &rds.ClusterInstanceArgs{
ApplyImmediately:           pulumi.Bool(true),
ClusterIdentifier:          cluster.ID(),
Engine:                     pulumi.String("aurora-mysql"),
EngineVersion:              pulumi.String("5.7.mysql_aurora.2.07.2"),
Identifier:                 pulumi.String(name),
InstanceClass:              pulumi.String(c.Require("aurora_instance_class")),
PerformanceInsightsEnabled: pulumi.Bool(false), // 2020/05/26 5.7.mysql_aurora.2.07.2 is not supported
PubliclyAccessible:         pulumi.Bool(false),
Tags:                       tags,
}
_, err := rds.NewClusterInstance(ctx, name, args)
if err != nil {
return err
}
}
return nil
}

Tips

使ってみた感じ

configuration variable

外部から注入する変数みたいなもので

$ pulumi config set env stg

env 変数を作って、コードで取得する際は c.Require(“env”) とするだけ
この変数もスタック(dev 用とか、stg 用とかで分けれる)ごとに分かれる。

configuration variable を取得する際にエラーハンドリングをしなくても落ちてくれる

Diagnostics:
pulumi:pulumi:Stack (test-pulumi-stg):
panic: fatal: A failure has occurred: missing required configuration variable 'test-pulumi:project'; run `pulumi config` to set
env = c.Require("env")
if env == "" {
return fmt.Errorf("'env' variable not defined")
}

こんなことしなくて OK

既存のリソースに対しての変更もちゃんとできる

pulumi で作ったリソースのコードにタグを追加した結果 ↓

Previewing update (stg):
Type                 Name                Plan       Info
pulumi:pulumi:Stack  test-pulumi-stg             1 message
~   └─ aws:ecs:Cluster   stg-test         update     [diff: ~tags]
Diagnostics:
pulumi:pulumi:Stack (test-pulumi-stg):
&{CustomResourceState:{ResourceState:{urn:{OutputState:0xc000207c00} providers:map[] aliases:[] name:stg-test transformations:[]} id:{OutputState:0xc000207b90}} Arn:{OutputState:0xc0002078f0} CapacityProviders:{OutputState:0xc000207960} DefaultCapacityProviderStrategies:{OutputState:0xc0002079d0} Name:{OutputState:0xc000207a40} Settings:{OutputState:0xc000207ab0} Tags:{OutputState:0xc000207b20}}

エラー内容も結構分かりやすい

Previewing update (stg):
Type                      Name                Plan     Info
pulumi:pulumi:Stack       test-pulumi-stg
└─ aws:ec2:SecurityGroup  aurora-stg-test           3 errors
Diagnostics:
aws:ec2:SecurityGroup (aurora-stg-test):
error: aws:ec2/securityGroup:SecurityGroup resource 'aurora-stg-test' has a problem: "ingress.0.from_port": required field is not set
error: aws:ec2/securityGroup:SecurityGroup resource 'aurora-stg-test' has a problem: "ingress.0.to_port": required field is not set
error: aws:ec2/securityGroup:SecurityGroup resource 'aurora-stg-test' has a problem: "ingress.0.protocol": required field is not set

手で消したリソースを pulumi にも反映させる方法

$ pulumi state delete urn:pulumi:stg::test-pulumi::aws:rds/cluster:Cluster::stg-test-cluster
warning: This command will edit your stack's state directly. Confirm? Yes
Multiple resources with the given URN exist, please select the one to edit: "tf-20200525154551533900000001" (Pending Deletion)
Resource deleted successfully