Day 23 – CDK 建置 Amazon Elastic Container Service(ECS) Service – Fargate 與 EC2 混搭

最近討論了 EC2、Spot 與 Fargate 有沒有想過我們是不是可以來一個混搭的系統呢? 混搭系統可以讓我們的系統可以更有彈性的被控制,像是今天可能有顯卡運算資源機器的需求我們就把它掛在 EC2 上面,一般服務型態的服務可以開 EC2 與 EC2 Spot 各半,排成服務就跑在 Fargate 上,流量高的時段就開 EC2 Spot 的機器,這樣聽起來是不是很完美呢 XD ~ 所以今天就來說明怎麼建構一個如此有彈性的系統吧!

https://ithelp.ithome.com.tw/upload/images/20201009/20117701ZCj2l2U57Z.jpg

建立一個 EC2 與 Fargate 混搭的 ECS Cluster

在開始說明之前先整理一下我們的目標,我們需要擁有三種不同的機器分別是

  • On-Demand Instance:t3.micro
  • Spot Instance:t3.small
  • Fargate

模擬服務可以在不同的機器上面運作,所以需要開三種不同的 Task 分別是

  • EC2
  • EC2 Spot
  • Fargate

建立 ECS Cluster

首先處理 ECS Cluster 並且設定 On-Demand Instance 的 ASG 與 Spot Instance 的 ASG,分別對應到機器等級

  • On-Demand Instance:t3.micro
  • Spot Instance:t3.small

Fargate 不用自己控制機器所以這邊不用處理

const cluster = new ecs.Cluster(this, "EcsCluster", { vpc });

const autoScalingGroup = cluster.addCapacity(
  "DefaultAutoScalingGroupCapacity",
  {
    instanceType: ec2.InstanceType.of(
      ec2.InstanceClass.T3,
      ec2.InstanceSize.MICRO
    ),
    desiredCapacity: 1,
    machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
  }
);
autoScalingGroup.scaleOnCpuUtilization("KeepCpuHalfwayLoaded", {
  targetUtilizationPercent: 50,
});

const spotAutoScalingGroup = cluster.addCapacity(
  "SpotAutoScalingGroupCapacity",
  {
    instanceType: ec2.InstanceType.of(
      ec2.InstanceClass.T3,
      ec2.InstanceSize.SMALL
    ),
    desiredCapacity: 1,
    machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
    spotPrice: "0.0104",
    spotInstanceDraining: true,
  }
);
spotAutoScalingGroup.scaleOnCpuUtilization("KeepCpuHalfwayLoaded", {
  targetUtilizationPercent: 50,
});

建立完後可以在 ECS Instances 看到有兩種不同大小的機器註冊

https://ithelp.ithome.com.tw/upload/images/20201007/20117701DvR0CJmlbq.png

建立 Task Definition

使用 Constraints 約束 Task

因為要實現不同類型的機器跑不同的服務這邊介紹一個新的參數 Constraints 它可以用來對 Task 做一些約束像是機器等級系統種類是否有掛 EFS 等等或是一些自訂參數,但是目前 AWS CDK 還不支援設定設定 Attributes 所以自動參數的部分這次沒有辦法做給大家看有興趣可以參考 AWS 文件直接在 console 或是使用 command 實作

使用 TaskDefinition 定義共用 Task

除了 Constraints 這次範例還使用了一個新的 function TaskDefinition 它有一個很好用的屬性 compatibility 使用 EC2_AND_FARGATE 就可以建立一個不管是 EC2 或是 Fargate 都可以一起共用的 Task

AWS VPC

另外還有使用一個特別的 Network Mode AWS VPC 它可以把 ENI 網卡直接掛在 Container 上面,往常我們如果直接使用 EC2 部署沒有使用 Load Balance 是沒有辦法兩個 Container 使用同一個 port 的,畢竟在系統上只有一個 process 可以 bind 一個 port,而使用了 AWS VPC 就不會發生這個問題了,因為大家都可以直接使用自己的獨立網卡拉!

查看 ECS Instance Attributes

上面有提到要使用 Constraints 約束機器可是可以約束的條件要從哪裡找呢?我們可以到 AWS Console ECS 找到 EC2 Instances,在裡面選擇一台機器按下 Actions -> View/Edit Attributes 就可以看到了,如下兩張圖片是我們開設的機器 Attributes

https://ithelp.ithome.com.tw/upload/images/20201007/20117701m8ZbIua97v.png

如圖可以看到機器等級是t3.micro

https://ithelp.ithome.com.tw/upload/images/20201007/20117701kqrC7l7xgU.png

如圖可以看到機器等級是t3.small

設定 Fargate Task

Fargate 雖然不受 Cluster 管理不過我們一樣要設定 Task

const fargateTaskDefinition = new ecs.FargateTaskDefinition(
  this,
  "FargateTaskDef",
  {
    cpu: 256,
    memoryLimitMiB: 512,
  }
);

https://ithelp.ithome.com.tw/upload/images/20201007/20117701TCEDRvljgH.png

可以在 Task 看到 Network Mode 設定為 awsvpc

https://ithelp.ithome.com.tw/upload/images/20201007/2011770194n2yUnDrR.png

可以看到 Constraint 裡面是空的

設定限制機器等級 t3.micro 的 Task Definition

在這邊使用了上面說的 TaskDefinition 所以需要設定 compatibility 為 EC2,不然就要如下使用 Ec2TaskDefinition 不過其實都可以

在前面我們設定 On-Demand Instance 等級為 t3.micro,所以 placementConstraints 設定機器等級限制 t3.micro

const taskDefinition = new ecs.TaskDefinition(this, "TaskDef", {
  cpu: "256",
  memoryMiB: "512",
  compatibility: ecs.Compatibility.EC2,
  networkMode: ecs.NetworkMode.AWS_VPC,
  placementConstraints: [
    ecs.PlacementConstraint.memberOf(
      "attribute:ecs.instance-type == t3.micro"
    ),
  ],
});

https://ithelp.ithome.com.tw/upload/images/20201007/20117701PUwO0IUMFa.png

可以在 Task 看到 Network Mode 設定為 awsvpc

https://ithelp.ithome.com.tw/upload/images/20201007/20117701EFGTqgXzTD.png

可以在 Constraint 看到限制 type 等級 t3.micro

設定限制機器等級 t3.small 的 EC2 Task Definition

而 Spot Instance 等級為 t3.small,所以在 placementConstraints 設定機器等級限制 t3.small

const spotTaskDefinition = new ecs.Ec2TaskDefinition(this, "SpotTaskDef", {
  networkMode: ecs.NetworkMode.AWS_VPC,
  placementConstraints: [
    ecs.PlacementConstraint.memberOf(
      "attribute:ecs.instance-type == t3.small"
    ),
  ],
});

https://ithelp.ithome.com.tw/upload/images/20201007/20117701zE6oJ9ruIC.png

可以在 Task 看到 Network Mode 設定為 awsvpc

https://ithelp.ithome.com.tw/upload/images/20201007/20117701t89389R4YB.png

可以在 Constraint 看到限制 type 等級 t3.small

在 ECS Tasks 檢查設定

可以在 ECS Tasks 看到上面設定的所有 Task

https://ithelp.ithome.com.tw/upload/images/20201007/201177016GZ50ndNNr.png

建立 Container

這次範例使用一樣的 image 來建立範例,不過我們分別使用了不同的 Task Definition 來限制不同的機器種類

const fargateContainer = fargateTaskDefinition.addContainer(
  "WebContainer",
  {
    image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
    memoryLimitMiB: 512,
  }
);
fargateContainer.addPortMappings({
  containerPort: 80,
});

const container = taskDefinition.addContainer("WebContainer", {
  image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
  memoryLimitMiB: 512,
});
container.addPortMappings({
  containerPort: 80,
});

const spotContainer = spotTaskDefinition.addContainer("WebContainer", {
  image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
  memoryLimitMiB: 512,
});
spotContainer.addPortMappings({
  containerPort: 80,
});

設定 Service

建立三種不同的 Service 分別管理不同機器等級的 Task

const fatgetService = new ecs.FargateService(this, "FargateService", {
  cluster,
  taskDefinition: fargateTaskDefinition,
});

const ecsService = new ecs.Ec2Service(this, "Ec2Service", {
  cluster,
  taskDefinition,
});

const spotEcsService = new ecs.Ec2Service(this, "SpotEc2Service", {
  cluster,
  taskDefinition: spotTaskDefinition,
});

https://ithelp.ithome.com.tw/upload/images/20201007/201177018tbnpB3bLA.png

建立 ALB

為了方便測試來把三種 Service 加入一樣的 target 吧!如果混搭的目標是用不同的機器跑不同的 Service 就可以分別投入對應的 Load Balancer 拉!

const lb = new elbv2.ApplicationLoadBalancer(this, "LB", {
  vpc,
  internetFacing: true,
});
const listener = lb.addListener("Listener", { port: 80 });

listener.addTargets("ECS2", {
  port: 80,

  targets: [fatgetService, ecsService, spotEcsService],
});

測試

這次也是測試網頁而已就不多提了 XD https://ithelp.ithome.com.tw/upload/images/20201007/20117701H0SV5b4Cpc.png


今天是一個機器混搭的測試,如此設定就可以在 ECS 部署上有更多的彈性