1. 程式人生 > >在 Docker 容器中運行應用程序

在 Docker 容器中運行應用程序

ges 文件中 機制 計算 http cannot info 應用 png

案例說明

運行 3 個容器,實現對網站的監控。

三個容器的說明:

  • 容器 web: 創建自 nginx 映像,使用 80 端口,運行於後臺,實現 web 服務。
  • 容器 mailer: 該容器中運行一個 mailer 程序,運行於後臺,當接收到事件後會向管理員發送郵件。
  • 容器 agent: 該容器運行一個 watcher 程序,以交互模式運行,用於不斷地監測 web 服務的運行情況,一旦出現故障會立即向 mailer 容器發送消息。

創建容器

創建並運行 web 容器

$ docker run --detach --name web nginx:latest

命令執行後,docker 會從 Docker Hub 上下載 nginx:latest

映像文件,根據該映像文件開啟一個容器,並在容器中運行 nginx 程序。

運行後,會輸出一行字符串,該字符串為該容器的唯一標識符,類似 7cb5d2b9a7eab87f07182b5bf58936c9947890995b1b94f412912fa822a9ecb5。通常我們可以將該標識符保存到一個變量裏,以便於在其它命令中使用。

--detach 選項使得該容器在後臺運行,也可以用其縮寫版本 -d

--name web 將當前容器命名為 web,以便之後引用。

創建並運行 mailer 容器

$ docker run -d --name mailer dockerinaction/ch2_mailer

創建並運行一個交互式的容器 agent

一個交互式的程序可以從用戶獲取輸入或將輸出顯示到終端中。在 Docker 中運行交互式程序需要將你的終端綁定到容器的輸入或輸出上。

運行一個交互式容器如下:

$ docker run --interactive --tty     --link web:myweb     --name web_test     busybox:latest /bin/sh

--interactive-i 選項告訴 Docker 為該容器開啟標準輸入 (stdin)。 --tty-t選項告訴 Docker 為該容器分配一個虛擬終端,以便於向容器發送信號。通常這兩個選項是一起使用的,合記為 -it

--link web:web 選項使得當前容器中能用 myweb 來引用 容器 web。

最後,/bin/sh 是指定在該容器中運行的程序,運行後,可以在 sh 中運行 wget -O - http://myweb:80/ 來檢測容器 web 的運行情況。這裏的 wget 命令實現向 nginx 服務器發送請求,並將獲取的頁面內容輸出到終端上。

通用 --tty 開啟的交互式容器,可以使用 Ctrl-P Q 來使其轉入後臺運行。

運行 agent 容器

$ docker run -it     --name agent     --link web:insideweb     --link mailer:insidemailer     dockerinaction/ch2_agent

該容器會每 1 秒對容器 web 檢測一次,並輸出類似 System up. 等信息。當看到這些信息後,可以用 Ctrl-P Q 將其轉入後臺運行。

容器命令

docker ps

docker ps 會列出每個正在運行的容器的下面信息:

  • 容器 ID
  • 使用的映像文件
  • 在容器中運行的命令
  • 自容器創建後的時間
  • 容器已運行的時間
  • 容器使用的端口號
  • 容器的名稱

重啟容器

$ docker restart web
$ docker restart mailer
$ docker restart agent

查看容器的日誌

$ docker logs web

由於容器 agent 對容器 web 進行了多次請求,故上面的命令會輸出一長串的 GET / HTTP/1.0" 200

容器運行是的每條輸出(或錯誤輸出)都會保存到容器的日誌文件中,因此,只要容器一直在運行,它的日誌文件會不斷的變大。由於沒有截斷的手段,因而最好用 Volume 來處理日誌數據。

$ docker logs mailer

mailer 的日誌輸出類似: CH2 Example Mailer has started.

docker logs 命令添加 --follow-f 選項時,會一直保持運行,並持續顯示最新的日誌。可以用 Ctrl C 中斷。

關閉容器

$ docker stop web

以上命令將中止容器中的 PID #1 程序的運行。

容器 web 中止後,容器 agent 將觸發對容器 mailer 的請求,進而可以看到容器 mailer 中相關日誌 Sending email: To: admin@work Message: The service is down!

已解決的問題及 PID 命名空間

PID 命名空間是可用於標識進程的一個數集。Linux 可創建多個 PID 命名空間,每個命名空間中使用的 PID 相互獨立,即每個命名空間都各自可使用 1, 2, 3 等而互不幹擾。

Docker 默認為每個容器創建一個 PID 命名空間:

$ docker run -d --name namespaceA     busybox:latest /bin/sh -c "sleep 30000"
$ docker run -d --name namespaceB     busybox:latest /bin/sh -c "nc -l -p 0.0.0.0:80"

運行這兩個容器後,

$ docker exec namespaceA ps

PID   USER     TIME   COMMAND
    1 root       0:00 /bin/sh -c sleep 30000
    6 root       0:00 sleep 30000
    7 root       0:00 ps


$ docker exec namespaceB ps

PID   USER     TIME   COMMAND
    1 root       0:00 /bin/sh -c nc -l -p 0.0.0.0:80
    5 root       0:00 nc -l -p 0.0.0.0:80
    6 root       0:00 ps

可以看到,每個容器中使用的 PID 都是獨立的,例如都有 PID #1。

要使容器不創建自己的 PID 命名空間,在運行 docker createdocker run 時要加上 --pid host 選項:

$ docker run --pid host busybox:latest ps

以上命令將列出機器上的所有運行中的進程。

Docker 解決的問題

Docker 基於 Linux namespace, file system roots, virtualized network components 實現的容器隔離性解決了如下的沖突問題:

  • 多個程序想綁定到相同的端口
  • 多個程序想使用相同的臨時文件名
  • 各程序想使用全局安裝的代碼庫的不同版本
  • 相同程序的不同進程想使用相同的 PID 文件
  • 多個程序同時修改環境變量

消除 metaconflicts:創建一個網站集群

metaconflicts 即容器間的沖突。

繼續上面的例子,這次開啟多組 web + agent 容器對,然後只開啟一個 mailer 容器,所有的 agent 都將事件發送給容器 mailer。

靈活的容器標識

當執行 docker run -d --name webid nginx 時,生成的容器的名稱為 webid,容器的名稱不能重復。當沒有使用 --name 選項時,Docker 會自動為我們創建一個易讀的唯一容器名。

也可以重命名容器:

$ docker rename webid webid-old

每個容器還有一個 1024 位的十六進制編碼的唯一 ID,如 7cb5d2b9a7eab87f07182b5bf58936c9947890995b1b94f412912fa822a9ecb5。可以通過這個 ID 對該容器進行引用。如:

docker stop     7cb5d2b9a7eab87f07182b5bf58936c9947890995b1b94f412912fa822a9ecb5 

該 ID 值是完全唯一的,即永遠不會沖突,若要想在同一臺機器上保持唯一性,只需取其前 12 個字符長的字符串即可,因此,上面的命令也可以這樣:

```bash
docker stop 7cb5d2b9a7ea

容器 ID 值不適合人讀,但可在腳本處理或自動化程序中使用。

如何獲取容器 ID

當開啟一個在後臺運行的容器時,容器 ID 會自動輸出到終端,因此可以獲取。但如果開啟的是交互式的容器,就不能獲取 ID。這種情況下可以先用 docker create 命令創建一個容器(不立即運行),該命令和 docker run 的格式完成一樣,同樣也會輸出容器的 ID。

將 ID 值保存到一個 Shell 變量中:

CID=$(docker create nginx:latest)
echo $CID

這種方式獲取的 ID 只能在一個腳本或程序中使用,不能在多個程序間共享。如果要在多個程序間共享該容器 ID 值,可以將值保存在 container ID(CID) 文件中。docker rundocker create 命令都可以用 --cidfile 選項指定 CID 文件的位置,如:

$ docker create --cidfile /tmp/web.cid ngix

然後用 cat /tmp/web.cid 來獲取該值。用這種方式時, CID 文件可能會沖突。幸運的是,當指定的 CID 沖突時(即該文件已經存在),Docker 會報錯,不會創建該容器。 CID 文件可以在多個容器間共享,並且可以通過 Volume 功能進行重命名。

另一種獲取 ID 的方式是使用 docker ps:

CID=$(docker ps --latest --quiet) # or CID=$(docker ps -l -q)
echo $CID

這種方式獲取的是截取的 12 字節長的 ID,要想獲取整個 ID,要加 --no-trunc 選項。

容器 ID 不適合人使用,因此 Docker 還會為容器自動創建一個可讀的唯一名字,名字的結構是: 一個形容詞_某個名人的名字,如 hungry_swartz, distracted_turing等。

容器的狀態及其依賴

用腳本加載容器:

MAILER_CID=$(docker run -d dockerinaction/ch2_mailer)
WEB_CID=$(docker create nginx)

AGENT_CID=$(docker create --link $WEB_CID:insideweb     --link $MAILER_CID:insidemailer     dockerinaction/ch2_agent)

以上針對 web, agent 容器的命令只是創建容器,還沒有運行,因此 docker ps 默認不會列出 web, agent 這兩個容器,要想查看所有狀態的容器,使用 docker ps -a

容器的所有狀態為: running, paused, restarting, exited 等。各狀態相互轉化如下:

技術分享

容器創建後,再開啟:

docker start $AGENT_CID
docker start $WEB_ID

但運行以上的命令會出錯:

Error response from daemon: Cannot start container
03e65e3c6ee34e714665a8dc4e33fb19257d11402b151380ed4c0a5e38779d0a: Cannot
link to a non running container: /clever_wright AS /modest_hopper/
insideweb
FATA[0000] Error: failed to start one or more containers

這是因為 agent 容器依賴於 web 容器,故要先啟動 web 容器,如下:

docker start $WEB_ID
docker start $AGENT_CID

創建環境無關的系統

安裝軟件和維護的工作量主要在於對計算環境的定制。這種定制工作有:

  • 全局依賴(如主機上的文件系統位置)
  • 硬編碼的部署架構(如在代碼或配置中檢測變量值)
  • 數據的存儲位置(如數據保存在一個特定的機器上)

Docker 可以利用以下 3 個特性來幫助實現環境無關的系統,從而減少維護量:

  • 只讀文件系統
  • 環境變量註入
  • Volume

本次實現的案例是使用 Docker 運行多個 WordPress 博客。每個博客共享 WordPress 程序,只是博客內容不同。

只讀文件系統

使用 --read-only 選項開啟一個只讀的 WordPress 容器:

$ docker run -d --name wp --read-only wordpress:4

--read-only 確保該容器的內容不可修改。

執行後,再使用 docker inspect --format "" wp 來查看容器是否已經開啟了,輸出 true 和 false。

這裏會輸出 false,用 docker logs wp 查看日誌:

error: missing required WORDPRESS_DB_PASSWORD environment variable
  Did you forget to -e WORDPRESS_DB_PASSWORD=... ?

  (Also of interest might be WORDPRESS_DB_USER and WORDPRESS_DB_NAME.)

可見,WordPress 依賴 MySQL。

使用 docker 運行一個 Mysql 容器:

$ docker run -d --name wpdb     -e MYSQL_ROOT_PASSWORD=ch2demo     mysql:5

上面命令中的 -e 選項向容器註入了一個環境變量值,以便容器使用。

現在再開啟一個新的 WordPress 容器,並與 MySQL 數據庫連接起來:

$ docker run -d --name wp2     --link wpdb:mysql     -p 80 --read-only     wordpress:4

再查看該容器是否已正常運行:

$ docker inspect --format "" wp2

發現還是沒有啟動,用 docker logs wp2 再次檢查,可看到類似以下的日誌:

Fatal Error Unable to create lock file: Bad file descriptor (9)

可以看到因為 WordPress 容器是只讀的,從而無法生成一個 lock 文件,而導致該容器啟動失敗。

因此,需要通過掛載 Volume 使該只讀容器中的某些目錄可寫:

# start the container with specific volumes for read only exceptions
$ docker run -d --name wp3 --link wpdb:mysql -p 80     -v /run/lock/apach2/     -v /run/apache2/     --read-only wordpress:4

上面 -v /datadir 選項使得主機上的某個臨時目錄掛載到容器中的 /datadir 目錄。

至此,一個可用於開啟 WordPress 及監控程序的腳本如下:

SQL_CID=$(docker create -e MYSQL_ROOT_PASSWORD=ch2demo mysql:5)

docker start $SQL_CID

MAILER_CID=$(docker create dockerinaction/ch2_mailer)
docker start $MAILER_CID

WP_CID=$(docker create --link $SQL_CID:mysql -p 80    -v /run/lock/apache2/ -v /run/apache2/     --read-only wordpress:4)

docker start $WP_CID

AGENT_CID=$(docker create --link $WP_CID:insideweb     --link $MAILER_CID:insidemailer     dockerinaction/ch2_agent)

docker start $AGENT_CID

環境變量註入

很多程序可根據環境變量進行配置。而 Docker 也會利用環境變量來共享主機名、容器等信息,同時還有可向容器註入環境變量的機制。

env 命令可列出當前會話上下文裏的所有環境變量值。向容器註入環境變量並顯示:

$ docker run --env MY_ENVIRONMENT_VAR="this is a test"     busybox:latest env

上面的 --env-e 選項可用來向容器註入環境變量值,如果映像裏已經設置了該變量,那麽本次設置會覆蓋原來的設置值。

WordPress 用到下面這些環境變量:

  • WORDPRESS_DB_HOST
  • WORDPRESS_DB_USER
  • WORDPRESS_DB_PASSWORD
  • WORDPRESS_DB_NAME
  • WORDPRESS_AUTH_KEY
  • WORDPRESS_SECURE_AUTH_KEY
  • WORDPRESS_LOGGED_IN_KEY
  • WORDPRESS_NONCE_KEY
  • WORDPRESS_AUTH_SALT
  • WORDPRESS_SECURE_AUTH_SALT
  • WORDPRESS_LOGGED_IN_SALT
  • WORDPRESS_NONCE_SALT

創建 WordPress 容器時這樣註入環境變量:

$ docker create 
    --env WORDPRESS_DB_HOST=<my_database_hostname> 、
    --env WORDPRESS_DB_USER=site_admin     --env WORDPRESS_DB_PASSWORD=MeowMix42     wordpress:4

要能開啟多個 WordPress 容器,還需要為每個容器指定使用的數據庫名:

docker create --link wpdb:mysql     -e WORDPRESS_DB_NAME=client_a_wp wordpress:4

docker create --link wpdb:mysql     -e WORDPRESS_DB_NAME=client_b_wp wordpress:4

至此,可以更新啟動腳本了:

# 先啟動 mysql 和 mailer 容器:
DB_CLD=$(docker run -d -e MYSQL_ROOT_PASSWORD=ch2demo mysql:5)
MAILER_CID=$(docker run -d dockerinaction/ch2_mailer)

# 假設 $CLIENT_ID 變量會傳入腳本
if [ ! -n "$CLIENT_ID" ]; then
    echo "Client ID not set"
    exit 1
fi

WP_CID=$(docker create     --link $DB_CID:mysql     --name wp_$CLIENT_ID     -p 80     -v /run/lock/apach2/ -v /run/apache2/     -e WORDPRESS_DB_NAME=$CLIENT_ID     --read-only wordpress:4)

docker start $WP_CID

AGENT_CID=$(docker create     --name agent_$CLIENT_ID     --link $WP_CID:insideweb     --link $MAILER_CID:insidemailer     dockerinaction/ch2_agent)

docker start $AGENT_CID

創建可持續運行的容器

Docker 的選項可用於監測並自動重啟容器。

自動重啟容器

在創建容器時,可用 --restart 選項指定以下的重啟策略:

  • 不重啟(默認)
  • 當檢測到某種條件後才重啟
  • 不管什麽情況問題重啟

重啟的等待時間采用 exponential backoff strategy。

采用這種方式重啟會有空白時間,期間容器沒有啟動。

使用監管程序來保持容器運行

監管進程,或者 init 進程,可用來加載和維護其它進程狀態。在 Linux 上,PID #1 是一個 init 進程,它用於開啟所有其它系統進程,並且當出現異常時重啟這些系統進程。

在容器中也可以采用類似的模式,主要可用的監管程序有 init, systemd, runit, upstart 和 supervisord 等。

Tutum 公司有一個 Docker 映像,包含 LAMP 和 supervisord,通過以下方式運行容器後可確保該容器一直運行:

$ docker run -d -p 80:80 --name lamp-test tutum/lamp

可以用 docker exec lamp-test ps 來查看容器中當前運行的進程,可以看到運行有 supervisord, mysqld_safe 和 apache2 等進程。

PID TTY          TIME CMD
    1 ?        00:00:00 supervisord
  439 ?        00:00:00 mysqld_safe
  440 ?        00:00:00 apache2
  827 ?        00:00:00 ps

現可以測試 supervisord 的監控重啟功能,先 kill 掉 apache2 進程:

$ docker exec lamp-test kill 440 # 440 是 apache2 的 PID

當 apache2 結束後,supervisord 會記錄日誌,並重啟該進程,可用 docker logs lamp-test 查看:

2016-10-10 01:23:39,784 INFO exited: apache2 (exit status 0; expected)
2016-10-10 01:23:40,787 INFO spawned: ‘apache2‘ with pid 841
2016-10-10 01:23:41,821 INFO success: apache2 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)

使用 entrypoint 腳本

相對於使用 init 或監管程序,另一種方法是使用啟動腳本(通常的腳本名為 entrypoint.sh) 來至少檢測容器能正常開啟的一些先決條件。這個啟動腳本有時也會用作容器的默認開啟程序。

例如,之前開啟的 WordPress 容器中就有一個啟動腳本,它會在開啟 WordPress 進程前先驗證和配置相關的環境變量。可以通過覆蓋默認命令為 cat 來查看該啟動腳本:

$ docker run --entrypoint="cat" wordpress:4 /entrypoint.sh

以上命令覆蓋設置了默認命令為 cat, 同時最後的 /entrypoint.sh 為該默認命令的參數。

啟動腳本 + Docker 的重啟策略是確保容器持續運行的重要方式。

清理

所有的容器都會使用硬盤空間來存儲日誌、容器元數據和寫入容器內的文件。所有容器也會消耗全局命名空間的資源(如容器名,主機端口綁定等)。因此,不再使用的容器應該要刪除。

狀態為 exited 的容器可以用 docker rm container_name 來刪除,而其它狀態(如 running, paused, restarting) 的容器必須用 docker stop container_name 先關閉後才能刪除,或者用 docker rm -f container_name 來強制刪除。

docker stop 會向容器發送 SIG_HUG 信號,從而容器有時間來進行一些清理工作,而強制刪除會向容器發送 SIG_KILL 信號,從而直接退出。 docker kill 命令也可用於向容器發送 SIG_KILL 信號 。

docker run 命令加 --rm 選項時,該容器在運行退出後,即狀態為 existed 時,會自動刪除,如:

$ docker run --rm --name auto-exit-test busybox:latest echo Hello World

下面的命令能刪除所有的容器(沒有退出的會強制刪除):

$ docker rm -vf $(docker ps -a -q)
  • -v 選項表示一並刪除容器的 Volumes
  • -q 選項表示只列出容器的數字 ID

參考文獻:

  • 《Docker in Action》by Jeff Nickoloff: Running software in containers
http://www.atjiang.com/running-software-in-docker-containers/

在 Docker 容器中運行應用程序