日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Docker镜像构成和定制

發布時間:2023/12/9 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Docker镜像构成和定制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Docker鏡像構成和定制

利用 commit 理解鏡像構成

docker commit 命令應用場合

docker commit 命令除了學習之外,還有一些特殊的應用場合,比如被***后保存現場等。但是,不要使用 docker commit 定制鏡像,定制鏡像應該使用 Dockerfile 來完成。

慎用 docker commit

使用 docker commit 意味著所有對鏡像的操作都是黑箱操作,生成的鏡像也被稱為黑箱鏡像,換句話說,就是除了制作鏡像的人知道執行過什么命令、怎么生成的鏡像,別人根本無從得知。而且,即使是這個制作鏡像的人,過一段時間后也無法記清具體在操作的。雖然 docker diff 或許可以告訴得到一些線索,但是遠遠不到可以確保生成一致鏡像的地步。這種黑箱鏡像的維護工作是非常痛苦的。

使用 Dockerfile 定制鏡像

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

Dockerfile 指令介紹

COPY 復制文件

格式: * COPY <源路徑>... <目標路徑> * COPY ["<源路徑1>",... "<目標路徑>"] 比如: COPY package.json /usr/src/app/ 說明: <源路徑> 可以是多個,甚至可以是通配符,其通配符規則要滿足 Go 的 filepath.Match 規則,如: COPY hom* /mydir/ COPY hom?.txt /mydir/ <目標路徑> 可以是容器內的絕對路徑,也可以是相對于工作目錄的相對路徑(工作目錄可以用 WORKDIR 指令來指定)。目標路徑不需要事先創建,如果目錄不存在會在復制文件前先行創建缺失目錄。 注意: 使用 COPY 指令,源文件的各種元數據都會保留。比如讀、寫、執行權限、文件變更時間等。這個特性對于鏡像定制很有用。特別是構建相關文件都在使用 Git 進行管理的時候。

ADD 更高級的復制文件

ADD 指令和 COPY 的格式和性質基本一致。但是在 COPY 基礎上增加了一些功能。
如果 <源路徑> 為一個 tar 壓縮文件的話,壓縮格式為 gzip, bzip2 以及 xz 的情況下,ADD 指令將會自動解壓縮這個壓縮文件到 <目標路徑> 去。
最適合使用 ADD 的場合,就是當我們需要自動解壓縮的場合。如官方鏡像 ubuntu 中:

FROM scratch ##空白鏡像 ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz / ...

由于ADD 則包含了更復雜的功能,其行為也不一定很清晰。它不像COPY 的語義很明確,就是復制文件而已。所以,我們還是盡可能使用COPY吧。

CMD 容器啟動命令

CMD 指令就是用于指定默認的容器主進程的啟動命令的。

CMD 指令的格式和 RUN 相似,也是兩種格式: * shell 格式:CMD <命令> * exec 格式:CMD ["可執行文件", "參數1", "參數2"...] * 參數列表格式:CMD ["參數1", "參數2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具體的參數。

在運行時可以指定新的命令來替代鏡像設置中的這個默認命令,比如,nginx:1.7.9 鏡像默認的 CMD 是 /bin/bash ,如果我們直接使用 docker run -it nginx:1.7.9 的話,會直接進入 bash 。
我們也可以在運行時指定運行別的命令,如 docker run -it nginx:1.7.9 cat /etc/os-release。這就是用 cat /etc/os-release 命令替換了默認的 /bin/bash 命令了,輸出了系統版本信息。
在指令格式上,一般推薦使用 exec 格式,這類格式在解析時會被解析為 JSON 數組,因此一定要使用雙引號 ",而不要使用單引號。

shell格式: CMD echo $HOME exec格式: CMD [ "sh", "-c", "echo $HOME" ] 這就是為什么我們可以使用環境變量的原因,因為這些環境變量會被 shell 進行解析處理。

容器中應用在前臺執行和后臺執行的問題

Docker 不是虛擬機,容器中的應用都應該以前臺執行,而不是像虛擬機、物理機里面那樣,用 upstart/systemd 去啟動后臺服務,容器內沒有后臺服務的概念。

比如,關于nginx的啟動,我們錯誤的寫成:

CMD service nginx start 或 CMD systemctl start nginx 然后發現容器執行后就立即退出了。對于容器而言,其啟動程序就是容器應用進程,容器就是為了主進程而存在的,主進程退出,容器就失去了存在的意義,從而退出,其它輔助進程不是它需要關心的東西。 而使用 service nginx start 命令,則是希望 upstart 來以后臺守護進程形式啟動 nginx 服務。通過上面內容我們了解到 CMD service nginx start 會被理解為 CMD [ "sh", "-c", "service nginx start"],因此主進程實際上是 sh。那么當 service nginx start 命令結束后,sh 也就結束了,sh 作為主進程退出了,自然就會令容器退出。

正確的做法是直接執行 nginx 可執行文件,并且以前臺形式運行,如:

CMD ["nginx", "-g", "daemon off;"]

ENTRYPOINT 入口點

ENTRYPOINT 的格式和 RUN 指令格式一樣,分為 exec 格式和 shell 格式。
ENTRYPOINT 的目的和 CMD 一樣,都是在指定容器啟動程序及參數。ENTRYPOINT 在運行時也可以替代,不過比 CMD 要略顯繁瑣,需要通過 docker run 的參數 --entrypoint 來指定。
當指定了 ENTRYPOINT 后,CMD 的含義就發生了改變,不再是直接的運行其命令,而是將 CMD 的內容作為參數傳給 ENTRYPOINT 指令,換句話說實際執行時,將變為:

<ENTRYPOINT> "<CMD>"

那么有了 CMD 后,為什么還要有 ENTRYPOINT 呢?這種 <ENTRYPOINT> "<CMD>" 有什么好處么?讓我們來看兩個場景。

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

假設我們需要一個得知自己當前公網 IP 的鏡像,那么可以先用 CMD 來實現:

FROM ubuntu:16.04 RUN apt-get update \&& apt-get install -y curl \&& rm -rf /var/lib/apt/lists/* CMD [ "curl", "-s", "http://ip.cn" ]

假如我們使用 docker build -t myip . 來構建鏡像的話,如果我們需要查詢當前公網 IP,只需要執行:

$ docker run myip 當前 IP:61.148.226.66 來自:北京市 聯通

從上面的 CMD 中可以看到實質的命令是 curl,那么如果我們希望顯示 HTTP 頭信息,就需要加上 -i 參數。那么我們可以直接加 -i 參數給 docker run myip 么?

docker run myip -i docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".

執行報錯,executable file not found。之前我們說過,跟在鏡像名后面的是 command,運行時會替換 CMD 的默認值。因此這里的 -i 替換了原來的 CMD,而不是添加在原來的 curl -s http://ip.cn 后面。而 -i 根本不是命令,所以自然找不到。
那么如果我們希望加入 -i 這參數,我們就必須重新完整的輸入這個命令:

docker run myip curl -s http://ip.cn -i

這顯然不是很好的解決方案,而使用 ENTRYPOINT 就可以解決這個問題。現在我們重新用 ENTRYPOINT 來實現這個鏡像:

FROM ubuntu:16.04 RUN apt-get update \&& apt-get install -y curl \&& rm -rf /var/lib/apt/lists/* ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]

這次我們再來嘗試直接使用 docker run myip -i:

docker run myip 當前 IP:61.148.226.66 來自:北京市 聯通docker run myip -i HTTP/1.1 200 OK ...

這次成功了。這是因為當存在 ENTRYPOINT 后,CMD 的內容將會作為參數傳給 ENTRYPOINT,而這里 -i 就是新的 CMD,因此會作為參數傳給 curl,從而達到了我們預期的效果。

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

啟動容器就是啟動主進程,但有些時候,啟動主進程前,需要一些準備工作。
比如 mysql 類的數據庫,可能需要一些數據庫配置、初始化的工作,這些工作要在最終的 mysql 服務器運行之前解決。
此外,可能希望避免使用 root 用戶去啟動服務,從而提高安全性,而在啟動服務前還需要以 root 身份執行一些必要的準備工作,最后切換到服務用戶身份啟動服務?;蛘叱朔胀?#xff0c;其它命令依舊可以使用 root 身份執行,方便調試等。
這些準備工作是和容器 CMD 無關的,無論 CMD 為什么,都需要事先進行一個預處理的工作。這種情況下,可以寫一個腳本,然后放入 ENTRYPOINT 中去執行,而這個腳本會將接到的參數(也就是 <CMD>)作為命令,在腳本最后執行。比如官方鏡像 redis 中就是這么做的:

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 腳本。

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

該腳本的內容就是根據 CMD 的內容來判斷,如果是 redis-server 的話,則切換到 redis 用戶身份啟動服務器,否則依舊使用 root 身份執行。比如:

docker run -it redis id uid=0(root) gid=0(root) groups=0(root)

ENV 設置環境變量

這個指令很簡單,就是設置環境變量.

格式有兩種:

* ENV <key> <value> * ENV <key1>=<value1> <key2>=<value2>...

實例如下:

ENV MYSQL_ROOT_PASSWORD="123456" \MYSQL_DATABASE="edusoho" \ MYSQL_USER="edusoho" \MYSQL_PASSWORD="edusoho"

這個例子中演示了如何換行,以及對含有空格的值用雙引號括起來的辦法,這和 Shell 下的行為是一致的。
定義了環境變量,那么在后續的指令中,就可以使用這個環境變量。比如在官方 node 鏡像 Dockerfile 中,就有類似這樣的代碼:

ENV NODE_VERSION 7.2.0RUN 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

在這里先定義了環境變量 NODE_VERSION,其后的 RUN 這層里,多次使用 $NODE_VERSION 來進行操作定制??梢钥吹?#xff0c;將來升級鏡像構建版本的時候,只需要更新 7.2.0 即可,Dockerfile 構建維護變得更輕松了。
下列指令可以支持環境變量展開:

ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD

可以從這個指令列表里感覺到,環境變量可以使用的地方很多,很強大。通過環境變量,我們可以讓一份 Dockerfile 制作更多的鏡像,只需使用不同的環境變量即可。

ARG 構建參數

格式:

  • ARG <參數名>[=<默認值>]

構建參數和 ENV 的效果一樣,都是設置環境變量。所不同的是,ARG 所設置的構建環境的環境變量,在將來容器運行時是不會存在這些環境變量的。但是不要因此就使用 ARG 保存密碼之類的信息,因為 docker history 還是可以看到所有值的。
Dockerfile 中的 ARG 指令是定義參數名稱,以及定義其默認值。該默認值可以在構建命令 docker build 中用 --build-arg <參數名>=<值> 來覆蓋。
在 1.13 之前的版本,要求 --build-arg 中的參數名,必須在 Dockerfile 中用 ARG 定義過了,換句話說,就是 --build-arg 指定的參數,必須在 Dockerfile 中使用了。如果對應參數沒有被使用,則會報錯退出構建。
從 1.13 開始,這種嚴格的限制被放開,不再報錯退出,而是顯示警告信息,并繼續構建。報錯信息如下例所示:

[Warning] One or more build-args [foo] were not consumed.

VOLUME 定義匿名卷

格式為:

  • VOLUME ["<路徑1>", "<路徑2>"...]
  • VOLUME <路徑>

容器運行時應該盡量保持容器存儲層不發生寫操作,對于數據庫類需要保存動態數據的應用,其數據庫文件應該保存于卷(volume)中,關于Docker 卷的概念和使用,可參考本庫文章
“Docker基本介紹和操作.md”。
為了防止運行時用戶忘記將動態文件所保存目錄掛載為卷,在 Dockerfile 中,我們可以事先指定某些目錄掛載為匿名卷,這樣在運行時如果用戶不指定掛載,其應用也可以正常運行,不會向容器存儲層寫入大量數據。
比如:

VOLUME /data

這里的 /data 目錄就會在運行時自動掛載為匿名卷,任何向 /data 中寫入的信息都不會記錄進容器存儲層,從而保證了容器存儲層的無狀態化。
如果我們想把這個匿名卷中的內容掛載到主機上呢?

docker run -itd --name busytest --mount type=bind,source=/teng,target=/data busytest:v1 或 docker run -itd --name busytest -v /teng:/data busytest:v1

EXPOSE 聲明端口

EXPOSE <port> [<port>/<protocol>...]
該EXPOSE指令通知Docker容器在運行時偵聽指定的網絡端口??梢灾付ǘ丝谑莻陕燭CP還是UDP,如果未指定協議,則默認為TCP。
EXPOSE 指令是聲明運行時容器提供服務端口,這只是一個聲明,在運行時并不會因為這個聲明應用就會開啟這個端口的服務。在 Dockerfile 中寫入這樣的聲明有兩個好處,一個是幫助鏡像使用者理解這個鏡像服務的守護端口,以方便配置映射;另一個用處則是在運行時使用隨機端口映射時,也就是 docker run -P 時,會自動隨機映射 EXPOSE 的端口。

比如,我這里編寫一個Dockerfile文件:

FROM busybox VOLUME /data EXPOSE 80docker build -t busytest:v2 .docker run -itd --name busytest -P busytest:v2docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 30614a66bff0 busytest:v2 "sh" 3 seconds ago Up 2 seconds 0.0.0.0:32771->80/tcp busytest

無論EXPOSE設置如何,您都可以使用-p標志在運行時覆蓋它們。例如

docker run -itd --name busytest -p 8080:80 busytest:v2docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b07c5575afa1 busytest:v2 "sh" 3 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp busytest

WORKDIR 指定工作目錄

格式為 WORKDIR <工作目錄路徑>。
使用 WORKDIR 指令可以來指定工作目錄(或者稱為當前目錄),以后各層的當前目錄就被改為指定的目錄,如該目錄不存在,WORKDIR 會幫你建立目錄。
在Dockerfile中可以多次使用WORKDIR指令。如果提供了相對路徑,則它將相對于前一條WORKDIR指令的路徑 。例如:

WORKDIR /a WORKDIR b WORKDIR c RUN pwd最終pwd命令的輸出Dockerfile將是 /a/b/c。

該WORKDIR指令可以解析先前使用的環境變量 ENV。您只能使用顯式設置的環境變量Dockerfile。例如:

ENV DIRPATH /path WORKDIR $DIRPATH/$DIRNAME RUN pwd最終pwd命令的輸出Dockerfile將是 /path/$DIRNAME

USER 指定當前用戶

格式:
USER <user>[:<group>] or
USER <UID>[:<GID>]

USER 指令和 WORKDIR 相似,都是改變環境狀態并影響以后的層。WORKDIR 是改變工作目錄,USER 則是改變之后層的執行 RUN, CMD 以及 ENTRYPOINT 這類命令的身份。
當然,和 WORKDIR 一樣,USER 只是幫助你切換到指定用戶而已,這個用戶必須是事先建立好的,否則無法切換。
如:

RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN [ "redis-server" ]

如果以 root 執行的腳本,在執行期間希望改變身份,比如希望以某個已經建立好的用戶來運行某個服務進程,不要使用 su 或者 sudo,這些都需要比較麻煩的配置,而且在 TTY 缺失的環境下經常出錯。建議使用 gosu。

# 建立 redis 用戶,并使用 gosu 換另一個用戶執行命令 RUN groupadd -r redis && useradd -r -g redis redis # 下載 gosu RUN wget -O /usr/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" ]

參考文檔

  • Dockerfile 最佳實踐文檔
  • Dockerfie 官方文檔

轉載于:https://blog.51cto.com/wutengfei/2156797

總結

以上是生活随笔為你收集整理的Docker镜像构成和定制的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。