Introduction
- This is a book notes on docker 從入門到實(shí)踐
- book in web
Docker 簡介
- 虛擬機(jī)(VMs):在 Host OS(主操作系統(tǒng)) 上虛擬化硬件,并使用 Guest OS(虛擬OS) 控制虛擬化硬件
- Hypervisor 將 Host OS 中計(jì)算資源進(jìn)行分配構(gòu)成虛擬化硬件
- 內(nèi)核作為 os 的最基本的組成部分,是硬件和軟件進(jìn)行交流的媒介
- 因此一個(gè) VM 由三部分組成:Guest os, Bins/Libs, App
- 容器:容器只包含 App 和它所依賴的環(huán)境 Bins/Libs。同時(shí)因?yàn)槿萜骱?VMs 的主要區(qū)別只有內(nèi)核不同,因此能夠?qū)崿F(xiàn) VMs 的大部分功能,它相比 VM 還具有以下優(yōu)點(diǎn)
- 不需要將硬件虛擬化,具有更高的計(jì)算資源使用效率
- 不需要啟動(dòng)完整的 os,啟動(dòng)更快
- 更易維護(hù)和拓展,可移植性更高
- Docker 在容器的基礎(chǔ)上進(jìn)行再封裝,簡化了容器的創(chuàng)建和維護(hù),使得
Docker技術(shù)比虛擬機(jī)技術(shù)更為輕便、快捷。
基本概念
鏡像 (image)
- 鏡像相當(dāng)于一個(gè) root 文件系統(tǒng)
- 鏡像不包含任何動(dòng)態(tài)數(shù)據(jù),其內(nèi)容在構(gòu)建之后不再發(fā)生改變 (或者說鏡像的內(nèi)容不可寫)
- 利用 Union FS 技術(shù),將鏡像設(shè)計(jì)為分層存儲(chǔ)的架構(gòu),它有以下好處
- 節(jié)省存儲(chǔ)空間
- 使得鏡像的復(fù)用、定制更容易
- 參考
- 鏡像分層存儲(chǔ)與鏡像精簡 中舉例簡單介紹了分層存儲(chǔ)
- 文件系統(tǒng)分層存儲(chǔ)原理 中對(duì)分層存儲(chǔ)進(jìn)行了較詳細(xì)的描述
容器 (Container)
- 從原理上來說,容器是鏡像運(yùn)行時(shí)的實(shí)體,當(dāng)容器運(yùn)行時(shí),以鏡像為基礎(chǔ)層,并在其上創(chuàng)建一個(gè)容器存儲(chǔ)層。從功能上來說,容器是獨(dú)立運(yùn)行的一組應(yīng)用,以及它們的運(yùn)行態(tài)環(huán)境
- 容器的實(shí)質(zhì)是進(jìn)程,但與直接在 host 中執(zhí)行的進(jìn)程不同,容器進(jìn)程運(yùn)行于屬于自己的獨(dú)立的 命名空間,因此容器運(yùn)行在一個(gè)獨(dú)立于 host 的隔離的環(huán)境中
- 按照 Docker 最佳實(shí)踐的要求,容器不應(yīng)該向其存儲(chǔ)層內(nèi)寫入任何數(shù)據(jù),容器存儲(chǔ)層要保持無狀態(tài)化。所有的 (數(shù)據(jù)) 文件寫入操作,都應(yīng)該使用 數(shù)據(jù)卷 (Volume)、或者 掛載 host 目錄
- 參考
- 有狀態(tài) VS 無狀態(tài) 簡單介紹了有狀態(tài)和無狀態(tài)應(yīng)用
倉庫 (Repository)
- 倉庫是一個(gè)集中的存儲(chǔ)、分發(fā)鏡像的服務(wù),例如 Docker Registry
- Docker Registry 公開服務(wù):例如 Docker Hub,可以使用國內(nèi)鏡像提高下載速度
- 私有 Docker Registry
- 一個(gè) Docker Registry 中可以包含多個(gè) 倉庫(
Repository);每個(gè)倉庫可以包含多個(gè) 標(biāo)簽(Tag);每個(gè)標(biāo)簽對(duì)應(yīng)一個(gè)鏡像
安裝 Docker
- 介紹了不同操作系統(tǒng)下 Docker 的安裝
- 介紹了 Docker Hub 國內(nèi)鏡像的安裝
使用鏡像
獲取鏡像
$ docker pull [選項(xiàng)] [Docker Registry 地址[:端口號(hào)]/]倉庫名[:標(biāo)簽]
- 鏡像名稱的格式
- Docker 鏡像倉庫地址:地址的格式一般是
<域名/IP>[:端口號(hào)]。默認(rèn)地址是 Docker Hub(docker.io)。 - 倉庫名:如之前所說,這里的倉庫名是兩段式名稱,即
<用戶名>/<軟件名>。對(duì)于 Docker Hub,如果不給出用戶名,則默認(rèn)為library,也就是官方鏡像
- Docker 鏡像倉庫地址:地址的格式一般是
- 從下載過程中可以看到我們之前提及的分層存儲(chǔ)的概念,鏡像是由多層存儲(chǔ)所構(gòu)成。下載也是一層層的去下載,并非單一文件
- 運(yùn)行鏡像,例如
$ docker run -it --rm ubuntu:18.04 bash
列出鏡像
$ docker image ls
-
鏡像 ID 是鏡像的唯一標(biāo)識(shí),相對(duì)地,一個(gè)鏡像可以對(duì)應(yīng)多個(gè) Tag
- 唯一標(biāo)識(shí)意思為一個(gè)標(biāo)識(shí)能唯一確定一個(gè)鏡像,不同鏡像擁有不同的標(biāo)識(shí)
- 鏡像在下載和上傳過程中是保持壓縮狀態(tài)的,Docker Hub 中展示的大小是壓縮后的大小 (下載所需流量),而
docker image ls中顯示的大小是展開后各層所占空間的總和 (由于分層存儲(chǔ)的優(yōu)勢,實(shí)際消耗硬盤空間的大小一般遠(yuǎn)小于顯示的大小) -
虛懸鏡像(dangling image)
- 不是所有沒有標(biāo)簽的鏡像都是虛懸鏡像,許多中間層鏡像也沒有標(biāo)簽。區(qū)別是前者是頂層鏡像,而后者不是 (增加參數(shù)
-a顯示中間層鏡像) -
$ docker image ls -f dangling=true查看虛懸鏡像 -
$ docker image prune刪除虛懸鏡像
- 不是所有沒有標(biāo)簽的鏡像都是虛懸鏡像,許多中間層鏡像也沒有標(biāo)簽。區(qū)別是前者是頂層鏡像,而后者不是 (增加參數(shù)
-
$ docker image ls (--filter|-f) <filter>過濾器,列出部分鏡像 -
$ docker image ls --format <format string>以特定格式顯示鏡像
刪除鏡像
$ docker image rm [選項(xiàng)] <鏡像1> [<鏡像2> ...]
<鏡像>可以由鏡像短ID,鏡像長ID,鏡像名,鏡像摘要(digest)指定刪除鏡像實(shí)際上在做什么:先
Untagged再Deleted當(dāng)刪除一個(gè)鏡像時(shí),首先將該鏡像標(biāo)簽取消,即
Untagged如果還有別的標(biāo)簽指向該鏡像,那么不進(jìn)行實(shí)際的刪除
如果該鏡像無標(biāo)簽指向,由上層向基礎(chǔ)層方向依次進(jìn)行判斷刪除:如果沒有任何層依賴當(dāng)前層,執(zhí)行
Deleted-
批量刪除鏡像:
docker image rm $(docker image ls -q <篩選鏡像>)- 類似 Linux Shell,使用
$(<cmd>)獲取cmd的標(biāo)準(zhǔn)輸出 - 參數(shù)
-q控制docker image ls的標(biāo)準(zhǔn)輸出格式:僅輸出鏡像短ID
- 類似 Linux Shell,使用
利用 commit 理解鏡像構(gòu)成
當(dāng)運(yùn)行一個(gè)容器時(shí),我們在容器內(nèi) (不使用卷和掛載 host) 所作的任何文件修改都會(huì)被記錄于容器的存儲(chǔ)層里,
docker commit直接將容器的存儲(chǔ)層保存下來成為鏡像-
docker commit有一些特殊的應(yīng)用場合,但不要用于定制鏡像,它有以下缺點(diǎn)-
輔助文件等無關(guān)內(nèi)容也被記錄:在容器內(nèi)修改文件(包括安裝和編譯等)時(shí),還會(huì)有大量無關(guān)的內(nèi)容(例如日志
.log和臨時(shí)文件.tmp)被修改并添加進(jìn)容器存儲(chǔ)層,這會(huì)導(dǎo)致鏡像非常臃腫 -
黑箱鏡像:使用
docker commit意味著黑箱操作,生成的鏡像被稱為黑箱鏡像。無從得知在該鏡像中執(zhí)行過什么命令、怎么生成的鏡像,不易于使用和維護(hù) -
只做加法不做減法:因?yàn)殓R像是分層存儲(chǔ)的,而
docker commit只是將容器存儲(chǔ)層保存為鏡像,所刪除的上一層的東西不會(huì)丟失,因此鏡像的每一次后期修改都只會(huì)讓它更加臃腫
-
輔助文件等無關(guān)內(nèi)容也被記錄:在容器內(nèi)修改文件(包括安裝和編譯等)時(shí),還會(huì)有大量無關(guān)的內(nèi)容(例如日志
使用 Dockerfile 定制鏡像
鏡像分層存儲(chǔ)的結(jié)構(gòu)帶來諸多好處的同時(shí),也帶來很多問題。因此希望能在 Docker 引擎控制下設(shè)計(jì)一系列新的指令,并按照我們的需求構(gòu)建和定制鏡像,將記錄這些指令的純文本文件稱為 Dockerfile。
Dockerfile 是純文本文件,其內(nèi)包含了一條條的 指令(Instruction),每一條指令構(gòu)建鏡像的一層。通過 commit 容器存儲(chǔ)層會(huì)得到一個(gè)黑箱層,相反 Dockerfile 中的指令被設(shè)計(jì)為在 Docker 引擎下進(jìn)行鏡像定制,因此容易記錄鏡像多層結(jié)構(gòu)的歷史變動(dòng)(使用 docker history <image> 查看),接下來首先了解兩種最基礎(chǔ)的 Dockerfile 指令。
FROM 繼承自繼承鏡像
FROM <基礎(chǔ)鏡像>,基礎(chǔ)鏡像的選擇可分為三種
直接在官方提供的鏡像作為基礎(chǔ)鏡像進(jìn)行定制
如果沒有合適的官方基礎(chǔ)鏡像,使用更基礎(chǔ)的操作系統(tǒng)鏡像,不同操作系統(tǒng)的軟件庫為我們提供了廣闊的擴(kuò)展空間
-
使用空白的基礎(chǔ)鏡像
FROM scratch對(duì)于 Linux 下靜態(tài)編譯的程序來說,并不需要有操作系統(tǒng)提供運(yùn)行時(shí)支持,所需的一切庫都已經(jīng)在可執(zhí)行文件里了,因此直接
FROM scratch會(huì)讓鏡像體積更加小巧。
RUN 執(zhí)行命令
觀察 RUN 執(zhí)行命令的一個(gè)例子
FROM debian:stretch
RUN set -x; 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/* \ # 清理 apt 緩存文件
&& rm redis.tar.gz \ # 清理下載文件:redis 源碼壓縮包
&& rm -r /usr/src/redis \ # 清理 redis.tar.gz 的展開
&& apt-get purge -y --auto-remove $buildDeps # 清理 wget 以及依賴關(guān)系
-
RUN有兩種格式:shell 格式RUN <命令>;exec 格式RUN ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"] -
精簡的鏡像:記得在
RUN的最后清理所有無用的文件 - trick of 清理:使用字符串
buildDeps='gcc libc6-dev make wget'記錄軟件包,以及使用$buildDeps展開安裝軟件的依賴關(guān)系,最后干凈地清理了wget
構(gòu)建鏡像
$ docker build [選項(xiàng)] <(上下文路徑|URL|-)>
傳遞上下文目錄
在 docker build . 中 . 傳遞的是上下文 context 目錄。要理解這一點(diǎn),Docker 在運(yùn)行時(shí)分為 Docker 引擎和客戶端工具。Docker 引擎提供 Docker Remote API,而 docker 命令作為客戶端工具通過 API 和 Docker 引擎交互。因此表面上我們在 host 中執(zhí)行各種 docker 功能,實(shí)際上一切都是通過遠(yuǎn)程調(diào)用的形式在服務(wù)端(Docker 引擎)完成。
關(guān)于命令 docker build <path>,它首先指定 <path> 為上下文目錄,并將 <path> 下的所有文件打包交給 Docker 引擎。在此之后,諸如 docker copy, docker add 等指令通過指定基于上下文目錄的相對(duì)路徑移動(dòng)文件。
注意,Docker 會(huì)將上下文目錄中的所有文件打包交給 Docker 引擎,即 Docker 認(rèn)為上下文目錄中的所有文件都是構(gòu)建鏡像時(shí)需要的文件,因此我們在構(gòu)建上下文目錄時(shí)需要謹(jǐn)慎。建議做加法:每次定制鏡像時(shí),新建一個(gè)文件夾作為上下文目錄,然后僅將所需要的文件放進(jìn)去。此外為了方便維護(hù)等需求 .git, .log 文件也需要放入上下文目錄中,使用文本文件 .dockerignore 忽略它們。
指定 Dockerfile
前文提到過應(yīng)該使用 Dockerfile 定制鏡像,在 docker build 指令中,有不同的方式指定 Dockerfile 文件
- 默認(rèn)情況下,將上下文目錄下的名為
Dockerfile的文件作為 Dockerfile - 通過參數(shù)
-f <dockerfile>指定 Dockerfile - 從標(biāo)準(zhǔn)輸入中讀取 Dockerfile:如果標(biāo)準(zhǔn)輸入傳入文本文件,則將其視為 Dockerfile,例如
docker build - < Dockerfilecat Dockerfile | docker build -
此外,docker build 也支持使用不同的方式指定上下文目錄
- 指定 Host 中的本地路徑
- 從 URL 構(gòu)建,包括 Git repo 和壓縮包(例如
http://<path>/context.tar.gz) - 從標(biāo)準(zhǔn)輸入中讀取上下文壓縮包
Dockerfile 指令詳解
COPY 復(fù)制文件
-
COPY <path of file1> [<path of file2> ...] <path of directory>將文件或目錄下的文件復(fù)制到鏡像的路徑下 - 使用參數(shù)
--chown=<user>:<group>可修改文件所屬用戶和所屬組
ADD 更高級(jí)的復(fù)制文件
ADD 相比 COPY 增加了一些功能,但僅建議在需要自動(dòng)解壓縮的場合使用 ADD
CMD 容器啟動(dòng)命令
CMD 指令是用于指定默認(rèn)的容器啟動(dòng)命令(顯然 CMD 只能指定一次),例如 ubuntu 鏡像默認(rèn)的 CMD 是 /bin/bash。
關(guān)于 CMD 指令格式,推薦使用 exec 格式。實(shí)際上,shell 格式會(huì)被自動(dòng)解釋為 exec 格式進(jìn)行執(zhí)行,下面二者等價(jià)
-
shell格式:CMD echo <command> -
exec格式:CMD ["sh", "-c", "<command>"]
這是因?yàn)槟J(rèn)的 SHELL 指令是 ["/bin/sh", "-c"],具體見 SHELL 指令一節(jié)。
容器是進(jìn)程
容器的啟動(dòng)程序 CMD 就是容器的主進(jìn)程,容器就是為了主進(jìn)程而存在的,當(dāng)主進(jìn)程結(jié)束,容器就失去了存在的意義,從而退出。因此容器沒有后臺(tái)服務(wù),當(dāng)有類似需求時(shí)應(yīng)將對(duì)應(yīng)的后臺(tái)服務(wù)以前臺(tái)形式運(yùn)行,例如 CMD ["nginx", "-g", "daemon off;"]。
ENTRYPOINT 入口點(diǎn)
當(dāng)指定了 ENTRYPOINT 后,CMD 的含義就發(fā)生了改變,不再是直接的運(yùn)行其命令,而是將 CMD 的內(nèi)容作為參數(shù)傳給 ENTRYPOINT 指令:<ENTRYPOINT> "<CMD>"。它的設(shè)計(jì)目的是進(jìn)行靈活的定制,它主要應(yīng)用于
- 讓鏡像在使用時(shí)像命令一樣添加可選參數(shù):將原本
CMD的內(nèi)容傳給ENTRYPOINT,在運(yùn)行容器時(shí)輸入可選參數(shù),它們會(huì)自動(dòng)傳給CMD - 在執(zhí)行
CMD前進(jìn)行一些準(zhǔn)備工作:使用ENTRYPOINT執(zhí)行一個(gè).sh腳本,它由兩部分組成,執(zhí)行CMD前的準(zhǔn)備工作以及在最后執(zhí)行CMD
ENV 設(shè)置環(huán)境變量
- 設(shè)置環(huán)境變量,它有兩種格式
ENV <key> <value>和ENV <key>=<value> - 使用
$<key>展開環(huán)境變量 -
<key>一般使用大寫字母表示,提高可讀性
ARG 構(gòu)建參數(shù)
-
ARG設(shè)置環(huán)境變量,并且它們在容器運(yùn)行時(shí)不存在。在構(gòu)建完鏡像后ARG環(huán)境變量仍是存在的,例如在CMD中使用ARG環(huán)境變量時(shí)就需要這一特性 -
靈活的定制:
ARG被設(shè)計(jì)用于定義參數(shù)名稱以及它的默認(rèn)值,進(jìn)而使得在不修改 Dockerfile 的情況下構(gòu)建出不同的鏡像。在docker build中添加參數(shù)--build-arg <參數(shù)名>=<值>修改ARG環(huán)境變量的值,實(shí)現(xiàn)靈活的定制 -
ARG指令有生效范圍,它的生效范圍由FROM指令截?cái)?/li>
VOLUME 構(gòu)建匿名卷
舉例說明,使用 VOLUME /data 可以將工作目錄下的 /data 目錄掛載為匿名卷。當(dāng)運(yùn)行容器時(shí),如果用戶
- 不指定掛載,應(yīng)用可以將數(shù)據(jù)文件寫入
/data以保證正常運(yùn)行,容器刪除以后匿名卷中的數(shù)據(jù)消失 - 需要指定掛載,那么使用
docker run -v mydata:/data將mydata掛載到/data目錄下,保存了容器運(yùn)行時(shí)數(shù)據(jù)的修改
綜上,VOLUME 實(shí)現(xiàn)了一種 靈活的定制:在配置容器內(nèi)的應(yīng)用時(shí),將應(yīng)用程序的數(shù)據(jù)寫入目錄 /data 下。此時(shí)使用 VOLUME 構(gòu)建匿名卷可以使得鏡像的定制更加靈活,當(dāng)用戶
- 不需要數(shù)據(jù)時(shí),不指定掛載,此時(shí)不需要的數(shù)據(jù)不會(huì)寫入容器存儲(chǔ)層
- 需要數(shù)據(jù)時(shí),只需要將
mydata掛載到/data目錄下就可以獲取數(shù)據(jù)
EXPOSE 暴露端口
要將 EXPOSE 和在運(yùn)行時(shí)使用 -p <宿主端口>:<容器端口> 區(qū)分開來
-
-p將容器的對(duì)應(yīng)端口服務(wù)公開給外界訪問 -
EXPOSE僅聲明容器打算使用什么端口,不進(jìn)行端口映射
WORKDIR 指定工作目錄
使用 WORKDIR <工作目錄路徑> 指定工作目錄,如果該目錄不存在,則自動(dòng)建立目錄并指定。以后各層(各指令)的當(dāng)前目錄就被改為指定的目錄,包括 WORKDIR 指令,例如
WORKDIR /a # 絕對(duì)路徑 /a
WORKDIR b # 目錄 /a 下相對(duì)路徑 b -> /a/b
WORKDIR c # 目錄 /a/b 下相對(duì)路徑 c -> /a/b/c
RUN pwd # RUN pwd 的工作目錄為 /a/b/c
USER 指定當(dāng)前用戶
-
USER指令和WORKDIR相似,都是改變環(huán)境狀態(tài)并影響以后的層,改變以后各層執(zhí)行命令(例如RUN,CMD,ENTRYPOINT)的用戶身份 - 如果希望在執(zhí)行指令期間改變身份,使用
gosu
HEALTHCHECK 健康檢查
為什么要進(jìn)行健康檢查
如果沒有 HEALTHCHECK,容器只能根據(jù)主進(jìn)程是否退出來判斷容器狀態(tài)是否異常。如果容器內(nèi)的程序進(jìn)入死鎖或死循環(huán),該容器已經(jīng)無法提供正常服務(wù)了,但是容器無法退出。
如何防止容器進(jìn)入死循環(huán)而不退出
首先每隔一段時(shí)間執(zhí)行檢查程序檢查容器是否異常,但這是不夠的,因?yàn)闄z查程序可能無法正常啟動(dòng)或運(yùn)行(例如容器崩潰或檢查程序進(jìn)入死循環(huán));因此設(shè)置檢查程序運(yùn)行的超時(shí)時(shí)長;除此之外,一次失敗就認(rèn)為容器異常太過苛刻,允許多嘗試幾次。以上分別對(duì)應(yīng)了 HEALTHCHECK 的三個(gè)參數(shù):時(shí)間間隔 --interval,超時(shí)時(shí)長 --timeout,連續(xù)失敗次數(shù)--retries。
容器的健康狀態(tài)變化
當(dāng)運(yùn)行鏡像后,容器最初的狀態(tài)為 starting;等待一段時(shí)間后迎來第一次 HEALTHCHECK,如果檢查成功則狀態(tài)變?yōu)?healthy;之后如果檢測失敗則狀態(tài)變?yōu)?unhealthy??梢杂?docker inspect 查看歷史健康狀態(tài),有助于維護(hù)和排障。
HEALTHCHECK 如何判斷健康狀態(tài)
關(guān)于在 HEALTHCHECK CMD <命令> 中 <命令> 返回的值,如果
- 返回 0,認(rèn)為健康檢查結(jié)果為成功,即程序健康
- 返回 1,認(rèn)為健康檢查結(jié)果為失敗,即程序異常
- 返回 2,保留,或者說未失敗,但也不標(biāo)記為成功導(dǎo)致終止連續(xù)失敗次數(shù)的計(jì)數(shù)
奇怪的是,為什么返回 0 認(rèn)為成功,而返回 1 認(rèn)為失?。ㄍǔUJ(rèn)為 1 表示 true 而 0 表示 false)。這是因?yàn)?HEALTHCHECK 的目的是檢查是否有異常,因此返回 0 表示沒有異常,返回 1 表示有異常。
補(bǔ)充,進(jìn)程的 exit 返回值。當(dāng)進(jìn)程正常結(jié)束時(shí),執(zhí)行 exit 0 或者說 exit 返回值為 0;相反,當(dāng)進(jìn)程出現(xiàn)異常時(shí),為了讓使用該程序的人知道異常的發(fā)生,執(zhí)行 exit 1,即 exit 的返回值為 1。
ONBUILD 構(gòu)建基礎(chǔ)鏡像
ONBUILD <#1:其它 Dockerfile 指令> 只有以當(dāng)前鏡像為基礎(chǔ)鏡像,構(gòu)建下一級(jí)鏡像的時(shí)候 #1 才會(huì)被執(zhí)行。
為什么我們需要 ONBUILD
- ****基礎(chǔ)鏡像****:當(dāng)需要定制許多類似的鏡像時(shí),使用基礎(chǔ)鏡像進(jìn)行繼承更易于維護(hù)
- ****靈活的定制****:在基礎(chǔ)鏡像中使用 ONBUILD,可以將基礎(chǔ)鏡像寫成模板鏡像,更靈活地定制鏡像
關(guān)于書中的例子
FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]
在 ONBUILD COPY 后面的 ./package.json 以及 . 都是基于上下文目錄的相對(duì)路徑,因此這里的上下文目錄相當(dāng)于提供了定制鏡像的模板。當(dāng)我們使用以上例子作為基礎(chǔ)鏡像時(shí),提供不同的上下文目錄,就可以引用不同的 package.json 以及執(zhí)行不同的應(yīng)用。
LABEL 為鏡像添加元數(shù)據(jù)
-
LABEL指令給鏡像提供元數(shù)據(jù)LABEL <key>=<value> <key>=<value> <key>=<value> ... - 補(bǔ)充:元數(shù)據(jù) 簡單來說就是描述數(shù)據(jù)的數(shù)據(jù)
SHELL 指令
SHELL ["executable", "parameters"] 指定默認(rèn)運(yùn)行的 Shell 以及運(yùn)行參數(shù),Linux 中默認(rèn)為 ["/bin/sh", "-c"]
參考文檔
Dockerfile 多階段構(gòu)建
單個(gè) Dockerfile
上一節(jié)中構(gòu)建鏡像時(shí)將所有的構(gòu)建過程(項(xiàng)目及其依賴庫的編譯、測試、打包等流程)包含在一個(gè) Dockerfile 中,在前面已提過:一個(gè)好的 Dockerfile 需要在繼續(xù)下一層之前清除所有不需要的資源,例如我們在 “利用 Dockerfile 定制鏡像” 這一節(jié)的 RUN 指令中手動(dòng)清除下載文件、源碼和 apt 緩存。
思考一下構(gòu)建鏡像時(shí)的需求:我們需要一個(gè)精簡的鏡像,這個(gè)鏡像將生成一個(gè)容器來運(yùn)行程序,因此我們的唯一要求就是良好地執(zhí)行程序。在 “利用 Dockerfile 定制鏡像” 這一節(jié)中,我們在做減法:手動(dòng)刪除不需要的資源以實(shí)現(xiàn)鏡像的精簡。但這樣做是麻煩的,并且也是不足夠精簡的:因?yàn)榫幾g的時(shí)候也生成了一些層,它們不是執(zhí)行程序時(shí)所需要的數(shù)據(jù)。
多個(gè) Dockerfile
相反,參考 軟件開發(fā)中常見的流程,也可以做加法:每一個(gè)階段結(jié)束后只傳遞必要的文件到下一階段,即編譯結(jié)束后僅將可執(zhí)行程序傳給下一階段。要實(shí)現(xiàn)這個(gè)目的,可以構(gòu)建兩個(gè)鏡像 image-build 和 image-server。在書中 “分散到多個(gè) Dockerfile” 一節(jié)的具體例子中得知,我們需要一個(gè) .sh 腳本才能將兩個(gè)鏡像聯(lián)系起來,它需要做到以下幾件事
- 根據(jù)鏡像
image-build生成的容器獲得可執(zhí)行程序app - 將
app復(fù)制到 host,再由 host 復(fù)制到image-server - 運(yùn)行鏡像
image-server為用戶提供服務(wù) - 執(zhí)行清理工作:刪除鏡像
image-build以及在 host 中刪除app
這樣確實(shí)構(gòu)建了精簡的鏡像,但也有一些缺點(diǎn)
- 定制多個(gè)鏡像需要同時(shí)維護(hù)多個(gè) Dockerfile 文件
- 還需要一個(gè)額外的 shell 腳本文件來執(zhí)行復(fù)制和清理工作
多階段構(gòu)建
為解決這個(gè)問題,Docker v17.05 開始支持多階段構(gòu)建 (multistage builds),使用兩個(gè)語法 FROM <image> as <name> 以及 COPY --from=<name> 就可以完成多階段構(gòu)建:在一個(gè)鏡像中可以使用多個(gè) FROM,每個(gè) FROM 對(duì)應(yīng)一個(gè)鏡像構(gòu)建的階段,并根據(jù) <name> 指定從哪個(gè)鏡像中獲取當(dāng)前階段需要的文件。
補(bǔ)充,也可以不指定標(biāo)簽 <name>,通過 --from=0 從上一個(gè)階段復(fù)制文件到當(dāng)前階段。但是為了提高可讀性,建議每個(gè)階段指定標(biāo)簽 <name> 進(jìn)行階段命名和復(fù)制文件。
構(gòu)建多種系統(tǒng)架構(gòu)支持的 Docker 鏡像
使用鏡像創(chuàng)建一個(gè)容器,該鏡像必須與 Docker 宿主機(jī)系統(tǒng)架構(gòu)一致,例如在 Linux x86_64 架構(gòu)的系統(tǒng)中只能創(chuàng)建 Linux x86_64 架構(gòu)的鏡像,同時(shí)也只能使用 Linux x86_64 的鏡像創(chuàng)建容器。
Windows、macOS 除外,其使用了 binfmt_misc 提供了多種架構(gòu)支持,在 Windows、macOS 系統(tǒng)上 (x86_64) 可以運(yùn)行 arm 等其他架構(gòu)的鏡像。
因此需要為不同的系統(tǒng)架構(gòu)準(zhǔn)備對(duì)應(yīng)的鏡像,但是要做到這一點(diǎn),需要將鏡像的名稱加長,例如從 python:3.10 加長為 python:3.10:linux_x86_64,這樣很不方便。我們希望僅提供 python:3.10,然后 Docker 引擎能夠根據(jù)系統(tǒng)架構(gòu)自動(dòng)獲取正確的鏡像。
使用 manifest 列表 (manifest list) 可以記錄<image name> 對(duì)應(yīng)的不同系統(tǒng)架構(gòu)鏡像的 digest 值。當(dāng) pull image 時(shí),Docker 引擎會(huì)先查看 <image name> 是否存在 manifest
- 如果存在,查找對(duì)應(yīng)的
digest值并獲取正確的鏡像 - 如果不存在,直接獲取當(dāng)前鏡像
操作容器
Again: 容器是獨(dú)立運(yùn)行的一個(gè)或一組應(yīng)用,以及它們的運(yùn)行態(tài)環(huán)境
- 啟動(dòng):
docker run <image>新建并啟動(dòng);docker container start <container>啟動(dòng)運(yùn)行一個(gè)已終止(exited)的容器 -
-d后臺(tái)運(yùn)行:輸出容器 ID,并且不會(huì)把輸出的結(jié)果輸出到 Host(或者說 stdout 重定向?yàn)槟硞€(gè) log 文件,使用docker container log查看) -
docker container stop終止容器(但不刪除),此外當(dāng)容器的主程序(CMD或ENTRYPOINT)終結(jié)時(shí)容器自動(dòng)終止 - 進(jìn)入在后臺(tái)運(yùn)行的容器:
docker attach <container>將 stdin, stdout, stderr 等附著在容器上,此時(shí)若在 stdin 中輸入exit,則容器終止;docker exec <container> CMD在已運(yùn)行的容器中執(zhí)行一條命令,不創(chuàng)建和啟動(dòng)新的容器,在 stdin 中輸入exit容器也不會(huì)終止 -
docker export導(dǎo)出容器快照到本地文件;docker import從容器快照導(dǎo)入為鏡像,容器快照文件將丟棄所有的歷史記錄和元數(shù)據(jù)信息(即僅保存容器當(dāng)時(shí)的快照狀態(tài)) - 刪除容器:
docker container rm刪除已終止容器;docker container rm -f強(qiáng)制刪除容器,包括運(yùn)行中的容器;docker container prune清理所有已終止容器;docker run --rm <image>當(dāng)容器終止時(shí)自動(dòng)刪除
訪問倉庫
Todo
Notes
- 操作系統(tǒng)與內(nèi)核
- os: Operating System,操作系統(tǒng)
- 內(nèi)核:參考 內(nèi)核 in wiki。內(nèi)核是現(xiàn)代操作系統(tǒng) (os) 中最基本的組成成分,它接受并管理軟件的 I/O 需求,并將這些需求轉(zhuǎn)化為指令交由 CPU, Memory 等硬件進(jìn)行處理,是軟件和硬件進(jìn)行交流的媒介
- 無標(biāo)簽中間層鏡像出現(xiàn)的一種方式
- 使用
base-image:latest作為基礎(chǔ)層構(gòu)建app-image - 更新鏡像
base-image:latest。此時(shí)舊版本的base-image不會(huì)在鏡像的更新中被刪除,因?yàn)樗?app-image依賴,但是它已經(jīng)失去了標(biāo)簽,因此舊版本的base-image成為了無標(biāo)簽中間層鏡像
- 使用
- Bash 簡介