這篇文章是受 dockboard 之托幫忙翻譯的與 docker 有關的技術文章。譯自 Using Supervisor with Docker to manage processes (supporting image inheritance) ,作者 Quinten Krijger。
在八月份,我寫了一篇關於如何創建 tomcat 鏡像的 blog 。從那以后,docker 又改進了很多,我對 docker 的了解也增加了很多。我很高興和你分們享我找到的關於管理 container 進程的好辦法。在讀完這篇文章后,我希望你能善加利用我 github 倉庫 里的 supervisor 鏡像。
Docker 命令
在之前的文章里,我提到 Docker(只能)支持運行一個前台進程。我們通常習慣使用類似 upstart 這種管理服務來初始化啟動流程,但是 Docker 默認沒有這些服務的支持。剛開始使用 Docker 時會很不習慣,你必須指定你想要運行的進程。這種行為和虛擬機相比有個優點,會盡可能的保持輕量的 container。你可以通過 run 命令最后的參數,在啟動 container 時指定進程命令,比如:
docker run ubuntu echo "hello world"
另外一種方法,你可以利用 CMD 指令,在 Dockerfile 里指定 docker run 命令的默認參數。比如,如果你目錄下的 Dockerfile 包含以下內容:
FROM ubuntu CMD echo "hello world"
再使用下面的指令構造 hello_world_printer 鏡像:
docker build -t "hello_world_printer" .
使用下面的命令,你可以得到和之前 run 命令相同的執行結果。
docker run hello_world_printer
要注意,因為你可以覆蓋掉 CMD 指定的命令行參數,這個只是個運行時的指令。有趣的事情是,在 Linux container 里,你可以只調用 upstart 命令然后得到和普通虛擬機大致相同的行為。
運行多個命令
運行多個進程是個很正常的想法。比如,一個 ssh 服務(這樣就能登錄到正在運行的 container)和實際的應用。你可以用下面的方法運行 container:
docker run ... /usr/sbin/sshd && run_your_app_in_foreground
這在開發時很方便。這樣,當應用進程退出后,因為唯一的前台程序退出了,container 會自動關閉。當然你可以使用 using /usr/bin/sshd -D 保證 container 不會退出,但是這里真正的問題是,這種使用 run 命令設置初始程序的方式不夠簡潔。而且,隨着你的 container 變復雜,run 命令會越來越長。
所以,在運行更復雜的 container 的時候,很多人使用復雜的 bash 腳本。典型的 bash 腳本會執行一個前台進程,並開啟一個或者多個(renegade)守護進程。與只是用 Docker 命令行的方式相比,這種方法最重要的改進在於,bash 腳本是可以做版本控制的:啟動腳本在你的 Docker 鏡像里,新的改動可以和軟件項目一起分發。不過,使用 bash 腳本管理進程依舊簡陋枯燥,而且容易出錯。
……使用 supervisor
更好的方法是使用 supervisor 。supervisor 可以更好的管理進程:使用更加簡潔的代碼管理進程;在崩潰時可以重啟進程;允許重啟一組進程並且有命令行工具和網頁界面來管理進程。當然,越大的能力要求 越大的責任:大量使用 supervisor 特性的代碼,預示着你應該將整個服務更好的拆分成多個小的 supervisor 來管理。
個人來講,我喜歡 supervisor 讓我用更清晰的代碼管理啟動的進程。我見過最簡潔的使用例子,是子鏡像擴展出一個進程組。比如,如果你經常使用 SSH,使用一個 SSH 鏡像作為基礎鏡像就是很合理的。這種情況,在所有基於這個鏡像的擴展鏡像上實現啟動 SSH 進程的代碼,形式少就是一種重復代碼。我來給你們展示下我找到的解決這個問題的好辦法。
supervisor 基礎鏡像
首先,因為我默認使用 supervisor,所以我所有的鏡像都擴展自一個只包含 supervisor 和最新版本 ubuntu 的基礎鏡像。你可以在 這里 找到這個 Dockerfile。這個基礎鏡像包括一個配置文件 /etc/supervisor.conf :
[supervisord] nodaemon=true [include] files = /etc/supervisor/conf.d/*.conf
這個配置讓 supervisor 本身以前台進程運行,這樣可以讓我們的 container 啟動后持續運行。第二,這個配置將包含所有在 /etc/supervisor/conf.d/ 目錄下的配置文件,啟動任何在這里定義的程序。
擴展基礎鏡像
是的,想法很簡單。所有的子 container 通過將特定的 service.sv.conf 放到特定的目錄的方式,將其自己的服務加入到 supervisor 的管理里。之后,使用如下命令啟動 container:
docker run child_image_name "supervisor -c /etc/supervisor.conf"
會自動啟動所有指定的進程。你可以對鏡像做多層擴展,每層擴展加入一個或者多個服務到配置目錄。在 Docker 里使用 supervisor 啟動命令代替 upstart 也更有效和有范。
作為例子,讓我們看看之前 blog 提到的 Tomcat 工作棧,是如何使用這種改進后的方法的。
- 首先,和之前討論的一樣,我們使用從 ubuntu 擴展而來的 supervisor 基礎鏡像
- 之后,我們使用在 supervisor 上安裝了 Java 的 JDK 鏡像 。Java 只是其他服務使用的庫,所以我們在這層不指定任何啟動服務。這層要做一些類似設置 JAVA_HOME 環境變量的通常任務
- Tomcat 鏡像在工作棧上安裝 Tomcat 並暴露 8080 端口。這層包括一個名字是 Tomcat 的服務,定義在 tomcat.sv.conf :
-
[program:webapp] command=/bin/bash -c "env > /tmp/tomcat.env && cat /etc/default/tomcat7 >> /tmp/tomcat.env && mv /tmp/tomcat.env /etc/default/tomcat7 && service tomcat7 start" redirect_stderr=true
當然,我們這里只安裝了默認的文件,沒有真正的網絡應用程序。
- 你的網絡應用程序應當擴展自 Tomcat 鏡像,並安裝入真正的應用程序。當啟動 supervisor 的時候,會自動啟動 Tomcat。
一個 Tomcat 網絡程序的 Dockerfile 例子
如何安裝實際的網絡應用超出了本文的范疇,不過,作為結束,我給出了個 Dockerfile 例子,演示如何使用這個工作棧。這個例子完全基於 Java Tomcat,所以如果你對這個不感興趣,別讀了,玩別的去吧:)
假設,我們有一個使用 Elasticsearch 的網絡應用:
FROM quintenk/tomcat:7 # 安裝一些項目的依賴,這些依賴在每次更新時不會改變 # RUN apt-get -y install ... RUN rm -rf /var/lib/tomcat7/webapps/* # 將配置加入 /etc/default/tomcat7,比如: ... RUN echo 'DOCKER_OPTS="-DELASTICSEARCH_SERVER_URL=${ELASTICSEARCH_PORT_9200_TCP_ADDR}"' >> /etc/default/tomcat7 RUN echo 'CATALINA_OPTS="... ${DOCKER_OPTS}"' >> /etc/default/tomcat7 # 加入類似 log4j.properties 的配置文件,並將其 chown root:tomcat7 # 假設項目已經構建好了,而且 ROOT.war 在你構建 Docker 的目錄(包含 Dockerfile 的目錄)。基於緩存的考慮,這個作為最后的步驟 ADD ROOT.war /var/lib/tomcat7/webapps/ RUN chown root:tomcat7 /var/lib/tomcat7/webapps/ROOT.war CMD supervisord -c /etc/supervisor.conf
In this code, the variables for elasticsearch (a search index), are set because the Supervisor configuration for Tomcat prepends all variables to the /etc/default/tomcat7 file at start-up time. Of course, we would need to start the webapp with a link to the elasticsearch container: e.g. 這段代碼里,elasticsearch 的相關環境變量(搜索索引)已經被設置了,因為 supervisor 關於 Tomcat 的配置,會在啟動時將所有環境變量添加到 /etc/default/tomcat7。當然,我們在啟動網絡應用鏡像時需要關聯到 elasticsearch containter,比如:
docker run -link name_of_elasticsearch_instance:elasticsearch -d name_of_webapp_image "supervisor -c /etc/supervisor.conf"
你現在的網絡應用可以去訪問 ELASTICSEARCH_SERVER_URL 路徑了。你可以在配置文件里使用這個變量,像這樣:
FROM ubuntu CMD echo "hello world" 0
這樣就可以將配置暴露給你的應用程序。如果你是個 Java 開發者,並且也閱讀了前一篇文章,希望這讓你能開始一段愉快的代碼之旅。