背景
如果只是在一臺主機或者虛擬機上面運行一個容器,那么Docker命令行就足夠了,e.g: docker run xxximage。 但是如果需要管理一個100X的容器集群,就比較有挑戰(zhàn)了。
在AWS眾多服務中,就有提供集群資源管理和服務調(diào)度的服務,比如EKS和ECS, 由于EKS還沒有進入中國區(qū),本篇博客重點介紹在ECS上面運行基于Spring Cloud全家桶的微服務架構。
ECS的工作原理

我們將基于上面的ECS架構圖,簡單介紹下ECS的一些重要組件和工作原理。
-
ECS agent, 在每一個實例,它都有一個運行的docker進程,它就是ECS agent, 其主要負責接收ECS的命令,并且將這些命令轉(zhuǎn)化為docker對應的命令;所以它能夠控制EC2實例,什么時候啟動、停止容器并且監(jiān)控已經(jīng)使用的和空閑的資源情況。 -
ECS pause, 第一啟動的容器,協(xié)調(diào)以后容器進行協(xié)議的分配,防止沖突。 -
Cluster mangement Engine負責協(xié)調(diào)整個集群的實例,它可以被認為是虛擬的資源池,比如內(nèi)存、存儲、CPU、網(wǎng)絡等;所以通過它,能夠知道整個集群的資源現(xiàn)狀。 -
Scheduler負責調(diào)度容器或者tasks的執(zhí)行;它能知道每個task的運行狀態(tài),是否是alive或者dead,是否需要rescheduled等。 -
Kev-Value Store, 負責整個集群狀態(tài)的管理。 -
API, ECS另一個比較獨特的地方,在于它將容器的調(diào)度從集群管理中解耦出來,并且提供一些的API,以供使用者使用。
演示介紹
基于Spring Cloud全家桶的微服務架構,如何運行在ECS容器云平臺上面,本文準備了三個服務,用于演示:
- 服務一:
simple-application, 使用https://start.spring.io/進行創(chuàng)建,主要用于從配置服務器獲取動態(tài)配置。 - 服務二:
simple-config-server, 主要用于配置服務器,進行微服務配置的管理。 - 服務三:
simple-eureka, 主要用于服務注冊與發(fā)現(xiàn)。
每個服務基于docker,需要通過./mvn clean package的方式,進行打包和構建新的docker鏡像,然后通過下面類似的命令,將最新的鏡像發(fā)布到ECR鏡像注冊服務器上面。
- 在ECR創(chuàng)建對應的鏡像庫。
$(aws ecr get-login --no-include-email --region ap-southeast-1)
aws ecr create-repository --repository-name simple-cluster/simple-application
- 將最新的鏡像,發(fā)布到ECR對應的鏡像庫。
docker tag simple-cluster/simple-application:latest 613175009525.[dkr.ecr.ap-southeast-1.amazonaws.com/simple-cluster/simple-application:latest](http://dkr.ecr.ap-southeast-1.amazonaws.com/simple-cluster/simple-application:latest)
docker push 613175009525.[dkr.ecr.ap-southeast-1.amazonaws.com/simple-cluster/simple-application:latest](http://dkr.ecr.ap-southeast-1.amazonaws.com/simple-cluster/simple-application:latest)
- 發(fā)布成功之后,更新Cloudformation對應的鏡像倉庫地址:
Sample:
cpu: 512
mem: 1024
javaopt: "-Xms512m -Xmx1024m -Xss512k"
port: 3000
hostport: 9050
name: simple-sample
context: /simple-application
taskcount: 1
minhealthcount: 50
maxhealthcount: 200
HealthCheckGracePeriodSeconds: 300
logPrefix: simple-application
image: '613175009525.dkr.ecr.ap-southeast-1.amazonaws.com/simple-cluster/simple-application:latest'
HealthCheckIntervalSeconds: 30
HealthCheckPath: /simple-application/actuator/info
HealthCheckPort: traffic-port
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 5
ECS集群:
可以通過命令行工具ecs-cli up進行創(chuàng)建,或者使用Cloudformation進行創(chuàng)建,本文演示基于Cloudformation的創(chuàng)建方式。
$ aws cloudformation create-stack --stack-name simple-ecs-cluster-cf-template --template-body file://simple-ecs-cluster-cf.yaml --region ap-southeast-1 --parameters ParameterKey=EcsInstanceType,ParameterValue=t2.medium,ParameterKey=KeyName,ParameterValue=aws-singapore-key,ParameterKey=VpcId,ParameterValue=vpc-0cbbd87895dbbfc5b
{
"StackId": "arn:aws:cloudformation:ap-southeast-1:613175009525:stack/simple-ecs-cluster-cf-template/008ff110-2685-11e9-94ea-064b1a6199c8"
}
創(chuàng)建成功界面如下:

請指定正確的VPC,Subnet,SG和指定EC2的類型,數(shù)量,AMI,EBS和key pair,之后ECS會自動創(chuàng)建EC2,并且為其配置如下信息:
"#!/bin/bash\n","echo ECS_CLUSTER=",!Ref EcsClusterName," >> /etc/ecs/ecs.config\n"
創(chuàng)建公共的ALB, 并且根據(jù)path進行微服務的分發(fā),對外提供接口。

部分相關Cloudformation配置:
############### ALB Default Target-Group ####################
DefaultALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Condition: CreateSpringCloud
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref DefaultTargetGroup
LoadBalancerArn: !Ref PublicApplicationLoadBalancer
Port: 80
Protocol: HTTP
DefaultTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Condition: CreateSpringCloud
DependsOn: PublicApplicationLoadBalancer
Properties:
HealthCheckIntervalSeconds: !FindInMap [ SpringCloudMiddlewares, Eureka, HealthCheckIntervalSeconds ]
HealthCheckPath: !FindInMap [ SpringCloudMiddlewares, Eureka, HealthCheckPath ]
HealthCheckPort: !FindInMap [ SpringCloudMiddlewares, Eureka, HealthCheckPort ]
HealthCheckProtocol: !FindInMap [ SpringCloudMiddlewares, Eureka, HealthCheckProtocol ]
HealthCheckTimeoutSeconds: !FindInMap [ SpringCloudMiddlewares, Eureka, HealthCheckTimeoutSeconds ]
HealthyThresholdCount: !FindInMap [ SpringCloudMiddlewares, Eureka, HealthyThresholdCount ]
Matcher:
HttpCode: 200
Name: default-target-group
Port: 80
Protocol: HTTP
TargetGroupAttributes:
-
Key: deregistration_delay.timeout_seconds
Value: 300
-
Key: slow_start.duration_seconds
Value: 0
-
Key: stickiness.enabled
Value: false
VpcId: !Ref VpcId
############## Spring Cloud Sample Resources start ####################
SpringCloudSampleECSALBListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Condition: CreateSampleTask
DependsOn: DefaultALBListener
Properties:
Actions:
- Type: forward
TargetGroupArn: !Ref SpringCloudSampleTargetGroup
Conditions:
- Field: path-pattern
Values: [/simple-application*]
ListenerArn: !Ref DefaultALBListener
Priority: 3
指定服務運行環(huán)境:
方法一: 通過ECS task配置環(huán)境變量
environment": [
{
"name": "JAVA_OPTS",
"value": "-Dspring.profiles.active=aws"
}
]
方法二: 通過Cloudformation配置環(huán)境變量
Environment:
-
Name: JAVA_OPTS
Value: !FindInMap [ SpringCloudMiddlewares, ConfigServer, javaopt ]
-
Name: EUREKA_URL
Value: !Join [ "" , [ "http://", !GetAtt PublicApplicationLoadBalancer.DNSName, "/simple-eureka/eureka"]]
-
Name: SPRING_PROFILES_ACTIVE
Value: aws
確保端口動態(tài)綁定(非靜態(tài)綁定),防止端口沖突問題:
方法一: ECS的task配置,hostPost設置為0.
"portMappings": [
{
"hostPort": 0,
"protocol": "tcp",
"containerPort": 8080
}
]
方法二: 通過Cloudformation配置動態(tài)端口綁定
-
ContainerPort: !FindInMap [ SpringCloudMiddlewares, ConfigServer, port ]
HostPort: 0
確保SG安全組,允許在動態(tài)端口范圍內(nèi)的訪問:

創(chuàng)建Task的時候,確保每個task里面都開啟了日志記錄:
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "simple-ecs-cluster-log",
"awslogs-region": "cn-northwest-1",
"awslogs-stream-prefix": "simple-application"
}
}
############### CloudWatchLog start ####################
ECSCloudWatchLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Join [ "-" , [ !Ref EcsClusterName, !Ref ECSCloudWatchLogGroupName]]
RetentionInDays: !Ref ECSCloudWatchLogGroupRetentionInDays
############### CloudWatchLog end ####################
創(chuàng)建Task的時候,確保每個task里面都擁有足夠多的訪問權限,比如你的服務需要訪問SQS,Redis,S3等。
集群部署
部署成功后,可通過下面Eureka界面,查看注冊成功的服務:

查看ECS集群運行狀態(tài):

時區(qū)的一致性:
- EC2上面的時區(qū)一致性:
for ec2 in 10.208.195.1xx 10.208.194.xx 10.208.194.1xx
do
echo $ec2;
ssh -i ~/.ssh/yourkey.pem ec2-user@$ec2 << sshoff
sudo yum -y erase 'ntp*'
sudo yum -y install chrony
sudo service chronyd start
sudo chkconfig chronyd on
sudo sed -i 's/ZONE=\"UTC/ZONE=\"Asia\/Shanghai/g' /etc/sysconfig/clock
sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
exit
sshoff
done
-
RDS上面的時區(qū)一致性:
設置Parameter Group,并且重啟實例。
-
Docker上面的時區(qū)一致性:
重新構建滿足條件的base鏡像,并更新響應的配置文件pom.xml或者dockerfile
編碼上,保證時區(qū)的方法:
System.setProperty("user.timezone","Asia/Shanghai");
- 通過傳遞參數(shù)的方式,保證時區(qū)的方法:
-Duser.timezone=GMT+8