IaC は DevOps の中で重要な立ち位置に居て、インフラ専門部隊だけではなく、バックエンドの開発者も柔軟に構成が変更できるためには
今までの Terraform や Ansible では敷居が高かった(ツール独自のループの書き方とか色々)。だけど Pulumi や aws cdk の登場により好きな言語でリソース管理ができるならその敷居はもっと下がって無駄なコストを減らせると思う(リソースの変更を願いせずとも PR 出せばいいとか)
Pulumi
好きな言語で Infrastructure as Code を実現できるフレームワーク?ツール?
AWS だけではなく、GCP、Azure、Kubernetes、その他 SaaS にも対応してる。
対応言語は Go, Python, Node.js, .NET.Core
仕組みは Terraform とほぼ一緒。
Pulumi の良いところ
Terraform や Ansible といった独自言語(HCL)、もしくはツールに対しての知識は不要で好きな言語で IaC を実現できるのでインフラ専属じゃない人でも気軽にリソースを弄ることができる。(Terraform のバージョン追従みたいので辛い思いしなくていいし、loop の書き方をいちいち調べなくても良い)
また、Pulumi は Go にも対応してるので aws-cdk にはないメリット。
そして、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