容器優雅關閉方案,進程id為1


  • 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、對於應用本身代碼層面沒有實現優雅關閉的業務,建議使用方案三。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM