Link Search Menu Expand Document

Docker Swarm - さん

Advanced

Dựa trên tính linh hoạt của thư viện phát triển AWS CDK, bài viết này tiếp tục mở rộng những chức năng của Swarm Cluster đã được xây dựng trong bài viết trước. Cụ thể hơn, chúng ta sẽ thực hiện những thay đổi trong project HelloCdk:

  • Tự động cài đặt một hệ thống theo dõi và giám sát chất lượng - Prometheus.

  • Sử dụng docker stack command để triển khai đồng thời services RemindersManagement và Nginx Reverse-proxy.

  • Kết hợp AWS Route 53 và AWS Certificate Manager (ACM) cài đặt Sub-domain Name và HTTPS cho Load Balancer của Swarm Cluster

Swarm ALB Ouput

Trong phần ví dụ VpcCdkStack.cs của ứng dụng HelloCdk, chúng ta đã sử dụng những phương thức AddCommands của construct ec2Instance để tự động thực hiện cài đặt những system packages và liên kết Swarm Cluster khi triển khai các EC2 Instances trong một VPC.

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

namespace HelloCdk
{
    public class VpcCdkStack : Stack
    {
        [System.Obsolete]
        public VpcCdkStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
          // existing source code
          ...
          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");

          // ISSUE: Can not do overwrite
          if (isMasterNode)
          {
              ec2Instance.UserData.AddCommands("docker swarm init");
              ec2Instance.UserData.AddCommands("WORKER_TOKEN=`docker swarm join-token worker | sed -n 3p`");
              ec2Instance.UserData.AddCommands("aws ssm put-parameter --region ap-southeast-2 --name '/swarm/token/worker' --type SecureString --value \"${WORKER_TOKEN}\" --overwrite");
          }
          else
          {
              ec2Instance.UserData.AddCommands("WORKER_TOKEN=$(aws ssm get-parameter --region ap-southeast-2 --name '/swarm/token/worker' --with-decryption --query 'Parameter.Value' --output text)");
              ec2Instance.UserData.AddCommands("eval \"${WORKER_TOKEN}\"");
          }
          ...  
          // existing source code        
        }
    }
}

Tuy nhiên, cách làm này không thực sự thuận tiện khi muốn cài đặt nhiều commands hoặc bổ sung những cấu hình phức tạp trong quá trình khởi động EC2 Instances. Một trong những giải pháp để khắc phục điều này là khai báo command trong *.sh file và lưu trữ trong một AWS S3 Bucket. File manager.sh được sử dụng để cài đặt cho Manager Node và worker.sh được áp dụng cho Worker Node. Trong quá trình khởi động, EC2 Instance download và thực thi những file *.sh này để bổ sung những cài đặt hay cấu hình mong muốn trên Swarm Cluster. Đây là ý tưởng chủ đạo của bài thực hành này, cụ thể hơn chúng ta có các bước thực hành như sau:


Bước 1: Tạo ECR Repository cho NGINX

Do kết hợp hoạt động của RemindersManagement và reverse proxy NGINX trong một Docker Stack, chúng ta cần tạo các ECR Repositories cho những services này.

Hoạt động tạo ECR Repository cho RemindersManagement được giới thiệu trong các bài viết:

Trong bước này, chúng ta tạo và push NGINX image lên một ECR Repository khác:

  • Tạo ECR Repository với lệnh:
aws ecr create-repository \
    --repository-name nginx  \
    --image-scanning-configuration scanOnPush=true \
    --region ap-southeast-2

Output

{
    "repository": {
        "repositoryArn": "arn:aws:ecr:ap-southeast-2:729365137003:repository/nginx",
        "registryId": "729365137003",
        "repositoryName": "nginx",
        "repositoryUri": "729365137003.dkr.ecr.ap-southeast-2.amazonaws.com/nginx",
        "createdAt": "2020-09-29T12:57:46+10:00",
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": true
        }
    }
}
  • Trong folder RemindersManagement.API/Proxy, thực hiện build NGINX image
docker build -t 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com/nginx .

Output

Sending build context to Docker daemon  17.41kB
Step 1/4 : FROM nginx:latest
 ---> 4bb46517cac3
Step 2/4 : COPY nginx.conf /etc/nginx/nginx.conf
 ---> e04997aff688
Step 3/4 : COPY localhost.crt /etc/ssl/certs/localhost.crt
 ---> 24575468bd14
Step 4/4 : COPY localhost.key /etc/ssl/private/localhost.key
 ---> 6b120a2afbcb
Successfully built 6b120a2afbcb
Successfully tagged 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com/nginx:latest
  • Xác thực và push NGINX image lên ECR Repository
// Login ECR
aws ecr get-login-password --region ap-southeast-2 | docker login --username AWS --password-stdin 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com
// Push Image to Repository
docker push 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com/nginx

Output

ECR Nginx Push Ouput


Bước 2: Tạo và lưu trữ *.sh files trên AWS S3 thông qua S3CdkStack

Trong project folder hello-cdk, tạo thư mục userdata, bao gồm command files:

worker.sh

# Update and install packages
yum update -y
yum install -y git docker
service docker start
usermod -a -G docker ec2-user

# Update docker experiment configure
tee -a /etc/docker/daemon.json << END
{
  "metrics-addr" : "0.0.0.0:9323",
  "experimental" : true
}
END
service docker restart

# Node Exporter
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

# Swarm worker Node - Move here to get delay
sleep 30
WORKER_TOKEN=$(aws ssm get-parameter --region ap-southeast-2 --name '/swarm/token/worker' --with-decryption --query 'Parameter.Value' --output text)
eval $WORKER_TOKEN

# cAdvisor
VERSION=v0.36.0 
docker run \
  --volume=/:/rootfs:ro \
  --volume=/var/run:/var/run:ro \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  --volume=/dev/disk/:/dev/disk:ro \
  --publish=8080:8080 \
  --detach=true \
  --name=cadvisor \
  --privileged \
  --device=/dev/kmsg \
  gcr.io/cadvisor/cadvisor:$VERSION

worker.sh

# Update and install packages
yum update -y
yum install -y git docker
service docker start
usermod -a -G docker ec2-user

# Update docker experiment configure
tee -a /etc/docker/daemon.json << END
{
  "metrics-addr" : "0.0.0.0:9323",
  "experimental" : true
}
END
service docker restart

# Swarm master Node
docker swarm init
WORKER_TOKEN=`docker swarm join-token worker | sed -n 3p`
aws ssm put-parameter --region ap-southeast-2 --name '/swarm/token/worker' --type SecureString --value "$WORKER_TOKEN" --overwrite

# Node Exporter
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

# cAdvisor
VERSION=v0.36.0 
docker run \
  --volume=/:/rootfs:ro \
  --volume=/var/run:/var/run:ro \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  --volume=/dev/disk/:/dev/disk:ro \
  --publish=8080:8080 \
  --detach=true \
  --name=cadvisor \
  --privileged \
  --device=/dev/kmsg \
  gcr.io/cadvisor/cadvisor:$VERSION

# Install Grafana
yum install -y https://dl.grafana.com/oss/release/grafana-7.1.5-1.x86_64.rpm
systemctl daemon-reload
systemctl start grafana-server

# Prothemeus Setup and start
curl -L -O  https://github.com/prometheus/prometheus/releases/download/v2.21.0/prometheus-2.21.0.linux-amd64.tar.gz
tar -xzvf prometheus-2.21.0.linux-amd64.tar.gz
mv prometheus-2.21.0.linux-amd64 /home/prometheus/prometheus
rm prometheus-2.21.0.linux-amd64.tar.gz
chown -R prometheus:prometheus /home/prometheus/prometheus
/home/prometheus/prometheus/prometheus --config.file=/tmp/prometheus.yml

Nội dung các command files tương đối dài, tuy nhiên logic không thực sự phức tạp. Về cơ bản, các câu lệnh này thực hiện các logic sau đây:

  • Cài đặt các packages trên Amazon Linux với lệnh yum
  • Sử dụng SSM chia sẻ token để tự động tạo, liên kết Nodes trong Swarm
  • Cấu hình Docker deamon cho phép báo cáo Docker metrics qua port 9323
  • Cài đặt Node Exporter cung cấp thông tin giám sát cho hệ thống Prometheus
    • cAdvisor: áp dụng cho Docker Monitoring
    • node_exporter: áp dụng cho EC2 Instance Monitoring
  • Cài đặt Grafana Dashboard và Prometheus trên Manager Worker

Ngoài *.sh command files, chúng ta khai báo các file sau:

  • prometheus.yml - cấu hình hoạt động cho hệ thống giám sát Prometheus
  • aws-secret-dev.env - khai báo AWS credential cho phép service sử dụng CloudWatch
  • docker-compose.yaml - khai báo Docker Stack triển khai trong Swarm Cluster

prometheus.yml

# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'
    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.
    static_configs:
    - targets: ['localhost:9090']
  - job_name: 'node'
    ec2_sd_configs:
      - region: ap-southeast-2
        port: 9100
        refresh_interval: 1m
    relabel_configs:
      - source_labels:
        - '__meta_ec2_tag_Name'
        target_label: 'instance'
  - job_name: 'docker'
    ec2_sd_configs:
      - region: ap-southeast-2
        port: 9323
        refresh_interval: 1m
    relabel_configs:
      - source_labels:
        - '__meta_ec2_tag_Name'
        target_label: 'instance'
  - job_name: 'cAdvisor'
    ec2_sd_configs:
      - region: ap-southeast-2
        port: 8080
        refresh_interval: 1m
    relabel_configs:
      - source_labels:
        - '__meta_ec2_tag_Name'
        target_label: 'instance'

Cấu hình trên khai báo job cho những giám sát:

  • node : Node Exporter qua port 9100
  • docker : Dockerd Metrics qua port 9323
  • cAdvisor : giám sát Containers trên port 8080
  • prometheus : kiểm tra Prometheus trên port 9090

Ngoài ra, việc sử dụng Service Discovery - ec2_sd_configs cho phép Prometheus tự động tìm kiếm EC2 Instances thay vì phải định nghĩa danh sách IP của các Targets.

aws-secret-dev.env

AWS_ACCESS_KEY_ID=[Your_Secret_ID]
AWS_SECRET_ACCESS_KEY=[Your_Secret_Access_Key]

docker-compose.yaml

version: '3.8'

services:
  nginx:
    image: 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com/nginx:latest
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - remindersmgtservice    
  remindersmgtservice:
    image: 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com/remindersmgtservice:latest
    deploy:
      replicas: 3
    env_file:
      - ./aws-secret-dev.env

Bước 3: Thay đổi VpcCdktack để download và thực thi files từ S3

  • Trước khi cài đặt EC2 Instances với các command files, chúng ta cần sử dụng S3CdkStack để upload các files này lên AWS S3 bucket. Do vậy, hoạt động của VpcCdkStack phụ thuộc vào kết quả thực thi của S3CdkStack. Để khai báo sự phụ thuộc này, chúng ta bổ sung khai báo trong Program.cs:
using Amazon.CDK;

namespace HelloCdk
{
    sealed class Program
    {
        [System.Obsolete]
        public static void Main(string[] args)
        {
            var app = new App();

            var s3stack = new S3CdkStack(app, "S3CdkStack");
            new VpcCdkStack(app, "VpcCdkStack", s3stack.UserDataBucket).AddDependency(s3stack, "Userdata");

            app.Synth();
        }
    }
}
  • Bổ sung packages cho phép VpcCdkStack làm việc với AWS Route53 và Certificate Manager
dotnet add package  Amazon.CDK.AWS.Route53
dotnet add package  Amazon.CDK.AWS.Route53.Targets
dotnet add package Amazon.CDK.AWS.CertificateManager

Danh sách các packages đã cài đặt có thể tham khảo trong file HelloCdk.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <!-- CDK Construct Library dependencies -->
    <PackageReference Include="Amazon.CDK" Version="1.64.1" />
    <PackageReference Include="Amazon.CDK.AWS.AutoScaling" Version="1.64.1" />
    <PackageReference Include="Amazon.CDK.AWS.CertificateManager" Version="1.64.1" />
    <PackageReference Include="Amazon.CDK.AWS.EC2" Version="1.64.1" />
    <PackageReference Include="Amazon.CDK.AWS.ElasticLoadBalancing" Version="1.64.1" />
    <PackageReference Include="Amazon.CDK.AWS.IAM" Version="1.64.1" />
    <PackageReference Include="Amazon.CDK.AWS.RDS" Version="1.64.1" />
    <PackageReference Include="Amazon.CDK.AWS.Route53" Version="1.64.1" />
    <PackageReference Include="Amazon.CDK.AWS.Route53.Targets" Version="1.64.1" />
    <PackageReference Include="Amazon.CDK.AWS.S3" Version="1.64.1" />
    <PackageReference Include="Amazon.CDK.AWS.S3.Deployment" Version="1.64.1" />
  </ItemGroup>
</Project>
  • Cập nhật nội dung VpcCdkStack.cs:
using Amazon.CDK;
using Amazon.CDK.AWS.EC2;
using Amazon.CDK.AWS.IAM;
using Amazon.CDK.AWS.ElasticLoadBalancingV2;
using Amazon.CDK.AWS.S3;
using Amazon.CDK.AWS.Route53;
using Amazon.CDK.AWS.Route53.Targets;
using Amazon.CDK.AWS.CertificateManager;

namespace HelloCdk
{
    public class VpcCdkStack : Stack
    {
        [System.Obsolete]
        public VpcCdkStack(Construct scope, string id, Bucket userDataBucket, IStackProps props = null) : base(scope, id, props)
        {
            bool isMasterNode = true;

            // 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, "CdkVpn", new VpcProps
            {
                Cidr = "10.0.0.0/16",
                EnableDnsHostnames = true,
                EnableDnsSupport = true,
                MaxAzs = 2,
                SubnetConfiguration = subnets,
            });

            // Define EC2 Security Group
            var ec2SecurityGroup = new SecurityGroup(this, "CdkEC2Sg", new SecurityGroupProps
            {
                Vpc = vpc,
                Description = "EC2 Instance Security Group",
                AllowAllOutbound = true,
            });
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(22),   "Allow ssh access");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(80),   "Allow HTTP RemindersManagement service");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(443),  "Allow HTTPS RemindersManagement service");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(2377), "Allow cluster management communication");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(7946), "Allow communication among nodes");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Udp(7946), "Allow communication among nodes");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Udp(4789), "Overlay network traffic");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(3000), "Grafana dashboard");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(8080), "cAdvisor for docker monitoring");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(9090), "Prometheus metric");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(9100), "Node exporter");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(9323), "Docker daemon");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(9093), "Alert manager");

            // Define ALB Security Group
            var albSecurityGroup = new SecurityGroup(this, "CdkALBSg", new SecurityGroupProps
            {
                Vpc = vpc,
                Description = "Application Load Balancer Security Group",
                AllowAllOutbound = true,
            });
            albSecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(80), "Allow HTTP Connect for ALB");
            albSecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(443), "Allow HTTPS Connect for ALB");

            // Define EC2 Service Role (SSM and S3)
            var ec2Role = new Role(this, "EC2 Service Role", new RoleProps
            {
                AssumedBy = new ServicePrincipal("ec2.amazonaws.com"),
                ManagedPolicies = new IManagedPolicy[]
                {
                    ManagedPolicy.FromAwsManagedPolicyName("AmazonS3FullAccess"),
                }
            });
            ec2Role.AddToPolicy(new PolicyStatement(new PolicyStatementProps
            {
                Resources = new[] { "*" },
                Actions = new[]
                {
                    // Swarm Creation
                    "ssm:PutParameter",
                    "ssm:GetParameter",
                    // Node Export Label
                    "ec2:DescribeInstances",
                    // ECR Image
                    "ecr:GetAuthorizationToken",
                    "ecr:BatchCheckLayerAvailability",
                    "ecr:GetDownloadUrlForLayer",
                    "ecr:GetRepositoryPolicy",
                    "ecr:DescribeRepositories",
                    "ecr:ListImages",
                    "ecr:DescribeImages",
                    "ecr:BatchGetImage",
                    "ecr:GetLifecyclePolicy",
                    "ecr:GetLifecyclePolicyPreview",
                    "ecr:ListTagsForResource"
                }
            }));

            // Create target group
            var tg = new ApplicationTargetGroup(this, "CdkAtg", new ApplicationTargetGroupProps
            {
                Port = 80,
                Protocol = ApplicationProtocol.HTTP,
                TargetType = TargetType.INSTANCE,
                HealthCheck = new HealthCheck
                {
                    Port = "80",
                    Path = "/health",
                    Protocol = Amazon.CDK.AWS.ElasticLoadBalancingV2.Protocol.HTTP
                },
                Vpc = vpc,
            });

            // Create EC2 Instances
            var amznLinux = MachineImage.LatestAmazonLinux(new AmazonLinuxImageProps
            {
                Generation = AmazonLinuxGeneration.AMAZON_LINUX_2,
                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 InstanceProps
                {
                    Vpc = vpc,
                    VpcSubnets = new SubnetSelection
                    {
                        SubnetType = SubnetType.PUBLIC
                    },
                    SecurityGroup = ec2SecurityGroup,
                    MachineImage = amznLinux,
                    AvailabilityZone = vpc.AvailabilityZones[i],
                    InstanceType = new InstanceType("t2.micro"),
                    KeyName = "FriendReminders",
                    Role = ec2Role,
                });
                // Download file to setup User Data
                var userDataFileName = isMasterNode ? "manager.sh" : "worker.sh";
                ec2Instance.UserData.AddS3DownloadCommand(new S3DownloadOptions
                {
                    Bucket = userDataBucket,
                    BucketKey = userDataFileName,
                    LocalFile = $"/tmp/{userDataFileName}"
                });
                // Download file to setup Prometheus
                if (isMasterNode)
                {
                    ec2Instance.UserData.AddS3DownloadCommand(new S3DownloadOptions
                    {
                        Bucket = userDataBucket,
                        BucketKey = "prometheus.yml",
                        LocalFile = "/tmp/prometheus.yml"
                    });
                    ec2Instance.UserData.AddS3DownloadCommand(new S3DownloadOptions
                    {
                        Bucket = userDataBucket,
                        BucketKey = "aws-secret-dev.env",
                        LocalFile = "/tmp/aws-secret-dev.env"
                    });
                    ec2Instance.UserData.AddS3DownloadCommand(new S3DownloadOptions
                    {
                        Bucket = userDataBucket,
                        BucketKey = "docker-compose.yaml",
                        LocalFile = "/tmp/docker-compose.yaml"
                    });                                                            
                }
                ec2Instance.UserData.AddCommands($"chmod +x /tmp/{userDataFileName}");
                ec2Instance.UserData.AddCommands($"/tmp/{userDataFileName}");
                // Only 1 Master Node in Docker Cluster
                isMasterNode = false;
                tg.AddTarget(new InstanceTarget(ec2Instance.InstanceId));
            }

            // Create Application Load Balancer
            var lb = new ApplicationLoadBalancer(this, "CdkAlb", new ApplicationLoadBalancerProps
            {
                Vpc = vpc,
                InternetFacing = true,
                IpAddressType = IpAddressType.IPV4,
                SecurityGroup = albSecurityGroup,
            });
            lb.AddListener("CdkAlbHttpListener", new BaseApplicationListenerProps
            {
                Port = 80,
                DefaultTargetGroups = new IApplicationTargetGroup[] { tg }
            });

            // Adding subdomain for microservice
            var zone = HostedZone.FromHostedZoneAttributes(this, "friendreminders", new HostedZoneAttributes
            {
                ZoneName = "letcode101.com",
                HostedZoneId = "Z04296143GGPUKP56WT7J"
            });
            new ARecord(this, "remindersmgt", new ARecordProps
            {
                Zone = zone,
                Target = RecordTarget.FromAlias(new LoadBalancerTarget(lb)),
                RecordName = "remindersmgt",
                Comment = "Something to comment"
            });
            var cert = new Certificate(this, "remindersmgtcertificate", new CertificateProps
            {
                DomainName = "remindersmgt.letcode101.com",
                Validation = CertificateValidation.FromDns(zone)
            });

            // Adding HTTPS listener once ALB and ACM completed
            lb.AddListener("CdkAlbHttpsListener", new BaseApplicationListenerProps
            {
                Port = 443,
                DefaultTargetGroups = new IApplicationTargetGroup[] { tg },
                CertificateArns = new string[] { cert.CertificateArn }
            });
        }
    }
}

Các điểm thay đổi trong VpcCdkStack:

  • Cập nhật Ports trên ec2SecurityGroup cho phép hoạt động của các thành phần monitoring:
    • Prometheus: 9090
    • Docker cAdvisor: 8080
    • Node Exporter: 9100
    • Dockerd Daemon: 9323
  • Cập nhật Port 443 trên các security group của EC2 và Application Load Balancer
    • ec2SecurityGroup
    • albSecurityGroup
  • Construct ec2Instance gọi phương thức AddS3DownloadCommand download files từ S3
    • Worker node: worker.sh cài đặt workder node trong Swarm
    • Manager node:
      • manager.sh cài đặt manager node, monitoring component
      • prometheus.yml cấu hình prometheus
      • aws-secret-dev.env AWS credential file
      • docker-compose.yaml khai báo docker stack thực thi trong Swarm
  • Chuyển đổi Health Check /health với port từ 8000 sang 80 do sử dụng NGINX proxy
  • Tham chiếu đến Hosted Zoned ID của domain letcode101.com với mục đích
    • Tạo A Record tham chiếu Sub-domain đến Application Load Balancer lb
    • Tạo SSL certification cho Sub-domain remindersmgt.letcode101.com
    • Tạo Listener cho Port 443 với ACM Certificate

Giá trị Hosted Zone ID tham khảo trên AWS Console: Route53 Service -> Hosted Zone

Hosted Zone ID Ouput

  • Thực thi cdk command và triển khai stack

Liệt kê danh sách Stacks:

cdk ls

Output

S3CdkStack
VpcCdkStack

Triển khai VpcCdkStack:

cdk deploy VpcCdkStack

Do VpcCdkStack dựa trên S3CdkStack trong khai báo của Program.cs, việc triển khai VpcCdkStack sẽ tự động thực thi S3CdkStack:

Vpc Stack Setup 1 Ouput

Xác nhận y để thực hiện triển khai S3CdkStack

Vpc Stack Setup 2 Ouput

Tiếp tục xác nhận y để triển khai VpcCdkStack

Vpc Stack Setup 3 Ouput


Bước 4: Xác nhận kết quả cài đặt Swarm Cluster và Prometheus

  • Xác nhận danh sách EC2 Instances trên AWS Console

EC2 Instances Ouput

  • Truy cập EC2 Instances và sử dụng docker info để xác nhận Swarm Cluster:

Swarm Cluster Ouput

  • Trong EC2 Instance tương ứng Manager Node, cài đặt remindersmanagement service từ ECR:
// Move to folder include configure files
cd /tmp/

// Login ECR
aws ecr get-login-password --region ap-southeast-2 | docker login --username AWS --password-stdin 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com

// Deploy Stack
docker stack deploy --compose-file docker-compose.yaml remindersmgtstack --with-registry-auth

// List Cluster Stack
docker stack ls

// List Services
docker service ls

Stack Deploy Ouput

  • Xác nhận hoạt động của service qua URL https://remindersmgt.letcode101.com

Reminders Subdomain Ouput

  • Kết hợp IP của Manager Node với port 9090 để kiểm tra hoạt động của Prometheus:
http://[Manager_Node_IP]:9090/graph

Prometheus Ouput

  • Kết hợp IP của Manager Node và port 3000 để kiểm tra hoạt động của Grafana Dashboard:
http://[Manager_Node_IP]:3000/login

Grafana Login


Bước 5: Import Dashboard trong Grafana

  • Với username/password admin/admin, cập nhật password trong lần đầu tiên sử dụng Grafana.

Grafana Ouput

  • Từ màn hình chính của Grafana, truy cập Data Sources để bổ sung Prometheus

Grafana Data Source 1 Ouput

  • Sử dụng URL http://localhost:9090 do Promethus và Grafana cài đặt trên cùng EC2 Instance:

Grafana Data Source 2 Ouput

Click Save & Test button bên dưới màn hình sau khi điền thông tin URL

Grafana gồm nhiều Dashboards cho phép theo dõi các thành phần khác nhau trong một hệ thống ứng dụng (server, database, application, logging…). Để tạo ra một Dashboard, cách đơn giản nhất là import Dashboard được cung cấp sẵn bởi cộng đồng người dùng trên Grafana

Tham khảo danh sách Grafana Dashboard.

Mỗi Dashboard tương ứng một ID duy nhất được sử dụng để Import vào Grafana.

Ví dụ sử dụng Dashboard ID: 11074 giám sát Cluster Nodes thông qua Node Exporter

  • Thực hiện việc Import Dashboard. Click vào Import button:

Grafana Databoard Import 1 Ouput

  • Điền thông tin Dashboard ID và click Load button

Grafana Databoard Import 2 Ouput

  • Lựa chọn Prometheus từ danh sách và click Import button

Grafana Databoard Import 3 Ouput

  • Truy cập URL http://[Manager_Node_IP]:3000/dashboards, lựa chọn Dashboard vừa Import

Node Exporter Ouput

  • Import Dashboard 10566 giám sát Docker Container thông qua cAdvisor

Docker Metrics Ouput


Copyright © 2019-2022 Tuan Anh Le.