1. 程式人生 > >Docker學習筆記:Dockerfile

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。

參考文獻: