1.Dockerfile指令詳解
1.FROM指定基礎(chǔ)鏡像
FROM指令用于指定其后構(gòu)建新鏡像所使用的基礎(chǔ)鏡像。如果本地不存在,則默認(rèn)會去Docker Hub下載指定鏡像。FROM指令必是Dockerfile文件中的首條命令,啟動構(gòu)建流程后,Docker將基于該鏡像構(gòu)建新鏡像,F(xiàn)ROM后的命令也會基于這個基礎(chǔ)鏡像。
FROM語法格式為:
FROM <image>或
FROM <image>:<tag>或
FROM <image>:<digest>
通過 FROM 指定的鏡像,可以是任何有效的基礎(chǔ)鏡像。FROM有以下限制:
- FROM必須 是
Dockerfile中第一條非注釋命令 - 在一個
Dockerfile文件中創(chuàng)建多個鏡像時,F(xiàn)ROM可以多次出現(xiàn)。只需在每個新命令FROM之前,記錄提交上次的鏡像 ID。 - tag 或digest是可選的,如果不使用這兩個值時,會使用
ltest版本的基礎(chǔ)鏡像
2.RUN執(zhí)行命令
在鏡像的構(gòu)建過程中執(zhí)行特定的命令,并生成一個中間鏡像。格式:
#shell格式
RUN <command>
#exec格式
RUN ["executable", "param1", "param2"]
- RUN命令將在當(dāng)前
image中執(zhí)行任意合法命令并提交執(zhí)行結(jié)果。命令執(zhí)行提交后,就會自動執(zhí)行Dockerfile中的下一個指令。 - 層級 RUN指令和生成提交是符合Docker核心理念的做法。它允許像版本控制那樣,在任意一個點,對
image鏡像進(jìn)行定制化構(gòu)建。 - RUN指令創(chuàng)建的中間鏡像會被緩存,并會在下次構(gòu)建中使用。如果不想使用這些緩存鏡像,可以在構(gòu)建時指定
--no-cache參數(shù),如:docker build --no-cache。
每條RUN指令將在當(dāng)前鏡像的基礎(chǔ)上執(zhí)行指定命令,并提交為新的鏡像。當(dāng)命令較長時可以使用
\來換行。
3.CMD啟動容器
CMD用于指定在容器啟動時所要執(zhí)行的命令。CMD 有以下三種格式:
CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2
省略可執(zhí)行文件的exec格式,這種寫法使CMD中的參數(shù)當(dāng)做ENTRYPOINT的默認(rèn)參數(shù),此時ENTRYPOINT也應(yīng)該是 exec 格式,具體與ENTRYPOINT的組合使用,參考ENTRYPOINT。
注意
與 RUN 指令的區(qū)別:RUN 在構(gòu)建的時候執(zhí)行,并生成一個新的鏡像,CMD 在容器運行的時候執(zhí)行,在構(gòu)建時不進(jìn)行任何操作。
每個Dockerfile只能有一條CMD命令。如果指定了多條命令,只有最后一條會被執(zhí)行。如果用戶啟動容器時手動指定了運行的命令(作為run的參數(shù)),則會覆蓋掉CMD指定的命令。
4.LABEL添加元數(shù)據(jù)
LABEL用于為鏡像添加元數(shù)據(jù),元數(shù)以鍵值對的形式指定:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
使用LABEL指定元數(shù)據(jù)時,一條LABEL可以指定一條或多條元數(shù)據(jù),指定多條元數(shù)據(jù)時不同元數(shù)據(jù)之間通過空格分隔。推薦將所有的元數(shù)據(jù)通過一條LABEL指令指定,以免生成過多的中間鏡像。如,通過LABEL指定一些元數(shù)據(jù):
LABEL version="1.0" description="這是一個Web服務(wù)器" by="IT筆錄"
指定后可以通過docker inspect查看:
docker inspect itbilu/test
"Labels": {
"version": "1.0",
"description": "這是一個Web服務(wù)器",
"by": "IT筆錄"
},
5.EXPOSE設(shè)置監(jiān)聽端口
為構(gòu)建的鏡像設(shè)置監(jiān)聽端口,是容器在運行時監(jiān)聽。格式:
EXPOSE <port>[<port>...]
#例如:EXPOSE 22 80 8443
注意:該指令知識起到聲明作用,并不會自動完成端口映射。在啟動容器時需要使用
-P,Docker主機會自動分配一個宿主機的臨時端口轉(zhuǎn)發(fā)到指定的端口;使用-p(注意大小寫),則可以具體指定哪個宿主機的本地端口有會映射過來。
6.ENV設(shè)置環(huán)境變量
指定環(huán)境變量,在鏡像生成過程中會被后續(xù)RUN指令使用,在鏡像啟動的容器中也會存在。
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
7.COPY復(fù)制
格式如下:
COPY <源路徑>... <目標(biāo)路徑>
COPY ["<源路徑1>",... "<目標(biāo)路徑>"]
復(fù)制本地主機的源地址(為Dockerfile所在目錄的相對路徑、文件或目錄)下的內(nèi)容到鏡像的目的地址下。目標(biāo)路徑不存在時,會自動創(chuàng)建。當(dāng)使用本地目錄為源目錄時,推薦使用COPY。
8.ADD復(fù)制
該命令將復(fù)制指定的源路徑下的內(nèi)容到容器中的目的路徑下。ADD指令和COPY的格式和性質(zhì)基本是一致的。但是在COPY基礎(chǔ)上增加了一些功能。例如源路徑可以是一個URL,這種情況下,Docker引擎會試圖去下載這個鏈接的文件到目標(biāo)路徑去。
在構(gòu)建鏡像時,復(fù)制上下文中的文件到鏡像內(nèi),格式:
ADD <源路徑>... <目標(biāo)路徑>
ADD ["<源路徑>",... "<目標(biāo)路徑>"]
9.ENTRYPOINT
ENTRYPOINT用于給容器配置一個可執(zhí)行程序。也就是說,每次使用鏡像創(chuàng)建容器時,通過ENTRYPOINT指定的程序都會被設(shè)置成默認(rèn)程序。ENTRYPOINT有以下兩種形式:
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2
ENTRYPOINT 與 CMD 非常類似,不同的是通過docker run執(zhí)行的命令不會覆蓋 ENTRYPOINT,而docker run命令中指定的任何參數(shù),都會被當(dāng)做參數(shù)再次傳遞給 ENTRYPOINT。Dockerfile 中只允許有一個 ENTRYPOINT 命令,多指定時會覆蓋前面的設(shè)置,而只執(zhí)行最后的 ENTRYPOINT 指令。
docker run運行容器時指定的參數(shù)都會被傳遞給 ENTRYPOINT ,且會覆蓋 CMD 命令指定的參數(shù)。如,執(zhí)行docker run <image> -d時,-d 參數(shù)將被傳遞給入口點。
也可以通過docker run --entrypoint重寫 ENTRYPOINT 入口點。如:可以像下面這樣指定一個容器執(zhí)行程序:
ENTRYPOINT ["/usr/bin/nginx"]
完整構(gòu)建代碼:
# Version: 0.0.3
FROM ubuntu:16.04
MAINTAINER 何民三 "cn.liuht@gmail.com"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Hello World, 我是個容器' \
> /var/www/html/index.html
ENTRYPOINT ["/usr/sbin/nginx"]
EXPOSE 80
使用docker build構(gòu)建鏡像,并將鏡像指定為 itbilu/test:
docker build -t="itbilu/test" .
構(gòu)建完成后,使用itbilu/test啟動一個容器:
docker run -i -t itbilu/test -g "daemon off;"
在運行容器時,我們使用了 -g "daemon off;",這個參數(shù)將會被傳遞給 ENTRYPOINT,最終在容器中執(zhí)行的命令為 /usr/sbin/nginx -g "daemon off;"。
10.VOLUME 定義匿名卷
VOLUME用于創(chuàng)建掛載點,即向基于所構(gòu)建鏡像創(chuàng)始的容器添加卷:
VOLUME ["/data"]
一個卷可以存在于一個或多個容器的指定目錄,該目錄可以繞過聯(lián)合文件系統(tǒng),并具有以下功能:
- 卷可以容器間共享和重用
- 容器并不一定要和其它容器共享卷
- 修改卷后會立即生效
- 對卷的修改不會對鏡像產(chǎn)生影響
- 卷會一直存在,直到?jīng)]有任何容器在使用它
VOLUME 讓我們可以將源代碼、數(shù)據(jù)或其它內(nèi)容添加到鏡像中,而又不并提交到鏡像中,并使我們可以多個容器間共享這些內(nèi)容。
11.WORKDIR指定工作目錄
WORKDIR用于在容器內(nèi)設(shè)置一個工作目錄:
WORKDIR /path/to/workdir
通過WORKDIR設(shè)置工作目錄后,Dockerfile 中其后的命令 RUN、CMD、ENTRYPOINT、ADD、COPY 等命令都會在該目錄下執(zhí)行。 如,使用WORKDIR設(shè)置工作目錄:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
在以上示例中,pwd 最終將會在 /a/b/c 目錄中執(zhí)行。在使用 docker run 運行容器時,可以通過-w參數(shù)覆蓋構(gòu)建時所設(shè)置的工作目錄。
12.USER指定當(dāng)前用戶
指定運行容器時的用戶名或UID,后續(xù)的RUN等指令也會使用指定的用戶身份。語法格式為:
USER daemon
使用USER指定用戶時,可以使用用戶名、UID或GID,或者兩者的組合。以下都是合法的指定:
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group
13.ARG
指定一些鏡像內(nèi)使用的參數(shù)(例如版本號信息等),這些參數(shù)在執(zhí)行docker build命令時才以--build-arg<varname>=<value>格式傳入。語法格式為:
ARG <name>[=<default value>]
docker build --build-arg site=itiblu.com -t itbilu/test .
14.ONBUILD
配置當(dāng)前所創(chuàng)建的鏡像作為其他鏡像的基礎(chǔ)鏡像時,所執(zhí)行的創(chuàng)建操作指令。語法格式為:
ONBUILD [INSTRUCTION]
例如,Dockerfile使用如下的內(nèi)容創(chuàng)建了鏡像image-A:
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
如果基于image-A創(chuàng)建新的鏡像時,新的Dockerfile中使用FROM image-A指定基礎(chǔ)鏡像,會自動執(zhí)行ONBUILD指令的內(nèi)容,等價于在后面添加了兩條指令:
FROM image-A
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
使用ONBUILD指令的鏡像,推薦在標(biāo)簽中注明,例如ruby:1.9-onbuild。
15.STOPSIGNAL
STOPSIGNAL用于設(shè)置停止容器所要發(fā)送的系統(tǒng)調(diào)用信號:
STOPSIGNAL signal
所使用的信號必須是內(nèi)核系統(tǒng)調(diào)用表中的合法的值,如:SIGKILL。
16.SHELL
指定其他命令使用shell時的默認(rèn)shell類型。
SHELL ["executable", "parameters"]
默認(rèn)值為["/bin/sh","-c"]。
對于Windows系統(tǒng),建議在Dockerfile開頭添加
# escape=來指定轉(zhuǎn)移信息。
2.Dockerfile示例
構(gòu)建Nginx運行環(huán)境:
# 指定基礎(chǔ)鏡像
FROM sameersbn/ubuntu:14.04.20161014
# 維護(hù)者信息
MAINTAINER sameer@damagehead.com
# 設(shè)置環(huán)境
ENV RTMP_VERSION=1.1.10 \
NPS_VERSION=1.11.33.4 \
LIBAV_VERSION=11.8 \
NGINX_VERSION=1.10.1 \
NGINX_USER=www-data \
NGINX_SITECONF_DIR=/etc/nginx/sites-enabled \
NGINX_LOG_DIR=/var/log/nginx \
NGINX_TEMP_DIR=/var/lib/nginx \
NGINX_SETUP_DIR=/var/cache/nginx
# 設(shè)置構(gòu)建時變量,鏡像建立完成后就失效
ARG BUILD_LIBAV=false
ARG WITH_DEBUG=false
ARG WITH_PAGESPEED=true
ARG WITH_RTMP=true
# 復(fù)制本地文件到容器目錄中
COPY setup/ ${NGINX_SETUP_DIR}/
RUN bash ${NGINX_SETUP_DIR}/install.sh
# 復(fù)制本地配置文件到容器目錄中
COPY nginx.conf /etc/nginx/nginx.conf
COPY entrypoint.sh /sbin/entrypoint.sh
# 運行指令
RUN chmod 755 /sbin/entrypoint.sh
# 允許指定的端口
EXPOSE 80/tcp 443/tcp 1935/tcp
# 指定網(wǎng)站目錄掛載點
VOLUME ["${NGINX_SITECONF_DIR}"]
ENTRYPOINT ["/sbin/entrypoint.sh"]
CMD ["/usr/sbin/nginx"]
構(gòu)建Tomcat環(huán)境:
# 指定基于的基礎(chǔ)鏡像
FROM ubuntu:13.10
# 維護(hù)者信息
MAINTAINER zhangjiayang "zhangjiayang@sczq.com.cn"
# 鏡像的指令操作
# 獲取APT更新的資源列表
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe"> /etc/apt/sources.list
# 更新軟件
RUN apt-get update
# Install curl
RUN apt-get -y install curl
# Install JDK 7
RUN cd /tmp && curl -L 'http://download.oracle.com/otn-pub/java/jdk/7u65-b17/jdk-7u65-linux-x64.tar.gz' -H 'Cookie: oraclelicense=accept-securebackup-cookie; gpw_e24=Dockerfile' | tar -xz
RUN mkdir -p /usr/lib/jvm
RUN mv /tmp/jdk1.7.0_65/ /usr/lib/jvm/java-7-oracle/
# Set Oracle JDK 7 as default Java
RUN update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-7-oracle/bin/java 300
RUN update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/java-7-oracle/bin/javac 300
# 設(shè)置系統(tǒng)環(huán)境
ENV JAVA_HOME /usr/lib/jvm/java-7-oracle/
# Install tomcat7
RUN cd /tmp && curl -L 'http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.8/bin/apache-tomcat-7.0.8.tar.gz' | tar -xz
RUN mv /tmp/apache-tomcat-7.0.8/ /opt/tomcat7/
ENV CATALINA_HOME /opt/tomcat7
ENV PATH $PATH:$CATALINA_HOME/bin
# 復(fù)件tomcat7.sh到容器中的目錄
ADD tomcat7.sh /etc/init.d/tomcat7
RUN chmod 755 /etc/init.d/tomcat7
# Expose ports. 指定暴露的端口
EXPOSE 8080
# Define default command.
ENTRYPOINT service tomcat7 start && tail -f /opt/tomcat7/logs/catalina.out
tomcat7.sh命令文件
export JAVA_HOME=/usr/lib/jvm/java-7-oracle/
export TOMCAT_HOME=/opt/tomcat7
case $1 in
start)
sh $TOMCAT_HOME/bin/startup.sh
;;
stop)
sh $TOMCAT_HOME/bin/shutdown.sh
;;
restart)
sh $TOMCAT_HOME/bin/shutdown.sh
sh $TOMCAT_HOME/bin/startup.sh
;;
esac
exit 0
3.原則和建議
首先,要盡量吃透每個指令的含義和執(zhí)行效果,自己多編寫一些簡單的例子進(jìn)行測試,弄清楚了在撰寫正式的Dockerfile。此外,Docker Hub官方倉庫中提供了大量的優(yōu)秀鏡像和對應(yīng)的Dockerfile,可以通過閱讀它們來學(xué)習(xí)如何撰寫高效的Dockerfile。
- 容器輕量化。從鏡像中產(chǎn)生的容器應(yīng)該盡量輕量化,能在足夠短的時間內(nèi)停止、銷毀、重新生成并替換原來的容器。
- 使用
.gitignore。在大部分情況下,Dockerfile 會和構(gòu)建所需的文件放在同一個目錄中,為了提高構(gòu)建的性能,應(yīng)該使用.gitignore來過濾掉不需要的文件和目錄。 - 為了減少鏡像的大小,減少依賴,僅安裝需要的軟件包。
- 一個容器只做一件事。解耦復(fù)雜的應(yīng)用,分成多個容器,而不是所有東西都放在一個容器內(nèi)運行。如一個 Python Web 應(yīng)用,可能需要 Server、DB、Cache、MQ、Log 等幾個容器。一個更加極端的說法:
One process per container。 - 減少鏡像的圖層。不要多個 Label、ENV 等標(biāo)簽。
- 對續(xù)行的參數(shù)按照字母表排序,特別是使用
apt-get install -y安裝包的時候。 - 使用構(gòu)建緩存。如果不想使用緩存,可以在構(gòu)建的時候使用參數(shù)
--no-cache=true來強制重新生成中間鏡像。
參考資料
http://www.ityouknow.com/docker/2018/03/15/docker-dockerfile-command-introduction.html