こんにちは、株式会社アドグローブ web&アプリ事業部の富澤です。
前回はAWSの認定試験について記事を執筆させていただきました。
blog.adglobe.co.jp
なんとなくAWSの概要を掴んだところで、今回はAWS CDKを使用して実際に簡単なAWS環境を構築してみたいと思います。
また、AWSの用語が出てきますので想定読者としてAWS用語がある程度わかる人とさせていただきます。
AWSの環境を構築する方法として、AWSコンソール画面から構築する方法のほかにAWS CloudFormationというサービスを使用して環境構築を行うことができます。
CloudFormationはCloudformationテンプレートと呼ばれるでYAMLもしくはJSONといったテキスト形式でAWSの環境を構築・管理できるIaC(Infrastructure as Code)サービスになります。
{
"AWSTemplateFormatVersion " : "2010-09-09 ",
"Description " : "AWS CloudFormation Sample Template LAMP_Single_Instance: Create a LAMP stack using a single EC2 instance and a local MySQL database for storage. This template demonstrates using the AWS CloudFormation bootstrap scripts to install the packages and files necessary to deploy the Apache web server, PHP and MySQL at instance launch time. **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. "
} ,
"DBName ": {
"Default ": "MyDatabase ",
"Description " : "MySQL database name ",
"Type ": "String ",
"MinLength ": "1 ",
"MaxLength ": "64 ",
"AllowedPattern " : "[a-zA-Z][a-zA-Z0-9]* ",
"ConstraintDescription " : "must begin with a letter and contain only alphanumeric characters. "
} ,
"DBUser ": {
"NoEcho ": "true ",
"Description " : "Username for MySQL database access ",
"Type ": "String ",
"MinLength ": "1 ",
"MaxLength ": "16 ",
"AllowedPattern " : "[a-zA-Z][a-zA-Z0-9]* ",
"ConstraintDescription " : "must begin with a letter and contain only alphanumeric characters. "
} ,
"DBPassword ": {
"NoEcho ": "true ",
"Description " : "Password for MySQL database access ",
"Type ": "String ",
"MinLength ": "1 ",
"MaxLength ": "41 ",
"AllowedPattern " : "[a-zA-Z0-9]* ",
"ConstraintDescription " : "must contain only alphanumeric characters. "
} ,
"DBRootPassword ": {
"NoEcho ": "true ",
"Description " : "Root password for MySQL ",
"Type ": "String ",
"MinLength ": "1 ",
"MaxLength ": "41 ",
"AllowedPattern " : "[a-zA-Z0-9]* ",
"ConstraintDescription " : "must contain only alphanumeric characters. "
} ,
"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 " : "HVM64 " } ,
"t2.nano " : { "Arch " : "HVM64 " } ,
"t2.micro " : { "Arch " : "HVM64 " } ,
"t2.small " : { "Arch " : "HVM64 " } ,
"t2.medium " : { "Arch " : "HVM64 " } ,
"t2.large " : { "Arch " : "HVM64 " } ,
"m1.small " : { "Arch " : "HVM64 " } ,
"m1.medium " : { "Arch " : "HVM64 " } ,
"m1.large " : { "Arch " : "HVM64 " } ,
"m1.xlarge " : { "Arch " : "HVM64 " } ,
"m2.xlarge " : { "Arch " : "HVM64 " } ,
"m2.2xlarge " : { "Arch " : "HVM64 " } ,
"m2.4xlarge " : { "Arch " : "HVM64 " } ,
"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 " : "HVM64 " } ,
"c1.xlarge " : { "Arch " : "HVM64 " } ,
"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 " : "NATHVM64 " } ,
"t2.nano " : { "Arch " : "NATHVM64 " } ,
"t2.micro " : { "Arch " : "NATHVM64 " } ,
"t2.small " : { "Arch " : "NATHVM64 " } ,
"t2.medium " : { "Arch " : "NATHVM64 " } ,
"t2.large " : { "Arch " : "NATHVM64 " } ,
"m1.small " : { "Arch " : "NATHVM64 " } ,
"m1.medium " : { "Arch " : "NATHVM64 " } ,
"m1.large " : { "Arch " : "NATHVM64 " } ,
"m1.xlarge " : { "Arch " : "NATHVM64 " } ,
"m2.xlarge " : { "Arch " : "NATHVM64 " } ,
"m2.2xlarge " : { "Arch " : "NATHVM64 " } ,
"m2.4xlarge " : { "Arch " : "NATHVM64 " } ,
"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 " : "NATHVM64 " } ,
"c1.xlarge " : { "Arch " : "NATHVM64 " } ,
"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 " : {
"af-south-1 " : { "HVM64 " : "ami-064cc455f8a1ef504 ", "HVMG2 " : "NOT_SUPPORTED "} ,
"ap-east-1 " : { "HVM64 " : "ami-f85b1989 ", "HVMG2 " : "NOT_SUPPORTED "} ,
"ap-northeast-1 " : { "HVM64 " : "ami-0b2c2a754d5b4da22 ", "HVMG2 " : "ami-09d0e0e099ecabba2 "} ,
"ap-northeast-2 " : { "HVM64 " : "ami-0493ab99920f410fc ", "HVMG2 " : "NOT_SUPPORTED "} ,
"ap-northeast-3 " : { "HVM64 " : "ami-01344f6f63a4decc1 ", "HVMG2 " : "NOT_SUPPORTED "} ,
"ap-south-1 " : { "HVM64 " : "ami-03cfb5e1fb4fac428 ", "HVMG2 " : "ami-0244c1d42815af84a "} ,
"ap-southeast-1 " : { "HVM64 " : "ami-0ba35dc9caf73d1c7 ", "HVMG2 " : "ami-0e46ce0d6a87dc979 "} ,
"ap-southeast-2 " : { "HVM64 " : "ami-0ae99b503e8694028 ", "HVMG2 " : "ami-0c0ab057a101d8ff2 "} ,
"ca-central-1 " : { "HVM64 " : "ami-0803e21a2ec22f953 ", "HVMG2 " : "NOT_SUPPORTED "} ,
"cn-north-1 " : { "HVM64 " : "ami-07a3f215cc90c889c ", "HVMG2 " : "NOT_SUPPORTED "} ,
"cn-northwest-1 " : { "HVM64 " : "ami-0a3b3b10f714a0ff4 ", "HVMG2 " : "NOT_SUPPORTED "} ,
"eu-central-1 " : { "HVM64 " : "ami-0474863011a7d1541 ", "HVMG2 " : "ami-0aa1822e3eb913a11 "} ,
"eu-north-1 " : { "HVM64 " : "ami-0de4b8910494dba0f ", "HVMG2 " : "ami-32d55b4c "} ,
"eu-south-1 " : { "HVM64 " : "ami-08427144fe9ebdef6 ", "HVMG2 " : "NOT_SUPPORTED "} ,
"eu-west-1 " : { "HVM64 " : "ami-015232c01a82b847b ", "HVMG2 " : "ami-0d5299b1c6112c3c7 "} ,
"eu-west-2 " : { "HVM64 " : "ami-0765d48d7e15beb93 ", "HVMG2 " : "NOT_SUPPORTED "} ,
"eu-west-3 " : { "HVM64 " : "ami-0caf07637eda19d9c ", "HVMG2 " : "NOT_SUPPORTED "} ,
"me-south-1 " : { "HVM64 " : "ami-0744743d80915b497 ", "HVMG2 " : "NOT_SUPPORTED "} ,
"sa-east-1 " : { "HVM64 " : "ami-0a52e8a6018e92bb0 ", "HVMG2 " : "NOT_SUPPORTED "} ,
"us-east-1 " : { "HVM64 " : "ami-032930428bf1abbff ", "HVMG2 " : "ami-0aeb704d503081ea6 "} ,
"us-east-2 " : { "HVM64 " : "ami-027cab9a7bf0155df ", "HVMG2 " : "NOT_SUPPORTED "} ,
"us-west-1 " : { "HVM64 " : "ami-088c153f74339f34c ", "HVMG2 " : "ami-0a7fc72dc0e51aa77 "} ,
"us-west-2 " : { "HVM64 " : "ami-01fee56b22f308154 ", "HVMG2 " : "ami-0fe84a5b4563d8f27 "}
}
},
"Resources " : {
"WebServerInstance ": {
"Type ": "AWS::EC2::Instance ",
"Metadata " : {
"AWS::CloudFormation::Init " : {
"configSets " : {
"InstallAndRun " : [ "Install ", "Configure " ]
} ,
"Install " : {
"packages " : {
"yum " : {
"mysql " : [] ,
"mysql-server " : [] ,
"mysql-libs " : [] ,
"httpd " : [] ,
"php " : [] ,
"php-mysql " : []
}
} ,
"files " : {
"/var/www/html/index.php " : {
"content " : { "Fn::Join " : [ "", [
"<html> \n ",
" <head> \n ",
" <title>AWS CloudFormation PHP Sample</title> \n ",
" <meta http-equiv= \" Content-Type \" content= \" text/html; charset=ISO-8859-1 \" > \n ",
" </head> \n ",
" <body> \n ",
" <h1>Welcome to the AWS CloudFormation PHP Sample</h1> \n ",
" <p/> \n ",
" <?php \n ",
" // Print out the current data and time \n ",
" print \" The Current Date and Time is: <br/> \" ; \n ",
" print date( \" g:i A l, F j Y. \" ); \n ",
" ?> \n ",
" <p/> \n ",
" <?php \n ",
" // Setup a handle for CURL \n ",
" $curl_handle=curl_init(); \n ",
" curl_setopt($curl_handle,CURLOPT_CONNECTTIMEOUT,2); \n ",
" curl_setopt($curl_handle,CURLOPT_RETURNTRANSFER,1); \n ",
" // Get the hostname of the intance from the instance metadata \n ",
" curl_setopt($curl_handle,CURLOPT_URL,'http://169.254.169.254/latest/meta-data/public-hostname'); \n ",
" $hostname = curl_exec($curl_handle); \n ",
" if (empty($hostname)) \n ",
" { \n ",
" print \" Sorry, for some reason, we got no hostname back <br /> \" ; \n ",
" } \n ",
" else \n ",
" { \n ",
" print \" Server = \" . $hostname . \" <br /> \" ; \n ",
" } \n ",
" // Get the instance-id of the intance from the instance metadata \n ",
" curl_setopt($curl_handle,CURLOPT_URL,'http://169.254.169.254/latest/meta-data/instance-id'); \n ",
" $instanceid = curl_exec($curl_handle); \n ",
" if (empty($instanceid)) \n ",
" { \n ",
" print \" Sorry, for some reason, we got no instance id back <br /> \" ; \n ",
" } \n ",
" else \n ",
" { \n ",
" print \" EC2 instance-id = \" . $instanceid . \" <br /> \" ; \n ",
" } \n ",
" $Database = \" localhost \" ; \n ",
" $DBUser = \" ", { "Ref " : "DBUser "} , "\" ; \n ",
" $DBPassword = \" ", { "Ref " : "DBPassword "} , "\" ; \n ",
" print \" Database = \" . $Database . \" <br /> \" ; \n ",
" $dbconnection = mysql_connect($Database, $DBUser, $DBPassword) \n ",
" or die( \" Could not connect: \" . mysql_error()); \n ",
" print ( \" Connected to $Database successfully \" ); \n ",
" mysql_close($dbconnection); \n ",
" ?> \n ",
" <h2>PHP Information</h2> \n ",
" <p/> \n ",
" <?php \n ",
" phpinfo(); \n ",
" ?> \n ",
" </body> \n ",
"</html> \n "
]]} ,
"mode " : "000600 ",
"owner " : "apache ",
"group " : "apache "
} ,
"/tmp/setup.mysql " : {
"content " : { "Fn::Join " : [ "", [
"CREATE DATABASE ", { "Ref " : "DBName " } , "; \n ",
"GRANT ALL ON ", { "Ref " : "DBName " } , ".* TO ' ", { "Ref " : "DBUser " } , "'@localhost IDENTIFIED BY ' ", { "Ref " : "DBPassword " } , "'; \n "
]]} ,
"mode " : "000400 ",
"owner " : "root ",
"group " : "root "
} ,
"/etc/cfn/cfn-hup.conf " : {
"content " : { "Fn::Join " : [ "", [
"[main] \n ",
"stack= ", { "Ref " : "AWS::StackId " } , "\n ",
"region= ", { "Ref " : "AWS::Region " } , "\n "
]]} ,
"mode " : "000400 ",
"owner " : "root ",
"group " : "root "
} ,
"/etc/cfn/hooks.d/cfn-auto-reloader.conf " : {
"content ": { "Fn::Join " : [ "", [
"[cfn-auto-reloader-hook] \n ",
"triggers=post.update \n ",
"path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init \n ",
"action=/opt/aws/bin/cfn-init -v ",
" --stack ", { "Ref " : "AWS::StackName " } ,
" --resource WebServerInstance ",
" --configsets InstallAndRun ",
" --region ", { "Ref " : "AWS::Region " } , "\n ",
"runas=root \n "
]]} ,
"mode " : "000400 ",
"owner " : "root ",
"group " : "root "
}
},
"services " : {
"sysvinit " : {
"mysqld " : { "enabled " : "true ", "ensureRunning " : "true " } ,
"httpd " : { "enabled " : "true ", "ensureRunning " : "true " } ,
"cfn-hup " : { "enabled " : "true ", "ensureRunning " : "true ",
"files " : [ "/etc/cfn/cfn-hup.conf ", "/etc/cfn/hooks.d/cfn-auto-reloader.conf "]}
}
}
},
"Configure " : {
"commands " : {
"01_set_mysql_root_password " : {
"command " : { "Fn::Join " : [ "", [ "mysqladmin -u root password ' ", { "Ref " : "DBRootPassword " } , "' "]]} ,
"test " : { "Fn::Join " : [ "", [ "$(mysql ", { "Ref " : "DBName " } , " -u root --password=' ", { "Ref " : "DBRootPassword " } , "' >/dev/null 2>&1 </dev/null); (( $? != 0 )) "]]}
} ,
"02_create_database " : {
"command " : { "Fn::Join " : [ "", [ "mysql -u root --password=' ", { "Ref " : "DBRootPassword " } , "' < /tmp/setup.mysql "]]} ,
"test " : { "Fn::Join " : [ "", [ "$(mysql ", { "Ref " : "DBName " } , " -u root --password=' ", { "Ref " : "DBRootPassword " } , "' >/dev/null 2>&1 </dev/null); (( $? != 0 )) "]]}
}
}
}
}
},
"Properties ": {
"ImageId " : { "Fn::FindInMap " : [ "AWSRegionArch2AMI ", { "Ref " : "AWS::Region " } ,
{ "Fn::FindInMap " : [ "AWSInstanceType2Arch ", { "Ref " : "InstanceType " } , "Arch " ] } ] } ,
"InstanceType " : { "Ref " : "InstanceType " } ,
"SecurityGroups " : [ { "Ref " : "WebServerSecurityGroup "} ] ,
"KeyName " : { "Ref " : "KeyName " } ,
"UserData " : { "Fn::Base64 " : { "Fn::Join " : [ "", [
"#!/bin/bash -xe \n ",
"yum update -y aws-cfn-bootstrap \n ",
"# Install the files and packages from the metadata \n ",
"/opt/aws/bin/cfn-init -v ",
" --stack ", { "Ref " : "AWS::StackName " } ,
" --resource WebServerInstance ",
" --configsets InstallAndRun ",
" --region ", { "Ref " : "AWS::Region " } , "\n ",
"# Signal the status from cfn-init \n ",
"/opt/aws/bin/cfn-signal -e $? ",
" --stack ", { "Ref " : "AWS::StackName " } ,
" --resource WebServerInstance ",
" --region ", { "Ref " : "AWS::Region " } , "\n "
]]}}
},
"CreationPolicy " : {
"ResourceSignal " : {
"Timeout " : "PT5M "
}
}
},
"WebServerSecurityGroup " : {
"Type " : "AWS::EC2::SecurityGroup ",
"Properties " : {
"GroupDescription " : "Enable HTTP access via port 80 ",
"SecurityGroupIngress " : [
{ "IpProtocol " : "tcp ", "FromPort " : "80 ", "ToPort " : "80 ", "CidrIp " : "0.0.0.0/0 "} ,
{ "IpProtocol " : "tcp ", "FromPort " : "22 ", "ToPort " : "22 ", "CidrIp " : { "Ref " : "SSHLocation "}}
]
}
}
},
"Outputs " : {
"WebsiteURL " : {
"Description " : "URL for newly created LAMP stack ",
"Value " : { "Fn::Join " : [ "", [ "http:// ", { "Fn::GetAtt " : [ "WebServerInstance ", "PublicDnsName " ]}]] }
}
}
}
参照:AWS アプリケーションフレームワーク
例を見ていただければ分かるように、正直読みづらいと感じた人が多いのではないでしょうか。
これを覚えて活用していくのは結構な学習コストがかかることが予想されます。
そこで、今回の題材としたAWS CDKを利用することで可読性の向上だったり、学習コストの低減といったメリットを受けることができます。
AWS CDKとは
CDKはCloud Development Kitの略称になります。
どういったものかを簡単に説明すると、プログラムからCloudFormationテンプレートを作成できるツールになります。
2019年にAWS公式で発表された比較的新しいツールで、2022年4月現時点では、JavaScript、TypeScript、Python、Java、C#が一般公開されて使用できる言語になります。
まだ開発プレビューですがGo言語も使用可能なようです。
CDKにはコンストラクトライブラリというものが含まれていて、これがAWSリソースを表すコンストラクト(単一または複数のAWSリソースのようなもの)を定義しています。
このコンストラクトには3つのレイヤーがあり、L1やL2といった感じで呼ばれています。
レイヤーの違いについて簡単に説明すると以下のようになります。
L1:
CFNリソース(または「レイヤー1」の略)またはCfn(CloudFormationの略)リソースと呼ばれる低レベルの構成から始まる、3つの異なるレベルの構成があります。これらのコンストラクトは、AWSCloudFormationで利用可能なすべてのリソースを直接表します。CFNリソースは、AWSCloudFormationリソース仕様から定期的に生成されます。それらはCfnXyzという名前で、Xyzはリソースの名前です。たとえば、CfnBucketはAWS :: S3::Bucketを表しますAWSCloudFormationリソース。Cfnリソースを使用する場合は、すべてのリソースプロパティを明示的に設定する必要があります。これには、基盤となるAWSCloudFormationリソースモデルの詳細を完全に理解する必要があります。
CloudFormationテンプレートのように宣言したリソースと作成されるリソースが1対1で作成されるような感じです。
L2:
次のレベルのコンストラクトであるL2もAWSリソースを表しますが、より高いレベルのインテントベースのAPIを備えています。これらは同様の機能を提供しますが、CFNリソース構造を使用して自分で作成するデフォルト、ボイラープレート、およびグルーロジックを提供します。AWSコンストラクトは便利なデフォルトを提供し、それらが表すAWSリソースに関するすべての詳細を知る必要性を減らし、リソースの操作を簡単にする便利なメソッドを提供します。たとえば、s3.Bucketクラスは、バケットにライフサイクルルールを追加するbucket.addLifeCycleRule()などの追加のプロパティとメソッドを持つAmazonS3バケットを表します
AWSのベストプラクティスに沿って、いい感じにリソースが作成されるような感じです。(例えばEC2でサーバを立てようとした場合、EC2リソースを作成すると自動でネットワークの構成等もやってくれるようなイメージ)
L3:
AWSで一般的なタスクを完了するのに役立つように設計されており、多くの場合、複数の種類のリソースが関係しています。たとえば、aws-ecs-patterns.ApplicationLoadBalancedFargateServiceコンストラクトは、Application Load Balancer(ALB)を採用したAWSFargateコンテナクラスターを含むアーキテクチャを表します。aws-apigateway.LambdaRestApiコンストラクトは、 AWSLambda関数によってサポートされるAmazonAPIGatewayAPIを表します。
L3についてはまだ触っていないのですが、おそらくAWS側がアーキテクチャのパターンとして持っているテンプレートのようなもので環境を構築してくれるものだと思います。
引用:Constructs - AWS Cloud Development Kit (AWS CDK) v2
今回はまずL1のみで簡単なAWS環境を構築してみようと思います。
言語はpythonを使用しますが、他のCDKの記事ではTypeScriptが多そうでした。
pythonはサンプルが少ないという理由とTypeScriptで書くとコピペする自信があるのでpythonにしました。
作成するAWS構成
今回はEC2インスタンスでwebサーバを立てて、ブラウザ画面からアクセスできるようにしたいと思います。
また、VPC内の通信ログを取ってみようと思います。
作成するAWS構成図
実装
aws-cdkが必要になりますが、ローカルPCにインストールしているものとして進めます(npmからインストール可能)。
aws-cdkのバージョンは2.18.0で言語にpythonを使用します。
まずはcdkの初期化コマンドを実行します。
以下のようなメッセージが出たら成功です。
$ mkdir try-aws-cdk
$ cdk init --language python
Applying project template app for python
This is a blank project for Python development with CDK.
The `cdk.json` file tells the CDK Toolkit how to execute your app.
This project is set up like a standard Python project. The initialization
process also creates a virtualenv within this project, stored under the `.venv`
directory. To create the virtualenv it assumes that there is a `python3`
( or `python` for Windows) executable in your path with access to the `venv`
package. If for any reason the automatic creation of the virtualenv fails,
you can create the virtualenv manually.
To manually create a virtualenv on MacOS and Linux:
$ python3 -m venv .venv
After the init process completes and the virtualenv is created, you can use the following
step to activate your virtualenv.
$ source .venv/bin/activate
If you are a Windows platform, you would activate the virtualenv like this:
% .venv\S cripts\a ctivate.bat
Once the virtualenv is activated, you can install the required dependencies.
$ pip install -r requirements.txt
At this point you can now synthesize the CloudFormation template for this code.
$ cdk synth
To add additional dependencies, for example other CDK libraries, just add
them to your `setup.py` file and rerun the `pip install -r requirements.txt`
command .
* `cdk ls ` list all stacks in the app
* `cdk synth` emits the synthesized CloudFormation template
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk docs` open CDK documentation
Enjoy!
Initializing a new git repository...
Please run ' python3 -m venv .venv '!
Executing Creating virtualenv...
チェックマーク_緑: All done !
以下のようなディレクトリが作成されます。
README.mdの手順に従って初期設定を行いましょう。
.
├── README.md
├── app.py
├── cdk.json
├── requirements-dev.txt
├── requirements.txt
├── source.bat
├── try_aws_cdk
│ ├── _init_.py
│ └── try_aws_cdk_stack.py
└── tests
├── _init_.py
└── unit
├── _init_.py
└── test_try_aws_cdk_stack.py
try_aws_cdk_stack.pyを開くと以下のような記述があるのですが、こちらにリソースを定義していきます。
from aws_cdk import (
Stack,
)
from constructs import Construct
class TryAwsCdkStack (Stack):
def _init_ (self, scope: Construct, construct_id: str , **kwargs) -> None :
super ()._init_(scope, construct_id, **kwargs)
→ ここから書きます
VPCの作成
CIDRブロックに192.168.0.0/24を割り当てていますが、ある程度のアドレス数が確保できれば「/24」ではなくても大丈夫です。
「enable_dns_hostnames」と「enable_dns_support」を両方TrueにすることでAWS側が名前解決を行ってくれるらしいので一応設定しました。
属性の両方が true に設定されている場合、次のようになります。
・パブリック IP アドレスを持つインスタンスは、対応するパブリック DNS ホスト名を受け取ります。
・Amazon Route 53 Resolver サーバーは、Amazon が提供するプライベート DNS ホスト名を解決できます。
引用:VPC の DNS 属性 - Amazon Virtual Private Cloud
cfn_vpc = ec2.CfnVPC(self, 'VPC' ,
cidr_block='192.168.0.0/24' ,
enable_dns_hostnames=True ,
enable_dns_support=True ,
tags=[cdk.CfnTag(
key='Name' ,
value='My-Vpc' ,
)]
)
サブネットの作成
CIDRブロックは先ほどVPCで定義した「192.168.0.0/24」の範囲内で設定します。
vpc_idにcfn_vpcのrefプロパティを入れることで先ほど作成したVPCを参照できるようになります。
「map_public_ip_on_launch」をtrueにしてサブネットで起動したインスタンスにパブリックIPアドレスの割り当てを許可します。
cfn_subnet = ec2.CfnSubnet(self, 'MyCfnSubnet' ,
cidr_block='192.168.0.1/26' ,
vpc_id=cfn_vpc.ref,
map_public_ip_on_launch=True ,
availability_zone='ap-northeast-1a' ,
tags=[cdk.CfnTag(
key='Name' ,
value='My-Public-Subnet' ,
)]
)
ルートテーブルの作成
ルートテーブルを作成します。
サブネットと同じようにvpc_idは作成済みのcfn_vpcを参照します。
ルーティング自体は後ほど別途定義します。
cfn_route_table = ec2.CfnRouteTable(self, 'MyCfnRouteTable' ,
vpc_id=cfn_vpc.ref,
tags=[cdk.CfnTag(
key='Name' ,
value='My-Public-Route-Table' ,
)]
)
インターネットゲートウェイの作成
インターネットとの接続用にインターネットゲートウェイを作成します。
cfn_internet_gateway = ec2.CfnInternetGateway(self, 'MyCfnInternetGateway' ,
tags=[cdk.CfnTag(
key='Name' ,
value='My-Internet-Gatway' ,
)]
)
インターネットゲートウェイをVPCにアタッチ
作成したインターネットゲートウェイをVPCにアタッチします。
cfn_vPCGateway_attachment = ec2.CfnVPCGatewayAttachment(self, 'MyCfnVPCGatewayAttachment' ,
vpc_id=cfn_vpc.ref,
internet_gateway_id=cfn_internet_gateway.ref,
)
ルーティング定義
インターネットとの接続用のルート定義を作成します。
「0.0.0.0/0」は全てのIPv4アドレスになります。
ここではインターネットゲートウェイのトラフィックがインターネットに出られるようにしています。
cfn_route = ec2.CfnRoute(self, 'MyCfnRoute' ,
route_table_id=cfn_route_table.ref,
destination_cidr_block='0.0.0.0/0' ,
gateway_id=cfn_internet_gateway.ref,
)
サブネットとルートテーブルの関連付け
作成済みのサブネットとルートテーブルを関連付けます。
注意点として、サブネットを明示的にルートテーブルに関連付けを行わない場合はデフォルトで用意されたルートテーブルに自動で関連付けられます。
意図しない挙動になることもあるため気を付けましょう(体験談)。
ec2.CfnSubnetRouteTableAssociation(self, 'MySubnetRouteTableAssociation' ,
route_table_id=cfn_route_table.ref,
subnet_id=cfn_subnet.ref
)
セキュリティグループ
EC2インスタンス用にセキュリティグループを作成します。
セキュリティグループはインスタンスに対するファイアウォールのようなのもで、IPアドレスやIPプロトコル、ポート番号を設定することで通信を許可することができます。
ここではIPアドレス「0.0.0.0/0」や「80」番ポートを設定してhttpの通信を許可させます。
必要に応じてSSHも許可したりします。
注意点としてSSHを許可する場合は「0.0.0.0/0」にしないようにIPは絞りましょう。
そうしないとどこからでもこのサーバにログインできてしまうので。
cfn_public_security_group = ec2.CfnSecurityGroup(self, 'My-Security-Group-1' ,
group_description='Allow ssh access to ec2 instances' ,
security_group_ingress=[
ec2.CfnSecurityGroup.IngressProperty(
ip_protocol='tcp' ,
cidr_ip='0.0.0.0/0' ,
description='open world' ,
from_port=80 ,
to_port=80 ,
)
],
tags=[cdk.CfnTag(
key='Name' ,
value='My-Public-Sg' ,
)],
vpc_id=cfn_vpc.ref
)
EC2インスタンスの作成
EC2インスタンスを作成する前にユーザデータを作成します。
ユーザデータを作成してインスタンスに渡すことで、インスタンスが起動後にスクリプトやコマンドを実行することができます。
今回はphpとapacheをインストールしてapacheを起動しています。
ユーザデータはbase64形式に変換する必要があるようです。
EC2は設定項目が少し多めですが、ポイントはimage_idはAMIのidを設定するところです。
存在しないidを設定しないように注意しましょう(体験談として、idが間違えていたことで通信できずに原因究明で時間かかりました...)。
OSはAmazon Linux2を使用しています。
また、SSH用のキーペアをCDKで作成しようと思ったのですが、公式ではAPIが用意されていませんでした。
そのためサードパーティー製のライブラリを使用してみたのですがキー自体は作成できるもののうまくSSHログインができなかったのでコマンドから作成したキーペアを使用するようにしました(そのため、ここはCDKではなくAWSコンソール画面からキーをアップロードしています)。
commands_user_data = ec2.UserData.for_linux()
commands_user_data.add_commands('yum -y update' )
commands_user_data.add_commands('amazon-linux-extras install -y php8.0' )
commands_user_data.add_commands('yum install -y httpd' )
commands_user_data.add_commands('systemctl start httpd.service' )
cfn_instance = ec2.CfnInstance(self, 'MyCfnInstance' ,
instance_type='t2.nano' ,
block_device_mappings=[ec2.CfnInstance.BlockDeviceMappingProperty(
device_name='/dev/xvda' ,
ebs=ec2.CfnInstance.EbsProperty(
delete_on_termination=True ,
encrypted=True ,
iops=3000 ,
volume_size=8 ,
volume_type='gp3'
),
no_device=ec2.CfnInstance.NoDeviceProperty(),
)],
subnet_id=cfn_subnet.ref,
security_group_ids=[cfn_public_security_group.ref],
image_id='ami-0ab0bbbd329f565e6' ,
key_name='traning-key' ,
user_data=cdk.Fn.base64(commands_user_data.render()),
tags=[cdk.CfnTag(
key='Name' ,
value='My-Public-Server' ,
)]
)
ネットワークACLの作成
ネットワークACLはサブネットに対するファイアウォールのようなものでVPCのセキュリティオプションレイヤーです。
この後通信ルールを設定します。
cfn_network_acl = ec2.CfnNetworkAcl(self, 'MyCfnNetworkAcl' ,
vpc_id=cfn_vpc.ref,
tags=[cdk.CfnTag(
key='Name' ,
value='My-Network-Acl' ,
)]
)
ネットワークACLのルール作成
ここでは通信を全て許可する設定を行っています。
cfn_network_acl_entry = ec2.CfnNetworkAclEntry(self, 'MyCfnNetworkAclEntry2' ,
network_acl_id=cfn_network_acl.ref,
protocol=-1 ,
rule_action='allow' ,
rule_number=100 ,
cidr_block='0.0.0.0/0' ,
egress=True ,
)
サブネットとネットワークACLの関連付け
作成済みのネットワークACLとVPCを関連付けます。
cfn_subnet_network_acl_association = ec2.CfnSubnetNetworkAclAssociation(self, 'MyCfnSubnetNetworkAclAssociation' ,
network_acl_id=cfn_network_acl.ref,
subnet_id=cfn_subnet.ref,
)
ここまででEC2インスタンスとネットワーク設定を行いました。
次にVPCフローログを取得して、CloudWatch Logsに送信するまでの設定を行っていきます。
VPCフローログ用のロググループの作成
VPCフローログ用のロググループを作成します。
log_group = logs.CfnLogGroup(self, 'MyLogGroup' ,
log_group_name='vpc-flow-logs' ,
tags=[cdk.CfnTag(
key='Name' ,
value='My-Vpc-Flow-Log' ,
)]
)
ロールの作成
CloudWatch LogsへVPCフローログを発行するためのポリシーとロールを作成します。
IAMポリシーが一見難しそうですが以下を参照しました。
参照:CloudWatch Logs へのフローログの発行 - Amazon Virtual Private Cloud
cfn_role = iam.CfnRole(self, 'MyCfnRole' ,
assume_role_policy_document={
'Version' : '2012-10-17' ,
'Statement' : [{
'Action' : 'sts:AssumeRole' ,
'Effect' : 'Allow' ,
'Principal' : {'Service' : 'vpc-flow-logs.amazonaws.com' }
}]
},
description='vpc flow log role' ,
policies=[iam.CfnRole.PolicyProperty(
policy_document={
'Version' : '2012-10-17' ,
'Statement' : [
{
'Effect' : 'Allow' ,
'Action' : [
'logs:CreateLogGroup' ,
'logs:CreateLogStream' ,
'logs:PutLogEvents' ,
'logs:DescribeLogGroups' ,
'logs:DescribeLogStreams'
],
'Resource' : '*'
}
]
},
policy_name='My-Cloud-Watch-Log-Policy' ,
)],
role_name='My-Vpc-Flow-Log-Role' ,
tags=[cdk.CfnTag(
key='Name' ,
value='My-Role' ,
)]
)
VPCフローログの作成
VPCフローログを作成します。
「deliver_logs_permission_arn」はCloudWatch Logsのロググループにフローログを公開するためのIAMロールのARN(Amazon リソースネーム)の設定です。
「log_destination」は先ほど作成したロググループにします。
cfn_flow_log = ec2.CfnFlowLog(self, 'MyCfnFlowLog' ,
resource_id=cfn_subnet.ref,
resource_type='Subnet' ,
traffic_type='ALL' ,
deliver_logs_permission_arn=cfn_role.attr_arn,
max_aggregation_interval=60 ,
log_format='${version} ${vpc-id} ${subnet-id} ${instance-id} ${account-id} ${type} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${pkt-src-aws-service} ${pkt-dst-aws-service} ${flow-direction} ${traffic-path} ${action}' ,
log_destination=log_group.attr_arn,
log_destination_type='cloud-watch-logs' ,
tags=[cdk.CfnTag(
key='Name' ,
value='My-Flow-Log'
)]
)
これで全てのAWSリソースの作成は終わりました。
デプロイ
これまで書いてきたものをAWS環境にデプロイする必要があります。
まずは以下のコマンドを実行してCloudformation用のS3バケットを作成します。
もし、AWS側でアクセスキー等の設定をしていない場合は設定してから実行してください。
$ cdk bootstrap
S3バケットが作成できたら以下コマンドでデプロイします。
$ cdk deploy
AWSリソースが作成されていれば作業完了になります。
まとめ
いかがでしたでしょうか。
今回はL1のみで環境構築を行っているため宣言したAWSリソースと構築されたAWSリソースが1対1の関係で構築されるので分かりやすいと思います。
おそらくCloudformationテンプレートを直書きするよりは、可読性だったり学習しやすさは個人的にはAWS CDKに軍配が上がりそうです。
とはいえ、まだまだAWS CDKの良さは引き出せてはいないと思うので今後も勉強を続けていきます。
「入門編」として記事を執筆していますので今度は「応用編」等書いていこうと思います。