5. 制作docker鏡像

1 手動(dòng)制作docker鏡像

以制作sshd+nginx鏡像為例

1.1 啟動(dòng)基礎(chǔ)容器

docker run -it --name ssh_nginx centos:centos7

1.2 容器中安裝服務(wù)

yum -y install openssh-server epel-release initscripts
yum -y install nginx

# 生成ssh server的公私鑰
/usr/sbin/sshd-keygen

1.3 配置初始化腳本

mkdir /script
vi /script/init.sh

#! /bin/bash
if [ -z $SSH_PWD ]
        then
                SSH_PWD="centos"
fi
echo $SSH_PWD | passwd --stdin root
/usr/sbin/sshd start && /usr/sbin/nginx -g "daemon off;"

1.4 把容器提交為鏡像

docker container commit [OPTIONS] 容器名字或ID 新的鏡像名[:TAG]

docker container commit centos:centos7 ssh_nginx:v1

1.5 測(cè)試鏡像功能

拉起docker鏡像,使用-e參數(shù)給容器內(nèi)的變量SSH_PWD賦值,-p將物理機(jī)的端口映射給docker

docker run  -d --name  ssh_nginx_test  -p 822:22 -p 880:80 -e "SSH_PWD=123" ssh_nginx:v1 /bin/bash /script/init.sh

測(cè)試鏡像的功能

ssh 127.0.0.1 -p 822
curl 127.0.0.1:880

2 使用dockerfile

Dockerfile可以通過 docker build 命令來構(gòu)建鏡像,運(yùn)行該命令需要指定DockerFile的位置以及要構(gòu)建的鏡像名稱

2.1 FROM 指定基礎(chǔ)鏡像

是必備的指令,并且必須是第一條指令。
FROM 基礎(chǔ)鏡像名 如果沒有基礎(chǔ)鏡像 FROM scratch

2.2 RUN 執(zhí)行命令

RUN 命令,就像直接在命令行中輸入的命令一樣。
Dockerfile 中每一個(gè)指令都會(huì)建立一層,每一個(gè) RUN 的行為,就會(huì)新建立一層臨時(shí)鏡像,在其上執(zhí)行這些命令,執(zhí)行結(jié)束后,commit 這一層的修改,構(gòu)成新的鏡像。所以一般使用\來執(zhí)行多行命令

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/* \
    && rm redis.tar.gz \
    && rm -r /usr/src/redis \
    && apt-get purge -y --auto-remove $buildDeps

2.3 COPY 復(fù)制文件

復(fù)制文件
COPY [--chown=user:group] 宿主機(jī)源路徑... 容器內(nèi)路徑

例子:

COPY --chown=bin files* /mydir/

2.4 ADD 復(fù)制文件

復(fù)制文件的時(shí)候,支持自動(dòng)解壓縮gzipbzip2 以及xz。
ADD [--chown=user:group] 宿主機(jī)源路徑... 容器內(nèi)路徑

添加多個(gè)文件時(shí),容器內(nèi)路徑需要以/結(jié)尾

在 COPY 和 ADD 指令中選擇的時(shí)候,可以遵循這樣的原則,所有的文件復(fù)制均使用 COPY 指令,僅在需要自動(dòng)解壓縮的場(chǎng)合使用 ADD

例子:

ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /

2.5 CMD 容器啟動(dòng)命令

指定默認(rèn)的容器主進(jìn)程的啟動(dòng)命令。在運(yùn)行時(shí)可以指定新的命令來替代鏡像設(shè)置中CMD的這個(gè)默認(rèn)命令。
CMD ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"...] 注意使用雙引號(hào)
CMD shell命令

例子:

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

2.6 ENTRYPOINT 容器啟動(dòng)命令

指定容器啟動(dòng)程序及參數(shù),在運(yùn)行時(shí)指定的新命令會(huì)接在ENTRYPOINT的命令后充當(dāng)參數(shù)后續(xù)命令
ENTRYPOINT ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"...] 注意使用雙引號(hào)
ENTRYPOINT shell命令
例子:

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

2.7 ENV 設(shè)置環(huán)境變量

設(shè)置環(huán)境變量,后面的其它指令和是運(yùn)行時(shí)的應(yīng)用,都可以直接使用這里定義的環(huán)境變量
ENV key1=value1 key2=value2...

例子:

ENV PWD=345!@ DEBUG=on \
    NAME="Happy Feet"

2.8 VOLUME 定義匿名卷

容器運(yùn)行時(shí)應(yīng)該盡量避免在容器內(nèi)進(jìn)行寫操作,對(duì)于需要經(jīng)常保存數(shù)據(jù)的應(yīng)用,可以將寫入的目錄定義為VOLUME,避免用戶在使用時(shí)不指定掛載。VOLUME自動(dòng)掛載到容器外部,避免向容器內(nèi)大量寫入。
在運(yùn)行容器時(shí),可以使用-v參數(shù)替代VOLUME的掛載配置
VOLUME 路徑1 路徑2VOLUME ["路徑1", "路徑2"...]

例子:

VOLUME /myvol

2.9 EXPOSE 暴露端口

聲明容器運(yùn)行時(shí)提供服務(wù)的端口,有2個(gè)作用:

  1. 幫助鏡像使用者理解這個(gè)鏡像服務(wù)的守護(hù)端口,以方便配置映射;
  2. 在運(yùn)行容器使用隨機(jī)端口映射時(shí),也就是 docker run -P 時(shí),會(huì)自動(dòng)隨機(jī)映射 EXPOSE 的端口。

EXPOSE 端口1 [端口2...]
例子:

EXPOSE 80 443

2.10 WORKDIR 指定工作目錄

指定工作目錄,以后各層的當(dāng)前目錄就被改為指定的目錄,如該目錄不存在,WORKDIR 會(huì)幫你建立目錄
WORKDIR 工作目錄路徑
例子:

WORKDIR /app

RUN echo "hello" > world.txt

2.11 HEALTHCHECK 健康檢查

當(dāng)在一個(gè)鏡像指定了 HEALTHCHECK 指令后,用其啟動(dòng)容器,初始狀態(tài)會(huì)為 starting,在 HEALTHCHECK 指令檢查成功后變?yōu)?healthy,如果連續(xù)一定次數(shù)失敗,則會(huì)變?yōu)?unhealthy。
CMD命令的返回值決定了該次健康檢查的成功與否:0成功、1失敗。
HEALTHCHECK [選項(xiàng)] CMD 命令:設(shè)置檢查容器健康狀況的命令,CMD后面的命令格式["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"...]shell命令
HEALTHCHECK NONE:如果基礎(chǔ)鏡像有健康檢查指令,屏蔽掉其健康檢查指令

支持下列選項(xiàng):
--interval=間隔:兩次健康檢查的間隔,默認(rèn)為 30 秒;
--timeout=時(shí)長(zhǎng):健康檢查命令運(yùn)行超時(shí)時(shí)間,默認(rèn) 30 秒;
--retries=次數(shù):當(dāng)連續(xù)失敗指定次數(shù)后,則將容器狀態(tài)視為 unhealthy,默認(rèn) 3 次。

例子:

HEALTHCHECK --interval=5s --timeout=3s CMD cat /var/run/auditd.pid > /dev/null

2.12 LABEL 為鏡像添加元數(shù)據(jù)

以鍵值對(duì)的形式為鏡像添加一些元數(shù)據(jù)
LABEL key=value key=value key=value ...

2.13 docker build

以dockerfile創(chuàng)建鏡像,PATH為dockerfile所在目錄
docker build -t name:tag PATH

2.14 dockerfile 例子,制作ssh+nginx鏡像

準(zhǔn)備工作目錄

mkdir -p /docker/ssh_nginx

準(zhǔn)備dockerfile、初始化腳本、健康檢查腳本。

  • dockerfile中定義WORKDIR目錄為/script,并在其中傳入腳本;定義環(huán)境變量SSH_PWD;暴露端口2280
  • 初始化腳本使用環(huán)境變量SSH_PWD初始化用戶密碼,也可以在docker run時(shí)使用-e修改環(huán)境變量SSH_PWD
  • 健康檢查腳本,檢查服務(wù)狀態(tài),返回01
# dockerfile
vim /docker/ssh_nginx/dockerfile

FROM centos:centos7
RUN mkdir /etc/yum.repos.d/backup \
   && mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup \
   && curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo \
   && curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo \
   && yum -y install openssh-server initscripts nginx \
   && /usr/sbin/sshd-keygen
WORKDIR /script
ENV SSH_PWD="123456"
ADD init.sh health_check.sh ./
EXPOSE 22 80
ENTRYPOINT ["/bin/bash", "init.sh"]
HEALTHCHECK --interval=5s --timeout=3s CMD bash health_check.sh
# 初始化腳本
vim /docker/ssh_nginx/init.sh

#! /bin/bash
echo $SSH_PWD | passwd --stdin root
/usr/sbin/sshd && /usr/sbin/nginx -g "daemon off;"
# 健康檢查腳本
vim /docker/ssh_nginx/health_check.sh

if curl 127.0.0.1:80 &> /dev/null && ps -ef | grep "/usr/sbin/ssh[d]" > /dev/null ;then
    exit 0
else
    exit 1
fi

創(chuàng)建鏡像

docker build -t ssh_nginx:v2 /docker/ssh_nginx/

簡(jiǎn)單運(yùn)行鏡像,使用隨機(jī)端口映射

docker run -d -P ssh_nginx:v2

3 docker鏡像分層

3.1 分層結(jié)構(gòu)

為什么說是鏡像分層技術(shù),因?yàn)镈ocker 鏡像是以層來組織的,我們可以通過命令 docker image inspect <image> 或者 docker inspect <image> 來查看鏡像包含哪些層。下面是一個(gè)示例。

[root@docker ~]# docker image inspect busybox:latest
...
"RootFS": {
     "Type": "layers",
     "Layers": [
          "sha256:195be5f8be1df6709dafbba7ce48f2eee785ab7775b88e0c115d8205407265c5"
      ]
 },

如上圖所示,其中 RootFS 就是鏡像 busybox:latest 的鏡像層,只有一層,那么這層數(shù)據(jù)是存儲(chǔ)在宿主機(jī)哪里的呢?好問題。動(dòng)手實(shí)踐的同學(xué)會(huì)在上面的輸出中看到一個(gè)叫做 GraphDriver 的字段內(nèi)容如下。

"GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/cd7a.../diff",
                "MergedDir": "/var/lib/docker/overlay2/da4c.../merged",
                "UpperDir": "/var/lib/docker/overlay2/da4c../diff",
                "WorkDir": "/var/lib/docker/overlay2/da4c.../work"
            },
            "Name": "overlay2"
        },

GraphDriver 負(fù)責(zé)鏡像本地的管理和存儲(chǔ)以及運(yùn)行中的容器生成鏡像等工作,可以將 GraphDriver 理解成鏡像管理引擎,我們這里的例子對(duì)應(yīng)的引擎名字是 overlay2(overlay 的優(yōu)化版本)。除了 overlay 之外,Docker 的 GraphDriver 還支持 btrfs、aufs、devicemappervfs 等。

我們可以看到其中的 Data 包含了多個(gè)部分,這個(gè)對(duì)應(yīng) OverlayFS 的鏡像組織形式,在下面我們?cè)龠M(jìn)行詳細(xì)介紹。雖然我們上面的例子中的 busybox 鏡像只有一層,但是正常情況下很多鏡像都是由多層組成的。

這個(gè)時(shí)候很多同學(xué)應(yīng)該會(huì)有這么一個(gè)疑問,鏡像中的層都是讀寫的,那么我們運(yùn)行著的容器的運(yùn)行時(shí)數(shù)據(jù)是存儲(chǔ)在哪里的呢?

鏡像和容器在存儲(chǔ)上的主要差別就在于容器多了一個(gè)讀寫層。鏡像由多個(gè)只讀層組成,通過鏡像啟動(dòng)的容器在鏡像之上加了一個(gè)讀寫層。下圖是官方的一個(gè)配圖。我們知道可以通過 docker commit 命令基于運(yùn)行時(shí)的容器生成新的鏡像,那么 commit 做的其中一個(gè)工作就是將讀寫層數(shù)據(jù)寫入到新的鏡像中。下圖是一個(gè)示例圖:

Container最上面是一個(gè)可寫的容器層,以及若干只讀的鏡像層組成,Container的數(shù)據(jù)就存放在這些層中,這樣的分層結(jié)構(gòu)最大的特性是Copy-On-Write(寫時(shí)復(fù)制):

1、新數(shù)據(jù)會(huì)直接存放在最上面的Container層。

2、修改現(xiàn)有的數(shù)據(jù)會(huì)先從Image層將數(shù)據(jù)復(fù)制到容器層,修改后的數(shù)據(jù)直接保存在Container層,Image層保持不變。
所有寫入或者修改運(yùn)行時(shí)容器的數(shù)據(jù)都會(huì)存儲(chǔ)在讀寫層,當(dāng)容器停止運(yùn)行的時(shí)候,讀寫層的數(shù)據(jù)也會(huì)被同時(shí)刪除掉。因?yàn)殓R像層的數(shù)據(jù)是只讀的,所有如果我們運(yùn)行同一個(gè)鏡像的多個(gè)容器副本,那么多個(gè)容器則可以共享同一份鏡像存儲(chǔ)層,下圖是一個(gè)示例。

3.2 UnionFS

Docker 的存儲(chǔ)驅(qū)動(dòng)的實(shí)現(xiàn)是基于 Union File System,簡(jiǎn)稱 UnionFS,中文可以叫做聯(lián)合文件系統(tǒng)。UnionFS 設(shè)計(jì)將其他文件系統(tǒng)聯(lián)合到一個(gè)聯(lián)合掛載點(diǎn)的文件系統(tǒng)服務(wù)。

所謂聯(lián)合掛載技術(shù),是指在同一個(gè)掛載點(diǎn)同時(shí)掛載多個(gè)文件系統(tǒng),將掛載點(diǎn)的源目錄與被掛載內(nèi)容進(jìn)行整合,使得最終可見的文件系統(tǒng)將會(huì)包含整合之后的各層的文件和目錄。

舉個(gè)例子:比如我們運(yùn)行一個(gè) ubuntu 的容器。由于初始掛載時(shí)讀寫層為空,所以從用戶的角度來看:該容器的文件系統(tǒng)與底層的 rootfs 沒有區(qū)別;然而從內(nèi)核角度來看,則是顯式區(qū)分的兩個(gè)層。

當(dāng)需要修改鏡像中的文件時(shí),只對(duì)處于最上方的讀寫層進(jìn)行改動(dòng),不會(huì)覆蓋只讀層文件系統(tǒng)的內(nèi)容,只讀層的原始文件內(nèi)容依然存在,但是在容器內(nèi)部會(huì)被讀寫層中的新版本文件內(nèi)容隱藏。當(dāng) docker commit 時(shí),讀寫層的內(nèi)容則會(huì)被保存。

寫時(shí)復(fù)制(Copy On Write)
這里順便介紹一下寫實(shí)復(fù)制技術(shù)。

我們知道 Linux 系統(tǒng)內(nèi)核啟動(dòng)時(shí)首先掛載的 rootfs 是只讀的,在系統(tǒng)正式工作之后,再將其切換為讀寫模式。Docker 容器啟動(dòng)時(shí)文件掛載類似 Linux 內(nèi)核啟動(dòng)的方式,將 rootfs 設(shè)置為只讀模式。不同之處在于:在掛載完成之后,利用上面提到的聯(lián)合掛載技術(shù)在已有的只讀 rootfs 上再掛載一個(gè)讀寫層。

讀寫層位于 Docker 容器文件系統(tǒng)的最上層,其下可能聯(lián)合掛載多個(gè)只讀層,只有在 Docker 容器運(yùn)行過程中文件系統(tǒng)發(fā)生變化時(shí),才會(huì)把變化的文件內(nèi)容寫到可讀寫層,并隱藏只讀層的老版本文件,這就叫做 寫時(shí)復(fù)制,簡(jiǎn)稱 CoW。

3.3 OverlayFS

OverlayFS 將鏡像層(只讀)稱為 lowerdir,將容器層(讀寫)稱為 upperdir,最后聯(lián)合掛載呈現(xiàn)出來的為 mergedir。文件層次結(jié)構(gòu)可以用下圖表示。

舉個(gè)例子,下圖是我們運(yùn)行中的 busybox 容器的 docker inspect 的結(jié)果。

"GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/cd7a.../diff",
                "MergedDir": "/var/lib/docker/overlay2/da4c.../merged",
                "UpperDir": "/var/lib/docker/overlay2/da4c../diff",
                "WorkDir": "/var/lib/docker/overlay2/da4c.../work"
            },
            "Name": "overlay2"
        },

我們?cè)谌萜髦凶龅母膭?dòng),都會(huì)在 upperdirmergeddir 中體現(xiàn)。比如我們?cè)谌萜髦械?/tmp 目錄下新建一個(gè)文件,那么在 upperdirmergeddir 中就能夠看到該文件。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容