使用 ECS Exec 直通 ECS 容器会话(适用于 Fargate 和 EC2)

基于 EC2 的 ECS 服务,要看看容器内的状态,一直以来都是先 SSM(Simple System Manager) 或 SSH 进到 EC2 实例,然后再 docker exec -it <container-id> sh, 查看容器的控制台日志则用 docker logs <container-id> [--follow]. 但是对使用 Farget 的 ECS 服务就无能为力了,因为找不到 SSM 或 SSH 的主体, 只能通过程序日志来大概了解容器内发生的事了。

Amazon 在 2021-03-15 推出了一个新的特性 ECS Exec 允许我们直接连接 Fargate 或 EC2 中的容器会话,见 Amazon ECS now allows you to run commands in a container running on Amazon EC2 or AWS Fargate. ECS Exec 支持 Container Agent 版本为 1.50.2 及以上的 ECS Optimized AMI 系列,和 Fargate Platform Version 1.4.0(Linux) 或 1.0.0(Windows) 及以上。

ECS Exec 的实现原理是以往在 EC2 实例上启动的 SSM Agent,也在容器内部启动一份,然后命令 aws ecs execute-command 直指容器本身。参考本人写过的一篇 AWS Session Manager 管理 EC2 实例,连接过程中唯一的不同就是容器中也运行了一个 SSM Agent, 所以这个容器也就无所谓是在 EC2 实例还是在 Fargate 中。


=〉

由于 AWS Cli 是通过 Session Manager 来连接容器的,所以在客户端也必须安装 Session Manager 插件,参见 Install the Session Manager plugin for the AWS CLI.

接下来我们用 Terraform 来创建 Fargate 的 ECS 集群,并启动一个容器(任务),并由 ECS Exce 进入到它的交互界面。

ecs-fargate.tf
 1resource "aws_ecs_cluster" ecs-exec-demo-cluster {
 2  name = "ecs-exec-demo-cluster"
 3}
 4
 5resource "aws_ecs_service" demo-service {
 6  name = "demo-service"
 7  cluster = aws_ecs_cluster.ecs-exec-demo-cluster.name
 8  task_definition = aws_ecs_task_definition.demo-task-definition.arn
 9  launch_type = "FARGATE"
10  desired_count = 1   # 设置为 1 来启动一个 task
11  enable_execute_command = true   # 这是必须的
12  network_configuration {
13    subnets = ["subnet-cf034d94"]
14  }
15}
16
17
18resource "aws_ecs_task_definition" "demo-task-definition" {
19  family = "demo-task-definition"
20  network_mode = "awsvpc"
21  requires_compatibilities = [
22    "FARGATE"
23  ]
24  cpu = 256
25  memory = 512
26  
27#  该 role 是 EC2 或 Fargate 用来 pull 和 运行 Docker 容器的
28  execution_role_arn = aws_iam_role.task-execution-role.arn
29  task_role_arn = aws_iam_role.task-role.arn # 这是容器内部使用的 role
30
31  container_definitions = <<EOF
32[
33  {
34    "name": "demo-container",
35    "image": "busybox",
36    "essential": true,
37    "command": ["sh", "-c", "echo hello world, sleep...; sleep 3600"],
38    "linuxParameters": {
39      "initProcessEnabled": true
40    }
41  }
42]
43EOF
44}
45
46resource "aws_iam_role" task-execution-role {
47  name = "demo-task-execution-role"
48  assume_role_policy = local.ecs_assume_role_policy
49  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
50}
51
52resource "aws_iam_role" "task-role" {
53  name = "demo-task-role"
54  assume_role_policy = local.ecs_assume_role_policy
55#  容器内使用的 task role 必须有以下的权限来启动容器内的 SSM Agent
56  inline_policy {
57    name = "SSM_agent_permissions"
58    policy = <<EOF
59{
60   "Version": "2012-10-17",
61   "Statement": [
62       {
63       "Effect": "Allow",
64       "Action": [
65            "ssmmessages:CreateControlChannel",
66            "ssmmessages:CreateDataChannel",
67            "ssmmessages:OpenControlChannel",
68            "ssmmessages:OpenDataChannel"
69       ],
70      "Resource": "*"
71      }
72   ]
73}
74EOF
75  }
76}
77
78locals {
79  ecs_assume_role_policy = <<EOF
80{
81  "Version": "2012-10-17",
82  "Statement": [
83    {
84      "Effect": "Allow",
85      "Principal": {
86        "Service": [
87          "ecs-tasks.amazonaws.com"
88        ]
89      },
90      "Action": "sts:AssumeRole"
91    }
92  ]
93}
94EOF
95}

简单说明:

  1. task execution role 是 EC2 或 Fargate 用来 pull 和运行 docker 容器的,所以只需要 AWS 现成的 AmazonECSTaskExecutionRolePolicy
  2. task role 是容器内部使用的,它用来启动容器内的 SSM Agent 和运行应用程序,所以必须给予它应用程序所需的权限和启动 SSM Agent 的权限,如 ssmmessages:*
  3. 创建 ECS service 时必须指定 enable_execute_command 为 true, 否则无法直连容器
  4. 任务定义中推荐设置 initProcessEnabled: true,为 false 时容器也是可被连接的,但会造成容器退出时 ECS Exec 执行的命令进程仍在 

在本地准备好 AWS 的 provider, 我们运行 terraform 命令
terraform init
terraform apply -auto-approve
然后,我们找到 task ID 来,从 AWS Web 界面或用 aws cli 命令找都行
1➜  / aws ecs list-tasks --cluster ecs-exec-demo-cluster
2{
3    "taskArns": [
4        "arn:aws:ecs:us-east-1:913903414417:task/ecs-exec-demo-cluster/62d6110c212f4849962deaa761852dd8"
5    ]
6}
7➜  / aws ecs describe-tasks --cluster ecs-exec-demo-cluster --tasks 62d6110c212f4849962deaa761852dd8 | grep enableExecuteCommand
8            "enableExecuteCommand": true,
有了 task/container ID 后就可以直接连接到该容器会话了
 1➜  / aws ecs execute-command --cluster ecs-exec-demo-cluster --command "sh" --interactive --task 62d6110c212f4849962deaa761852dd8
 2
 3The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.
 4
 5
 6Starting session with SessionId: ecs-execute-command-0574b7d8566ba9da7
 7/ #
 8/ # ps
 9PID   USER     TIME  COMMAND
10    1 root      0:00 sh -c echo hello world, sleep...; sleep 3600
11    7 root      0:00 /managed-agents/execute-command/amazon-ssm-agent
12   20 root      0:00 /managed-agents/execute-command/ssm-agent-worker
13  109 root      0:00 /managed-agents/execute-command/ssm-session-worker ecs-execute-command-0574b7d8566ba9da7
14  117 root      0:00 sh
15  118 root      0:00 ps
16/ # netstat -na|grep 443
17tcp        0    490 10.255.60.141:59228     209.54.181.251:443      ESTABLISHED
18tcp        0      0 10.255.60.141:39104     52.46.156.29:443        ESTABLISHED
19tcp        0      0 10.255.60.141:59216     209.54.181.251:443      ESTABLISHED

EC2 或 Fargate 在运行容器时会把 SSM Agent 放到 /managed-agents 目录中并执行. 如果任务包含多个容器时必须用参数 --container 指定容器名。

这实际上是使用了 Session Manager 连接容器的,回忆一下用 Session Manager 连接 EC2 实例的情形
1➜  ~ aws ssm start-session --target i-08e7fb57079b1ac48
2
3Starting session with SessionId: yanbin@example.com-01ed9ebeaf557b145
4sh-4.2$ ps -ef |grep ssm-agent
5root      3460     1  0 Jan11 ?        00:00:39 /usr/bin/amazon-ssm-agent
6root      3868  3460  0 Jan11 ?        00:07:50 /usr/bin/ssm-agent-worker
7ssm-user 12192 12123  0 20:00 pts/0    00:00:00 grep ssm-agent

无论是 EC2 还是 Fargate 启动的容器,我们都可以一步进到容器中,这对于 EC2 的容器确实是省了一个中间环节。不过一旦到了容器内部,反而无法查看当前容器的日志了,在容器外还能用 docker logs <containter-id>, 但在容器内就只能感叹身在此山中了。

注意到上面执行 aws ecs execute-command 是指了 --interactive 即进到交互界面,不过目前也只能支持交互界面,去掉 --interactive  参数则报错
1➜  / aws ecs execute-command --cluster ecs-exec-demo-cluster --command "sh" --task 62d6110c212f4849962deaa761852dd8
2
3The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.
4
5
6Parameter validation failed:
7Missing required parameter in input: "interactive"

你可能会觉得 sh 是交互界面,其实换成非交互的程序也不行,比如试图执行 echo 123
1➜  / aws ecs execute-command --cluster ecs-exec-demo-cluster --command "echo 123" --task 62d6110c212f4849962deaa761852dd8
2
3The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.
4
5
6Parameter validation failed:
7Missing required parameter in input: "interactive"

如果是非交互的命令执行完后直接退出
 1➜  / aws ecs execute-command --cluster ecs-exec-demo-cluster --command "echo 123" --interactive --task 2f1cb44b02f8454cbaf5ec317c6b16ec
 2
 3The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.
 4
 5
 6Starting session with SessionId: ecs-execute-command-05b5f8e4f4d871bd6
 7123
 8
 9
10Exiting session with sessionId: ecs-execute-command-05b5f8e4f4d871bd6.

--interactive 参数少不得

ECS Exec 的命令输出可记录到 CloudWatch 或 S3 Bucket 中,默认是会写入到定义任务时的 awslogs 指示的 LogGroup/LogStream, 也可在定义 ECS cluster 时指定 ECS Exec 的命令输出目的地,如我们在创建 ECS Cluster 时的 Terraform 要变成这样
 1resource "aws_ecs_cluster" ecs-exec-demo-cluster {
 2  name = "ecs-exec-demo-cluster"
 3  configuration {
 4    execute_command_configuration {
 5      log_configuration {
 6        cloud_watch_log_group_name = "ecs-exec-log"
 7        s3_bucket_name = "ecs-exec-log-bucket"
 8        s3_key_prefix = "demo"
 9      }
10    }
11  }
12}

相应的 Task Role 要有相应的 CloudWatch  或 S3 写的权限。

最后感受

先前一直还苦于无法连接 Fargate 或 Fargate 中的容器,如今试用一番之后反而有点鸡肋

如果是基于 EC2 的容器,还是倾向于先 SSM 登陆 EC2 实例,再用 docker 命令来观察和诊断容器。因为 EC2 比容器的生命周期要长,用 docker logs <container-id> 对存活的或已死的容器都能查看它们的日志。

如果是直连容器的话,容器因某种原因快速消亡后便来不急建立会话,或是要频繁的建立会话, 很多时候最是需要了解容器是怎么死的。因此已死的容器依然需要通过 AWS 控制台或 CloudWatch 日志来确定问题

Fargate 的容器如果运行比较稳定,大可不必接入它的会话,基本上观察程序执行日志就行了



[2023-12-23]

如果连接不上出现类似如下错误信息的话
The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.
An error occurred (TargetNotConnectedException) when calling the ExecuteCommand operation: The execute command failed due to an internal error. Try again later.
最好的检查办法就是运行 check-ecs-exec.sh 命令,它能帮我们检测 Task 为什么不能被连接的具体原因,从而进行快速修复。

参考:

  1. NEW - Using Amazon ECS Exec to access your containers on AWS Fargate and Amazon EC2
  2. Using Amazon ECS Exec for debugging
永久链接 https://yanbin.blog/ecs-exec-connect-ecs-container-on-fargate-and-ec2/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。