雖然 Dockerfile 簡化了鏡像構建的過程,而且把這個過程能夠進行版本控制,可是不正當的 Dockerfile 使用也會致使不少問題:java
容器模型是進程而不是機器,不須要開機初始化。在須要時運行,不須要時中止,可以刪除後重建,而且配置和啓動的最小化。node
在 docker build 的時候,忽略部分無用的文件和目錄能夠提升構建的速度。好比.git目錄。dockerignore的定義相似gitignore。具體使用方式能夠參考連接。python
爲了減小鏡像的複雜性、鏡像大小和構建時間,應該避免安裝無用的包。mysql
一個容器只運行一個進程。容器起到了隔離應用隔離數據的做用,不一樣的應用運行在不一樣的容器讓集羣的縱向擴展以及容器的複用都變的更加簡單。須要多個應用交互時請使用 link 命令進行組合或者使用docker-compose。nginx
須要掌握好Dockerfile的可讀性和鏡像層數之間的平衡。不推薦使用過多的鏡像層。git
命令行按字母順序排序有助於避免重複執行和提升Dockerfile可讀性。apt-get update 應與 apt-get install 組合,換行使用反斜槓(\)。例如:github
RUN apt-get update && apt-get install -y \ bzr \ cvs \ git \ mercurial \ subversion
Dockerfile的每條指令都會將結果提交爲新的鏡像。下一條指令基於上一條指令的鏡像進行構建。若是一個鏡像擁有相同的父鏡像和指令(除了 ADD ),Docker將會使用鏡像而不是執行該指令,即緩存。golang
所以,爲了有效的利用緩存,儘可能保持Dockerfile一致,而且將不變的放在前面而常常改變放在末尾。web
如不但願使用緩存,在執行 docker build 的時候加上參數 --no-cache=true 。sql
Docker匹配鏡像決定是否使用緩存的規則以下:
官方Image 使用的時區基本上都是標準的 UTC 時間,若是容器想使用中國標準時間,基於Debian的系統在Dockerfile中加入
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo "Asia/Shanghai" >> /etc/timezone
基於Centos的系統在Dockerfile中加入
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
有時候你可能感受官方的源更新或者安裝軟件比較慢,能夠在Dockerfile修改官方默認源,例如alpine想使用阿里的源能夠在Dockerfile中加入:
RUN echo -e "http://mirrors.aliyun.com/alpine/v3.5/main\nhttp://mirrors.aliyun.com/alpine/v3.5/community" > /etc/apk/repositories
推薦使用官方倉庫中的鏡像做爲基礎鏡像。
把複雜的或過長的 RUN 語句寫成以 \ 結尾的多行的形式,以提升可讀性和可維護性。
apt-get update 和 apt-get install 一塊兒執行,不然 apt-get install 會出現異常。
避免運行 apt-get upgrade 或 dist-upgrade ,在無特權的容器中,不少 必要 的包不能正常升級。若是基礎鏡像過期了,應當聯繫維護者。 推薦 apt-get update && apt-get install -y package-a package-b 這種方式,先更新,以後安裝最新的軟件包。
RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ build-essential \ curl \ dpkg-sig \ libcap-dev \ libsqlite3-dev \ mercurial \ reprepro \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.* \ && rm -rf /var/lib/apt/lists/*
此外,你能夠經過移除/var/lib/apt/lists減小鏡像大小。
注意:官方的Ubuntu和Debian會自動運行apt-get clean,因此不須要顯式的調用
推薦使用 CMD ["executable","param1","param2"] 這樣的格式。若是鏡像是用來運行服務,須要使用 CMD["apache2","-DFOREGROUND"] ,這種格式的指令適用於任何服務性質的鏡像。
ENTRYPOINT 應該用於 鏡像的主命令,並使用 CMD 做爲默認設置,以 s3cmd 爲例:
ENTRYPOINT ["s3cmd"] CMD ["--help"]
獲取幫助:
docker run s3cmd
或者執行命令:
docker run s3cmd ls s3://mybucket
這在鏡像名與程序重名時很是有用。
ENTRYPOINT 也能夠啓動自定義腳本: 定義腳本:
#!/bin/bash set -e if [ "$1" = 'postgres' ]; then chown -R postgres "$PGDATA" if [ -z "$(ls -A "$PGDATA")" ]; then gosu postgres initdb fi exec gosu postgres "$@" fi exec "$@"
注意:這段腳本使用了exec命令以確保最終應用程序在容器內啓動的PID爲1。這段腳本容許容器接收Unix signals。
COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"]
這段腳本爲用戶提供了多種和 Postgres 交互的途徑:
你能夠簡單地啓動 Postgres:
docker run postgres。
或者運行 postgres 並傳入參數:
docker run postgres postgres --help。
你甚至能夠從鏡像中啓動一個徹底不一樣的程序,好比 Bash:
docker run --rm -it postgres bash
應該儘量地使用默認端口。例如Apache web服務使用EXPOSE 80,MongoDB使用EXPOSE 27017。
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
雖然 ADD 與 COPY 功能相似,但推薦使用 COPY 。 COPY 只支持基本的文件拷貝功能,更加的可控。而 ADD 具備更多特定,好比tar文件自動提取,支持URL。 一般須要提取tarball中的文件到容器的時候纔會用到 ADD 。
若是在Dockerfile中使用多個文件,每一個文件應使用單獨的 COPY 指令。這樣,只有出現文件變化的指令纔會不使用緩存。
爲了控制鏡像的大小,不建議使用 ADD 指令獲取URL文件。正確的作法是在 RUN 指令中使用 wget 或 curl 來獲取文件,而且在文件不須要的時候刪除文件。
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
VOLUME 一般用做數據卷,對於任何可變的文件,包括數據庫文件、代碼庫、或者容器所建立的文件/目錄等都應該使用 VOLUME 掛載。
若是服務不須要特權來運行,使用 USER 指令切換到非root用戶。使用 **RUN groupadd -r mysql && useradd -r -g mysql mysql **以後用 USER mysql 切換用戶
要避免使用 sudo 來提高權限,由於它帶來的問題遠比它能解決的問題要多。若是你確實須要這樣的特性,那麼能夠選擇使用 gosu 。
最後,不要反覆的切換用戶。減小沒必要要的layers。
爲了清晰和可維護性,應該使用WORKDIR來定義工做路徑。推薦使用WORKDIR來代替RUN cd … && do-something 這樣的指令。
Dockerfile 中的其它指令都是爲了定製當前鏡像而準備的,惟有 ONBUILD 是爲了幫助別人定製本身而準備的。
ONBUILD指令用來設置一些觸發的指令,用於在當該鏡像被做爲基礎鏡像來建立其餘鏡像時(也就是Dockerfile中的FROM爲當前鏡像時)執行一些操做,ONBUILD中定義的指令會在用於生成其餘鏡像的Dockerfile文件的FROM指令以後被執行,上述介紹的任何一個指令均可以用於ONBUILD指令,能夠用來執行一些由於環境而變化的操做,使鏡像更加通用。
注意:
例如,Dockerfile使用以下的內容建立了鏡像 image-A :
[...] ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src [...]
若是基於 image-A 建立新的鏡像時,新的Dockerfile中使用FROM image-A指定基礎鏡像時,會自動執行ONBUILD指令內容,等價於在後面添加了兩條指令。
FROM image-A #Automatically run the following ADD . /app/src RUN /usr/local/bin/python-build --dir /app/src
假設咱們要製做 Node.js 所寫的應用的鏡像。咱們都知道 Node.js 使用 npm 進行包管理,全部依賴、配置、啓動信息等會放到 package.json 文件裏。在拿到程序代碼後,須要先進行 npm install 才能夠得到全部須要的依賴。而後就能夠經過 npm start 來啓動應用。所以,通常來講會這樣寫 Dockerfile:
FROM node:slim RUN mkdir /app WORKDIR /app COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/ CMD [ "npm", "start" ]
把這個 Dockerfile 放到 Node.js 項目的根目錄,構建好鏡像後,就能夠直接拿來啓動容器運行。可是若是咱們還有第二個 Node.js 項目也差很少呢?好吧,那就再把這個 Dockerfile 複製到第二個項目裏。那若是有第三個項目呢?再複製麼?文件的副本越多,版本控制就越困難,讓咱們繼續看這樣的場景維護的問題。
若是第一個 Node.js 項目在開發過程當中,發現這個 Dockerfile 裏存在問題,好比敲錯字了、或者須要安裝額外的包,而後開發人員修復了這個 Dockerfile,再次構建,問題解決。第一個項目沒問題了,可是第二個項目呢?雖然最初 Dockerfile 是複製、粘貼自第一個項目的,可是並不會由於第一個項目修復了他們的 Dockerfile,而第二個項目的 Dockerfile 就會被自動修復。
那麼咱們可不能夠作一個基礎鏡像,而後各個項目使用這個基礎鏡像呢?這樣基礎鏡像更新,各個項目不用同步 Dockerfile 的變化,從新構建後就繼承了基礎鏡像的更新?好吧,能夠,讓咱們看看這樣的結果。那麼上面的這個 Dockerfile 就會變爲:
FROM node:slim RUN mkdir /app WORKDIR /app CMD [ "npm", "start" ]
這裏咱們把項目相關的構建指令拿出來,放到子項目裏去。假設這個基礎鏡像的名字爲 my-node 的話,各個項目內的本身的 Dockerfile 就變爲:
FROM my-node COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/
基礎鏡像變化後,各個項目都用這個 Dockerfile 從新構建鏡像,會繼承基礎鏡像的更新。
那麼,問題解決了麼?沒有。準確說,只解決了一半。若是這個 Dockerfile 裏面有些東西須要調整呢?好比 npm install 須要統一加一些參數,那怎麼辦?這一行 RUN 是不可能放入基礎鏡像的,由於涉及到了當前項目的 ./package.json,難道又要一個個修改麼?因此說,這樣製做基礎鏡像,只解決了原來的 Dockerfile 的前4條指令的變化問題,然後面三條指令的變化則徹底沒辦法處理。
ONBUILD 能夠解決這個問題。讓咱們用 ONBUILD 從新寫一下基礎鏡像的 Dockerfile:
FROM node:slim RUN mkdir /app WORKDIR /app ONBUILD COPY ./package.json /app ONBUILD RUN [ "npm", "install" ] ONBUILD COPY . /app/ CMD [ "npm", "start" ]
此次咱們回到原始的 Dockerfile,可是此次將項目相關的指令加上 ONBUILD,這樣在構建基礎鏡像的時候,這三行並不會被執行。而後各個項目的 Dockerfile 就變成了簡單地:
FROM my-node
是的,只有這麼一行。當在各個項目目錄中,用這個只有一行的 Dockerfile 構建鏡像時,以前基礎鏡像的那三行 ONBUILD 就會開始執行,成功的將當前項目的代碼複製進鏡像、而且針對本項目執行 npm install,生成應用鏡像。
相似Java,Go等編譯型項目,可使用ONBUILD指令進行優化Dockerfile。
編寫onbuild Dockerfile以下:
FROM maven:3-jdk-8 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app ONBUILD ADD . /usr/src/app ONBUILD RUN mvn install
而後全部依賴maven編譯的項目Dockerfile能夠簡化成以下形式:
FROM maven:3.3-jdk-8-onbuild CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]