Link Search Menu Expand Document

Cloud Development Kit (CDK)

Nội dung

  1. Giới thiệu và cài đặt CDK
  2. Upload S3 Bucket
  3. EC2 Instances
  4. ERC Role Permission
  5. PostgreSQL Server

Giới thiệu và cài đặt CDK

CDK là bộ công cụ phát triển ứng dụng cung cấp những tính năng hỗ trợ việc xây dựng và triển khai trên nền tảng AWS. Khác với CloudFormation Template, CDK sử dụng các ngôn ngữ lập trình phổ biên như TypeScript, Java, C# giúp việc khai báo và cấu hình các tài nguyên tính toán theo hướng tiếp cận Imperative.

Cloud Development Kit Overview Cloud Development Kit Overview

Một ứng dụng CDK bao gồm tập hợp Cloud Components - hoặc còn gọi theo tên thuật ngữ là Construct blocks. Mỗi Construct có thể tương ứng với một tài nguyên AWS, ví dụ như một Bucket trong AWS S3, hoặc là một tập hợp liên kết các tài nguyên AWS cho một mục đích cụ thể, ví dụ như tạo ra một ECS Cluster với các thành phần Task, Service etc…

Để thuận tiện cho việc sử dụng, AWS CDK xây dựng sẵn một thư viện - AWS Construct Library tương ứng với những tài nguyên, dịch vụ cung cấp bởi AWS. Dựa theo mục đích sử dụng, các thành phần trong thư viện được chia thành nhiều cấp độ khác nhau:

  • Low level - L1: bao gồm các CFN Resources, đại diện trực tiếp cho những tài nguyên có thể khai báo thông qua dịch vụ AWS CloudFormation. Ví dụ: CfnBucket tương ứng với AWS::S3::Bucket trong đặc tả của CloudFormation Template.

  • Middle level - L2: bao gồm các thành phần có tính đóng gói cao hơn, tập trung nhiều hơn vào mục đích sử dụng cụ thể. Các thành phần này được khai báo kèm theo những thiết lập thuộc tính với giá trị mặc định, hoặc phương thức thường được áp dụng trong quá trình sử dụng trên thực tế. Ví dụ: thành phần s3.Bucket cùng phương thức addLiffeCycleRule() cho phép bổ sung những qui tắc quản lý vòng đời sử dụng của một S3 Bucket.

  • Higher level - L3: hay còn gọi là Patterns, các thành phần này giúp người sử dụng nhanh chóng thiết lập những cấu hình ứng dụng được sử dụng phổ biến. Ví dụ: khai báo và thiết lập Fargate hoặc EC2-based Service trong aws-ecs-pattern thông qua một đối tượng duy nhất thay vì thực hiện hàng loạt các cấu hình thành phần ở mức thấp hơn như VPC, Task, Service.

Cài đặt CDK qua Node Package Manager (NPM):

npm install -g aws-cdk 

Kiểm tra kết quả cài đặt

cdk doctor

Output

ℹ️ CDK Version: 1.60.0 (build 8e3f53a)
ℹ️ AWS environment variables:
  - AWS_STS_REGIONAL_ENDPOINTS = regional
  - AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1
  - AWS_SDK_LOAD_CONFIG = 1
ℹ️ No CDK environment variables

Trong thực hành tiếp theo, chúng ta sẽ sử dụng CDK để khai báo và cấu các AWS Resources.


Upload S3 Bucket

Tạo ứng dụng HelloCdk bao gồm S3CdkStack Stack với chức năng:

  • Tạo S3 Bucket
  • Upload folder

Chú ý

Khi sử dụng trong ứng dụng, mọi Construct phải được định nghĩa / khai báo bên trong một Construct đặc biệt gọi là Stack.

Bước 1: Tạo một folder mới cho ứng dụng

mkdir hello-ckd
cd hello-cdk

Bước 2: Khởi tạo ứng dụng với lệnh cdk init

Khi sử dụng môi trường .NET, thực hiện câu lệnh:

cdk init app --language csharp

Output

Applying project template app for csharp
Project `HelloCdk/HelloCdk.csproj` added to the solution.
...

## Useful commands
* `dotnet build src` compile this app
* `cdk deploy`       deploy this stack to your default AWS account/region
* `cdk diff`         compare deployed stack with current state
* `cdk synth`        emits the synthesized CloudFormation template
Initializing a new git repository...
✅ All done!
  • Ứng dụng CDK có thể gồm nhiều Stack kết hợp với nhau.

Để liệt kê các stack trong ứng dụng, sử dụng lệnh

cdk ls

Kết quả câu lệnh cho thấy ứng dụng bao gồm duy nhất Stack với tên gọi HelloCdkStack.

  • Để đổi tên cho Stack, chúng ta thay đổi tên file và tên class của HelloCdkStack thành S3CdkStack.

Cập nhật file Program.cs:

using Amazon.CDK;

namespace HelloCdk
{
    sealed class Program
    {
        public static void Main(string[] args)
        {
            var app = new App();
            new S3CdkStack(app, "S3CdkStack");
            app.Synth();
        }
    }
}

Trong ví dụ trên, chúng ta khai báo App Construct và sử dụng nó bên trong S3CdkStack.

  • CDK Bootstrap

Khi triển khai một ứng dụng CDK sử dụng S3 Construct, chúng ta cần thực hiện quá trình khởi tạo thông qua lệnh bootstrap.

cdk bootstrap  

Output:

 ⏳  Bootstrapping environment aws://729365137003/ap-southeast-2...
CDKToolkit: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (3/3)

 ✅  Environment aws://729365137003/ap-southeast-2 bootstrapped.

Hướng dẫn

CDK Boostrap là một công cụ cho phép thiết lập môi trường (bao gồm: AWS Account + Region) cùng những tài nguyên cần thiết cho việc triển khai ứng dụng CDK trong môi trường đó.

  • Tạo CloudFormation template tương ứng với Stack với lệnh cdk synth

Khi ứng dụng gồm nhiều Stack, chúng ta phải chỉ định tên Stack trong câu lệnh:

cdk syncth S3CdkStack

Output cho thấy Stack bao gồm CDKMetadata resource.

Resources:
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Modules: aws-cdk=1.60.0,@aws-cdk/cloud-assembly-schema=1.60.0,@aws-cdk/core=1.60.0,@aws-cdk/cx-api=1.60.0,jsii-runtime=DotNet/3.1.7/.NETCoreApp,Version=v3.1/1.0.0.0
    Condition: CDKMetadataAvailable
Conditions:
  CDKMetadataAvailable:
    ...
  • Triển khai Stack trên môi trường AWS - cdk deploy
S3CdkStack: deploying...
S3CdkStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (2/2)

 ✅  S3CdkStack

Stack ARN:
arn:aws:cloudformation:ap-southeast-2:729365137003:stack/S3CdkStack/ebd7dde0-f3e3-11ea-a67d-0a8ccfe1fca8

Bước 3: Bổ sung Constructs trong S3CdkStack

  • Trong folder src/HelloCdk, cài đặt thư viện CDK packages:
dotnet add package Amazon.CDK.AWS.S3
dotnet add package Amazon.CDK.AWS.S3.Deployment
  • Trong folder gốc hello-cdk, tạo folder userdata gồm file userdata.sh với nội dung
useradd -m -s /bin/bash prometheus
# (or adduser --disabled-password --gecos "" prometheus)

# Download node_exporter release from original repo
curl -L -O  https://github.com/prometheus/node_exporter/releases/download/v0.17.0/node_exporter-0.17.0.linux-amd64.tar.gz

tar -xzvf node_exporter-0.17.0.linux-amd64.tar.gz
mv node_exporter-0.17.0.linux-amd64 /home/prometheus/node_exporter
rm node_exporter-0.17.0.linux-amd64.tar.gz
chown -R prometheus:prometheus /home/prometheus/node_exporter

# Add node_exporter as systemd service
tee -a /etc/systemd/system/node_exporter.service << END
[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target
[Service]
User=prometheus
ExecStart=/home/prometheus/node_exporter/node_exporter
[Install]
WantedBy=default.target
END

systemctl daemon-reload
systemctl start node_exporter
systemctl enable node_exporter

Chú ý

File userdata.sh sẽ được sử dụng cho EC2 Instance User Data trong phần thực hành Swarm-P.3. Trong bài viết này, chúng ta tạm thời không cần quan tâm đến mục đích nội dung trong file này.

  • Cập nhật S3CdkStack.cs:
using System.IO;
using Amazon.CDK;
using Amazon.CDK.AWS.S3;
using Amazon.CDK.AWS.S3.Deployment;

namespace HelloCdk
{
    public class S3CdkStack : Stack
    {
        public S3CdkStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            var filePath = Directory.GetCurrentDirectory();

            var bucket = new Bucket(this, "S3CdkStack", new BucketProps
            {
                Versioned = true,
                RemovalPolicy = RemovalPolicy.DESTROY
            });

            var bucketDeploy = new BucketDeployment(this, "S3DeployCdkStack", new BucketDeploymentProps
            {
                DestinationBucket = bucket,
                Sources = new ISource[] { Source.Asset(filePath + "/userdata") }
            });
        }
    }
}

S3CdkStack khai báo các đối tượng Constructs :

  • bucket: tạo AWS S3 Bucket với các thuộc tính:
    • scope: chỉ định parent của constructs.
    • id: giá trị ID của bucket trong ứng dụng CDK.
    • props: tập hợp các thuộc tính của bucket
  • bucketDeploy: upload folder lên Bucket với các thuộc tính
    • DestinationBucket: s3 bucket sử dụng để upload
    • Sources: folder hoặc zip file upload

Bước 4: Triển khai Stack

Sử dụng lệnh cdk deploy để cập nhật Stack S3CdkStack trên môi trường AWS.

cdk deploy S3CdkStack

S3 Stack Deploy Confirmation S3 Stack Deploy Confirmation

Xác nhận y, chúng ta có kết quả khi câu lệnh kết thúc thành công:

S3CdkStack: deploying...
[0%] start: Publishing 4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50:current
[50%] success: Published 4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50:current
[50%] start: Publishing 965560db7b21fda081bce8f68ce0df96081bf9963378a35bec566633174ad756:current
[100%] success: Published 965560db7b21fda081bce8f68ce0df96081bf9963378a35bec566633174ad756:current
S3CdkStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (7/7)

 ✅  S3CdkStack

Stack ARN:
arn:aws:cloudformation:ap-southeast-2:729365137003:stack/S3CdkStack/76c4aac0-fae0-11ea-9f43-0a7293028854

Kiểm tra userdata.sh trên AWS S3

Userdata file in the S3 Console Userdata file in the S3 Console

  • Xoá bỏ Stack

Khi muốn xoá bỏ Stack trên môi trường AWS, chúng ta sử dụng câu lệnh cdk destroy

Output

Are you sure you want to delete: S3CdkStack (y/n)? y
S3CdkStack: destroying...
4:47:25 pm | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack | S3CdkStack
4:47:27 pm | DELETE_IN_PROGRESS   | AWS::S3::Bucket    | Bucket434

 ✅  S3CdkStack: destroyed

EC2 Instances

Trong ứng dụng HelloCdk, chúng ta bổ sung thêm VpcCdkStack bao gồm VPC, Subnets và EC2 Instances trên những Availability Zones khác nhau. Bên cạnh đó, Stack sử dụng InitElement và User Data để thực hiện việc cài đặt docker service trên mỗi EC2 Instance.

Bước 1: Trong folder src/HelloCdk, cài đặt thư viện CDK:

dotnet add package Amazon.CDK.AWS.AutoScaling
dotnet add package Amazon.CDK.AWS.EC2
dotnet add package Amazon.CDK.AWS.ElasticLoadBalancing
dotnet add package Amazon.CDK.AWS.EC2

Bước 2: Tạo file VpcCdkStack.cs:


using Amazon.CDK;
using Amazon.CDK.AWS.EC2;
using Amazon.CDK.AWS.RDS;

namespace HelloCdk
{
    public class VpcCdkStack : Stack
    {
        public VpcCdkStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            // Create VPC and Public Subnets
            var subnets = new ISubnetConfiguration[]
            {
                new SubnetConfiguration { CidrMask = 24, Name = "CdkPublicSubnet", SubnetType = SubnetType.PUBLIC },
                new SubnetConfiguration { CidrMask = 24, Name = "CdkPrivateSubnet", SubnetType = SubnetType.PRIVATE }
            };
            var vpc = new Vpc(this, "VpcCdk", new VpcProps
            {
                Cidr = "10.0.0.0/16",
                EnableDnsHostnames = true,
                EnableDnsSupport = true,
                MaxAzs = 2,
                SubnetConfiguration = subnets,
            });

            var ec2SecurityGroup = new SecurityGroup(this, "CdkEC2Sg", new SecurityGroupProps
            {
                Vpc = vpc,
                Description = "EC2 Instance Security Group",
                AllowAllOutbound = true,
            });
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.IcmpPing(), "Allowing ping check");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(22), "Allowing ssh access");

            // Create EC2 Instances
            var amznLinux = MachineImage.LatestAmazonLinux(new AmazonLinuxImageProps
            {
                Generation = AmazonLinuxGeneration.AMAZON_LINUX,
                Edition = AmazonLinuxEdition.STANDARD,
                Virtualization = AmazonLinuxVirt.HVM,
                Storage = AmazonLinuxStorage.GENERAL_PURPOSE,
                CpuType = AmazonLinuxCpuType.X86_64
            });

            for (int i = 0; i < vpc.AvailabilityZones.Length; i++)
            {
                var ec2Instance = new Instance_(this, $"CdkEc2Instance_{i}", new Amazon.CDK.AWS.EC2.InstanceProps
                {
                    Vpc = vpc,
                    VpcSubnets = new SubnetSelection
                    {
                        SubnetType = SubnetType.PUBLIC
                    },
                    SecurityGroup = ec2SecurityGroup,
                    MachineImage = amznLinux,
                    AvailabilityZone = vpc.AvailabilityZones[i],
                    InstanceType = new InstanceType("t2.micro"),
                    KeyName = "FriendReminders",
                    InitOptions = new ApplyCloudFormationInitOptions
                    {
                        PrintLog = true,
                        ConfigSets = new string[] { "default" }
                    },
                    Init = CloudFormationInit.FromElements(new InitElement[]
                    {
                        InitPackage.Yum("git"),
                        InitPackage.Yum("docker"),
                        InitService.Enable("docker")
                    }),
                });
                ec2Instance.UserData.AddCommands("sudo yum update -y");
                ec2Instance.UserData.AddCommands("sudo usermod -a -G docker ec2-user");
            }
        }
    }
}

Những điểm chú ý trong VpcCdkStack:

  • Khai báo Constructs vpc, subnets, ec2SecurityGroup, ec2Instance
  • VPC bao gồm 2 subnets: CdkPublicSubnetCdkPrivateSubnet
  • Liên kết Constructs vpcec2SecurityGroup
  • Cấu hình ICMP, SSH Protocols trên ec2SecurityGroup cho EC2 Instances.
  • Trong mỗi AZs, tạo EC2 instance trong dựa trên image - amznLinux
  • EC2 Instance sử dụng Public Subnet CdkPublicSubnet
  • Cài đặt và cấu hình packages trong EC2 instances qua Init / AddCommand

Bổ sung VpcCdkStack trong Program.cs:

using Amazon.CDK;

namespace HelloCdk
{
    sealed class Program
    {
        public static void Main(string[] args)
        {
            var app = new App();
            // Create two stacks
            new S3CdkStack(app, "S3CdkStack");
            new VpcCdkStack(app, "VpcCdkStack");
            app.Synth();
        }
    }
}

Trong thư mục hello-cdk, thực hiện lệnh cdk ls để kiểm tra danh sách Stacks

S3CdkStack
VpcCdkStack

Bước 3: Tạo EC2 KeyPair

Theo nội dung VpcCdkStack.cs, EC2 Instance sử dụng KeyPair FriendReminders để xác thực và đăng nhập.

Do vậy, trước khi thực thi Stack chúng ta tạo KeyPair với lệnh:

aws ec2 create-key-pair --key-name FriendReminders --query 'KeyMaterial' --output text > FriendReminders.pem

Chuyển permission KeyPair sang chế độ read-only:

chmod 400 FriendReminders.pem

Bước 4: Triển khai VpcCdkStack

  • Thực hiện lệnh:
cdk deploy VpcCdkStack

Output

This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬──────────────────────────────────────┬────────┬───────────────────────────────────────────────────────────────────┬──────────────────────────────────────┬───────────┐
│   │ Resource                             │ Effect │ Action                                                            │ Principal                            │ Condition │
├───┼──────────────────────────────────────┼────────┼───────────────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────┤
│ + │ ${AWS::StackId}                      │ Allow  │ cloudformation:DescribeStackResource                              │ AWS:${CdkEc2Instance_0/InstanceRole} │           │
│   │                                      │        │ cloudformation:SignalResource                                     │                                      │           │
│ + │ ${AWS::StackId}                      │ Allow  │ cloudformation:DescribeStackResource                              │ AWS:${CdkEc2Instance_1/InstanceRole} │           │
│   │                                      │        │ cloudformation:SignalResource                                     │                                      │           │
├───┼──────────────────────────────────────┼────────┼───────────────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────┤
│ + │ ${CdkEc2Instance_0/InstanceRole.Arn} │ Allow  │ sts:AssumeRole                                                    │ Service:ec2.${AWS::URLSuffix}        │           │
├───┼──────────────────────────────────────┼────────┼───────────────────────────────────────────────────────────────────┼──────────────────────────────────────┼───────────┤
│ + │ ${CdkEc2Instance_1/InstanceRole.Arn} │ Allow  │ sts:AssumeRole                                                    │ Service:ec2.${AWS::URLSuffix}        │           │
└───┴──────────────────────────────────────┴────────┴───────────────────────────────────────────────────────────────────┴──────────────────────────────────────┴───────────┘
Security Group Changes
┌───┬─────────────────────┬─────┬────────────┬─────────────────┐
│   │ Group               │ Dir │ Protocol   │ Peer            │
├───┼─────────────────────┼─────┼────────────┼─────────────────┤
│ + │ ${CdkEC2Sg.GroupId} │ In  │ ICMP 8--1  │ Everyone (IPv4) │
│ + │ ${CdkEC2Sg.GroupId} │ In  │ TCP 22     │ Everyone (IPv4) │
│ + │ ${CdkEC2Sg.GroupId} │ Out │ Everything │ Everyone (IPv4) │
└───┴─────────────────────┴─────┴────────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)?

Thông tin này cho biết các thay đổi trong Resources và Security Group khi thực thi Stack.

Lựa chọn y để xác nhận tiếp tục việc triển khai.

Output khi Stack được tạo ra:

VpcCdkStack: deploying...
VpcCdkStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (34/34)

 ✅  VpcCdkStack

Stack ARN:
arn:aws:cloudformation:ap-southeast-2:729365137003:stack/VpcCdkStack/598188c0-f61c-11ea-a16b-0a77915fb5d8
  • Trên AWS Console, lựa chọn một EC2 Instance trong danh sách EC2 và click Connect button.

Pop-window hiển thị thông tin hướng dẫn kết nối đến EC2 Instance

EC2 Instances Connect EC2 Instances Connection

  • Copy lệnh ssh trên pop-up window.

Câu lệnh sử dụng KeyPair FriendReminders.pem và connect đến EC2 Instance qua SSH Protocol

ssh -i "FriendReminders.pem" ec2-user@ec2-54-253-219-102.ap-southeast-2.compute.amazonaws.com

EC2 Instances Console EC2 Instances Console


ERC Role Permission

Trong phần này, chúng ta sẽ tạo IAM Role - ecrRole cho phép EC2 Instances có thể pull Docker Image từ Elastic Container Registry.

Các bước thực hiện như sau:

Bước 1:: Cài đặt CDK package

Trong folder src/HelloCdk, sử dụng câu lệnh:

dotnet add package Amazon.CDK.AWS.IAM

Bước 2: Tạo Service Role cho EC2 Instances.

Cập nhật nội dung VpcCdkStack.cs:

using Amazon.CDK;
using Amazon.CDK.AWS.EC2;
using Amazon.CDK.AWS.IAM;

namespace HelloCdk
{
    public class VpcCdkStack : Stack
    {
        public VpcCdkStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            // Create VPC and Public Subnets
            var subnets = new ISubnetConfiguration[]
            {
                new SubnetConfiguration { CidrMask = 24, Name = "CdkPublicSubnet", SubnetType = SubnetType.PUBLIC },
                new SubnetConfiguration { CidrMask = 24, Name = "CdkPrivateSubnet", SubnetType = SubnetType.PRIVATE }
            };
            var vpc = new Vpc(this, "CdkVpc", new VpcProps
            {
                Cidr = "10.0.0.0/16",
                EnableDnsHostnames = true,
                EnableDnsSupport = true,
                MaxAzs = 2,
                SubnetConfiguration = subnets,
            });
            var ec2SecurityGroup = new SecurityGroup(this, "CdkEC2Sg", new SecurityGroupProps
            {
                Vpc = vpc,
                Description = "EC2 Instance Security Group",
                AllowAllOutbound = true,
            });
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.IcmpPing(), "Allowing ping check");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(22), "Allowing ssh access");

            // Create EC2 Instances
            var amznLinux = MachineImage.LatestAmazonLinux(new AmazonLinuxImageProps
            {
                Generation = AmazonLinuxGeneration.AMAZON_LINUX,
                Edition = AmazonLinuxEdition.STANDARD,
                Virtualization = AmazonLinuxVirt.HVM,
                Storage = AmazonLinuxStorage.GENERAL_PURPOSE,
                CpuType = AmazonLinuxCpuType.X86_64
            });

            // Allow doing ECR pull permission on EC2 instances
            var ecrRole = new Role(this, "ECR Role", new RoleProps
            {
                AssumedBy = new ServicePrincipal("ec2.amazonaws.com")
            });

            ecrRole.AddToPolicy(new PolicyStatement(new PolicyStatementProps
            {
                Resources = new[] { "*" },
                Actions = new[]
                {
                    "ecr:GetAuthorizationToken",
                    "ecr:BatchCheckLayerAvailability",
                    "ecr:GetDownloadUrlForLayer",
                    "ecr:GetRepositoryPolicy",
                    "ecr:DescribeRepositories",
                    "ecr:ListImages",
                    "ecr:DescribeImages",
                    "ecr:BatchGetImage",
                    "ecr:GetLifecyclePolicy",
                    "ecr:GetLifecyclePolicyPreview",
                    "ecr:ListTagsForResource",
                    "ecr:DescribeImageScanFindings",
                    "ecr:InitiateLayerUpload",
                    "ecr:UploadLayerPart",
                    "ecr:CompleteLayerUpload",
                    "ecr:PutImage"
                }
            }));

            for (int i = 0; i < vpc.AvailabilityZones.Length; i++)
            {
                var ec2Instance = new Instance_(this, $"CdkEc2Instance_{i}", new Amazon.CDK.AWS.EC2.InstanceProps
                {
                    Vpc = vpc,
                    VpcSubnets = new SubnetSelection
                    {
                        SubnetType = SubnetType.PUBLIC
                    },
                    SecurityGroup = ec2SecurityGroup,
                    MachineImage = amznLinux,
                    AvailabilityZone = vpc.AvailabilityZones[i],
                    InstanceType = new InstanceType("t2.micro"),
                    KeyName = "FriendReminders",
                    InitOptions = new ApplyCloudFormationInitOptions
                    {
                        PrintLog = true,
                        ConfigSets = new string[] { "default" }
                    },
                    Init = CloudFormationInit.FromElements(new InitElement[]
                    {
                        InitPackage.Yum("git"),
                        InitPackage.Yum("docker"),
                        InitService.Enable("docker")
                    }),
                    Role = ecrRole
                });
                ec2Instance.UserData.AddCommands("sudo yum update -y");
                ec2Instance.UserData.AddCommands("sudo yum install - y postgresql");
                ec2Instance.UserData.AddCommands("sudo usermod -a -G docker ec2-user");
            }
        }
    }
}

Trong logic trên, chúng ta bổ sung ecrRole construct và liên kết với thuộc tính Role của ec2Instance construct.

Xác nhận nội dung thay đổi với lệnh cdk diff chúng ta có Output:

Stack S3CdkStack
There were no differences
Stack VpcCdkStack
IAM Statement Changes
┌───┬─────────────────┬────────┬────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────┬───────────┐
│   │ Resource        │ Effect │ Action                                                         │ Principal                                                       │ Condition │
├───┼─────────────────┼────────┼────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┼───────────┤
│ - │ ${AWS::StackId} │ Allow  │ cloudformation:DescribeStackResource                           │ AWS:${CdkEc2Instance0InstanceRole3A64BAF0}                      │           │
│   │                 │        │ cloudformation:SignalResource                                  │                                                                 │           │
│ - │ ${AWS::StackId} │ Allow  │ cloudformation:DescribeStackResource                           │ AWS:${CdkEc2Instance1InstanceRoleBAAADC7C}                      │           │
│   │                 │        │ cloudformation:SignalResource                                  │                                                                 │           │
├───┼─────────────────┼────────┼────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┼───────────┤
│ + │ ${AWS::StackId} │ Allow  │ cloudformation:DescribeStackResource                           │ AWS:${ECR Role}                                                 │           │
│   │                 │        │ cloudformation:SignalResource                                  │                                                                 │           │
├───┼─────────────────┼────────┼────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┼───────────┤
│ + │ ${ECR Role.Arn} │ Allow  │ sts:AssumeRole                                                 │ Service:ec2.${AWS::URLSuffix}                                   │           │
├───┼─────────────────┼────────┼────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┼───────────┤
│ + │ *               │ Allow  │ ecr:BatchCheckLayerAvailability                                │ AWS:${ECR Role}                                                 │           │
│   │                 │        │ ecr:BatchGetImage                                              │                                                                 │           │
│   │                 │        │ ecr:CompleteLayerUpload                                        │                                                                 │           │
│   │                 │        │ ecr:DescribeImageScanFindings                                  │                                                                 │           │
│   │                 │        │ ecr:DescribeImages                                             │                                                                 │           │
│   │                 │        │ ecr:DescribeRepositories                                       │                                                                 │           │
│   │                 │        │ ecr:GetAuthorizationToken                                      │                                                                 │           │
│   │                 │        │ ecr:GetDownloadUrlForLayer                                     │                                                                 │           │
│   │                 │        │ ecr:GetLifecyclePolicy                                         │                                                                 │           │
│   │                 │        │ ecr:GetLifecyclePolicyPreview                                  │                                                                 │           │
│   │                 │        │ ecr:GetRepositoryPolicy                                        │                                                                 │           │
│   │                 │        │ ecr:InitiateLayerUpload                                        │                                                                 │           │
│   │                 │        │ ecr:ListImages                                                 │                                                                 │           │
│   │                 │        │ ecr:ListTagsForResource                                        │                                                                 │           │
│   │                 │        │ ecr:PutImage                                                   │                                                                 │           │
│   │                 │        │ ecr:UploadLayerPart                                            │                                                                 │           │
└───┴─────────────────┴────────┴────────────────────────────────────────────────────────────────┴─────────────────────────────────────────────────────────────────┴───────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[-] AWS::IAM::Role CdkEc2Instance0InstanceRole3A64BAF0 destroy
[-] AWS::IAM::Policy CdkEc2Instance0InstanceRoleDefaultPolicy05595539 destroy
[-] AWS::EC2::Instance CdkEc2Instance0BAC1787D9d35dda19932e2b9 destroy
[-] AWS::IAM::Role CdkEc2Instance1InstanceRoleBAAADC7C destroy
[-] AWS::IAM::Policy CdkEc2Instance1InstanceRoleDefaultPolicy911C974D destroy
[-] AWS::EC2::Instance CdkEc2Instance15B3FF57E70239bdefa4df791 destroy
[+] AWS::IAM::Role ECR Role ECRRoleAC78BA58 
[+] AWS::IAM::Policy ECR Role/DefaultPolicy ECRRoleDefaultPolicyE017F4B5 
[+] AWS::EC2::Instance CdkEc2Instance_0 CdkEc2Instance0BAC1787Dba7ead12f16ce4e3 
[+] AWS::EC2::Instance CdkEc2Instance_1 CdkEc2Instance15B3FF57Ecd69f92b7d466aa1 
[~] AWS::IAM::InstanceProfile CdkEc2Instance_0/InstanceProfile CdkEc2Instance0InstanceProfile8936A1E4 
 └─ [~] Roles
     └─ @@ -1,5 +1,5 @@
        [ ] [
        [ ]   {
        [-]     "Ref": "CdkEc2Instance0InstanceRole3A64BAF0"
        [+]     "Ref": "ECRRoleAC78BA58"
        [ ]   }
        [ ] ]
[~] AWS::IAM::InstanceProfile CdkEc2Instance_1/InstanceProfile CdkEc2Instance1InstanceProfileCAAEF389 
 └─ [~] Roles
     └─ @@ -1,5 +1,5 @@
        [ ] [
        [ ]   {
        [-]     "Ref": "CdkEc2Instance1InstanceRoleBAAADC7C"
        [+]     "Ref": "ECRRoleAC78BA58"
        [ ]   }
        [ ] ]

Bước 3: Thực hiện triển khai thay đổi trên VpcCdkStack.

Sử dụng lệnh cdk deploy VpcCdkStack. Output:

This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬─────────────────┬────────┬────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────┬───────────┐
│   │ Resource        │ Effect │ Action                                                         │ Principal                                                       │ Condition │
├───┼─────────────────┼────────┼────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┼───────────┤
│ - │ ${AWS::StackId} │ Allow  │ cloudformation:DescribeStackResource                           │ AWS:${CdkEc2Instance0InstanceRole3A64BAF0}                      │           │
│   │                 │        │ cloudformation:SignalResource                                  │                                                                 │           │
│ - │ ${AWS::StackId} │ Allow  │ cloudformation:DescribeStackResource                           │ AWS:${CdkEc2Instance1InstanceRoleBAAADC7C}                      │           │
│   │                 │        │ cloudformation:SignalResource                                  │                                                                 │           │
├───┼─────────────────┼────────┼────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┼───────────┤
│ + │ ${AWS::StackId} │ Allow  │ cloudformation:DescribeStackResource                           │ AWS:${ECR Role}                                                 │           │
│   │                 │        │ cloudformation:SignalResource                                  │                                                                 │           │
├───┼─────────────────┼────────┼────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┼───────────┤
│ + │ ${ECR Role.Arn} │ Allow  │ sts:AssumeRole                                                 │ Service:ec2.${AWS::URLSuffix}                                   │           │
├───┼─────────────────┼────────┼────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┼───────────┤
│ + │ *               │ Allow  │ ecr:BatchCheckLayerAvailability                                │ AWS:${ECR Role}                                                 │           │
│   │                 │        │ ecr:BatchGetImage                                              │                                                                 │           │
│   │                 │        │ ecr:CompleteLayerUpload                                        │                                                                 │           │
│   │                 │        │ ecr:DescribeImageScanFindings                                  │                                                                 │           │
│   │                 │        │ ecr:DescribeImages                                             │                                                                 │           │
│   │                 │        │ ecr:DescribeRepositories                                       │                                                                 │           │
│   │                 │        │ ecr:GetAuthorizationToken                                      │                                                                 │           │
│   │                 │        │ ecr:GetDownloadUrlForLayer                                     │                                                                 │           │
│   │                 │        │ ecr:GetLifecyclePolicy                                         │                                                                 │           │
│   │                 │        │ ecr:GetLifecyclePolicyPreview                                  │                                                                 │           │
│   │                 │        │ ecr:GetRepositoryPolicy                                        │                                                                 │           │
│   │                 │        │ ecr:InitiateLayerUpload                                        │                                                                 │           │
│   │                 │        │ ecr:ListImages                                                 │                                                                 │           │
│   │                 │        │ ecr:ListTagsForResource                                        │                                                                 │           │
│   │                 │        │ ecr:PutImage                                                   │                                                                 │           │
│   │                 │        │ ecr:UploadLayerPart                                            │                                                                 │           │
└───┴─────────────────┴────────┴────────────────────────────────────────────────────────────────┴─────────────────────────────────────────────────────────────────┴───────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)?

Lựa chọn Y để tiếp tục quá trình.

Kết quả hiển thị khi hoàn thành câu lệnh

VpcCdkStack: deploying...
VpcCdkStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (14/14)

 ✅  VpcCdkStack

Stack ARN:
arn:aws:cloudformation:ap-southeast-2:729365137003:stack/VpcCdkStack/873363c0-f675-11ea-9a0f-02827d11d8f4

Bước 4: Login EC2 và pull Docker Image

  • Sử dụng SSH để login EC2 Instance theo hướng dẫn trong ví dụ trước

  • Sau khi login EC2, thực hiện xác thực với ECR theo lệnh:

aws ecr get-login-password --region ap-southeast-2 | docker login --username AWS --password-stdin 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com
  • Thực hiện lệnh docker pull
docker pull 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com/remindersmgtservice

Output:

ECR Pull Image ECR Pull Image

Chú ý

Chúng ta có thể kết hợp bước 3 và 4 bằng cách bổ sung thêm câu lệnh trong phần User Data của mỗi EC2 Instance.

    ec2Instance.UserData.AddCommands("sudo yum update -y");
    ec2Instance.UserData.AddCommands("sudo yum install - y postgresql");
    ec2Instance.UserData.AddCommands("sudo usermod -a -G docker ec2-user");
    ec2Instance.UserData.AddCommands("aws ecr get-login-password --region ap-southeast-2 | docker login --username AWS --password-stdin 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com");
    ec2Instance.UserData.AddCommands("docker pull 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com/remindersmgtservice");

Bằng cách này, khi EC2 Instance sẽ tự động thực hiện việc login và pull về Docker Image thay vì thực hiện các câu lệnh thủ công theo bước 3 và 4


PostgreSQL Server

Trên cùng VPC trong ví dụ trước, chúng ta sẽ tạo thêm một PostgreSQL DB Instance.

Bước 1: Cài đặt packages

Trong folder src/HelloCdk, sử dụng câu lệnh:

dotnet add package Amazon.CDK.AWS.RDS

Bước 2: Cập nhật VpcCdkStack.cs


using Amazon.CDK;
using Amazon.CDK.AWS.EC2;
using Amazon.CDK.AWS.IAM;
using Amazon.CDK.AWS.RDS;

namespace HelloCdk
{
    public class VpcCdkStack : Stack
    {
        public VpcCdkStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            // Create VPC and Public Subnets
            var subnets = new ISubnetConfiguration[]
            {
                new SubnetConfiguration { CidrMask = 24, Name = "CdkPublicSubnet", SubnetType = SubnetType.PUBLIC },
                new SubnetConfiguration { CidrMask = 24, Name = "CdkPrivateSubnet", SubnetType = SubnetType.PRIVATE }
            };
            var vpc = new Vpc(this, "CdkVpc", new VpcProps
            {
                Cidr = "10.0.0.0/16",
                EnableDnsHostnames = true,
                EnableDnsSupport = true,
                MaxAzs = 2,
                SubnetConfiguration = subnets,
            });
            var ec2SecurityGroup = new SecurityGroup(this, "CdkEC2Sg", new SecurityGroupProps
            {
                Vpc = vpc,
                Description = "EC2 Instance Security Group",
                AllowAllOutbound = true,
            });
            var dbSecurityGroup = new SecurityGroup(this, "CdkDBSg", new SecurityGroupProps
            {
                Vpc = vpc,
                Description = "Allow PostgreSQL connection",
                AllowAllOutbound = true,
            });

            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.IcmpPing(), "Allowing ping check");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(22), "Allowing ssh access");
            dbSecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(5432), "Allowing DB connection");

            // Create EC2 Instances
            var amznLinux = MachineImage.LatestAmazonLinux(new AmazonLinuxImageProps
            {
                Generation = AmazonLinuxGeneration.AMAZON_LINUX,
                Edition = AmazonLinuxEdition.STANDARD,
                Virtualization = AmazonLinuxVirt.HVM,
                Storage = AmazonLinuxStorage.GENERAL_PURPOSE,
                CpuType = AmazonLinuxCpuType.X86_64
            });

            // Allow doing ECR pull permission on EC2 instances
            var ecrRole = new Role(this, "ECR Role", new RoleProps
            {
                AssumedBy = new ServicePrincipal("ec2.amazonaws.com")
            });
            ecrRole.AddToPolicy(new PolicyStatement(new PolicyStatementProps
            {
                Resources = new[] { "*" },
                Actions = new[]
                {
                    "ecr:GetAuthorizationToken",
                    "ecr:BatchCheckLayerAvailability",
                    "ecr:GetDownloadUrlForLayer",
                    "ecr:GetRepositoryPolicy",
                    "ecr:DescribeRepositories",
                    "ecr:ListImages",
                    "ecr:DescribeImages",
                    "ecr:BatchGetImage",
                    "ecr:GetLifecyclePolicy",
                    "ecr:GetLifecyclePolicyPreview",
                    "ecr:ListTagsForResource",
                    "ecr:DescribeImageScanFindings",
                    "ecr:InitiateLayerUpload",
                    "ecr:UploadLayerPart",
                    "ecr:CompleteLayerUpload",
                    "ecr:PutImage"
                }
            }));

            for (int i = 0; i < vpc.AvailabilityZones.Length; i++)
            {
                var ec2Instance = new Instance_(this, $"CdkEc2Instance_{i}", new Amazon.CDK.AWS.EC2.InstanceProps
                {
                    Vpc = vpc,
                    VpcSubnets = new SubnetSelection
                    {
                        SubnetType = SubnetType.PUBLIC
                    },
                    SecurityGroup = ec2SecurityGroup,
                    MachineImage = amznLinux,
                    AvailabilityZone = vpc.AvailabilityZones[i],
                    InstanceType = new InstanceType("t2.micro"),
                    KeyName = "FriendReminders",
                    InitOptions = new ApplyCloudFormationInitOptions
                    {
                        PrintLog = true,
                        ConfigSets = new string[] { "default" }
                    },
                    Init = CloudFormationInit.FromElements(new InitElement[]
                    {
                        InitPackage.Yum("git"),
                        InitPackage.Yum("docker"),
                        InitService.Enable("docker")
                    }),
                    Role = ecrRole
                });
                ec2Instance.UserData.AddCommands("sudo yum update -y");
                ec2Instance.UserData.AddCommands("sudo yum install - y postgresql");
                ec2Instance.UserData.AddCommands("sudo usermod -a -G docker ec2-user");
                ec2Instance.UserData.AddCommands("aws ecr get-login-password --region ap-southeast-2 | docker login --username AWS --password-stdin 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com");
                ec2Instance.UserData.AddCommands("docker pull 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com/remindersmgtservice");                
            }

            //  Create Aurora instances
            var dbInstance = new DatabaseInstance(this, "FriendReminders", new DatabaseInstanceProps
            {
                Engine = DatabaseInstanceEngine.Postgres(new PostgresInstanceEngineProps
                {
                    Version = PostgresEngineVersion.VER_10_13
                }),
                // optional, defaults to m5.large
                InstanceType = InstanceType.Of(InstanceClass.BURSTABLE2, InstanceSize.SMALL),
                MasterUsername = "friend",
                Vpc = vpc,
                VpcSubnets = new SubnetSelection
                {
                    SubnetType = SubnetType.PRIVATE
                },
                SecurityGroups = new SecurityGroup[] { dbSecurityGroup },
                DeletionProtection = false
            });
        }
    }
}

Trong VPC, dbInstance Construct tạo PostgreSQL Server Instance version 10.13. PostgreSQL cài đặt trong Private Subnet, với master username friend.

Để kiểm tra danh sách các thay đổi, sử dụng lệnh:

cdk diff

Output:

Resources
[+] AWS::S3::Bucket Bucket43434 Bucket434340086743F 

Stack VpcCdkStack
Security Group Changes
┌───┬────────────────────┬─────┬────────────┬─────────────────┐
│   │ Group              │ Dir │ Protocol   │ Peer            │
├───┼────────────────────┼─────┼────────────┼─────────────────┤
│ + │ ${CdkDBSg.GroupId} │ In  │ TCP 5432   │ Everyone (IPv4) │
│ + │ ${CdkDBSg.GroupId} │ Out │ Everything │ Everyone (IPv4) │
└───┴────────────────────┴─────┴────────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[-] AWS::EC2::Instance CdkEc2Instance0BAC1787De6115c8be470f327 destroy
[-] AWS::EC2::Instance CdkEc2Instance15B3FF57E5dad100f3b0f62db destroy
[+] AWS::EC2::SecurityGroup CdkDBSg CdkDBSgDE2B05A6 
[+] AWS::EC2::Instance CdkEc2Instance_0 CdkEc2Instance0BAC1787Dcc157c3029e65c6c 
[+] AWS::EC2::Instance CdkEc2Instance_1 CdkEc2Instance15B3FF57E987300da3f6b9879 
[+] AWS::RDS::DBSubnetGroup FriendReminders/SubnetGroup FriendRemindersSubnetGroupD2513019 
[+] AWS::SecretsManager::Secret FriendReminders/Secret FriendRemindersSecret40441CD2 
[+] AWS::SecretsManager::SecretTargetAttachment FriendReminders/Secret/Attachment FriendRemindersSecretAttachment4BD450D7 
[+] AWS::RDS::DBInstance FriendReminders FriendRemindersC92B7C56 

Bước 3: Thực thi lệnh deploy

cdk deploy VpcCdkStack

Output hiển thị yêu cầu xác nhận thay đổi:

This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

Security Group Changes
┌───┬────────────────────┬─────┬────────────┬─────────────────┐
│   │ Group              │ Dir │ Protocol   │ Peer            │
├───┼────────────────────┼─────┼────────────┼─────────────────┤
│ + │ ${CdkDBSg.GroupId} │ Out │ Everything │ Everyone (IPv4) │
│ + │ ${CdkDBSg.GroupId} │ In  │ TCP 5432   │ Everyone (IPv4) │
└───┴────────────────────┴─────┴────────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)?

Xác nhận Y để tiếp tục.

Output khi câu lệnh hoàn thành:

VpcCdkStack: deploying...
VpcCdkStack: creating CloudFormation changeset...
[███████████████████████████████▋··························] (6/11)
[██████████████████████████████████████████████████████████] (11/11)

 ✅  VpcCdkStack

Stack ARN:
arn:aws:cloudformation:ap-southeast-2:729365137003:stack/VpcCdkStack/598188c0-f61c-11ea-a16b-0a77915fb5d8

Bước 4: Xác nhận kết quả

Do PostgreSQL Server hoạt động trong một Private Subnet, chúng ta cần cài đặt và sử dụng PostgreSQL Client trong một EC2 Instance để truy cập Database Server.

  • Thông tin credential (username / password) truy cập PostgreSQL Database server được lưu trong AWS Secret Mangager.

AWS Secret Manager AWS Secret Manager

Trong phần Secret Value, click button Retrieve secret value để hiển thị thông tin credential.

DB Connection Credential DB Connection Credential

  • Truy cập EC2 Instance và cài đặt PosgreSQL Client với lệnh:
sudo yum install -y postgresql

Output:

DB PogreSQL Install DB PogreSQL Installation

  • Kết nối đến PostgreSQL Server từ EC2 Instance

Sử dụng lệnh:

 psql -h vf1qnha0zhkxemv.ctcnoszp8xta.ap-southeast-2.rds.amazonaws.com -U friend -d postgres

Output xác nhận kết quả kết nối thành công

PogreSQL Connection Output PogreSQL Connection Output


Tài liệu tham khảo


Copyright © 2019-2022 Tuan Anh Le.