Docker學習筆記:Dockerfile
ocker 可以通過 Dockerfile 的內容來自動構建映象。
Dockerfile 是一個包含建立映象所有命令的文字檔案,通過docker build命令可以根據 Dockerfile 的內容構建映象,在介紹如何構建之前先介紹下 Dockerfile 的基本語法結構。
Dockerfile 有以下指令選項:
- FROM
- MAINTAINER
- RUN
- CMD
- EXPOSE
- ENV
- ADD
- COPY
- ENTRYPOINT
- VOLUME
- USER
- WORKDIR
- ONBUILD
7.1 FROM
用法:
FROM <image>
或者
FROM <image>
- FROM指定構建映象的基礎源映象,如果本地沒有指定的映象,則會自動從 Docker 的公共庫 pull 映象下來。
- FROM必須是 Dockerfile 中非註釋行的第一個指令,即一個 Dockerfile 從FROM語句開始。
- FROM可以在一個 Dockerfile 中出現多次,如果有需求在一個 Dockerfile 中建立多個映象。
- 如果FROM語句沒有指定映象標籤,則預設使用latest標籤。
7.2 MAINTAINER
用法:
MAINTAINER <name>
指定建立映象的使用者
RUN 有兩種使用方式
每條RUN指令將在當前映象基礎上執行指定命令,並提交為新的映象,後續的RUN都在之前RUN提交後的映象為基礎,映象是分層的,可以通過一個映象的任何一個歷史提交點來建立,類似原始碼的
exec 方式會被解析為一個 JSON 陣列,所以必須使用雙引號而不是單引號。exec 方式不會呼叫一個命令 shell,所以也就不會繼承相應的變數,如:
RUN [ "echo", "$HOME" ]
這種方式是不會達到輸出 HOME 變數的,正確的方式應該是這樣的
RUN [ "sh", "-c", "echo", "$HOME" ]
RUN產生的快取在下一次構建的時候是不會失效的,會被重用,可以使用--no-cache選項,即docker build --no-cache,如此便不會快取。
7.3 CMD
CMD有三種使用方式:
CMD指定在 Dockerfile 中只能使用一次,如果有多個,則只有最後一個會生效。
CMD的目的是為了在啟動容器時提供一個預設的命令執行選項。如果使用者啟動容器時指定了執行的命令,則會覆蓋掉CMD指定的命令。
CMD會在啟動容器的時候執行,build 時不執行,而RUN只是在構建映象的時候執行,後續映象構建完成之後,啟動容器就與RUN無關了,這個初學者容易弄混這個概念,這裡簡單註解一下。
7.4 EXPOSE
EXPOSE <port> [<port>...]
告訴 Docker 服務端容器對外對映的本地埠,需要在 docker run 的時候使用-p或者-P選項生效。
7.5 ENV
ENV <key> <value> # 只能設定一個變數 ENV <key>=<value> ... # 允許一次設定多個變數
指定一個環節變數,會被後續RUN指令使用,並在容器執行時保留。
例子:
ENV myName="John Doe" myDog=Rex\ The\ Dog \ myCat=fluffy
等同於
ENV myName John Doe ENV myDog Rex The Dog ENV myCat fluffy
7.6 ADD
ADD <src>... <dest>
ADD複製本地主機檔案、目錄或者遠端檔案 URLS 從 並且新增到容器指定路徑中 。
ADD hom* /mydir/ # adds all files starting with "hom" ADD hom?.txt /mydir/ # ? is replaced with any single character
- 路徑必須是絕對路徑,如果 不存在,會自動建立對應目錄
- 路徑必須是 Dockerfile 所在路徑的相對路徑
- 如果是一個目錄,只會複製目錄下的內容,而目錄本身則不會被複制
7.7 COPY
COPY <src>... <dest>
COPY複製新檔案或者目錄從 並且新增到容器指定路徑中 。用法同ADD,唯一的不同是不能指定遠端檔案 URLS。
7.8 ENTRYPOINT
- ENTRYPOINT command param1 param2 (shell form)
配置容器啟動後執行的命令,並且不可被 docker run 提供的引數覆蓋,而CMD是可以被覆蓋的。如果需要覆蓋,則可以使用docker run --entrypoint選項。
每個 Dockerfile 中只能有一個ENTRYPOINT,當指定多個時,只有最後一個生效。
Exec form ENTRYPOINT 例子
通過ENTRYPOINT使用 exec form 方式設定穩定的預設命令和選項,而使用CMD新增預設之外經常被改動的選項。
FROM ubuntu ENTRYPOINT ["top", "-b"] CMD ["-c"]
通過 Dockerfile 使用ENTRYPOINT展示前臺執行 Apache 服務
FROM debian:stable RUN apt-get update && apt-get install -y --force-yes apache2 EXPOSE 80 443 VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"] ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
Shell form ENTRYPOINT 例子
這種方式會在/bin/sh -c中執行,會忽略任何CMD或者docker run命令列選項,為了確保docker stop能夠停止長時間執行ENTRYPOINT的容器,確保執行的時候使用exec選項。
FROM ubuntu ENTRYPOINT exec top -b
如果在ENTRYPOINT忘記使用exec選項,則可以使用CMD補上:
FROM ubuntu ENTRYPOINT top -b CMD --ignored-param1 # --ignored-param2 ... --ignored-param3 ... 依此類推
7.9 VOLUME
VOLUME ["/data"]
建立一個可以從本地主機或其他容器掛載的掛載點,後續具體介紹。
7.10 USER
USER daemon
指定執行容器時的使用者名稱或 UID,後續的RUN、CMD、ENTRYPOINT也會使用指定使用者。
7.11 WORKDIR
WORKDIR /path/to/workdir
為後續的RUN、CMD、ENTRYPOINT指令配置工作目錄。可以使用多個WORKDIR指令,後續命令如果引數是相對路徑,則會基於之前命令指定的路徑。
WORKDIR /a WORKDIR b WORKDIR c RUN pwd
最終路徑是/a/b/c。
WORKDIR指令可以在ENV設定變數之後呼叫環境變數:
ENV DIRPATH /path WORKDIR $DIRPATH/$DIRNAME
最終路徑則為 /path/$DIRNAME。
7.12 ONBUILD
ONBUILD [INSTRUCTION]
配置當所建立的映象作為其它新建立映象的基礎映象時,所執行的操作指令。
例如,Dockerfile 使用如下的內容建立了映象 image-A:
[...] ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src [...]
如果基於 image-A 建立新的映象時,新的 Dockerfile 中使用 FROM image-A 指定基礎映象時,會自動執行 ONBUILD 指令內容,等價於在後面添加了兩條指令。
# Automatically run the following ADD . /app/src RUN /usr/local/bin/python-build --dir /app/src
使用ONBUILD指令的映象,推薦在標籤中註明,例如 ruby:1.9-onbuild。
7.13 Dockerfile Examples
# Nginx # # VERSION 0.0.1 FROM ubuntu MAINTAINER Victor Vieux <[email protected]> RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server # Firefox over VNC # # VERSION 0.3 FROM ubuntu # Install vnc, xvfb in order to create a 'fake' display and firefox RUN apt-get update && apt-get install -y x11vnc xvfb firefox RUN mkdir ~/.vnc # Setup a password RUN x11vnc -storepasswd 1234 ~/.vnc/passwd # Autostart firefox (might not be the best way, but it does the trick) RUN bash -c 'echo "firefox" >> /.bashrc' EXPOSE 5900 CMD ["x11vnc", "-forever", "-usepw", "-create"] # Multiple images example # # VERSION 0.1 FROM ubuntu RUN echo foo > bar # Will output something like ===> 907ad6c2736f FROM ubuntu RUN echo moo > oink # Will output something like ===> 695d7793cbe4 # You᾿ll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with # /oink.
7.14 docker build
$ docker build --help Usage: docker build [OPTIONS] PATH | URL | - Build a new image from the source code at PATH --force-rm=false Always remove intermediate containers, even after unsuccessful builds # 移除過渡容器,即使構建失敗 --no-cache=false Do not use cache when building the image # 不實用 cache -q, --quiet=false Suppress the verbose output generated by the containers --rm=true Remove intermediate containers after a successful build # 構建成功後移除過渡層容器 -t, --tag="" Repository name (and optionally a tag) to be applied to the resulting image in case of success
7.15 dockerfile 最佳實踐
- 使用.dockerignore檔案
為了在docker build過程中更快上傳和更加高效,應該使用一個.dockerignore檔案用來排除構建映象時不需要的檔案或目錄。例如,除非. Git 在構建過程中需要用到,否則你應該將它新增到.dockerignore檔案中,這樣可以節省很多時間。
- 避免安裝不必要的軟體包
為了降低複雜性、依賴性、檔案大小以及構建時間,應該避免安裝額外的或不必要的包。例如,不需要在一個 資料庫 映象中安裝一個文字編輯器。
- 每個容器都跑一個程序
在大多數情況下,一個容器應該只單獨跑一個程式。解耦應用到多個容器使其更容易橫向擴充套件和重用。如果一個服務依賴另外一個服務,可以參考 Linking Containers Together 。
- 最小化層
我們知道每執行一個指令,都會有一次映象的提交,映象是分層的結構,對於Dockerfile,應該找到可讀性和最小化層之間的平衡。
- 多行引數排序
如果可能,通過字母順序來排序,這樣可以避免安裝包的重複並且更容易更新列表,另外可讀性也會更強,新增一個空行使用\換行:
RUN apt-get update && apt-get install -y \ bzr \ cvs \ git \ mercurial \ subversion
- 建立快取
映象構建過程中會按照Dockerfile的順序依次執行,每執行一次指令 Docker 會尋找是否有存在的映象快取可複用,如果沒有則建立新的映象。如果不想使用快取,則可以在docker build時新增--no-cache=true選項。
從基礎映象開始就已經在快取中了,下一個指令會對比所有的子映象尋找是否執行相同的指令,如果沒有則快取失效。在大多數情況下只對比Dockerfile指令和子映象就足夠了。ADD和COPY指令除外,執行ADD和COPY時存放到映象的檔案也是需要檢查的,完成一個檔案的校驗之後再利用這個校驗在快取中查詢,如果檢測的檔案改變則快取失效。RUN apt-get -y update命令只檢查命令是否匹配,如果匹配就不會再執行更新了。
為了有效地利用快取,你需要保持你的 Dockerfile 一致,並且儘量在末尾修改。
Dockerfile 指令
- FROM: 只要可能就使用官方映象庫作為基礎映象
- RUN: 為保持可讀性、方便理解、可維護性,把長或者複雜的RUN語句使用\分隔符分成多行
- 不建議RUN apt-get update獨立成行,否則如果後續包有更新,那麼也不會再執行更新
- 避免使用RUN apt-get upgrade或者dist-upgrade,很多必要的包在一個非privileged許可權的容器裡是無法升級的。如果知道某個包更新,使用apt-get install -y xxx
- 標準寫法
- RUN apt-get update && apt-get install -y package-bar package-foo
例子:
RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ btrfs-tools \ build-essential \ curl \ dpkg-sig \ git \ iptables \ libapparmor-dev \ libcap-dev \ libsqlite3-dev \ lxc=1.0* \ mercurial \ parallel \ reprepro \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.0*
- CMD: 推薦使用CMD [“executable”, “param1”, “param2”…]這種格式,CMD [“param”, “param”]則配合ENTRYPOINT使用
- EXPOSE: Dockerfile 指定要公開的埠,使用docker run時指定對映到宿主機的埠即可
- ENV: 為了使新的軟體更容易執行,可以使用ENV更新PATH變數。如ENV PATH /usr/local/nginx/bin:$PATH確保CMD ["nginx"]即可執行
ENV也可以這樣定義變數:
ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && … ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
- ADDorCOPY:ADD比COPY多一些特性「tar 檔案自動解包和支援遠端 URL」,不推薦新增遠端 URL
如不推薦這種方式:
ADD http://example.com/big.tar.xz /usr/src/things/ RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things RUN make -C /usr/src/things all
推薦使用 curl 或者 wget 替換,使用如下方式:
RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.gz \ | tar -xJC /usr/src/things \ && make -C /usr/src/things all
如果不需要新增 tar 檔案,推薦使用COPY。
參考文獻: