基于 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
resource "aws_ecs_cluster" ecs-exec-demo-cluster { name = "ecs-exec-demo-cluster" } resource "aws_ecs_service" demo-service { name = "demo-service" cluster = aws_ecs_cluster.ecs-exec-demo-cluster.name task_definition = aws_ecs_task_definition.demo-task-definition.arn launch_type = "FARGATE" desired_count = 1 # 设置为 1 来启动一个 task enable_execute_command = true # 这是必须的 network_configuration { subnets = ["subnet-cf034d94"] } } resource "aws_ecs_task_definition" "demo-task-definition" { family = "demo-task-definition" network_mode = "awsvpc" requires_compatibilities = [ "FARGATE" ] cpu = 256 memory = 512 # 该 role 是 EC2 或 Fargate 用来 pull 和 运行 Docker 容器的 execution_role_arn = aws_iam_role.task-execution-role.arn task_role_arn = aws_iam_role.task-role.arn # 这是容器内部使用的 role container_definitions = <<EOF [ { "name": "demo-container", "image": "busybox", "essential": true, "command": ["sh", "-c", "echo hello world, sleep...; sleep 3600"], "linuxParameters": { "initProcessEnabled": true } } ] EOF } resource "aws_iam_role" task-execution-role { name = "demo-task-execution-role" assume_role_policy = local.ecs_assume_role_policy managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"] } resource "aws_iam_role" "task-role" { name = "demo-task-role" assume_role_policy = local.ecs_assume_role_policy # 容器内使用的 task role 必须有以下的权限来启动容器内的 SSM Agent inline_policy { name = "SSM_agent_permissions" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssmmessages:CreateControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenControlChannel", "ssmmessages:OpenDataChannel" ], "Resource": "*" } ] } EOF } } locals { ecs_assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ecs-tasks.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] } EOF } |
简单说明:
- task execution role 是 EC2 或 Fargate 用来 pull 和运行 docker 容器的,所以只需要 AWS 现成的 AmazonECSTaskExecutionRolePolicy
- task role 是容器内部使用的,它用来启动容器内的 SSM Agent 和运行应用程序,所以必须给予它应用程序所需的权限和启动 SSM Agent 的权限,如
ssmmessages:*
- 创建 ECS service 时必须指定
enable_execute_command
为 true, 否则无法直连容器 - 任务定义中推荐设置
initProcessEnabled: true
,为false
时容器也是可被连接的,但会造成容器退出时 ECS Exec 执行的命令进程仍在
在本地准备好 AWS 的 provider, 我们运行 terraform 命令
terraform init
terraform apply -auto-approve
然后,我们找到 task ID 来,从 AWS Web 界面或用 aws cli 命令找都行
12345678 ➜ / aws ecs list-tasks --cluster ecs-exec-demo-cluster{"taskArns": ["arn:aws:ecs:us-east-1:913903414417:task/ecs-exec-demo-cluster/62d6110c212f4849962deaa761852dd8"]}➜ / aws ecs describe-tasks --cluster ecs-exec-demo-cluster --tasks 62d6110c212f4849962deaa761852dd8 | grep enableExecuteCommand"enableExecuteCommand": true,
有了 task/container ID 后就可以直接连接到该容器会话了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
➜ / aws ecs execute-command --cluster ecs-exec-demo-cluster --command "sh" --interactive --task 62d6110c212f4849962deaa761852dd8 The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-0574b7d8566ba9da7 / # / # ps PID USER TIME COMMAND 1 root 0:00 sh -c echo hello world, sleep...; sleep 3600 7 root 0:00 /managed-agents/execute-command/amazon-ssm-agent 20 root 0:00 /managed-agents/execute-command/ssm-agent-worker 109 root 0:00 /managed-agents/execute-command/ssm-session-worker ecs-execute-command-0574b7d8566ba9da7 117 root 0:00 sh 118 root 0:00 ps / # netstat -na|grep 443 tcp 0 490 10.255.60.141:59228 209.54.181.251:443 ESTABLISHED tcp 0 0 10.255.60.141:39104 52.46.156.29:443 ESTABLISHED tcp 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 2 3 4 5 6 7 |
➜ ~ aws ssm start-session --target i-08e7fb57079b1ac48 Starting session with SessionId: yanbin@example.com-01ed9ebeaf557b145 sh-4.2$ ps -ef |grep ssm-agent root 3460 1 0 Jan11 ? 00:00:39 /usr/bin/amazon-ssm-agent root 3868 3460 0 Jan11 ? 00:07:50 /usr/bin/ssm-agent-worker ssm-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 2 3 4 5 6 7 |
➜ / aws ecs execute-command --cluster ecs-exec-demo-cluster --command "sh" --task 62d6110c212f4849962deaa761852dd8 The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Parameter validation failed: Missing required parameter in input: "interactive" |
你可能会觉得 sh
是交互界面,其实换成非交互的程序也不行,比如试图执行 echo 123
1 2 3 4 5 6 7 |
➜ / aws ecs execute-command --cluster ecs-exec-demo-cluster --command "echo 123" --task 62d6110c212f4849962deaa761852dd8 The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Parameter validation failed: Missing required parameter in input: "interactive" |
如果是非交互的命令执行完后直接退出
1 2 3 4 5 6 7 8 9 10 |
➜ / aws ecs execute-command --cluster ecs-exec-demo-cluster --command "echo 123" --interactive --task 2f1cb44b02f8454cbaf5ec317c6b16ec The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-05b5f8e4f4d871bd6 123 Exiting session with sessionId: ecs-execute-command-05b5f8e4f4d871bd6. |
但 --interactive
参数少不得
ECS Exec 的命令输出可记录到 CloudWatch 或 S3 Bucket 中,默认是会写入到定义任务时的 awslogs
指示的 LogGroup/LogStream, 也可在定义 ECS cluster 时指定 ECS Exec 的命令输出目的地,如我们在创建 ECS Cluster 时的 Terraform 要变成这样
1 2 3 4 5 6 7 8 9 10 11 12 |
resource "aws_ecs_cluster" ecs-exec-demo-cluster { name = "ecs-exec-demo-cluster" configuration { execute_command_configuration { log_configuration { cloud_watch_log_group_name = "ecs-exec-log" s3_bucket_name = "ecs-exec-log-bucket" s3_key_prefix = "demo" } } } } |
相应的 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.4.0 Fargate 容器配置好 Task Role 的权限,就能直接登陆容器中的 Shell - 参考 使用 ECS Exec 直通 ECS 容器会话(适用于 Fargate 和 EC2) aws ecs execute-command --cluster $ECS_CLUSTER --command "$COMMAND" --interactive --task […]