本地和 ECS 容器(EC2/Fargate) 如何处理 ENTRYPOINT

不觉一晃还是在五年前记录过一篇 Dockerfile 中命令的两种书写方式的区别,其中提到过 Dockerfile 中可选择用 ENTRYPOINT 或 CMD 来启动进程,并且在 ENTRYPOINT 和 CMD 都支持  exec, shell 和增强型 shell 方式。如果同时有 ENTRYPOINT 和  CMD(或 docker 运行时的 CMD), 则 CMD 将为 ENTRYPOINT 提供参数。

在原来那篇文中认为 shell 无法接收到 docker stop 或  docker -s SIGTERM 发来的信号,也许是随着 Docker 版本的变迁,Docker 变得越发聪明了起来,无论何种格式的 ENTRYPOINT, 都能够收到 SIGTERM 信号,比如在 Java 的 ShutdownHook 能捕捉到该信号,得以在进程停止之前作必要的清理工作。

进行本文相关研究的主因是部署在 ECS(Fargate) 中的 Java Web 服务,Task 总是因为 OutOfMemoryError 被杀掉,而在应用程序日志中却见不着半点线索说 JVM 的 OutOfMemoryError,即使后来给 Fargate 配上了 EFS, 加了 +XX:+HeapDumpOnOutOfMemoryError XX:HeapDumpPath=/efs JVM参数,在任务被 kill 时在 /efs 上从来就都没生成过内存映像文件。最后发现是因为 JVM 的 -Xmx 配置太高,留给 Fargate 容器的太少的缘故。

本文所测试的目的是观察 Dockerfile 中 ENTRYPOINT 三种不同写法时本地,ECS(包括 EC2 和 Fargate) 中如何被处理的

ENTRYPOINT java $JAVA_OPTS -jar /app.jar
ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar
ENTRYPOINT ["java", "-Xmx128M", "-jar", "/app.jar"]

测试用的 Docker 镜像, https://start.spring.io/ 选择创建一个 Maven 管理的  Springboot 3.2.0 Web 项目。

代码中加上 ShutdownHook

构建

mvn package -DskipTests

会生成 target/demo-0.0.1-SNAPSHOT.jar

创建 Dockerfile 文件

构建 Docker 镜像

docker build -t demo .

切换 ENTRYPOINT 为另外两种写法时,再分别构建相应的 Docker 镜像

测试环境有四,本地环境测试了 macOS 和 Linux

  1. 本地环境为 Mac OS X 的  Docker Desktop,版本为
  2. 本地 Linux 的 Docker 版本
  3. ECS: LunchType 为 EC2, ECS Agent version: 1.79.0. EC2 中的 docker version, 
  4. ECS: LunchType 为 Fargate, Platform version: 1.4.0
    Fargate 容器配置好 Task Role 的权限,就能直接登陆容器中的 Shell - 参考 使用 ECS Exec 直通 ECS 容器会话(适用于 Fargate 和 EC2)
        aws ecs execute-command --cluster $ECS_CLUSTER --command "$COMMAND" --interactive --task $TASK_ID
    然后可查看其中的进程

测试结果如下图

上面是用图片展示的,所以非常有必要用文字再总结一下

  1. 在本地(macOS 或 Linux) 启动 Docker 容器,无论何种写法,最终 Java 进程都将脱壳成为容器中的 1 号进程,所以它能直接捕捉到容器外部发来的 SIGTERM 信号,如 docker stop 或 docker kill -s SIGTERM <container-id>
  2. 在 ECS (EC2) 中启动的容器 1 号进程是 /sbin/docker-init, Java 进程是它的子进程,SIGTERM 信号会由 /sbin/docker-init 透传到 Java 进程
  3. 在 ECS(Fargate) 中启动的容器 1 号进程是 /dev/init, Java 进程是它的子进程,SIGTERM 信号会由 /dev/init 透传到 Java 进程
  4. 综合 #2 和 #3, 对容器的 SIGTERM 信号会直达 Java 进程,所以以下三种情况在 Java 进程中都能捕获到 SIGTERM
    1. ECS 控制台手动 Task
    2. ECS AutoScaling 停掉 Task (Deregister)
    3. Health check 失败时停掉 Task(Deregister)
  5. 最初的 shell 和 exec shell 的 ENTRYPOINT 写法不能捕获到 SIGTERM 的担忧也得到的释怀 
  6. 以上以 Java 应用为例,其他类型的进程也是一样的,如 Python, Node 等

最后强调一下,当初立此文的主业是为了研究为何 ECS 任务因 OutOfMemoryError 被杀却并未触发容器中 Java(已配置 -XX:+HeapDumpOnOutOfMemoryError) 进程转储内存映像。原因是给 JVM 分配的堆内存太多了,而 Fargate 实例所剩内存(128M)不足而造成的 Fargate 实例 OutOfMemoryError, 而 JVM 有 GC 在运作内存使用正常,最终 Fargate 实例被 SIGKILL 强行杀掉时连带的把 JVM 也强行杀了。

本文链接 https://yanbin.blog/docker-ecs-how-to-handle-entrypoint/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments