Recently, I haven't written many "I tried" articles. This is because the official websites are so extensive that you end up just tracing the official websites.
** This time, I happened to know AWS CDK and was deeply moved, so I will write it as it is. ** **
It's a quote from the official website, but you can create AWS resources in a familiar programming language without using CloudFormation or Teraform! !! ** This is now! It's too now! !! ** **
The AWS Cloud Development Kit (AWS CDK) is an open source software development framework for modeling and provisioning cloud application resources using familiar programming languages.
https://aws.amazon.com/jp/cdk/
I won't go into it because it deviates from the main subject, but the information that flows is very useful! I highly recommend reading it. https://tomomano.gitlab.io/intro-aws/
This time, I will trace the process of creating a new VPC using Python and provisioning ALB and EC2 from this. https://github.com/aws-samples/aws-cdk-examples/tree/master/python/new-vpc-alb-asg-mysql It would be nice to just pour the example into your environment, but I don't think that's the case. (Because the CIDR of VPC is already assigned and it may collide) ** Therefore, what part of the example is practical to play with? I would like to explain mainly from this perspective. ** **
I will finally get into the main subject here.
Of course, you need an AWS account. (This area is omitted as expected) And various installations are required. Since you can build an environment by completing the Workshop, it is only necessary to have Hello, CDK !, so we strongly recommend that you do it. https://cdkworkshop.com/
As a rough procedure, clone the example mentioned above and use Python's new-vpc-alb-asg-mysql. Let's change the example a little and pour it in first. (This is the first lap. Later, there will be the second lap)
clone I forgot to write it, but I'm using Ubuntu 18.04. For study material, subdirectories are dug for each theme in a directory called practice. So, clone it under ~ / practice / cdk /. (Please change this area to good)
$ git clone https://github.com/aws-samples/aws-cdk-examples.git
As a result, Python's new-vpc-alb-asg-mysql subordinate becomes as follows.
$ tree
.
├── app.py
├── cdk.json
├── cdk_vpc_ec2
│ ├── cdk_ec2_stack.py
│ ├── cdk_rds_stack.py
│ └── cdk_vpc_stack.py
├── img_demo_cdk_vpc.png
├── README.md
├── requirements.txt
├── setup.py
└── user_data
└── user_data.sh
2 directories, 10 files
First, make the minimum changes and actually pour it in. The file to be modified is
is. I'm sorry it's hard to understand, but the Japanese comments in the source code were made by me, so please refer to them.
Since it will be long, the contents of the file modification will be collapsed.
cdk_vpc_stack.py (before change)
from aws_cdk import core
import aws_cdk.aws_ec2 as ec2
class CdkVpcStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
# The code that defines your stack goes here
self.vpc = ec2.Vpc(self, "VPC",
max_azs=2,
cidr="10.10.0.0/16", #CIDR is written in a fixed manner!
# configuration will create 3 groups in 2 AZs = 6 subnets.
subnet_configuration=[ec2.SubnetConfiguration(
subnet_type=ec2.SubnetType.PUBLIC,
name="Public",
cidr_mask=24
), ec2.SubnetConfiguration(
subnet_type=ec2.SubnetType.PRIVATE,
name="Private",
cidr_mask=24
), ec2.SubnetConfiguration(
subnet_type=ec2.SubnetType.ISOLATED,
name="DB",
cidr_mask=24
)
],
# nat_gateway_provider=ec2.NatProvider.gateway(),
nat_gateways=2,
)
core.CfnOutput(self, "Output",
value=self.vpc.vpc_id)
The CIDR of the VPC is 10.10.0.0/16. If this is okay, you can run it as it is, but in my case I had to change it, so I will change it. Also, since it is not easy to use if it is written inline, I decided to cut it out as a variable.
After change
cdk_vpc_stack.py (after change)
from aws_cdk import core
import aws_cdk.aws_ec2 as ec2
vpc_cidr = "x.x.x.x/16" #Define CIDR as a variable and rewrite it to an arbitrary value.
class CdkVpcStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
# The code that defines your stack goes here
self.vpc = ec2.Vpc(self, "VPC",
max_azs=2,
cidr=vpc_cidr, #Changed to use variables
# configuration will create 3 groups in 2 AZs = 6 subnets.
subnet_configuration=[ec2.SubnetConfiguration(
subnet_type=ec2.SubnetType.PUBLIC,
name="Public",
cidr_mask=24
), ec2.SubnetConfiguration(
subnet_type=ec2.SubnetType.PRIVATE,
name="Private",
cidr_mask=24
), ec2.SubnetConfiguration(
subnet_type=ec2.SubnetType.ISOLATED,
name="DB",
cidr_mask=24
)
],
# nat_gateway_provider=ec2.NatProvider.gateway(),
nat_gateways=2,
)
core.CfnOutput(self, "Output",
value=self.vpc.vpc_id)
Next is EC2. (In short, change the key_name of the key pair.)
Change before
cdk_ec2_stack.py (before change)
from aws_cdk import core
import aws_cdk.aws_ec2 as ec2
import aws_cdk.aws_elasticloadbalancingv2 as elb
import aws_cdk.aws_autoscaling as autoscaling
ec2_type = "t2.micro"
key_name = "id_rsa" # Setup key_name for EC2 instance login
linux_ami = ec2.AmazonLinuxImage(generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX,
edition=ec2.AmazonLinuxEdition.STANDARD,
virtualization=ec2.AmazonLinuxVirt.HVM,
storage=ec2.AmazonLinuxStorage.GENERAL_PURPOSE
) # Indicate your AMI, no need a specific id in the region
with open("./user_data/user_data.sh") as f:
user_data = f.read()
class CdkEc2Stack(core.Stack):
def __init__(self, scope: core.Construct, id: str, vpc, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
# Create Bastion
bastion = ec2.BastionHostLinux(self, "myBastion",
vpc=vpc,
subnet_selection=ec2.SubnetSelection(
subnet_type=ec2.SubnetType.PUBLIC),
instance_name="myBastionHostLinux",
instance_type=ec2.InstanceType(instance_type_identifier="t2.micro"))
# Setup key_name for EC2 instance login if you don't use Session Manager
# bastion.instance.instance.add_property_override("KeyName", key_name)
bastion.connections.allow_from_any_ipv4(
ec2.Port.tcp(22), "Internet access SSH")
# Create ALB
alb = elb.ApplicationLoadBalancer(self, "myALB",
vpc=vpc,
internet_facing=True,
load_balancer_name="myALB"
)
alb.connections.allow_from_any_ipv4(
ec2.Port.tcp(80), "Internet access ALB 80")
listener = alb.add_listener("my80",
port=80,
open=True)
# Create Autoscaling Group with fixed 2*EC2 hosts
self.asg = autoscaling.AutoScalingGroup(self, "myASG",
vpc=vpc,
vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE),
instance_type=ec2.InstanceType(instance_type_identifier=ec2_type),
machine_image=linux_ami,
key_name=key_name,
user_data=ec2.UserData.custom(user_data),
desired_capacity=2,
min_capacity=2,
max_capacity=2,
# block_devices=[
# autoscaling.BlockDevice(
# device_name="/dev/xvda",
# volume=autoscaling.BlockDeviceVolume.ebs(
# volume_type=autoscaling.EbsDeviceVolumeType.GP2,
# volume_size=12,
# delete_on_termination=True
# )),
# autoscaling.BlockDevice(
# device_name="/dev/sdb",
# volume=autoscaling.BlockDeviceVolume.ebs(
# volume_size=20)
# # 20GB, with default volume_type gp2
# )
# ]
)
self.asg.connections.allow_from(alb, ec2.Port.tcp(80), "ALB access 80 port of EC2 in Autoscaling Group")
listener.add_targets("addTargetGroup",
port=80,
targets=[self.asg])
core.CfnOutput(self, "Output",
value=alb.load_balancer_dns_name)
After change
python:python:cdk_ec2_stack.py (after change)
from aws_cdk import core
import aws_cdk.aws_ec2 as ec2
import aws_cdk.aws_elasticloadbalancingv2 as elb
import aws_cdk.aws_autoscaling as autoscaling
ec2_type = "t2.micro"
key_name = "hogehoge" #Change to the key pair prepared in advance.
linux_ami = ec2.AmazonLinuxImage(generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX,
edition=ec2.AmazonLinuxEdition.STANDARD,
virtualization=ec2.AmazonLinuxVirt.HVM,
storage=ec2.AmazonLinuxStorage.GENERAL_PURPOSE
) # Indicate your AMI, no need a specific id in the region
with open("./user_data/user_data.sh") as f:
user_data = f.read()
#From this point onward, posting will be omitted.
Thank you for waiting. For the time being, let's create a resource in this state.
#Set venv.
$ cd ~/practice/cdk/aws-cdk-examples/python/new-vpc-alb-asg-mysql
$ python3 -m venv .env
$ source .env/bin/activate
#Installation of dependent libraries
$ pip install -r requirements.txt
#Enter the CDK command to generate a CloudFormation template.
$ cdk ls
cdk-vpc
cdk-ec2
cdk-rds
$ cdk synth
Successfully synthesized to /home/****/practice/cdk/aws-cdk-examples/python/new-vpc-alb-asg-mysql/cdk.out
A directory called cdk.out will be created, and CloudFormation templates will be created in it. (By the way, the template was json ... isn't it yaml?)
$ cdk bootstrap
$ cdk deploy cdk-vpc #This will take some time. (same as above)
$ cdk deploy cdk-ec2
$ cdk deploy cdk-rds
The CloudFormation Stack is completed, and eventually the desired resource is completed. I can't list everything, so I'll attach only Subnet and how it looks from the management console. [TBD] Paste the image.
--You cannot ssh because the key pair is not registered on the host called myBastion. It seems to be a prerequisite to use Session Manager --The Security Group on the same host is ** ssh fully open **
These will be resolved in the second lap. So I made it, but I will delete the resource easily!
#I erased it in the reverse order of when I made it. It disappeared cleanly. (As expected, the CloudFormation Stack will be deleted)
$ cdk destroy cdk-rds
$ cdk destroy cdk-ec2
$ cdk destroy cdk-vpc
For example, a private subnet 「cdk-vpc/VPC/PrivateSubnet1」 You can do it with the name.
You can use / (slash) in the resource name! ?? That was fresh. The place where kebab case (-connector) and CamelCase are mixed is playful.
How is the resource name determined? Speaking of which (details need confirmation) 最初のcdk-vpcの部分は、app.py Since the following is specified in, it seems to be decided by that.
app.py (excerpt only partly)
vpc_stack = CdkVpcStack(app, "cdk-vpc")
ec2_stack = CdkEc2Stack(app, "cdk-ec2",
vpc=vpc_stack.vpc)
rds_stack = CdkRdsStack(app, "cdk-rds",
vpc=vpc_stack.vpc,
asg_security_groups=ec2_stack.asg.connections.security_groups)
Therefore, for the second lap, I would like to change the cdk- part to a more significant value. This time I'll try akira-test-cdk- (although not very significant).
Let's go to the second lap! !! Let's et al!
--Change resource name to akira-test-cdk-beginning --Assign a key pair to Bastion --Narrow down the inbound IP address of Bastion's Security Group --Set to go to the app port (8080) --Don't create RDS, let's run a DB-independent smoke test app for the time being!
app.py
#!/usr/bin/env python3
from aws_cdk import core
from cdk_vpc_ec2.cdk_vpc_stack import CdkVpcStack
from cdk_vpc_ec2.cdk_ec2_stack import CdkEc2Stack
from cdk_vpc_ec2.cdk_rds_stack import CdkRdsStack
app = core.App()
vpc_stack = CdkVpcStack(app, "akira-test-cdk-vpc")
ec2_stack = CdkEc2Stack(app, "akira-test-cdk-ec2",
vpc=vpc_stack.vpc)
rds_stack = CdkRdsStack(app, "akira-test-cdk-rds",
vpc=vpc_stack.vpc,
asg_security_groups=ec2_stack.asg.connections.security_groups)
app.synth()
Each is a pseudonym, so please read it as you like.
cdk_ec2_stack.py
from aws_cdk import core
import aws_cdk.aws_ec2 as ec2
import aws_cdk.aws_elasticloadbalancingv2 as elb
import aws_cdk.aws_autoscaling as autoscaling
ec2_type = "t2.micro"
key_name = "hogehoge" #Change to the key pair prepared in advance.
linux_ami = ec2.AmazonLinuxImage(generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2, #Change to Amazon Linux 2
edition=ec2.AmazonLinuxEdition.STANDARD,
virtualization=ec2.AmazonLinuxVirt.HVM,
storage=ec2.AmazonLinuxStorage.GENERAL_PURPOSE
) # Indicate your AMI, no need a specific id in the region
with open("./user_data/user_data.sh") as f:
user_data = f.read()
class CdkEc2Stack(core.Stack):
def __init__(self, scope: core.Construct, id: str, vpc, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
# Create Bastion
bastion = ec2.BastionHostLinux(self, "myBastion",
vpc=vpc,
subnet_selection=ec2.SubnetSelection(
subnet_type=ec2.SubnetType.PUBLIC),
instance_name="myBastionHostLinux",
instance_type=ec2.InstanceType(instance_type_identifier="t2.micro"))
# Setup key_name for EC2 instance login if you don't use Session Manager
#Uncomment here and register the key pair
bastion.instance.instance.add_property_override("KeyName", key_name)
#Narrow down the source IP
# bastion.connections.allow_from_any_ipv4(
bastion.connections.allow_from(ec2.Peer.ipv4("x.x.x.x/32"), #Source IP here!
ec2.Port.tcp(22), "Internet access SSH")
# Create ALB
alb = elb.ApplicationLoadBalancer(self, "myALB",
vpc=vpc,
internet_facing=True,
load_balancer_name="myALB"
)
alb.connections.allow_from_any_ipv4(
ec2.Port.tcp(80), "Internet access ALB 80")
listener = alb.add_listener("my80",
port=80,
open=True)
# Create Autoscaling Group with fixed 2*EC2 hosts
self.asg = autoscaling.AutoScalingGroup(self, "myASG",
vpc=vpc,
vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE),
instance_type=ec2.InstanceType(instance_type_identifier=ec2_type),
machine_image=linux_ami,
key_name=key_name,
user_data=ec2.UserData.custom(user_data),
desired_capacity=2,
min_capacity=2,
max_capacity=2,
volume_type=autoscaling.EbsDeviceVolumeType.GP2,
)
self.asg.connections.allow_from(alb, ec2.Port.tcp(8080), "ALB access 8080 port of EC2 in Autoscaling Group")
listener.add_targets("addTargetGroup",
port=8080, # 80 ->Change to 8080
targets=[self.asg])
core.CfnOutput(self, "Output",
value=alb.load_balancer_dns_name)
Change user_data
user_data.sh
#!/bin/bash
sudo yum update -y
sudo yum install -y java-11-amazon-corretto-headless
sudo yum install -y maven
# sudo yum -y install httpd php
# sudo chkconfig httpd on
# sudo service httpd start
For the time being, run the Spring Boot app on EC2. (I'm sorry that I'm exhausted and the explanation is messy around here ... Well, it's not the main subject.)
HelloController.java
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String hello() {
return "Hello, Spring boot with AWS CDK";
}
}
** Since the Security Group linked to the EC2 of the app only accepts access from ALB, Bastion cannot ssh the EC2 of the app at this time. Therefore, change the SecurityGroup and manually change it to accept ssh from Bastion. ** **
[TODO] The explanation around here is too complicated, so I'll do it a little more ...
#Bring the jar to EC2 with SCP etc.
#to start
$ java -jar demo-0.0.1-SNAPSHOT.jar
#Hit the endpoint as a result of deploying ec2.
CloudFormation has the impression that it is a little difficult for the person in charge of the application to create an AWS environment. CDK is a popular programming language that wraps it around, giving the impression that the infrastructure code is easier to read as a result. I used Python this time, but I wanted to try it in other languages as well.
--Knowledge of AWS itself is of course necessary --It's tough if you don't know a little about CloudFormation (you need to know enough to read the result of cdk synth) --You don't need much knowledge of Python (beginner level is okay) --CDK API specifications need to be checked each time (or should be checked)
The API specifications can be found below. https://docs.aws.amazon.com/cdk/api/latest/python/modules.html
Recommended Posts