爲何您的Docker應用程序接收不到信號?

當您的應用程序是經過docker的方式運行時,在終止應用程序前進行適當的清理,也就是所謂的優雅退出也很是重要。儘管這僅僅取決於確保信號到達您的應用程序並對其進行處理,但仍有不少方面可能致使錯誤。python

從原則上講,這很是簡單:當您(或您的集羣管理工具)運行docker stop時,Docker會向您的應用程序的入口點發送可配置的信號; SIGTERM是默認值。在對個人應用程序進行容器化時,我肯定了四個陷阱(但只進入了其中三個!),以便爲您節省一些調查時間。docker

1:您使用了錯誤的ENTRYPOINT 格式 shell

Dockerfile容許您使用一種誘人的便捷字符串(稱爲shell格式)來定義入口點:數組

ENTRYPOINT "/app/bin/your-app arg1 arg2"

或者更使人討厭的 exec 格式,它使您能夠將命令行做爲JSON數組提供:app

ENTRYPOINT ["/app/bin/your-app", "arg1", "arg2"]

長話短說:始終使用後一種exec格式。 Shell格式將您的入口點做爲 /bin/sh -c 的子命令來運行,它帶有不少問題,其中一個值得注意的問題是您在應用程序中永遠看不到信號。框架

2:您的入口點是一個Shell腳本,您沒有exec工具

若是您以常規方式從Shell腳本運行應用程序,則Shell將以新進程生成應用程序,而且您的應用程序將不會收到來自Docker的信號。命令行

您須要作的就是告訴您的Shell用您的應用程序替換自身。爲此,shell具備 exec 命令(與前面講到的 exec 格式類似)。詳情見exec syscall日誌

因此替換code

/app/bin/your-app

爲:

exec /app/bin/your-app

您確實使用了exec,可是您經過啓動子shel​​l欺騙了本身

我一直是runit日誌記錄的忠實擁護者,您只需在沒有時間戳的狀況下就將日誌記錄到stdout,而且runit會在全部日誌條目前添加tai64n時間戳。

例如:

exec /app/bin/your-app | tai64n

可是,這致使您的應用程序在子shell中執行,其結果一般是:沒有信號給您。

3:您監聽了錯誤的信號

儘管SIGTERM在進程管理器中盛行,但許多框架仍但願使用SIGINT ,例如Control-C中止應用程序。特別是在Python生態系統中,一般要作:

try:
    do_work()
except KeyboardInterrupt:
    cleanup()

若是不採起任何進一步的措施,則若是您的應用程序收到SIGTERM,將永遠不會調用cleanup()。更糟的是,若是您的PID爲1,那麼超過最大等待時間,而後將SIGKILL發送到入口點以前,實際上什麼也不會發生。

所以,若是您曾經想知道爲何docker stop會花費這麼長時間–這可能的緣由:您沒有監聽SIGTERM,而且因爲PID爲1,信號從您的進程中彈回。沒有清理,緩慢關閉。

最簡單的解決方法是在Dockerfile中添加一行:

STOPSIGNAL SIGINT

可是,您實際上應該嘗試同時支持這兩種信號,並首先避免成爲PID 1。

總結

  • 使用ENTRYPOINT的 exec/JSON 數組形式。
  • 在shell程序入口點中使用exec。
  • 不要pipe應用程序的輸出。
  • 避免成爲PID 1。
  • 偵聽SIGTERM或在Dockerfile中設置STOPSIGNAL。
相關文章
相關標籤/搜索