zoukankan      html  css  js  c++  java
  • K8S容器应用优雅关闭修复5003 Error

    • 1、遇到的问题

    • 2、问题排查

    • 3. 根因分析

      • 3.1、SHELL 模式和 CMD 模式带来的差异

      • 3.2、直接启动应用和通过脚本启动区别

    • 4、总结

     

    K8S容器应用优雅关闭-修复5003 Error

    运维就要无所不能,无所不会

    大家好,我是Stanley「史丹利」,今天聊技术:容器优雅关闭方案 。

    1、遇到的问题

    公司某服务接入效能平台后,发布过程中,页面偶尔会出现5003报错,开始以为是Nacos没有及时的将服务反注册,即POD在已经正常关闭的情况下,注册中心依然有POD信息,请求依然到已经关闭的POD中导致

    图片5003报错图片5003-error-2

    2、问题排查

    2.1 首先找开发同学,协助排查了反注册逻辑及相关日志,没有发现什么异常

    2.2 后来偶然发现POD中的主进程PID不为1,而PID为1的进程为shell进程,这会导致容器关闭时业务进程无法接受k8s发送的SIGTERM信号,只能在等待15秒后被强行杀死

    图片process-shell

    2.3 修改了程序启动参数,通过EXEC启动模式,使应用主进程PID为1

    图片process-exec

    2.4 重新发布验证,5003报错问题修复

    3. 根因分析

    3.1、SHELL 模式和 CMD 模式带来的差异

    通常Dockerfile中CMD和ENTRYPOINT来启动应用,启动应用有两种模式,shell 模式和 exec 模式,对应的使用 shell 模式,PID 为 1 的进程为 shell,使用 exec 模式 PID 为 1 的进程为业务本身。

    SHELL模式

    FROM golang as builder
    WORKDIR /go/
    COPY app.go    .
    RUN go build app.go
    FROM ubuntu
    WORKDIR /root/
    COPY --from=builder /go/app .
    CMD ./app

    这种方式构建的镜像应用启动后PID为1的进程是shell进程

    EXEC模式

    FROM golang as builder
    WORKDIR /go/
    COPY app.go    .
    RUN go build app.go
    FROM ubuntu
    WORKDIR /root/
    COPY --from=builder /go/app .
    CMD ["./app"]

    这种方式构建的镜像应用启动后PID为1的进程是应用进程

    3.2、直接启动应用和通过脚本启动区别

    在实际生产环境中,因为应用启动命令后会接很多启动参数,所以通常我们会使用一个启动脚本来启动应用,方便我们启动应用。对应的在容器内 PID 为 1 的进程为 shell 进程但 shell 程序不转发 signals,也不响应退出信号。所以在容器应用中如果应用容器中启动 shell,占据了 pid=1 的位置,那么就无法接收 k8s 发送的 SIGTERM 信号,只能等超时后被强行杀死了。启动脚本 start.sh

    start.sh

    $ cat > start.sh<< EOF
    #!/bin/sh
    sh -c /root/app
    EOF

    Dockerfile

    FROM golang as builder
    WORKDIR /go/
    COPY app.go    .
    RUN go build app.go
    FROM alpine
    WORKDIR /root/
    COPY --from=builder /go/app .
    ADD start.sh /root/
    CMD ["/bin/sh","/root/start.sh"]

    3.2.1 解决方案

    方案一:通过 k8s 的 prestop 参数调用容器内进程关闭脚本,实现优雅关闭。

    在前面脚本启动的dockerfile  基础上,定义一个优雅关闭的脚本,通过k8s-prestop 在关闭 POD 前调用优雅关闭脚本,实现 pod 优雅关闭。

    stop.sh

    #!/bin/sh
    ps -ef|grep app|grep -v grep|awk '{print $1}'|xargs kill -15

    通过 yaml 部署到 k8s 中

    stop.sh

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: app-prestop
      labels:
        app: prestop
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: prestop
      template:
        metadata:
          labels:
            app: prestop
        spec:
          containers:
          - name: prestop
            image: xx/app:v1.0-prestop
            lifecycle:
              preStop:
                exec:
                  command:
                  - sh
                  - /root/stop.sh

    方案二:shell 脚本修改为 exec 执行

    修改start.sh脚本

    stop.sh

    #!/bin/sh
    exec ./app

    shell 中添加一个 exec 即可让应用进程替代当前 shell 进程,可将 SIGTERM 信号传递到业务层,让业务实现优雅关闭。

    方案三:通过第三方 init 进程传递 SIGTERM 到进程中。

    使用 dump-init 或 tini 做为容器的主进程,在收到退出信号的时候,会将退出信号转发给进程组所有进程。主要适用应用本身无关闭信号处理的场景。docker –init 本身也是集成的 tini

    stop.sh

    FROM golang as builder
    WORKDIR /go/
    COPY app.go    .
    RUN go build app.go
    FROM alpine
    WORKDIR /root/
    COPY --from=builder /go/app .
    ADD start.sh tini /root/
    RUN chmoad a+x start.sh && apk add --no-cache tini
    ENTRYPOINT ["/sbin/tini", "--"]
    CMD ["/root/tini", "--", /root/start.sh"]

    4、总结

    1、对于容器化应用启动命令建议使用 EXEC 模式。

    2、对于应用本身代码层面已经实现了优雅关闭的业务,但有 shell 启动脚本,容器化后部署到 k8s 上建议使方案一和方案二。

    3、对于应用本身代码层面没有实现优雅关闭的业务,建议使用方案三。

     

     

    转载于运维部落

  • 相关阅读:
    使用pymysql模块进行封装,自动化不可或缺的数据库校验
    使用paramiko模块进行封装,远程操作linux主机
    提高开发效率的 Eclipse 实用操作
    遍历Map的四种方法
    key可以重复的Map集合:IdentityHashMap
    Java根据条件删除Map中元素
    用POI读取具有任意合并单元的excel数据
    【转载】说说JSON和JSONP,也许你会豁然开朗,含jQuery用例
    面向对象的基本原则
    forward和redirect的区别
  • 原文地址:https://www.cnblogs.com/cheyunhua/p/15743800.html
Copyright © 2011-2022 走看看