AWS: ECS Fargate + ELB的使用(CDK)
一、ECS定义
Amazon Elastic Container Service (Amazon ECS)是一种高度可扩展的快速容器管理服务,它又包含两种服务:
- 使用 Fargate 启动类型,它是基于Container的Serverless服务, 用户无需关心服务器,只需要上传自定义的镜像,剩下的工作交给Fargate 就可以了;注意这里host与pod的比例是1:1,也就是一个虚拟机对应于一个pod。
- 使用 EC2 启动类型,它是使用EC2作为服务器,用户是需要关心服务器的;注意这里host与pod的比例是1:many,也就是一个虚拟机对应多个pod。
注意:AWS Fargate 与AWS Lambda无服务底层技术是使用Firecracker,不是Kubernetes,参考这里
二、使用场景
下图是Amazon ECS 架构,一个Region下包含多个可用区(AZ),一个ECS集群(Cluster)是跨多个可用区的,一个集群(Cluster)里包含一个或多个服务(Service),一个服务(Service)里包含一个或多个任务(Task),一个任务(Task)里包含一个或多个容器镜像(image)。
下图是它的常见使用案例,包含的内容如下:
- 首先开发人员需要创建自定义的镜像将其推送至Amazon ECR(EC2 Container Registry)
- 定义任务(Task),它是ECS调度的最小单元,类似于K8S中的Pod,里面包含一个或多个容器镜像
- 创建服务(Service),一个服务通常由多个一组同类的任务(Task)组成。
- 创建一个ELB(Elastic Load Balancer),通过它将服务暴露出去
- 在ELB(Elastic Load Balancer)的前面,通常会配置一个API Gateway,实现服务治理。
三、Demo with Fargate
本节将通过AWS CDK创建一个ECS Fargate集群,然后将集群里的服务通过负载均衡器暴露给外网访问。
1. 运行命令,创建一个CDK项目
mkdir ecs-demo && cd ecs-demo
cdk init -l typescript
2. 创建一个Stack
import * as cdk from "@aws-cdk/core"; // 必须引入的包
import * as ecs from "@aws-cdk/aws-ecs"; // 我们使用ECS服务,那就引入此包
import * as ec2 from "@aws-cdk/aws-ec2"; // for VPC
import * as elbv2 from "@aws-cdk/aws-elasticloadbalancingv2"; // 使用负载均衡器,需要引入此包
export class EcsFargateDemoStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// 通常VPC已经默认创建好了,这一步是为了寻找现有的VPC
const vpc = ec2.Vpc.fromLookup(this, "Vpc", { isDefault: true });
// 创建一个集群
const cluster: ecs.Cluster = new ecs.Cluster(this, "FargateCluster", {
vpc: vpc,
});
// 创建一个Task的定义,注意是FARGATE类型,同时也包含了cpu和内存
const taskDefinition = new ecs.TaskDefinition(this, "cong-task", {
compatibility: ecs.Compatibility.FARGATE,
cpu: "512",
memoryMiB: "1024",
});
// 向Task的定义里添加一个镜像,暴露80端
taskDefinition
.addContainer("cong-demo", {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
memoryLimitMiB: 512,
})
.addPortMappings({ containerPort: 80 });
// 创建一个service,service里包含task的定义,期望运行的task数量是4
const fargateService = new ecs.FargateService(this, "FargateService", {
cluster: cluster,
taskDefinition: taskDefinition,
desiredCount: 4,
serviceName: "Cong-Service",
});
// service有自动横向扩缩的功用,这里给task的数量定义一个扩缩范围
fargateService.autoScaleTaskCount({
maxCapacity: 10,
minCapacity: 1,
});
// 创建一个load balancer,它是由监听器(listener)和目标组(targate group)组成
const elb = new elbv2.ApplicationLoadBalancer(this, "Elb", {
vpc: vpc,
internetFacing: true, // 向外网暴露
});
const listener = elb.addListener("Listener", {
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
});
listener.addTargets("service-A", {
port: 80,
targets: [fargateService],
});
// 输出load balancer的DNS name,直接在浏览器中打开它就可以访问到本服务了。
new cdk.CfnOutput(this, "LB DNS NAME", {
value: listener.loadBalancer.loadBalancerDnsName,
});
}
}
3. 部署
运行如下命令可将其直接部署到AWS云上,部署的方式实际上是通过CloudFormation进行部署的。
cdk deploy
# 运行它会在cdk.out文件夹下生成一个CloudFormation的模板
# cdk synth
四、Demo with EC2
本节将通过AWS CDK创建一个ECS EC2集群,然后将集群里的服务通过负载均衡器暴露给外网访问。
1. 创建一个新的stack
在原有项目上创建一个新的stack(ecs-demo-stack.ts), 如下代码上面的fargate的内容基本相同,注意如下注释部分是稍有区别的地方
import * as cdk from "@aws-cdk/core";
import * as ecs from "@aws-cdk/aws-ecs";
import * as ec2 from "@aws-cdk/aws-ec2";
import * as elbv2 from "@aws-cdk/aws-elasticloadbalancingv2";
export class EcsDemoStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = ec2.Vpc.fromLookup(this, "Vpc", { isDefault: true });
const cluster: ecs.Cluster = new ecs.Cluster(this, "FargateCluster", {
vpc: vpc,
});
// 1. 通过addCapacity方法可将EC2实例添加到集群之中,instanceType是必填项;spotPrice是选填的,如果填了它,那么它就是一个竞价型的实例,价格更便宜,这个数值可以在当前region下的ec2 console页面上找到。
// 2. addCapacity方法返回的对象是AutoScalingGroup, 由此组管理了一组ecs实例
// 3. 一个集群中是可以创建多个AutoScalingGroup的,比如:可以由8台标准实例和2台spot实例组成。
cluster.addCapacity("DefaultAutoScalingGroupCapacity", {
instanceType: new ec2.InstanceType("t3.micro"),
desiredCapacity: 2,
spotPrice: "0.1168",
minCapacity: 1,
maxCapacity: 10
});
// 注意这里compatibility的值是ec2
const taskDefinition = new ecs.TaskDefinition(this, "ec2-task", {
compatibility: ecs.Compatibility.EC2,
cpu: "256",
memoryMiB: "512",
});
taskDefinition
.addContainer("nginx", {
image: ecs.ContainerImage.fromRegistry("nginx"),
memoryLimitMiB: 512,
})
.addPortMappings({ containerPort: 80 });
const ec2Service = new ecs.Ec2Service(this, "Ec2Service", {
cluster: cluster,
taskDefinition: taskDefinition,
desiredCount: 2,
});
// ec2Service.autoScaleTaskCount({
// maxCapacity: 11,
// minCapacity: 1
// });
const elb = new elbv2.ApplicationLoadBalancer(this, "Elb", {
vpc: vpc,
internetFacing: true,
});
const listener = elb.addListener("Listener", {
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
});
const lbTargetGroup = new elbv2.ApplicationTargetGroup(
this,
"TargetGroup",
{ vpc: vpc, port: 80, protocol: elbv2.ApplicationProtocol.HTTP }
);
lbTargetGroup.addTarget(ec2Service);
listener.addTargetGroups("TargetGroup1", {
targetGroups: [lbTargetGroup],
});
new cdk.CfnOutput(this, "LB DNS NAME", {
value: listener.loadBalancer.loadBalancerDnsName,
});
}
}
2. 填充bin/ecs-demo.ts
在bin/ecs-demo.ts文件中将此stack new出来。bin/ecs-demo.ts的文件内容如下:
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "@aws-cdk/core";
import { EcsDemoStack } from "../lib/ecs-demo-stack";
import { EcsFargateDemoStack } from "../lib/ecs-fargate-demo-stack";
const app = new cdk.App();
const env = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};
new EcsDemoStack(app, "EcsDemoStack", { env });
new EcsFargateDemoStack(app, "EcsFargateDemoStack", { env });
3. 配置实例的扩缩(Instance Auto-Scaling)
如果你的应用是运行在AWS ECS Fargate上,那AWS会自动帮你管理好底层真实的虚拟机及以容器的调度,但如果你使用AWS ECS EC2的话,你预先创建的虚拟机实例数是固定的,它有可能随着task的增加被填满。
为了避免这个放置问题(placement error),我们需要配置Auto-Scaling以便实例能够按需扩缩,代码如下:
const autoScalingGroup = cluster.addCapacity('DefaultAutoScalingGroup', {
instanceType: new ec2.InstanceType("t2.xlarge"),
minCapacity: 3,
maxCapacity: 30,
desiredCapacity: 3,
// Give instances 5 minutes to drain running tasks when an instance is
// terminated. This is the default, turn this off by specifying 0 or
// change the timeout up to 900 seconds.
taskDrainTime: Duration.seconds(300)
});
autoScalingGroup.scaleOnCpuUtilization('KeepCpuHalfwayLoaded', {
targetUtilizationPercent: 50
});
See the @aws-cdk/aws-autoscaling
library for more autoscaling options you can configure on your instances.