如何在一個 Docker 容器中運行多個服務

已發表: 2022-06-30

顯示 Docker 徽標的插圖

Docker 是一種將堆棧組件打包為隔離容器的技術。 通常的做法是在自己的容器中運行每個進程,從而在組件之間創建一個清晰的分界線。 這增強了模塊化並讓您獲得容器化的可擴展性優勢。

仍然可能存在您希望在單個容器中運行多個服務的情況。 雖然這在 Docker 生態系統中並非自然而然,但我們將展示一些不同的方法,您可以使用這些方法來創建具有多個長生命週期進程的容器。

識別問題

Docker 容器運行單個前台進程。 這是由圖像的ENTRYPOINTCMD指令定義的。 ENTRYPOINT在圖像的Dockerfile中設置,而CMD在創建容器時可以被覆蓋。 容器在其前台進程退出時自動停止。

您可以從CMD啟動其他進程,但容器只會在原始前台進程處於活動狀態時保持運行。 使用ENTRYPOINT/CMD機制無法直接通過兩個獨立服務的組合生命週期保持容器運行。

在一個入口點包裝多個進程

包裝腳本是該問題的最簡單解決方案。 您可以編寫一個腳本來啟動所有進程並等待它們完成。 將腳本設置為 Docker ENTRYPOINT會將其作為容器的前台進程運行,保持容器運行直到其中一個包裝腳本退出。

 #!/bin/bash

/opt/第一個進程 &

/opt/第二進程 &

等待-n

退出 $?

此腳本啟動容器內的/opt/first-process/opt/second-process二進製文件。 &的使用允許腳本繼續執行,而無需等待每個進程退出。 wait用於暫停腳本,直到其中一個進程終止。 然後腳本以完成的腳本發出的狀態碼退出。

該模型導致容器同時運行first-processsecond-process ,直到其中一個退出。 此時,容器將停止,即使其他進程可能仍在運行。

要使用此腳本,請修改 Docker 映像的ENTRYPOINTCMD以使其成為容器的前台進程:

 入口點 ["/bin/sh"]
CMD ["./path/to/script.sh"]

--init容器選項

管理容器進程的一個挑戰是在它們退出時有效地清理它們。 Docker 將您的CMD作為進程 ID 1 運行,使其負責處理信號和消除殭屍。 如果您的腳本沒有這些功能,您最終可能會在容器中保留孤立的子進程。

docker docker run命令有一個--init標誌,它修改入口點以使用tini作為 PID 1。這是一個最小的 init 進程實現,它運行你的CMD ,處理信號轉發,並不斷地收割殭屍。

如果您希望生成許多進程並且不想手動處理清理,那麼使用--init是值得的。 Tini 是一種輕量級的 init 風格,專為容器設計。 它比systemdupstart等成熟的替代品要小得多。

使用專用流程管理器

當您需要管理大量流程時,手動編寫腳本很快就會變得次優。 採用進程管理器是在 Docker 容器中運行多個服務的另一種方式。 流程管理器成為您的ENTRYPOINT ,並負責在您的工作流程之後啟動、維護和清理。

實施此方法時有幾個選項可用。 supervisord是一種流行的選擇,可以通過/etc/supervisor/conf.d/supervisord.conf文件輕鬆配置:

 [程序:apache2]
命令=/usr/sbin/apache2 -DFOREGROUND

[程序:mysqld]
命令=/usr/sbin/mysqld_safe

此配置文件將supervisord配置為啟動 Apache 和 MySQL。 要在 Docker 容器中使用它,請將所有必需的包添加到您的映像中,然後將您的supervisord配置文件複製到正確的位置。 將supervisord設置為鏡像的CMD以在容器啟動時自動運行它。

 來自 ubuntu:最新
運行 apt-get install -y apache2 mysql-server 主管
複製 supervisord.conf /etc/supervisor/conf.d/supervisord.conf
入口點 ["/bin/sh"]
CMD ["/usr/bin/supervisord"]

由於supervisord持續運行,因此當您的監控進程之一退出時,無法停止容器。 另一種選擇是s6-overlay ,它確實具有此功能。 它使用聲明式服務模型,您可以將服務腳本直接放入/etc/services.d

 # 添加 s6-overlay 到你的圖片
添加 https://github.com/just-containers/s6-overlay/releases/download/v3.1.0.0/s6-overlay-noarch.tar.xz /tmp
運行 tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz

運行 printf "#!/bin/shn/usr/sbin/apache2 -DFOREGROUND" > /etc/services.d/first-service/run
運行 chmod +x /etc/services.d/first-service/run

# 使用 s6-overlay 作為圖像的入口點
入口點 ["/init"]

你可以在你的服務目錄中添加一個可執行的finish腳本來處理使用docker stop停止容器。 當s6-overlay的進程由於stop命令而收到TERM信號時,它將自動運行這些腳本。

完成腳本接收其服務的退出代碼作為其第一個參數。 當服務由於未捕獲的信號而被終止時,代碼設置為 256。 該腳本需要將最終退出代碼寫入/run/s6-linux-init-container-results/exitcode ; s6-overlay 讀取此文件並以其中的值退出,導致該代碼用作容器的停止代碼。

 #!/bin/sh
echo "$1" > /run/s6-linux-init-container-results/exitcode

什麼時候應該在一個容器中運行多個進程?

此技術最適用於無法分離以作為獨立容器運行的緊密耦合進程。 您可能有一個依賴於後台幫助實用程序的程序或一個對各個進程執行其自己的管理的單一應用程序。 上面顯示的技術可以幫助您將這些類型的軟件容器化。

仍應盡可能避免在容器中運行多個進程。 堅持單一的前台進程可以最大限度地隔離,防止組件相互干擾,並提高您調試和測試特定部分的能力。 您可以使用容器編排器單獨擴展組件,讓您可以靈活地運行更多資源密集型流程的實例。

結論

容器通常有一個前台進程,只要它還活著就一直運行。 該模型與容器化最佳實踐相一致,讓您從該技術中獲得最大收益。

在某些情況下,您可能需要在容器中運行多個進程。 由於所有圖像最終都有一個入口點,因此您必須編寫一個包裝腳本或添加一個負責啟動目標二進製文件的進程管理器。

流程管理器為您提供所需的一切,但使用額外的包和配置使您的圖像膨脹。 包裝腳本更簡單,但可能需要與 Docker 的--init標誌配對以防止殭屍進程擴散。