Docker——Dockerfile 定製鏡像

介紹

定製鏡像,是以一個鏡像爲基礎,在其上進行定製所需添加的配置、文件。html

Dockerfile 是一個文本文件,其內包含了一條條的指令(Instruction),每一條指令構建一層,所以每一條指令的內容,就是描述該層應當如何構建。node

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
複製代碼

構建鏡像

使用 build 構建鏡像,docker build [選項] <上下文路徑/URL/->linux

-t參數用來指定鏡像文件的名字,後面還能夠用冒號指定標籤。若是不指定,默認的標籤就是latest。nginx

$ docker build -t nginx:v3 .
複製代碼

鏡像構建上下文

docker build 的工做原理:Docker在運行時分爲Docker引擎(也就是服務端守護進程)和客戶端工具。Docker的引擎提供了一組REST API ,而如docker命令這樣的客戶端工具,則是經過這組API與Docker引擎交互,從而完成各類功能。所以,雖然表面上咱們好像是在本機執行各類docker功能,但實際上,一切都是使用的遠程調用形式在服務端(Docker引擎)完成。redis

docker build 命令構建鏡像,其實並不是在本地構建,而是在服務端,也就是 Docker 引擎中構建的。docker

當構建的時候,用戶會指定構建鏡像上下文的路徑,docker build 命令得知這個路徑後,會將路徑下的全部內容打包,而後上傳給Docker引擎。這樣 Docker引擎收到這個上下文包後,展開就會得到構建鏡像所需的一切文件。shell

如在 Dockerfile 中這麼寫:數據庫

COPY ./package.json /app/
複製代碼

這並非要複製執行 docker build 命令所在的目錄下的 package.json,也不是複製 Dockerfile 所在目錄下的 package.json,而是複製 上下文(context)目錄下的 package.json。json

通常來講,應該會將 Dockerfile 置於一個空目錄下,或者項目根目錄下。若是該目錄下沒有所需文件,那麼應該把所需文件複製一份過來。若是目錄下有些東西確實不但願構建時傳給 Docker 引擎,那麼能夠用 .dockerignore 文件,該文件是用於剔除不須要做爲上下文傳遞給 Docker 引擎的。數組

Dockerfile 指令詳解

FROM 指定鏡像基礎

FROM就是指定基礎鏡像,所以一個 DockerfileFROM 是必備的指令,而且必須是第一條指令

FROM nginx
# 空白鏡像,不以任何鏡像爲基礎,接下來所寫的指令將做爲鏡像第一層開始存在
FROM scratch
複製代碼

RUN 執行指令

# shell格式:RUN <命令>
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
複製代碼

不要每條命令對應一個 RUN,由於每條指令會建一層,這樣不少運行時不須要的東西,都會裝進鏡像裏面,好比編譯環境,更新的軟件包,最後鏡像有很是多層,很是臃腫。

可使用一個 RUN 的指令,並使用 && 將所需命令串聯起來,使用 \ 命令換行,變成一層。

還能夠在最後使用清理工做的命令,刪除不須要的文件。

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
複製代碼

COPY 複製文件

COPY 指令將從構建上下文目錄中 <源路徑> 的文件/目錄複製到新的一層的鏡像內的 <目標路徑> 位置

# COPY [--chown=<user>:<group>] <源路徑> <目標路徑>
COPY package.json /usr/src/app/
# --chown=<user>:<group> 選項來改變文件的所屬用戶及所屬組。
COPY --chown=55:mygroup files* /mydir/
複製代碼

<目標路徑> 能夠是容器內的絕對路徑,也能夠是相對於工做目錄的相對路徑(工做目錄能夠用 WORKDIR 指令來指定)。

使用 COPY 指令,源文件的各類元數據都會保留。好比讀、寫、執行權限、文件變動時間等。

ADD 更高級的複製文件

ADD 指令和 COPY 的格式和性質基本一致。可是在 COPY 基礎上增長了一些功能。

<源路徑> 爲一個tar壓縮文件的話,壓縮格式爲gzip,bzip2以及xz的狀況下,ADD 指令將會自動解壓縮這個壓縮文件到 <目標路徑> 去。

COPYADD 指令中選擇的時候,遵循原則,全部的文件複製均使用 COPY 指令,僅在須要自動解壓縮的場合使用 ADD

CMD 容器啓動命令

CMD 指令是在 build 完成後,爲了給 docker run 啓動到容器時提供默認命令或參數,這些默認值能夠包含可執行的命令,也能夠只是參數(此時可執行命令就必須提早在ENTRYPOINT中指定)。

docker run 後面出現與 CMD 指定的相同命令,那麼 CMD 會被覆蓋。

一個 Dockerfile 裏只能有一個 CMD,若是有多個,只有最後一個生效。

推薦使用 exec 格式,這類格式在解析時會被解析爲 JSON 數組,所以必定要使用雙引號,而不要使用單引號。

CMD ["可執行文件", "參數1", "參數2"...]
複製代碼

ENTRYPOINT 入口點

ENTRYPOINT ["executable", "param1", "param2"] 
複製代碼

ENTRYPOINT 的目的和 CMD 同樣,都是容器啓動時提供默認的命令及參數。

ENTRYPOINT 在運行時也能夠替代,須要經過 docker run 的參數 --entrypoint 來指定。

當指定了 ENTRYPOINT 後,CMD 的含義就發生了改變,再也不是直接的運行其命令,而是將 CMD 的內容做爲參數傳給 ENTRYPOINT 指令。

場景一:讓鏡像變成像命令同樣使用

# Dockerfile文件
ENTRYPOINT [ "curl", "-s", "https://ip.cn" ]
# 執行Dockerfile文件
$ docker run myip -i
# 若是不實用ENTRYPOINT 而使用 CMD [ "curl", "-s", "https://ip.cn" ]
# 那麼 -i 替換了原來的CMD ,從而報錯
複製代碼

當存在 ENTRYPOINT 後,CMD 的內容將會做爲參數傳給 ENTRYPOINT,而這裏 -i 就是新的 CMD,所以會做爲參數傳給 curl,最終結果至關於:

$ docker run myip curl -s https://ip.cn -i
複製代碼

場景二:應用運行前的準備工做

啓動容器就是啓動主進程,有些時候,啓動主進程前,須要一些準備工做。

好比,可能但願避免使用 root 用戶去啓動服務,從而提升安全性,而在啓動服務前還須要以 root 身份執行一些必要的準備工做,最後切換到服務用戶身份啓動服務。

這種狀況下,能夠寫一個腳本,而後放入 ENTRYPOINT 中去執行,而這個腳本會將接到的參數(也就是 )做爲命令,在腳本最後執行。

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD [ "redis-server" ]
複製代碼

能夠看到其中爲了 redis 服務建立了 redis 用戶,並在最後指定了 ENTRYPOINT 爲 docker-entrypoint.sh 腳本。

該腳本的內容就是根據 CMD 的內容來判斷,若是是 redis-server 的話,則切換到 redis 用戶身份啓動服務器,不然依舊使用 root 身份執行

#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
    chown -R redis .
    exec su-exec redis "$0" "$@"
fi

exec "$@"
複製代碼

ENV 設置環境變量

設置環境變量,不管是後面的其它指令,如 RUN,仍是運行時的應用,均可以直接使用這裏定義的環境變量。

# 格式
ENV <key1>=<value1> <key2>=<value2>...
# 環境變量含有空格,須要使用雙引號
ENV VERSION=v1.0 DEBUG=on \
    NAME="Happy Feet"
# 其餘命令使用須要在環境變量加上$
RUN  rm "node-$VERSION-linux-x64.tar.xz"
複製代碼

ARG 構建參數

ARG <參數名>[=<默認值>]
複製代碼

ARG 設置的構建環境的環境變量,在未來容器運行時是不會存在這些環境變量的。

不要使用 ARG 保存密碼之類的信息,由於 docker history 仍是能夠看到全部值的。

Dockerfile 中的 ARG 指令是定義參數名稱,以及定義其默認值。該默認值能夠在構建命令 docker build 中用 --build-arg <參數名>=<值> 來覆蓋。

VOLUME 定義匿名卷

VOLUME ["<路徑1>", "<路徑2>"...]
VOLUME <路徑>
複製代碼

容器運行時應該儘可能保持容器存儲層不發生寫操做,對於數據庫類須要保存動態數據的應用,其數據庫文件應該保存於卷(volume)中。

爲了防止運行時用戶忘記將動態文件所保存目錄掛載爲卷,在 Dockerfile 中,能夠事先指定某些目錄掛載爲匿名卷,這樣在運行時若是用戶不指定掛載,其應用也能夠正常運行,不會向容器存儲層寫入大量數據。

# 這裏的/data目錄就會在運行時自動掛載爲匿名卷。
# 任何向/data中寫入的信息都不會記錄進容器存儲層,從而保證了容器存儲層的無狀態化。
VOLUME /data

# 使用mydata這個命名卷掛載到了/data這個位置,替代了Dockerfile中定義的匿名卷的掛載配置。
docker run -d -v mydata:/data xxxx
複製代碼

EXPOSE 聲明端口

EXPOSE 指令是聲明運行時容器提供服務端口,只是一個聲明,在運行時並不會由於這個聲明應用就會開啓這個端口的服務。

EXPOSE <端口1> [<端口2>...]
複製代碼

使用 docker run -P 時,會自動隨機映射 EXPOSE 的端口到宿主機的端口上。

EXPOSE 和在運行時使用 -p <宿主端口>:<容器端口> 區別:

  • -p,是映射宿主端口和容器端口,就是將容器的對應端口服務公開給外界訪問。
  • EXPOSE 僅僅是聲明容器打算使用什麼端口而已,並不會自動在宿主進行端口映射。

WORKDIR 指定工做目錄

WORKDIR 指令能夠來指定工做目錄(或者稱爲當前目錄),之後各層的當前目錄就被改成指定的目錄,如該目錄不存在,WORKDIR 會幫你創建目錄。

WORKDIR <工做目錄路徑>
複製代碼

例子:

RUN cd /app
RUN echo "hello" > world.txt
複製代碼

若是將這個 Dockerfile 進行構建鏡像運行後,會發現找不到 /app/world.txt 文件,或者其內容不是 hello。

緣由每個 RUN 都是啓動一個容器、執行命令、而後提交存儲層文件變動。第一層 RUN cd /app 的執行僅僅是當前進程的工做目錄變動,一個內存上的變化而已,其結果不會形成任何文件變動。而到第二層的時候,啓動的是一個全新的容器,跟第一層的容器更徹底不要緊,天然不可能繼承前一層構建過程當中的內存變化。

所以若是須要改變之後各層的工做目錄的位置,那麼應該使用 WORKDIR 指令。

USER 指定當前用戶

USER 指令和 WORKDIR 類似,都是改變環境狀態並影響之後的層。

WORKDIR 是改變工做目錄,USER 則是改變以後層的執行 RUN, CMD 以及 ENTRYPOINT 這類命令的身份。

USER <用戶名>[:<用戶組>]
複製代碼

LABEL 添加元數據

LABEL 用於爲鏡像添加元數據,元數以鍵值對的形式指定

LABEL <key>=<value> <key>=<value> <key>=<value>
複製代碼

如,經過 LABEL 指定一些元數據:

LABEL version="1.0" description="這是一個Web服務器" by="IT筆錄"
# 指定鏡像做者
LABEL maintainer="itbilu.com"
複製代碼

指定後能夠經過 docker inspect 查看:

$sudo docker inspect itbilu/test
"Labels": {
    "version": "1.0",
    "description": "這是一個Web服務器",
    "by": "IT筆錄"
},
複製代碼
相關文章
相關標籤/搜索