前言:鏡像的定製實際上就是定製每一層所添加的配置文件,若是咱們能夠把每一層的修改、安裝、構建、操做的命令都寫入一個腳本,而後用這個腳原本構建、定製鏡像,那麼鏡像構建透明性的問題、體積的問題就會獲得解決,這個腳本就是 Dockerfile; Dockerfile 是一個文本文件,其內包含了一條條的指令,每一條指令構建一層,每一層指令的內容,就是描述該層應該如何構建,而後經過 commit 構成新的鏡像。php
1,FROM:指定基礎鏡像,必須是第一條指令html
# 定製 nginx 鏡像的 Dockerfile FROM nginx RUN echo '<h1>Hello,Docker!</h1>' > /usr/share/nginx/html/index.html
注: Docker Hub 上有不少高質量的服務類的官方鏡像能夠拿來直接使用,好比:nginx 、redis 、mysql 、php 、mongo \ tomcat 等,能夠在其中找最符合的一個進行定製node
另外也有一些方便開發、構建、運行各類語言的鏡像,好比:node 、python 、golang 等python
若是沒有找到對應服務的鏡像,官方鏡像中還提供了一些更爲基礎的操做系統鏡像,好比:ubuntu 、debian 、fedora 、centos 等,也能夠利用這些操做系統提供的軟件庫mysql
2,RUN :用來執行命令行命令,格式有兩種:linux
1, shell 格式: RUN <命令>,就像直接在命令行中輸入的命令同樣nginx
2, exec 格式:RUN ["可執行文件",「參數1」,「參數2」],更像是函數調用中的格式git
warning:每個RUN命令都會在 docker鏡像中新建一層,因此應該儘可能少用 RUN 命令,並且要在RUN 的最後要作必要的清除工做github
# 構建層次太多,未作清理工做 FROM debian:stretch RUN apt-get update RUN apt-get install -y gcc libc6-dev make wget RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.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
# 一層構建,並在最後清理壓縮包等緩存文件 FROM debian:stretch RUN buildDeps='gcc libc6-dev make wget' \ && apt-get update \ && apt-get install -y $buildDeps \ && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.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
3, Dockerfile 上下文:golang
# 構建新的鏡像 # -t :指定鏡像名稱和 tag # . : 上下文,表示將本路徑下的全部文件打包上傳到 docker daemon,進行定製鏡像 $ docker build -t nginx:v3 .
4, COPY : 用來從構建上下文目錄中<原路徑>的文件/目錄複製到新一層鏡像內的 <目標路徑>位置,格式有兩種:
1,shell 格式:COPY [--chown=<user>:<group>] <原路徑>...<目標路徑>
2,exec 合適:COPY[--chown=<user>:<group>] ["原路徑1",... "<目標路徑>"]
原路徑:能夠是多個,甚至能夠是通配符
目標路徑:能夠是容器內的絕對路徑,也能夠是相對於工做目錄的相對路徑(工做目錄能夠用 WORKDIR 指令來指定,不須要事先建立,會自動建立)
1 # 利用 通配符 進行復制 2 COPY hom* /mydir/ 3 COPY hom?.txt /mydir/
note : COPY 會將原文件的各類數據都保留,好比 讀、寫、執行權限,能夠經過 --chown=<user>:<group> 選項來改變文件的所屬用戶及所屬組。
5,ADD : 和 COPY 指令的功能,性質基本一致,也能夠經過 --chown 改變文件所屬用戶和所屬組,可是在 COPY 的基礎上增長了一些功能:
1,原路徑爲 URL : Docker 會試圖下載這個文件放到 目標路徑去,默認下載後的文件權限爲 600,若是想要修改權限或者下載的是壓縮包,須要解壓,則還須要額外的一層 RUN 進行調整,還不如直接用 RUN 指令用 wget 進行下載,處理權限,解壓縮,而後清理無用文件更合理,因此該命令不經常使用,並且不推薦使用。
2,原路徑爲 tar 壓縮包 : 若是壓縮文件格式爲 gzip , bzip2 以及 xz 的狀況下,ADD 指令將自動解壓這個壓縮文件到 <目標路徑> 去,只有此種狀況適合使用 ADD 指令。
note: ADD 指令可能會使鏡像構建緩存失效,從而可能會令鏡像的構建變的比較緩慢,鏡像構造緩存點擊這裏查看
6,CMD : 和 RUN 指令類似,也是兩種格式:
1,shell 格式:CMD <命令>
2,exec 格式 : CMD ["可執行文件",「參數1」,「參數2」 ...]
3,參數格式列表:在指定了 ENTRYPOINT 指令後,用 CMD 指定具體的參數
CMD 指令用於指定默認的容器主進程的啓動命令的,例如 ubuntu 默認的 CMD 是 bash ,咱們也能夠在容器運行時指定運行別的命令,如:
# 直接進入 bash $ docker run -it ubuntu # 修改默認的 CMD # docker run -it ubuntu cat /etc/os-release
note1: 在指令格式上,通常推薦使用 exec 格式,這類格式在解析時會被解析爲 JSON 數組,所以必定要用 雙引號 「 而不要使用單引號 。
# 若是執行 CMD echo $HOME # 實際執行會變動爲: CMD ["sh" "-c" "echo $HOME"]
note2 : 容器的前臺執行和後臺執行問題
注:Docker 不是虛擬機,容器中的應用都應該之前臺執行,而不能像虛擬機用 systemd 去啓動後臺服務,容器內沒有後臺服務的概念。例如:
# 錯誤代碼 # 目的:啓動 nginx 在後臺以守護進程的形式在運行 CMD service nginx start # 實際上執行 # sh 爲主進程,執行完成進程退出,致使容器也會退出 CMD ["sh" "-c" "service nginx start"] # 正確作法 # nginx :可執行文件 CMD ["nginx", "-g", "daemon off;"]
7, ENTRYPOINT:格式和 RUN 指令格式同樣,分爲 exec 格式和 shell 格式,目的和 CMD 同樣,都是在指定容器啓動程序及參數;當指定了 ENTRYPOINT 後,CMD 的含義就發生了變化,再也不是直接的運行其命令,而是將 CMD 的內容做爲參數傳給 ENTRYPOINT 指令,換句話說實際執行時,將變爲: <ENTRYPOINT>"<CMD>"
用處 1 : 讓鏡像變成向命令同樣使用:
# 若是咱們須要一個得知本身當前的公網 IP 的鏡像 # Dockerfile 內容: FROM ubuntu:18.04 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* CMD ["curl", "-s", "https://ip.cn"] # 構建鏡像 docker build -t myip . # 查詢 ip 操做 # 不能添加參數,如上面的 -s 參數 docker run myip # 但願顯示 HTTP 頭信息,須要加上 -i 參數 # 試圖添加參數,會報錯,由於在容器後加入參數,會被解析成 CMD 命令,但 -i 不是任何命令 docker run myip -i # 用 ENTRYPOINT 的方式 # Dockerfile 內容: FROM ubuntu:18.04 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* ENTRYPOINT ["curl", "-s", "https://ip.cn"] # 查詢 ip 操做,可加參數 # 此時 CMD 的內容 -i 傳遞給了主進程 curl docker run myip -i
用處 2 : 應用運行前的準備工做:好比數據庫配置,初始化工做,此時能夠傳 ENTRYPOINT 一個腳本,而後經過 CMD 指定參數,在腳本最後執行
1 # allow the container to be started with `--user` 2 # Dockerfile 3 FROM alpine:3.4 4 RUN addgroup -S redis && adduser -S -G redis redis 5 ... 6 ENTRYPOINT ["docker-entrypoint.sh"] 7 EXPOSE 6379 8 CMD ["redis-server"] 9 10 # docker-entrypoint.sh 腳本文件 11 #!/bin/bash 12 if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then 13 chown -R redis . 14 exec su-exec redis "$0" "$@" 15 fi 16 exec "$@"
8, ENV : 用來設置環境變量,格式有兩種:
1,ENV <key> <value>
2,ENV <key1>=<value1> <key2>=<value2>...
在設置了環境變量以後,不管是後面的其它指令,如 RUN ,仍是運行時的應用,均可以直接使用這裏定義的環境變量
# 定義環境變量 ENV VERSION=1.0 DEBUG=ON \ NAME="Happy Feet" # 官方 node 鏡像 Dockerfile 中: ENV NODE_VERSION 7.2.0 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \ && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ && ln -s /usr/local/bin/node /usr/local/bin/nodejs
9, ARG : 構建參數,格式:
1,ARG <參數名>[=<默認值>]
構建參數和 ENV 的效果同樣,都是設置環境變量,所不一樣的是,ARG 所設置的是構建環境的環境變量,在未來容器運行時是不會存在這些環境變量的。
10,VOLUME:定義匿名卷,格式爲:
1,VOLUME ["<路徑1>」,」<路徑2>"...]
2,VOLUME <路徑>
以前說過,容器運行時應該儘可能保持容器存儲層不發生寫操做,對於數據庫類須要保存動態數據的應用,其數據庫文件應該保存在卷中,爲了防止運行時用戶忘記將動態文件所保存目錄掛載爲卷,在 Dockerfile 中,咱們能夠事先指定某些目錄掛載爲匿名卷,這樣在運行時若是用戶不指定掛載,其應用也能夠正常運行,不會向容器存儲層寫入大量數據。
# /data 目錄會在運行時自動掛載爲匿名卷 VOLUME /data # 運行時也能夠覆蓋這個掛載設置 # 用 mydata 這個命名卷掛載到了 /data 這個位置,代替 Dockerfile 中的匿名卷的掛載配置 docker run -d -v mydata:/data xxxx
11,EXPOSE:聲明端口,格式爲:
EXPOSE <端口1> [<端口2>...]
該條指令是聲明運行時容器提供的服務端口,這只是一個聲明,在運行時並不會由於這個聲明應用就會開啓這個端口的服務。這樣聲明帶來兩個好處:
1,幫助鏡像使用者理解這個鏡像服務的守護端口,以方便配置映射
2,在運行時使用隨機端口映射,也就是 docker run -P 時,會自動隨機映射 EXPOSE 的端口
note: 要將 EXPOSE 和在運行時使用 -p <宿主端口>:<容器端口> 區分開來。-p 是映射宿主端口和容器端口,就是將容器的對應端口服務公開給外界訪問,而 EXPOSE 僅僅是聲明容器打算使用什麼端口而已,並不會在宿主進行端口映射。
12, WORKDIR : 指定工做目錄,格式爲:
WORKDIR <工做目錄路徑>
該條指令能夠來指定工做目錄(或者稱爲當前目錄),之後各層的當前目錄就被改成指定的目錄,若是該目錄不存在,則會自動創建。
# 常見錯誤 $ RUN cd /app $ RUN echo "hello" > word.txt /× 若是將這個 Dockerfile 進行構建鏡像運行後,會發現根本找不到 /app/word.txt 文件,這是 由於在 Dockerfile 中,這兩行 RUN 命令的執行環境根本不一樣,是兩個徹底不一樣的容器。 沒一個 RUN 都是啓動一個容器、執行命令、而後提交存儲層文件變動;第一層的執行僅僅是 當前進程的工做目錄變動,一個內存上的變化而已,其結果不會形成任何文件改變;而到了第 二層的時候,啓動的是一個全新的容器,跟第一層的容器更徹底沒有關係,天然不可能繼承前一層構建過程當中的內存變化。 ×/ /× 所以,若是須要改變之後各層的工做目錄的位置,那麼應該使用 WORKDIR 指令 ×/
13,USER:指定當前用戶,格式爲:
USER <用戶名>[:<用戶組>]
該條指令和 WORKDIR 類似,都是改變環境狀態並影響之後的層,WORKDIR 是改變工做目錄, USER 是改變以後層的執行 RUN ,CMD 以及 ENTRYPOINT 這類命令的身份。若是以 root 執行的腳本,在執行期間但願改變身份,好比但願以某個已經創建好的用戶來運行某個服務進程,不要使用 su 或者 sudo ,這些都須要比較麻煩的配置,並且在 TTY 缺失的狀況下常常出錯,建議使用 gosu 。
# 創建 redis 用戶,並使用 gosu 換另外一個用戶執行命令 RUN groupadd -r redis && useradd -r -g redis redis # 下載 gosu RUN wget -O /user/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" && chmod +x /usr/local/bin/gosu && gosu nobody true # 設置 CMD ,並以另外的用戶執行 CMD ["exec", "gosu", "redis", "redis-server"]
14,HEALTHCHECK:健康檢查,格式爲:
HEALTHCHECK [選項] CMD <命令> :設置檢查容器健康情況的命令
HEALTHCHECK NONE : 若是基礎鏡像有健康檢查指令,使用這行能夠屏蔽掉其健康檢查指令
options:
--interval=<間隔> :兩次健康檢查的間隔,默認爲 30s;
--timeout=<時長>: 健康檢查命令運行超時時間,若是超過這個時間,本次健康檢查就被視爲失敗,默認 30s;
--retries=<次數> : 當連續失敗指定次數後,則將容器狀態視爲 unhealthy ,默認 3 次;
return value:
0 : 成功
1:失敗
2:保留(不要使用這個值)
15,ONBUILD:後構建指令,格式爲:
ONBUILD <其它指令>
ONBUILD 是一個特殊的指令,它後面跟的是其它指令,好比 RUN,COPY 等,而這些指令,在當前鏡像構建時並不會被執行。只有當以之前鏡像爲基礎鏡像,去構建下一級鏡像的時候纔會被執行。