CloudFormation + Run CommandでWEBサイト構築

こんにちは。katoです。

10月のAWS Release Noteを見ていたら、
Amazon Linux AMIにSSMエージェントがプリインストール」
との記事を見かけました。

最初はスルーしていましたが、使ってみたら結構便利だったので今回ご紹介させていただきます。

 

概要

今回試すのはCloudFormationを使った静的WEBサイトの構築になります。
CloudFormationで環境構築、SSM Document作成を行い、stackの作成後にRun Commandを実行するだけで静的WEBサイトの環境を立ち上げることが可能です。

構築する環境は下図の様になります。

 

 

CloudFormationテンプレート

いきなりですが、CloudFormationテンプレートを載せさせていただきます。
キーペアとバケット名は変更する必要が御座います(キーペアが存在しない場合は事前に作成する必要が御座います)。
記載している内容は簡単なものなので、必要に応じてカスタマイズ可能です。

 

{
    "AWSTemplateFormatVersion" : "2010-09-09",
    "Resources" : {
        "VPC" : {
            "Type" : "AWS::EC2::VPC",
            "Properties" : {
                "CidrBlock" : "10.0.0.0/16",
                "Tags" : [
                    {"Key" : "Name", "Value" : "AWS-DC" }
                ]
            }
        },
        "DMZ1" : {
            "Type" : "AWS::EC2::Subnet",
                        "Properties" : {
                                "VpcId" : { "Ref" : "VPC" },
                                "CidrBlock" : "10.0.0.0/24",
                                "AvailabilityZone" : "ap-northeast-1a",
                                "Tags" : [
                                        {"Key" : "Name", "Value" : "AWS-DC-public-primary" }
                                ]
                        }
                },
                "DMZ2" : {
                        "Type" : "AWS::EC2::Subnet",
                        "Properties" : {
                                "VpcId" : { "Ref" : "VPC" },
                                "CidrBlock" : "10.0.1.0/24",
                                "AvailabilityZone" : "ap-northeast-1c",
                                "Tags" : [
                                        {"Key" : "Name", "Value" : "AWS-DC-public-secondary" }
                                ]
                        }
                },
                "AP1" : {
                        "Type" : "AWS::EC2::Subnet",
                        "Properties" : {
                                "VpcId" : { "Ref" : "VPC" },
                                "CidrBlock" : "10.0.2.0/24",
                                "AvailabilityZone" : "ap-northeast-1a",
                                "Tags" : [
                                        {"Key" : "Name", "Value" : "AWS-DC-private-primary" }
                                ]
                        }
                },
                "AP2" : {
                        "Type" : "AWS::EC2::Subnet",
                        "Properties" : {
                                "VpcId" : { "Ref" : "VPC" },
                                "CidrBlock" : "10.0.3.0/24",
                                "AvailabilityZone" : "ap-northeast-1c",
                                "Tags" : [
                                        {"Key" : "Name", "Value" : "AWS-DC-private-secondary" }
                                ]
                        }
                },
                "DB1" : {
                        "Type" : "AWS::EC2::Subnet",
                        "Properties" : {
                                "VpcId" : { "Ref" : "VPC" },
                                "CidrBlock" : "10.0.4.0/24",
                                "AvailabilityZone" : "ap-northeast-1a",
                                "Tags" : [
                                        {"Key" : "Name", "Value" : "AWS-DC-db-primary" }
                                ]
                        }
                },
                "DB2" : {
                        "Type" : "AWS::EC2::Subnet",
                        "Properties" : {
                                "VpcId" : { "Ref" : "VPC" },
                                "CidrBlock" : "10.0.5.0/24",
                                "AvailabilityZone" : "ap-northeast-1c",
                                "Tags" : [
                                        {"Key" : "Name", "Value" : "AWS-DC-db-secondary" }
                                ]
                        }
                },
                "InternetGateway" : {
                        "Type" : "AWS::EC2::InternetGateway",
                        "Properties" : {
                                "Tags" : [
                                        {"Key" : "Name", "Value" : "AWS-DC-IGW" }
                                ]
                        }
                },
                "AttachGateway" : {
                        "Type" : "AWS::EC2::VPCGatewayAttachment",
                        "Properties" : {
                                 "VpcId" : { "Ref" : "VPC" },
                                 "InternetGatewayId" : { "Ref" : "InternetGateway" }
                        }
                },
                "PublicRouteTable" : {
                        "Type" : "AWS::EC2::RouteTable",
                        "Properties" : {
                                "VpcId" : {"Ref" : "VPC"},
                                "Tags" : [
                                        {"Key" : "Name", "Value" : "AWS-DC-Public-RouteTable" }
                                ]
                        }
                },
                "PublicRoute" : {
                        "Type" : "AWS::EC2::Route",
                        "Properties" : {
                                "RouteTableId" : { "Ref" : "PublicRouteTable" },
                                "DestinationCidrBlock" : "0.0.0.0/0",
                                "GatewayId" : { "Ref" : "InternetGateway" }
                        }
                },
                "PublicSubnetRouteTableDMZ1" : {
                        "Type" : "AWS::EC2::SubnetRouteTableAssociation",
                        "Properties" : {
                                "SubnetId" : { "Ref" : "DMZ1" },
                                "RouteTableId" : { "Ref" : "PublicRouteTable" }
                        }
                },
                "PublicSubnetRouteTableDMZ2" : {
                        "Type" : "AWS::EC2::SubnetRouteTableAssociation",
                        "Properties" : {
                                "SubnetId" : { "Ref" : "DMZ2" },
                                "RouteTableId" : { "Ref" : "PublicRouteTable" }
                        }
                },
                "PrivateRouteTable" : {
                        "Type" : "AWS::EC2::RouteTable",
                        "Properties" : {
                                "VpcId" : {"Ref" : "VPC"},
                                "Tags" : [
                                        {"Key" : "Name", "Value" : "AWS-DC-Private-RouteTable" }
                                ]
                        }
                },
                "PrivateSubnetRouteTableAP1" : {
                        "Type" : "AWS::EC2::SubnetRouteTableAssociation",
                        "Properties" : {
                                "SubnetId" : { "Ref" : "AP1" },
                                "RouteTableId" : { "Ref" : "PrivateRouteTable" }
                        }
                },
                "PrivateSubnetRouteTableAP2" : {
                        "Type" : "AWS::EC2::SubnetRouteTableAssociation",
                        "Properties" : {
                                "SubnetId" : { "Ref" : "AP2" },
                                "RouteTableId" : { "Ref" : "PrivateRouteTable" }
                        }
                },
                "PrivateSubnetRouteTableDB1" : {
                        "Type" : "AWS::EC2::SubnetRouteTableAssociation",
                        "Properties" : {
                                "SubnetId" : { "Ref" : "DB1" },
                                "RouteTableId" : { "Ref" : "PrivateRouteTable" }
                        }
                },
                "PrivateSubnetRouteTableDB2" : {
                        "Type" : "AWS::EC2::SubnetRouteTableAssociation",
                        "Properties" : {
                                "SubnetId" : { "Ref" : "DB2" },
                                "RouteTableId" : { "Ref" : "PrivateRouteTable" }
                        }
                },
        "NatGateway" : {
            "Type" : "AWS::EC2::NatGateway",
            "Properties" : {
                "AllocationId" : { "Fn::GetAtt" : ["EIP", "AllocationId"] },
                "SubnetId" : { "Ref" : "DMZ1" }
            }
        },
        "EIP" : {
            "Type" : "AWS::EC2::EIP",
            "Properties" : {
                "Domain" : "vpc"
            }
        },
        "PrivateRoute" : {
            "Type" : "AWS::EC2::Route",
                        "Properties" : {
                                "RouteTableId" : { "Ref" : "PrivateRouteTable" },
                                "DestinationCidrBlock" : "0.0.0.0/0",
                                "NatGatewayId" : { "Ref" : "NatGateway" }
                        }
        },
        "SecurityGroupSSH" : {
            "Type" : "AWS::EC2::SecurityGroup",
            "Properties" : {
                "GroupName" : "AWS-SSH-Access",
                "GroupDescription" : "AWS-SSH-Access",
                "SecurityGroupIngress" : [{
                    "IpProtocol" : "tcp",
                    "FromPort" : "22",
                    "ToPort" : "22",
                    "CidrIp" : "10.0.0.0/16"
                }],
                "VpcId" : { "Ref" : "VPC" }
            }
        },
        "SecurityGroupHTTP" : {
            "Type" : "AWS::EC2::SecurityGroup",
            "Properties" : {
                "GroupName" : "AWS-HTTP-Access",
                "GroupDescription" : "AWS-HTTP-Access",
                "SecurityGroupIngress" : [{
                    "IpProtocol" : "tcp",
                    "FromPort" : "80",
                    "ToPort" : "80",
                    "CidrIp" : "10.0.0.0/16"
                }],
                "VpcId" : { "Ref" : "VPC" }
            }
        },
        "SecurityGroupELB" : {
            "Type" : "AWS::EC2::SecurityGroup",
            "Properties" : {
                "GroupName" : "AWS-ELB-Access",
                "GroupDescription" : "AWS-ELB-Access",
                "SecurityGroupIngress" : [{
                    "IpProtocol" : "tcp",
                    "FromPort" : "80",
                    "ToPort" : "80",
                    "CidrIp" : "0.0.0.0/0"
                }, {
                    "IpProtocol" : "tcp",
                    "FromPort" : "443",
                    "ToPort" : "443",
                    "CidrIp" : "0.0.0.0/0"
                }],
                "VpcId" : { "Ref" : "VPC" }
            }
        },
        "SecurityGroupFromELB" : {
            "Type" : "AWS::EC2::SecurityGroup",
            "Properties" : {
                "GroupName" : "AWS-From-ELB-Access",
                "GroupDescription" : "AWS-From-ELB-Access",
                "SecurityGroupIngress" : [{
                    "IpProtocol" : "tcp",
                    "FromPort" : "80",
                    "ToPort" : "80",
                    "SourceSecurityGroupId" : { "Ref" : "SecurityGroupELB" }
                }],
                "VpcId" : { "Ref" : "VPC" }
            }
        },
        "SecurityGroupBastionSSH" : {
            "Type" : "AWS::EC2::SecurityGroup",
            "Properties" : {
                "GroupName" : "AWS-Bastion-SSH",
                "GroupDescription" : "AWS-Bastion-SSH",
                "SecurityGroupIngress" : [{
                    "IpProtocol" : "tcp",
                    "FromPort" : "22",
                    "ToPort" : "22",
                    "CidrIp" : "10.0.0.0/16"
                }],
                "VpcId" : { "Ref" : "VPC" }
            }
        },
        "SecurityGroupVpcAll" : {
            "Type" : "AWS::EC2::SecurityGroup",
            "Properties" : {
                "GroupName" : "AWS-VPC-ALL",
                "GroupDescription" : "AWS-VPC-ALL",
                "SecurityGroupIngress" : [{
                    "IpProtocol" : "-1",
                    "FromPort" : "-1",
                    "ToPort" : "-1",
                    "CidrIp" : "10.0.0.0/16"
                }],
                "VpcId" : { "Ref" : "VPC" }
            }
        },
        "SSMRole" : {
            "Type" : "AWS::IAM::Role",
            "Properties" : {
                "AssumeRolePolicyDocument": {
                    "Version" : "2012-10-17",
                    "Statement": [ {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": [ "ec2.amazonaws.com" ]
                        },
                        "Action": [ "sts:AssumeRole" ]
                    } ]
                },
                "Path" : "/",
                "Policies" : [{
                    "PolicyName": "CustomSSM",
                    "PolicyDocument": {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "ssm:DescribeAssociation",
                                    "ssm:GetDeployablePatchSnapshotForInstance",
                                    "ssm:GetDocument",
                                    "ssm:GetManifest",
                                    "ssm:GetParameters",
                                    "ssm:ListAssociations",
                                    "ssm:ListInstanceAssociations",
                                    "ssm:PutInventory",
                                    "ssm:PutComplianceItems",
                                    "ssm:PutConfigurePackageResult",
                                    "ssm:UpdateAssociationStatus",
                                    "ssm:UpdateInstanceAssociationStatus",
                                    "ssm:UpdateInstanceInformation"
                                ],
                                "Resource": "*"
                            },
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "ec2messages:AcknowledgeMessage",
                                    "ec2messages:DeleteMessage",
                                    "ec2messages:FailMessage",
                                    "ec2messages:GetEndpoint",
                                    "ec2messages:GetMessages",
                                    "ec2messages:SendReply"
                                ],
                                "Resource": "*"
                            },
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "cloudwatch:PutMetricData"
                                ],
                                "Resource": "*"
                            },
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "ec2:DescribeInstanceStatus"
                                ],
                                "Resource": "*"
                            },
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "ds:CreateComputer",
                                    "ds:DescribeDirectories"
                                ],
                                "Resource": "*"
                            },
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "logs:CreateLogGroup",
                                    "logs:CreateLogStream",
                                    "logs:DescribeLogGroups",
                                    "logs:DescribeLogStreams",
                                    "logs:PutLogEvents"
                                ],
                                "Resource": "*"
                            },
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "s3:PutObject",
                                    "s3:GetObject",
                                    "s3:AbortMultipartUpload",
                                    "s3:ListMultipartUploadParts",
                                    "s3:ListBucket",
                                    "s3:ListBucketMultipartUploads"
                                ],
                                "Resource": "*"
                            }
                        ]
                    }
                }],
                "RoleName" : "SSM"
            }
        },
        "InstanceProfile" : {
            "Type" : "AWS::IAM::InstanceProfile",
            "Properties": {
                "Path" : "/",
                "Roles" : [ { "Ref" : "SSMRole" } ],
                "InstanceProfileName" : "CustomProfile"
            }
        },
        "Web01" : {
            "Type" : "AWS::EC2::Instance",
            "Properties" : {
                "AvailabilityZone" : "ap-northeast-1a",
                "ImageId" : "ami-2a69be4c",
                "InstanceType" : "t2.micro",
                "IamInstanceProfile": {
                    "Ref" : "InstanceProfile"
                },
                "KeyName" : "xxxxxxxx",
                "PrivateIpAddress" : "10.0.2.10",
                "SecurityGroupIds" : [
                    { "Ref" : "SecurityGroupSSH" },
                    { "Ref" : "SecurityGroupFromELB" }
                ],
                "SubnetId" : { "Ref" : "AP1" },
                "Tags" : [{ "Key" : "Name", "Value" : "web01" }]
            }
        },
        "Web02" : {
            "Type" : "AWS::EC2::Instance",
            "Properties" : {
                "AvailabilityZone" : "ap-northeast-1c",
                "ImageId" : "ami-2a69be4c",
                "InstanceType" : "t2.micro",
                "IamInstanceProfile": {
                    "Ref" : "InstanceProfile"
                },
                "KeyName" : "xxxxxxxx",
                "PrivateIpAddress" : "10.0.3.10",
                "SecurityGroupIds" : [
                    { "Ref" : "SecurityGroupSSH" },
                    { "Ref" : "SecurityGroupFromELB" }
                ],
                "SubnetId" : { "Ref" : "AP2" },
                "Tags" : [{ "Key" : "Name", "Value" : "web02" }]
            }
        },
        "NFS" : {
            "Type" : "AWS::EC2::Instance",
            "Properties" : {
                "AvailabilityZone" : "ap-northeast-1a",
                "ImageId" : "ami-2a69be4c",
                "InstanceType" : "t2.micro",
                "BlockDeviceMappings" : [
                    {
                        "DeviceName" : "/dev/xvda",
                        "Ebs" : { "VolumeSize" : "10" }
                    },
                    {
                        "DeviceName" : "/dev/xvdb",
                        "Ebs" : { "VolumeSize" : "30" }
                    }
                ],
                "IamInstanceProfile": {
                    "Ref" : "InstanceProfile"
                },
                "KeyName" : "xxxxxxxx",
                "PrivateIpAddress" : "10.0.4.10",
                "SecurityGroupIds" : [
                    { "Ref" : "SecurityGroupSSH" },
                    { "Ref" : "SecurityGroupVpcAll" }
                ],
                "SubnetId" : { "Ref" : "DB1" },
                "Tags" : [{ "Key" : "Name", "Value" : "NFS" }]
            }
        },
        "Bastion" : {
            "Type" : "AWS::EC2::Instance",
            "Properties" : {
                "AvailabilityZone" : "ap-northeast-1a",
                "ImageId" : "ami-2a69be4c",
                "InstanceType" : "t2.micro",
                "KeyName" : "xxxxxxxx",
                "PrivateIpAddress" : "10.0.0.10",
                "SecurityGroupIds" : [
                    { "Ref" : "SecurityGroupBastionSSH" }
                ],
                "SubnetId" : { "Ref" : "DMZ1" },
                "Tags" : [{ "Key" : "Name", "Value" : "bastion" }]
            }
        },
        "BastionEIP" : {
            "Type" : "AWS::EC2::EIP",
            "Properties" : {
                "InstanceId" : { "Ref" : "Bastion" }
            }
        },
        "NFSDocument" : {
            "Type" : "AWS::SSM::Document",
            "Properties" : {
                "Content" : {
                    "schemaVersion" : "1.2",
                    "parameters" : {
                        "commands" : {
                            "type" : "String",
                            "default": ""
                        }
                    },
                    "runtimeConfig" : {
                        "aws:runShellScript" : {
                            "properties" : [
                                {
                                    "id" : "0.aws:runShellScript",
                                    "runCommand" : [
                                        "#!/bin/bash",
                                        "yum install -y xfsprogs expect",
                                        "echo \"#! /usr/bin/expect -f\" >> /tmp/expect",
                                        "echo \"spawn fdisk /dev/xvdb\">> /tmp/expect",
                                        "echo \"expect \\\"Command \\\" {send \\\"n\\n\\\"}\" >> /tmp/expect",
                                        "echo \"expect \\\"Select \\\"        {send \\\"\\n\\\"}\" >> /tmp/expect",
                                        "echo \"expect \\\"Partition number\\\"      {send \\\"1\\n\\\"}\" >> /tmp/expect",
                                        "echo \"expect \\\"First sector\\\"        {send \\\"\\n\\\"}\" >> /tmp/expect",
                                        "echo \"expect \\\"Last sector\\\"       {send \\\"\\n\\\"}\" >> /tmp/expect",
                                        "echo \"expect \\\"Command \\\" {send \\\"w\\n\\\"}\" >> /tmp/expect",
                                        "echo \"expect eof\" >> /tmp/expect",
                                        "expect /tmp/expect",
                                        "mkdir /share-vol",
                                        "mkfs -t xfs /dev/xvdb1",
                                        "mount -t xfs /dev/xvdb1 /share-vol",
                                        "echo \"/share-vol      10.0.2.10/255.255.255.0(sync,rw,no_root_squash)\" >> /etc/exports",
                                        "echo \"/share-vol      10.0.3.10/255.255.255.0(sync,rw,no_root_squash)\" >> /etc/exports",
                                        "/etc/rc.d/init.d/rpcbind restart",
                                        "chkconfig rpcbind on",
                                        "/etc/rc.d/init.d/nfs restart",
                                        "chkconfig nfs on"
                                    ]
                                }
                            ]
                        }
                    }
                }
            }
        },
        "APDocument" : {
            "Type" : "AWS::SSM::Document",
            "Properties" : {
                "Content" : {
                    "schemaVersion" : "1.2",
                    "parameters" : {
                        "commands" : {
                            "type" : "String",
                            "default": ""
                        }
                    },
                    "runtimeConfig" : {
                        "aws:runShellScript" : {
                            "properties" : [
                                {
                                    "id" : "0.aws:runShellScript",
                                    "runCommand" : [
                                        "#!/bin/bash",
                                        "yum install -y nfs-utils httpd",
                                        "/etc/rc.d/init.d/rpcbind restart",
                                        "chkconfig rpcbind on",
                                        "/etc/rc.d/init.d/nfs restart",
                                        "chkconfig nfs on",
                                        "mount -t nfs 10.0.4.10:/share-vol /var/www/html",
                                        "echo \"10.0.4.10:/share-vol    /var/www/html   nfs     defaults        0   0\" >> /etc/fstab",
                                        "service httpd restart",
                                        "chkconfig httpd on"
                                    ]
                                }
                            ]
                        }
                    }
                }
            }
        },
        "ALB" : {
            "Type" : "AWS::ElasticLoadBalancingV2::LoadBalancer",
            "Properties" : {
                "Name" : "AWS-DC-ALB",
                "Scheme" : "internet-facing",
                "SecurityGroups" : [ { "Ref" : "SecurityGroupELB" } ],
                "Subnets" : [
                    { "Ref" : "DMZ1" },
                    { "Ref" : "DMZ2" }
                ],
                "Tags" : [ { "Key" : "Name", "Value" : "AWS-DC-ALB" } ]
            }
        },
        "ALBTargetGroup" : {
            "Type" : "AWS::ElasticLoadBalancingV2::TargetGroup",
            "Properties" : {
                "HealthCheckPort" : "traffic-port",
                "HealthCheckProtocol" : "HTTP",
                "Name" : "AWS-DC-ALB-Targets",
                "Port" : "80",
                "Protocol" : "HTTP",
                "Tags" : [ { "Key" : "Name", "Value" : "AWS-DC-ALB-Targets" } ],
                "Targets" : [
                    { "Id" : { "Ref" : "Web01" }, "Port" : "80" },
                    { "Id" : { "Ref" : "Web02" }, "Port" : "80" }
                ],
                "VpcId" : { "Ref" : "VPC"}
            }
        },
        "ALBListener" : {
            "Type" : "AWS::ElasticLoadBalancingV2::Listener",
            "Properties" : {
                "DefaultActions" : [{
                    "Type" : "forward",
                    "TargetGroupArn" : { "Ref" : "ALBTargetGroup" }
                }],
                "LoadBalancerArn" : { "Ref" : "ALB" },
                "Port" : "80",
                "Protocol" : "HTTP"
            }
        },
        "S3Bucket": {
            "Type": "AWS::S3::Bucket",
            "Properties": {
                "BucketName" : "xxxxxxxx",
                "LifecycleConfiguration" : {
                    "Rules" : [
                        {
                            "Status" : "Enabled",
                            "Id" : "365-delete",
                            "ExpirationInDays" : 365
                        }
                    ]
                }
            }
        },
        "BucketPolicy" : {
            "Type" : "AWS::S3::BucketPolicy",
            "Properties" : {
                "Bucket" : {"Ref" : "S3Bucket"},
                "PolicyDocument" : {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Sid": "AWSCloudTrailAclCheck",
                            "Effect": "Allow",
                            "Principal": {
                                "Service":"cloudtrail.amazonaws.com"
                            },
                            "Action": "s3:GetBucketAcl",
                            "Resource": { "Fn::Join" : ["", ["arn:aws:s3:::", {"Ref":"S3Bucket"}]]}
                        },
                        {
                            "Sid": "AWSCloudTrailWrite",
                            "Effect": "Allow",
                            "Principal": {
                                "Service":"cloudtrail.amazonaws.com"
                            },
                            "Action": "s3:PutObject",
                            "Resource": { "Fn::Join" : ["", ["arn:aws:s3:::", {"Ref":"S3Bucket"}, "/AWSLogs/", {"Ref":"AWS::AccountId"}, "/*"]]},
                            "Condition": {
                                "StringEquals": {
                                    "s3:x-amz-acl": "bucket-owner-full-control"
                                }
                            }
                        }
                    ]
                }
            }
        },
        "myTrail" : {
            "DependsOn" : ["BucketPolicy"],
            "Type" : "AWS::CloudTrail::Trail",
            "Properties" : {
                "IncludeGlobalServiceEvents" : true ,
                "S3BucketName" : {"Ref":"S3Bucket"},
                "IsMultiRegionTrail" : true ,
                "IsLogging" : true,
                "TrailName" : "AWS-DC-CloudTrail"
            }
        }
    }
}

 

ネットワーク回りに関しては一般的なテンプレートなので説明を省略させていただきます。

 

SSMRole

「AmazonEC2RoleforSSM」と同じポリシーを持ったポリシーを作成し、新規ロールにアタッチしています。
既存のポリシーを使っても良かったのですが、stack作成時にIAM関連はカスタム名つけろと警告が出ていたので新規作成としています。

InstanceProfile

インスタンスプロファイルの作成になります。
インスタンスのPropertiesでRefで呼び出して、インスタンスにSSMRoleを割当てています。

Document

run command用のdocumentを作成します。
shell scriptの形式で、「runCommand」以降にコマンドを記載していきます。
なお、ここで指定した論理ID(リソース名)がrun command実行時のdocument名に反映されます。

ALB

今回ロードバランサーはALBを利用しています。
ALBは「ElasticLoadBalancingV2」との記載になるので注意してください(ターゲット、リスナーも同様)。
基本的にはAWSのドキュメント通りです。

S3

S3に関してはCloudTrail用にバケットを用意し、ライフサイクルのルールを設定しています。

 

Stackの作成

上記テンプレートを適宜修正し、jsonファイルとしてアップロードします。
注意する点はstack名の指定とIAMに関しての承認のみです。

stack名はrun commandのdocument名に反映されるので、わかりやすいものを指定することをお勧めします。

IAMの承認は、一意のカスタム名がつけられていること、権限が適切に設定されていることを確認してきています。
承認のチェックボックスにチェックを入れてstackを作成してください。

 

stackの作成後に「CREATE_COMPLETE」と表示されればOKです。

 

Run Commandの実行

CloudFormationでDocumentの作成、IAMロールの割り当てが完了しているので、コマンドを実行するだけになります。

 

Documentは上図の様に作成されます(stack名-論理ID-ランダム文字列)。
ドキュメントを選択後、対象のインスタンスを選択し、「Run」でコマンドを実行します。

今回の場合、NFSのマウントがあるので、NFSのDocumentから必ず実行してください。

コマンドが正常に実行されれば、WEBサイトの環境構築が完了となります。
コンテンツをアップロードし、ALBにアクセスするとWEBサイトの確認ができます。

 

注意事項

・今回の構成では各インスタンスへのSSH接続は踏み台(Bastion)経由となります。
・踏み台へのSSH接続用のセキュリティグループは別途開ける必要が御座います。
・stackの削除後もresourceを残す場合は、「"DeletionPolicy" : "Retain"」を各リソースに設定する必要が御座います。
amazon linux以外のAMIを利用する場合は、SSMエージェントを各サーバにインストールする必要が御座います(UserDataでインストールも可能)。

 

まとめ

今回はWEBサイト環境を、CloudFormationとrun commandで作成するというものを試しましたが、SSMエージェントがインストールされていれば、他のインスタンスでもDocumentが利用可能となるので、ansibleのような感覚で簡単に環境構築ができるというイメージを抱きました。

Documentを事前に用意しておき、要件に応じた設定を複数サーバにまとめて設定、という感じで作業の工数を大幅に削減することも可能となります。

便利な機能なので一度試してみてはいかがでしょうか。