1

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

 8 months ago
source link: https://yanbin.blog/docker-ecs-how-to-handle-entrypoint/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

2023-12-23 | 阅读(12)

不觉一晃还是在五年前记录过一篇 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

static {
    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        System.out.println("JVM shutdown captured by shutdown hook");

mvn package -DskipTests

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

创建 Dockerfile 文件

FROM amazoncorretto:17
RUN yum install -y procps
COPY target/*.jar app.jar
ENTRYPOINT java $JAVA_OPTS -jar /app.jar

构建 Docker 镜像

docker build -t demo .

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

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

  1. 本地环境为 Mac OS X 的  Docker Desktop,版本为
    $ docker version --format 'Server.Version: {{.Server.Version}},Client.Version: {{.Client.Version}}' | tr , '\n'
    Server.Version: 24.0.6
    Client.Version: 24.0.6
  2. 本地 Linux 的 Docker 版本
    $ docker version --format 'Server.Version: {{.Server.Version}},Client.Version: {{.Client.Version}}' | tr , '\n'
    Server.Version: 24.0.5
    Client.Version: 24.0.5
  3. ECS: LunchType 为 EC2, ECS Agent version: 1.79.0. EC2 中的 docker version, 
    docker version --format 'Server.Version: {{.Server.Version}},Client.Version: {{.Client.Version}}' | tr , '\n'
    Server.Version: 20.10.25
    Client.Version: 20.10.25
  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
    然后可查看其中的进程

测试结果如下图

docker-entrypoint-ps4-800x361.png

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

  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 也强行杀了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK