tjinjin's blog

さらなる高みに1nmずつ近づくブログ

CloudFormationを使ってみる

About

これまでTerraformを使って環境構築することが多かったですが、最近YAMLサポートもされたAWS謹製のCloudFormationを使ってみます。Templateの書き方とかではなく、CloudFormationの概要を自分なりにまとめたものです。

CloudFormationとは

長いのでcfnと略すらしいです。

AWSリソースのサービスを設定ファイルを元に自動化できるサービスです。

CloudFormationの概念

大きく2つのポイントがあります。

テンプレート

スタックの設計図。どんなリソースを立てるのかなどを記述していきます。Terraformでいうところのtfファイルっぽいですね。

スタック

テンプレートからプロビジョニングされるリソースの集合 スタック単位でリソースの管理が可能。スタック破棄を実行すると、スタックにひもづくリソースを破棄することが可能 使用するリソースおよびリソースの構築順は、テンプレートの依存関係からCloudFormationが自動的に決定

https://image.slidesharecdn.com/20161124-aws-blackbelt-cloudformationv2-161206105655/95/aws-black-belt-online-seminar-2016-aws-cloudformation-19-638.jpg?cb=1481021881

となっているので、テンプレートから実際に作られたリソースってことになるでしょうか。Terraformと比較するとtfstateと近しい存在なのかなと思います。

AWScliを使ってstackの情報を参照してみると、

$ aws cloudformation describe-stacks --stack-name EC2SecurityGroupSample
{
    "Stacks": [
        {
            "StackId": "hogehoge",
            "Description": "AWS CloudFormation Sample Template EC2InstanceWithSecurityGroupSample: Create an Amazon EC2 instance running the Amazon Linux AMI. The AMI is chosen based on the region in which the stack is run. This example creates an EC2 security group for the instance to give you SSH access. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",
            "Parameters": [
                {
                    "ParameterValue": "hogehoge",
                    "ParameterKey": "KeyName"
                },
                {
                    "ParameterValue": "hogehoge",
                    "ParameterKey": "SSHLocation"
                },
                {
                    "ParameterValue": "t2.micro",
                    "ParameterKey": "InstanceType"
                }
            ],
            "Tags": [
                {
                    "Value": "cfn-test",
                    "Key": "Name"
                }
            ],
            "Outputs": [
                {
                    "Description": "InstanceId of the newly created EC2 instance",
                    "OutputKey": "InstanceId",
                    "OutputValue": "hogehoge"
                },
                {
                    "Description": "Public IP address of the newly created EC2 instance",
                    "OutputKey": "PublicIP",
                    "OutputValue": "xx.xx.xx.xx"
                },
                {
                    "Description": "Availability Zone of the newly created EC2 instance",
                    "OutputKey": "AZ",
                    "OutputValue": "ap-northeast-1a"
                },
                {
                    "Description": "Public DNSName of the newly created EC2 instance",
                    "OutputKey": "PublicDNS",
                    "OutputValue": "hogehoge.ap-northeast-1.compute.amazonaws.com"
                }
            ],
            "CreationTime": "2017-05-29T04:09:26.583Z",
            "StackName": "EC2SecurityGroupSample",
            "NotificationARNs": [],
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false
        }
    ]
}

のように表示されていて、実際の値はmaskしてますが、実際に立てられているリソースのIPなどの情報を確認することができます。

Terraformとの比較

まだcfnの方はそこまで触ってないのでなんとも言えないです。

参考までに適当にググってみつかった比較記事も貼っておきます。

以前Terraformのほうがtf形式で設定を書けるのでよさそうと思ってましたが、CloudFormationもYAMLで書けるようになりコメントも書けるようになったようなので、以前よりはマシになったかなという気がします。あとは細かい機能や使い勝手で選べばよさそう。

上に上げたブログではTerraformはAWS以外でも使えるという点を上げていますが、これまでAWS以外でTerraform使ってなかったのであまりメリットを感じませんでした。マルチクラウド環境とかで作業するようになったら、やっぱりTerraformとなる気もします。

とりあえず試してみる

初期構築

何はともあれ触ってみます。サンプルがあるのでそちらを使います。

f:id:cross_black777:20170528162019p:plain

コードの中身はこんな感じになっています。

{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "AWS CloudFormation Sample Template EC2InstanceWithSecurityGroupSample: Create an Amazon EC2 instance running the Amazon Linux AMI. The AMI is chosen based on the region in which the stack is run. This example creates an EC2 security group for the instance to give you SSH access. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",

  "Parameters" : {
    "KeyName": {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instance",
      "Type": "AWS::EC2::KeyPair::KeyName",
      "ConstraintDescription" : "must be the name of an existing EC2 KeyPair."
    },

    "InstanceType" : {
      "Description" : "WebServer EC2 instance type",
      "Type" : "String",
      "Default" : "t2.small",
      "AllowedValues" : [ "t1.micro", "t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large", "m1.small", "m1.medium", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "m3.medium", "m3.large", "m3.xlarge", "m3.2xlarge", "m4.large", "m4.xlarge", "m4.2xlarge", "m4.4xlarge", "m4.10xlarge", "c1.medium", "c1.xlarge", "c3.large", "c3.xlarge", "c3.2xlarge", "c3.4xlarge", "c3.8xlarge", "c4.large", "c4.xlarge", "c4.2xlarge", "c4.4xlarge", "c4.8xlarge", "g2.2xlarge", "g2.8xlarge", "r3.large", "r3.xlarge", "r3.2xlarge", "r3.4xlarge", "r3.8xlarge", "i2.xlarge", "i2.2xlarge", "i2.4xlarge", "i2.8xlarge", "d2.xlarge", "d2.2xlarge", "d2.4xlarge", "d2.8xlarge", "hi1.4xlarge", "hs1.8xlarge", "cr1.8xlarge", "cc2.8xlarge", "cg1.4xlarge"]
,
      "ConstraintDescription" : "must be a valid EC2 instance type."
    },

    "SSHLocation" : {
      "Description" : "The IP address range that can be used to SSH to the EC2 instances",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "0.0.0.0/0",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
   }
  },

  "Mappings" : {
    "AWSInstanceType2Arch" : {
      "t1.micro"    : { "Arch" : "PV64"   },
      "t2.nano"     : { "Arch" : "HVM64"  },
      "t2.micro"    : { "Arch" : "HVM64"  },
      "t2.small"    : { "Arch" : "HVM64"  },
      "t2.medium"   : { "Arch" : "HVM64"  },
      "t2.large"    : { "Arch" : "HVM64"  },
      "m1.small"    : { "Arch" : "PV64"   },
      "m1.medium"   : { "Arch" : "PV64"   },
      "m1.large"    : { "Arch" : "PV64"   },
      "m1.xlarge"   : { "Arch" : "PV64"   },
      "m2.xlarge"   : { "Arch" : "PV64"   },
      "m2.2xlarge"  : { "Arch" : "PV64"   },
      "m2.4xlarge"  : { "Arch" : "PV64"   },
      "m3.medium"   : { "Arch" : "HVM64"  },
      "m3.large"    : { "Arch" : "HVM64"  },
      "m3.xlarge"   : { "Arch" : "HVM64"  },
      "m3.2xlarge"  : { "Arch" : "HVM64"  },
      "m4.large"    : { "Arch" : "HVM64"  },
      "m4.xlarge"   : { "Arch" : "HVM64"  },
      "m4.2xlarge"  : { "Arch" : "HVM64"  },
      "m4.4xlarge"  : { "Arch" : "HVM64"  },
      "m4.10xlarge" : { "Arch" : "HVM64"  },
      "c1.medium"   : { "Arch" : "PV64"   },
      "c1.xlarge"   : { "Arch" : "PV64"   },
      "c3.large"    : { "Arch" : "HVM64"  },
      "c3.xlarge"   : { "Arch" : "HVM64"  },
      "c3.2xlarge"  : { "Arch" : "HVM64"  },
      "c3.4xlarge"  : { "Arch" : "HVM64"  },
      "c3.8xlarge"  : { "Arch" : "HVM64"  },
      "c4.large"    : { "Arch" : "HVM64"  },
      "c4.xlarge"   : { "Arch" : "HVM64"  },
      "c4.2xlarge"  : { "Arch" : "HVM64"  },
      "c4.4xlarge"  : { "Arch" : "HVM64"  },
      "c4.8xlarge"  : { "Arch" : "HVM64"  },
      "g2.2xlarge"  : { "Arch" : "HVMG2"  },
      "g2.8xlarge"  : { "Arch" : "HVMG2"  },
      "r3.large"    : { "Arch" : "HVM64"  },
      "r3.xlarge"   : { "Arch" : "HVM64"  },
      "r3.2xlarge"  : { "Arch" : "HVM64"  },
      "r3.4xlarge"  : { "Arch" : "HVM64"  },
      "r3.8xlarge"  : { "Arch" : "HVM64"  },
      "i2.xlarge"   : { "Arch" : "HVM64"  },
      "i2.2xlarge"  : { "Arch" : "HVM64"  },
      "i2.4xlarge"  : { "Arch" : "HVM64"  },
      "i2.8xlarge"  : { "Arch" : "HVM64"  },
      "d2.xlarge"   : { "Arch" : "HVM64"  },
      "d2.2xlarge"  : { "Arch" : "HVM64"  },
      "d2.4xlarge"  : { "Arch" : "HVM64"  },
      "d2.8xlarge"  : { "Arch" : "HVM64"  },
      "hi1.4xlarge" : { "Arch" : "HVM64"  },
      "hs1.8xlarge" : { "Arch" : "HVM64"  },
      "cr1.8xlarge" : { "Arch" : "HVM64"  },
      "cc2.8xlarge" : { "Arch" : "HVM64"  }
    },

    "AWSInstanceType2NATArch" : {
      "t1.micro"    : { "Arch" : "NATPV64"   },
      "t2.nano"     : { "Arch" : "NATHVM64"  },
      "t2.micro"    : { "Arch" : "NATHVM64"  },
      "t2.small"    : { "Arch" : "NATHVM64"  },
      "t2.medium"   : { "Arch" : "NATHVM64"  },
      "t2.large"    : { "Arch" : "NATHVM64"  },
      "m1.small"    : { "Arch" : "NATPV64"   },
      "m1.medium"   : { "Arch" : "NATPV64"   },
      "m1.large"    : { "Arch" : "NATPV64"   },
      "m1.xlarge"   : { "Arch" : "NATPV64"   },
      "m2.xlarge"   : { "Arch" : "NATPV64"   },
      "m2.2xlarge"  : { "Arch" : "NATPV64"   },
      "m2.4xlarge"  : { "Arch" : "NATPV64"   },
      "m3.medium"   : { "Arch" : "NATHVM64"  },
      "m3.large"    : { "Arch" : "NATHVM64"  },
      "m3.xlarge"   : { "Arch" : "NATHVM64"  },
      "m3.2xlarge"  : { "Arch" : "NATHVM64"  },
      "m4.large"    : { "Arch" : "NATHVM64"  },
      "m4.xlarge"   : { "Arch" : "NATHVM64"  },
      "m4.2xlarge"  : { "Arch" : "NATHVM64"  },
      "m4.4xlarge"  : { "Arch" : "NATHVM64"  },
      "m4.10xlarge" : { "Arch" : "NATHVM64"  },
      "c1.medium"   : { "Arch" : "NATPV64"   },
      "c1.xlarge"   : { "Arch" : "NATPV64"   },
      "c3.large"    : { "Arch" : "NATHVM64"  },
      "c3.xlarge"   : { "Arch" : "NATHVM64"  },
      "c3.2xlarge"  : { "Arch" : "NATHVM64"  },
      "c3.4xlarge"  : { "Arch" : "NATHVM64"  },
      "c3.8xlarge"  : { "Arch" : "NATHVM64"  },
      "c4.large"    : { "Arch" : "NATHVM64"  },
      "c4.xlarge"   : { "Arch" : "NATHVM64"  },
      "c4.2xlarge"  : { "Arch" : "NATHVM64"  },
      "c4.4xlarge"  : { "Arch" : "NATHVM64"  },
      "c4.8xlarge"  : { "Arch" : "NATHVM64"  },
      "g2.2xlarge"  : { "Arch" : "NATHVMG2"  },
      "g2.8xlarge"  : { "Arch" : "NATHVMG2"  },
      "r3.large"    : { "Arch" : "NATHVM64"  },
      "r3.xlarge"   : { "Arch" : "NATHVM64"  },
      "r3.2xlarge"  : { "Arch" : "NATHVM64"  },
      "r3.4xlarge"  : { "Arch" : "NATHVM64"  },
      "r3.8xlarge"  : { "Arch" : "NATHVM64"  },
      "i2.xlarge"   : { "Arch" : "NATHVM64"  },
      "i2.2xlarge"  : { "Arch" : "NATHVM64"  },
      "i2.4xlarge"  : { "Arch" : "NATHVM64"  },
      "i2.8xlarge"  : { "Arch" : "NATHVM64"  },
      "d2.xlarge"   : { "Arch" : "NATHVM64"  },
      "d2.2xlarge"  : { "Arch" : "NATHVM64"  },
      "d2.4xlarge"  : { "Arch" : "NATHVM64"  },
      "d2.8xlarge"  : { "Arch" : "NATHVM64"  },
      "hi1.4xlarge" : { "Arch" : "NATHVM64"  },
      "hs1.8xlarge" : { "Arch" : "NATHVM64"  },
      "cr1.8xlarge" : { "Arch" : "NATHVM64"  },
      "cc2.8xlarge" : { "Arch" : "NATHVM64"  }
    }
,
    "AWSRegionArch2AMI" : {
      "us-east-1"        : {"PV64" : "ami-2a69aa47", "HVM64" : "ami-6869aa05", "HVMG2" : "ami-61e27177"},
      "us-west-2"        : {"PV64" : "ami-7f77b31f", "HVM64" : "ami-7172b611", "HVMG2" : "ami-60aa3700"},
      "us-west-1"        : {"PV64" : "ami-a2490dc2", "HVM64" : "ami-31490d51", "HVMG2" : "ami-4b694d2b"},
      "eu-west-1"        : {"PV64" : "ami-4cdd453f", "HVM64" : "ami-f9dd458a", "HVMG2" : "ami-2955524f"},
      "eu-west-2"        : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-886369ec", "HVMG2" : "NOT_SUPPORTED"},
      "eu-central-1"     : {"PV64" : "ami-6527cf0a", "HVM64" : "ami-ea26ce85", "HVMG2" : "ami-81ac71ee"},
      "ap-northeast-1"   : {"PV64" : "ami-3e42b65f", "HVM64" : "ami-374db956", "HVMG2" : "ami-46220c21"},
      "ap-northeast-2"   : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-2b408b45", "HVMG2" : "NOT_SUPPORTED"},
      "ap-southeast-1"   : {"PV64" : "ami-df9e4cbc", "HVM64" : "ami-a59b49c6", "HVMG2" : "ami-c212aba1"},
      "ap-southeast-2"   : {"PV64" : "ami-63351d00", "HVM64" : "ami-dc361ebf", "HVMG2" : "ami-0ad2db69"},
      "ap-south-1"       : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-ffbdd790", "HVMG2" : "ami-ca3042a5"},
      "us-east-2"        : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-f6035893", "HVMG2" : "NOT_SUPPORTED"},
      "ca-central-1"     : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-730ebd17", "HVMG2" : "NOT_SUPPORTED"},
      "sa-east-1"        : {"PV64" : "ami-1ad34676", "HVM64" : "ami-6dd04501", "HVMG2" : "NOT_SUPPORTED"},
      "cn-north-1"       : {"PV64" : "ami-77559f1a", "HVM64" : "ami-8e6aa0e3", "HVMG2" : "NOT_SUPPORTED"}
    }

  },

  "Resources" : {
    "EC2Instance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "InstanceType" : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ],
        "KeyName" : { "Ref" : "KeyName" },
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }
      }
    },

    "InstanceSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable SSH access via port 22",
        "SecurityGroupIngress" : [ {
          "IpProtocol" : "tcp",
          "FromPort" : "22",
          "ToPort" : "22",
          "CidrIp" : { "Ref" : "SSHLocation"}
        } ]
      }
    }
  },

  "Outputs" : {
    "InstanceId" : {
      "Description" : "InstanceId of the newly created EC2 instance",
      "Value" : { "Ref" : "EC2Instance" }
    },
    "AZ" : {
      "Description" : "Availability Zone of the newly created EC2 instance",
      "Value" : { "Fn::GetAtt" : [ "EC2Instance", "AvailabilityZone" ] }
    },
    "PublicDNS" : {
      "Description" : "Public DNSName of the newly created EC2 instance",
      "Value" : { "Fn::GetAtt" : [ "EC2Instance", "PublicDnsName" ] }
    },
    "PublicIP" : {
      "Description" : "Public IP address of the newly created EC2 instance",
      "Value" : { "Fn::GetAtt" : [ "EC2Instance", "PublicIp" ] }
    }
  }
}

jsonを見るとParametersという部分があり、どうやらここで動的に変更できる要素を指定するようです。

画面左上のアップロードっぽいボタンでstackの作成ができるので進みます。

f:id:cross_black777:20170528173719p:plain

ここでtemplateのロケーションを指定します。

f:id:cross_black777:20170528173734p:plain

この画面ではParametersとして設定できる項目について指定します。先程のTemplateで設定した部分と一致しています。

f:id:cross_black777:20170528173756p:plain

タグやIAM Roleを設定してあげて最終確認をすると、

f:id:cross_black777:20170528173816p:plain

あとは勝手にEC2が起動しました。

リソース更新

先程のstackを改造して更新の実験をします。先程t2.smallでインスタンスを立ててしまったので、t2.nanoに変更します。

instance typeはparametersで指定しているので、Specify Detailsという場面で変更すればよさそうです。

stackの画面を勧めていくと最後にPreview your changesという項目があり、今回の変更でどういった変更がおこるかが出力されていました。

f:id:cross_black777:20170528165958p:plain

これがTerraformでいうところのplanに当たるのでしょうか。今回のinstance typeの変更ではどうやらinstanceのstop -> change instance tyep -> startの動作をしたようなので、インスタンス自体が置き換わったわけではないみたいです。

調べてみるとStackの更新には2種類存在するようです。

直接更新

さきほど私がやった方法です。コンソールからだとUpdate Stackで実行します。

変更セット(ChangeSet)

現在の環境と新しく作りたいものとの差分を比較し、いったんChangeSetというものを作りそこで確認後、実際に適用するかを確認することができます。より安全にやるならこちらを使うのがよさそうです。実行時にアカウントのリソース上限のチェックなどは各自でする必要があるとのことです。

リソースの更新や削除はいろいろ考慮すべきところがありそうなので、詳細を確認しておいたほうがよさそうです。

その他調べたこと

チーム開発

基本的にコンソール上でのリソースの適用は禁止にしたほうがよさそう。もし仮に実行してしまった際のterraformでいうところのimport機能はあるのだろうか?まだ調べきれていません。

チーム開発を考えるとこちらのブログのようにGitHubでtemplateを管理するのがよさそうですね。

インフラチームと開発チームの垣根をなくすためにAWSのCI環境を構築した話 - VOYAGE GROUP techlog

おまけ

調べたところによるとTerraformでCloudFormationのスタックを使えるようです。なんかすごい

AWS: aws_cloudformation_stack - Terraform by HashiCorp

便利ツール達

まとめ

cfnは今まで触ったことはなかったですがなんとなく理解しました。templateの書き方などは少しずつ学んでいきたいと思います。

参考資料

まずはこちらがおすすめです!

www.slideshare.net

ベストプラクティス AWS CloudFormation のベストプラクティス - AWS CloudFormation