Link Search Menu Expand Document

Docker Swarm - に

Advanced

Trong bài viết Swarm - P.1 chúng ta đã triển khai services trong một Swarm Cluster theo các bước:

  1. Sử dụng Cloud Development Kit (CDK) để xây dựng VPC và EC2 Instances
  2. Tạo Docker Swarm, Manager Nodes và Worker Nodes theo các câu lệnh docker swarm
  3. Thực thi docker service create để download và thực thi Image từ Docker Hub
  4. Kiểm tra kết quả qua truy cập địa chỉ Public IP của Manager và Worker Node

Dựa trên các kết quả trên, chúng ta sẽ tiếp tục cải thiện một số chức năng của project HelloCdk:

  • Tự động thực hiện bước (1), (2) khi triển khai VpcCdkStack trong HelloCdk
  • Kết hợp Application Load Balancer (ALB) và VPC để phân tải cho Cluster Nodes.

Swarm ALB Ouput

Trong mô hình trên, ALB là thành phần trao đổi thông tin trực tiếp với client. ALB phân phối các yêu cầu từ client đến các đối tượng Targets hoạt động trên những Avaialbility Zone khác nhau nhằm duy trì tính ổn định và sẵn sàng của một hệ thống.

Hoạt động của ALB kết hợp với những thành phần sau:

  • Listener: kiểm tra thuộc tính Protocol và Port của các liên kết từ Client, kết hợp với các Rules để chuyển hướng yêu cầu đến Target Groups.

  • Target Group: tập hợp Targets (EC2, Lambda) xử lý các yêu cầu gửi đến qua Load Balancer. Targets trong một Group sử dụng chung một qui tắc Health Check.

  • Swarm Cluster: quản lý Docker Containers thực thi trên Swarm Nodes (EC2 Instances), download Docker Image remindersmgtservice từ Container Registry (ECR).

Để kết hợp ALB với hoạt động của Docker Swarm, chúng ta thực hiện các bước thay đổi sau trong ứng dụng HelloCdk:

Bước 1: Bổ sung Cdk Packages

Trong folder hello-cdk/src/HelloCdk, sử dụng lệnh

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

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.63.0" />
    <PackageReference Include="Amazon.CDK.AWS.AutoScaling" Version="1.63.0" />
    <PackageReference Include="Amazon.CDK.AWS.EC2" Version="1.63.0" />
    <PackageReference Include="Amazon.CDK.AWS.ElasticLoadBalancing" Version="1.63.0" />
    <PackageReference Include="Amazon.CDK.AWS.IAM" Version="1.63.0" />
    <PackageReference Include="Amazon.CDK.AWS.RDS" Version="1.63.0" />
    <PackageReference Include="Amazon.CDK.AWS.S3" Version="1.63.0" />
  </ItemGroup>
</Project>

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

Nội dung thay đổi của file VpcCdkStack.cs

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)
        {
            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),   "Allowing ssh access");
            ec2SecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(888),  "Swarmpit administration");
            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(8000), "Allow RemindersManagement service");

            // 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");

            // Define EC2 Service Role
            var ec2Role = new Role(this, "EC2 Service Role", new RoleProps
            {
                AssumedBy = new ServicePrincipal("ec2.amazonaws.com")
            });
            ec2Role.AddToPolicy(new PolicyStatement(new PolicyStatementProps
            {
                Resources = new[] { "*" },
                Actions = new[]
                {
                    // Swarm Creationn
                    "ssm:PutParameter",
                    "ssm:GetParameter",
                    // 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 = 8000,
                Protocol = ApplicationProtocol.HTTP,
                TargetType = TargetType.INSTANCE,
                HealthCheck = new HealthCheck
                {
                    Port = "8000",
                    Path = "/health",
                    Protocol = Amazon.CDK.AWS.ElasticLoadBalancingV2.Protocol.HTTP
                },
                Vpc = vpc,
            });

            // 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")
                    }),
                    Role = ec2Role,
                });
                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}\"");
                }

                // 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("CdkAlbListener", new BaseApplicationListenerProps
            {
                Port = 80,
                DefaultTargetGroups = new IApplicationTargetGroup[] { tg }
            });
        }
    }
}

Nôi dung trên bổ sung các cập nhật:

  • Tạo Security Group cho Application Load Balancer với Port 80
  • Port 8000 áp dụng cho microservice RemindersManagement
  • Bổ sung service role - ec2Role permission cho phép pull Docker Image từ Private AWS ECR
  • Sử dụng AWS Secrets Manager chia sẻ Swarm Token dựa trên ec2Role construct
  • Tạo Target Group, tham chiếu đến EC2 Instances qua AddTarget method
  • Target Group kiểm tra Health Check các services trên EC2 Instances qua endpoint /health
  • Sử dụng Application Load Balancer phân tải đến EC2 Instances

Bước 3: Triển khai stack

  • Trong folder gốc của HelloCdk project, sử dụng lệnh:
cdk deploy VpcCdkStack

Auto Swarm Output

  • Trên AWS Console, kiểm tra danh sách EC2 Instances trong Swarm Cluster

EC2 List in a Swarm Ouput

  • Sử dụng SSH kết nối đến VpcCdkStack/CdkEc2Instance_0:
ssh -i "FriendReminders.pem" ec2-user@ec2-3-25-88-236.ap-southeast-2.compute.amazonaws.com
  • Kiểm tra trạng thông tin Docker Engine với lệnh docker info:
Client:
 Debug Mode: false

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
 Server Version: 19.03.6-ce
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: active
  NodeID: te79w75qoy6ozvggsb16yjz0y
  Is Manager: true
  ClusterID: pro7iqg93b40b6v595rgnhmo8
  Managers: 1
  Nodes: 2
  Default Address Pool: 10.0.0.0/8  
  SubnetSize: 24
  Data Path Port: 4789
  Orchestration:
   Task History Retention Limit: 5
  Raft:
   Snapshot Interval: 10000
   Number of Old Snapshots to Retain: 0
   Heartbeat Tick: 1
   Election Tick: 10
  Dispatcher:
   Heartbeat Period: 5 seconds
  CA Configuration:
   Expiry Duration: 3 months
   Force Rotate: 0
  Autolock Managers: false
  Root Rotation In Progress: false
  Node Address: 10.0.0.126
  Manager Addresses:
   10.0.0.126:2377
...

Thông tin xác nhận Swarm đã được thiết lập trên Master Node với trạng thái active.

// Login AWS ECR
aws ecr get-login-password --region ap-southeast-2 | docker login --username AWS --password-stdin 729365137003.dkr.ecr.ap-southeast-2.amazonaws.com
// Swarm Service
docker service create --replicas 2 --with-registry-auth --name remindersmgtservice  --publish 8000:8000  729365137003.dkr.ecr.ap-southeast-2.amazonaws.com/remindersmgtservice:latest

Bước 4: Triển khai Stack và xác nhận kết quả

Sử dụng AWS Console, EC2 -> Target Group, kiểm tra thông tin Target Group được tạo ra:

Target Group Ouput

Danh sách Target của Target Group VpcCd-CdkAt-4UXRFDEZJZUN liên kết với các EC2 Instances trong Swarm Cluster. Trạng thái Status với giá trị Healthy cho chúng ta biết service được thực thi thành công trong Cluster với Port 8000.

Chú ý

  • Health Check có thể trễ một khoảng thời gian. Do vậy, nếu trạng thái hiển thị Unhealthy, chúng ta có thể chờ thêm và refresh lại.

  • Khi triển khai VpcCdkStack, Worker Node có thể hoàn thành trước khi Cluster được khởi tạo, trong trường hợp đó chúng ta có thể kiểm tra và khắc phục bằng cách login vào Worker Node, thực hiện câu lệnh docker swarm leave và join lại theo giá trị lưu trong AWS SSM. Vấn đề này sẽ được khắc phục trong Swarm - P.3

Trên AWS Console, EC2 -> Load Balancer, Application Load Balance VpcCd-CdkAl-MYU06FXH5JNG liên kết với Target Group VpcCd-CdkAt-4UXRFDEZJZUN thực hiện quá trình phân tải các yêu cầu đến EC2 Instances trong Target Group.

Load Balancer Ouput

Dựa trên giá trị DNS Name của Application Load Balancer, truy cập địa chỉ:

VpcCd-CdkAl-MYU06FXH5JNG-362625888.ap-southeast-2.elb.amazonaws.com

Application Load Balancer Ouput


Copyright © 2019-2022 Tuan Anh Le.