一、先看最簡單的例子,定制nginx鏡像,打印出 <h1>Hello, Docker!</h1>
Dockerfile文件:
FROM nginx RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
在該Dockerfile目錄下運行
docker build -t nginx:v1 .
則將生成一個 nginx:v1 的新鏡像,運行該鏡像
docker run -p 80:80 nginx:v1
在瀏覽器直接訪問地址 localhost,可以看到nginx的主頁已被修改
二、RUN 命令關鍵解析
Dockerfile 中每一個指令都會建立一層, RUN 也不例外。每一個 RUN 的行為,新建立一層,在其上執行這些命令,執行結束后, commit 這一層的修改,構成新的鏡像。
FROM debian:jessie RUN apt-get update RUN apt-get install -y gcc libc6-dev make RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" RUN mkdir -p /usr/src/redis RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 RUN make -C /usr/src/redis RUN make -C /usr/src/redis install
如上dockerfile所示,創建了 7 層鏡像,這是完全沒有意義的,而且很多運行時不需要的東西,都被裝進了鏡像里,比如編譯環境、更新的軟件包等等。結果就是產生非常臃腫、非常多層的鏡像,不僅僅增加了構建部署的時間,也很容易出錯。
正確的寫法:
FROM debian:jessie RUN buildDeps='gcc libc6-dev make' \ && apt-get update \ && apt-get install -y $buildDeps \ && wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \ && mkdir -p /usr/src/redis \ && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \ && make -C /usr/src/redis \ && make -C /usr/src/redis install \ && rm -rf /var/lib/apt/lists/* \ && rm redis.tar.gz \ && rm -r /usr/src/redis \ && apt-get purge -y --auto-remove $buildDeps
三、CMD 與 ENTRYPOINT對比
場景1、讓鏡像變成像命令一樣使用
假設我們需要一個得知自己當前公網 IP 的鏡像,那么可以先用 CMD 來實現:
FROM ubuntu:16.04 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* CMD [ "curl", "-s", "http://ip.cn" ]
使用docker build -t myip . 來構建鏡像的話,如果我們需要查詢當前公網 IP,只需要執行:
docker run myip
可以打印出當前的ip地址
這么看起來好像可以直接把鏡像當做命令使用了,不過命令總有參數,如果我們希望加參數呢?比如從上面的 CMD 中可以看到實質的命令是 curl ,那么如果我們希望顯示 HTTP頭信息,就需要加上 -i 參數。那么我們可以直接加 -i 參數給 docker run myip 么?
$ docker run myip -i docker: Error response from daemon: invalid header field value "oci runtime error: con tainer_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
可以看到可執行文件找不到的報錯, executable file not found 。之前我們說過,跟在鏡像名后面的是 command ,運行時會替換 CMD 的默認值。因此這里的 -i 替換了遠了的CMD ,而不是添加在原來的 curl -s http://ip.cn 后面。而 -i 根本不是命令,所以自然找不到。
那么如果我們希望加入 -i 這參數,我們就必須重新完整的輸入這個命令:
$ docker run myip curl -s http://ip.cn -i
這顯然不是很好的解決方案,而使用 ENTRYPOINT 就可以解決這個問題。現在我們重新用ENTRYPOINT 來實現這個鏡像:
FROM ubuntu:16.04 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
這次我們再來嘗試直接使用 docker run myip -i
成功!這是因為當存在 ENTRYPOINT 后, CMD 的內容將會作為參數傳給ENTRYPOINT ,而這里 -i 就是新的 CMD ,因此會作為參數傳給 curl ,從而達到了我們預期的效果
場景2:應用運行前的准備工作
啟動容器就是啟動主進程,但有些時候,啟動主進程前,需要一些准備工作。
比如 mysql 類的數據庫,可能需要一些數據庫配置、初始化的工作,這些工作要在最終的mysql 服務器運行之前解決。
此外,可能希望避免使用 root 用戶去啟動服務,從而提高安全性,而在啟動服務前還需要以 root 身份執行一些必要的准備工作,最后切換到服務用戶身份啟動服務。或者除了服務外,其它命令依舊可以使用 root 身份執行,方便調試等。
這些准備工作是和容器 CMD 無關的,無論 CMD 為什么,都需要事先進行一個預處理的工作。這種情況下,可以寫一個腳本,然后放入 ENTRYPOINT 中去執行,而這個腳本會將接到的參數(也就是 <CMD> )作為命令,在腳本最后執行。
四、HEALTHCHECK 健康檢查
HEALTHCHECK 指令是告訴 Docker 應該如何進行判斷容器的狀態是否正常,這是 Docker 1.12引入的新指令
HEALTHCHECK 支持下列選項:
--interval=<時長> :兩次健康檢查的間隔,默認為 30 秒;
--timeout=<時長> :健康檢查命令運行超時時間,如果超過這個時間,本次健康檢查就被
視為失敗,默認 30 秒;
--retries=<次數> :當連續失敗指定次數后,則將容器狀態視為 unhealthy ,默認 3
次。
FROM nginx RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s \ CMD curl -fs http://localhost/ || exit 1
這里我們設置了每 5 秒檢查一次(這里為了試驗所以間隔非常短,實際應該相對較長),如果健康檢查命令超過 3 秒沒響應就視為失敗,並且使用 curl -fs http://localhost/ || exit 1 作為健康檢查命令。
構建鏡像,並且啟動容器
$ docker build -t myweb:v1 .
$ docker run -d --name web -p 80:80 myweb:v1
當運行該鏡像后,可以通過 docker ps 看到最初的狀態為 (health: starting)
在等待幾秒鍾后,再次 docker ps ,就會看到健康狀態變化為了 (healthy)
如果健康檢查連續失敗超過了重試次數,狀態就會變為 (unhealthy) 。
為了幫助排障,健康檢查命令的輸出(包括 stdout 以及 stderr )都會被存儲於健康狀態里,可以用 docker inspect 來查看。
$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool { "FailingStreak": 0, "Log": [ { "End": "2016-11-25T14:35:37.940957051Z", "ExitCode": 0, "Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</titl e>\n<style>\n body {\n width: 35em;\n margin: 0 auto;\n font-f amily: Tahoma, Verdana, Arial, sans-serif;\n }\n</style>\n</head>\n<body>\n<h1>Welc ome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully inst alled and\nworking. Further configuration is required.</p>\n\n<p>For online documentat ion and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCo mmercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n <p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n", "Start": "2016-11-25T14:35:37.780192565Z" } ], "Status": "healthy" }
五、COPY與ADD區別
在 Docker 官方的最佳實踐文檔中要求,盡可能的使用 COPY ,因為 COPY 的語義很明確,就是復制文件而已,而 ADD 則包含了更復雜的功能,其行為也不一定很清晰。最適合使用ADD 的場合,就是所提及的需要自動解壓縮的場合。
另外需要注意的是, ADD 指令會令鏡像構建緩存失效,從而可能會令鏡像構建變得比較緩慢。
因此在 COPY 和 ADD 指令中選擇的時候,可以遵循這樣的原則,所有的文件復制均使用COPY 指令,僅在需要自動解壓縮的場合使用 ADD 。