目录
以企业级应用为背景,总结Docker架构/技术栈/核心对象/Dockerfile多阶段构建/镜像分层/工作流等使用经验。
文章内容较多,建议根据需要查看对应章节。
Docker概述
Docker是用Go编程语言的,遵从Apache2.0协议开源。
Docker是Client/Server架构,用户在客户端输入指令,客户端将指令转换为DockerAPI调用,Docker守护进程侦听DockerAPI请求并管理Docker对象(如:镜像、容器、网络、卷、仓库等)。
Docker运行体系架构
Docker是一种轻量化的虚拟技术,将应用软件及其依赖的环境整体打包成镜像,可以移植到任何Docker环境上运行,而无需担心环境依赖问题。
Docker容器之间相互独立,可以灵活组装,互不影响,如果我们把Docker守护进程理解成货轮,则容器就是货轮上的集装箱。Docker容器共享操作系统内核,可以秒级启动。
架构对比:
图(a)是linux体系架构图,应用软件直接运行在宿主机上,没有进行隔离,资源的使用没有限制。
图(b)是Docker体系架构图,应用软件运行在容器中,容器是一个被隔离的拥有独立的命名空间和文件系统的进程。Docker Engine也是一个进程,负责容器的管理。这些进程都共享宿主机内核。
图(c)是VM体系架构图,应用软件运行在虚拟机中,每一个VM都拥有一个完成的GuestOS。
Docker核心技术栈
Docker是通过命名空间(Namespace)、控制组(Control Group)、联合文件系统(Union File System)三大技术来实现容器隔离的。
1.命名空间(Namespace)
命名空间是Linux内核的一个核心功能,是容器实现隔离的基础,Docker为每个容器创建不同的命名空间,每个命名空间拥有独立的隔离环境。
- pid 命名空间
隔离进程。
Docker为容器进程创建一个独立的pid命名空间,在同一pid命名空间中只能看到当前命名空间的进程。两个不同的命名空间可以拥有相同的pid,互不影响。
容器内的所有进程的父进程为Docker进程,Docker进程由Docker守护进程创建,并通过pip命名空间进行进程隔离,最后交由linux内核进行管理。- net 命名空间
隔离网络(端口/设备)。
Docker为容器进程创建一个独立的net命名空间,每个net命名空间拥有独立的虚拟网络设备(如:网卡), 路由表等。
Docker默认采用veth的方式,将容器中的虚拟网卡同host上的Docker虚拟网桥docker0连接在一起。- user 命名空间
隔离用户和用户组。
Docker为容器进程创建一个独立的user命名空间,每个容器拥有独立的用户和用户组。
每个容器内都可以通过useradd/groupadd命令创建用户和组,不同的容器中创建的用户名和UID可以相同,也可以不同。- mount 命名空间
隔离挂载点。
Docker为容器进程创建一个独立的mount命名空间,每个容器拥有独立的文件系统和mount挂载配置。
可以为每个容器挂载不同的文件目录,每个容器没只能看到为它挂载的文件目录。在容器中执行df -h即可查看容器的mount。- uts 命名空间
隔离域名。
Docker为容器进程创建一个独立的uts命名空间,每个容器拥有独立的hostname和domain name。会被当作网络上的一个独立的节点。- ipc 命名空间
隔离进程间通信。
只有运行在同一个容器内的进程才可以进行通信。
2.控制组(Cgroups)
控制组是Linux内核的一个功能,用来限制、控制与分离一个进程组(如CPU、内存、磁盘输入输出等)。
- 资源限制:进程组可以使用的资源数量。可以设置进程组可使用的内存限制,包括虚拟内存。
比如:memory子系统可以为进程组设定一个memory使用上限,一旦进程组使用的内存达到限额再申请内存,就会触发OOM(out of memory)。- 优先级:进程组的优先级控制。进程组可以优化得到大量的CPU或磁盘IO吞吐量。
比如:可以使用cpu子系统为某个进程组分配特定cpu share。- 结算:记录进程组使用的资源数量。用来衡量系统资源实际使用情况。
比如:可以使用cpuacct子系统记录某个进程组使用的cpu时间。- 进程组隔离。
比如:使用ns子系统可以使不同的进程组使用不同的namespace,以达到隔离的目的,不同的进程组有各自的进程、网络、文件系统挂载空间。- 进程组控制(Control)。比如:使用freezer子系统可以将进程组挂起和恢复。
Docker相关命令选项
- 控制CPU使用份额
- –cpu-shares或-c
设置每个容器占用cpu的时间比例(权重,相对限制)。
示例:
docker run --name “C1” --cpu-shares=10 ubuntu;
docker run --name “C2” --cpu-shares=30 ubuntu;
容器“C1”和容器“C2”对cpu时间的占用就是1:3- 控制CPU核的使用
- –cpus
限制容器运行的核数。
示例:
docker run --name “C3” --cpus=2 ubuntu;
容器“C3”的进程只能使用主机上的2个cpu- –cpuset-cpus
限制容器运行在哪个CPU核心上。
示例:
docker run --name “C4” --cpuset-cpus=“0-1” ubuntu;
容器“C4”的进程只可在 CPU-0、CPU-1上执行。- 控制CPU使用周期
- –cpu-period
设置每个容器进程的调度周期(单位:us)。- –cpu-quota
设置在每个调度周期内。容器能使用的CPU时间(单位:us)。
示例1:
docker run --name “C5” --cpu-period=100000 --cpu-quota=50000 ubuntu;
每100ms的周期内,容器的CPU配额为50ms(不限制cpu核),换成百分比:每周期50%的cpu使用时间。
示例2:
docker run --name “C6” --cpu-period=100000 --cpu-quota=200000 ubuntu;
每100ms的周期内,容器的CPU配额为200ms(不限制cpu核),换成百分比:每周期100%使用两个cpu核。- 控制内存使用
- -m或–memory
设置每个容器进程的内存使用额度(默认值:-1,表示无限制,–memory-swap是-m的两倍)。- –memory-swap
设置“内存+交换分区”的使用额度。
示例:
docker run --name “C7” -m 500M --memory-swap=700M ubuntu;
容器“C7”的进程,最多只能使用500M内存和200M交换内存swap。- 控制磁盘IO
默认情况下,所有容器能平等地读写磁盘,可通过设置权重、bps和iops来限制磁盘读写的带宽。
bps(byte per second):表示每秒读写的数据量。
iops (io per second):表示每秒的输入输出量(读写次数)。
- –blkio-weight:权重,默认值500。
- –device-read-bps和–device-write-bps:限制读写某个设备的bps。
- –device-read-iops和–device-write-iops:限制读写某个设备的iops。
3.联合文件系统(Union FS)
我们都知道,linux上“一切皆文件”,Docker容器能够运行,就需要一套可运行的文件系统。
通常来说,Docker镜像都是多层的,每一层都是在上一层做的差异修改,那么每层都会有很多文件目录,如何将多层的文件系统整合为一个可用的文件系统?
Docker使用了“Union FS
”技术,顾名思义,就是将镜像的各个层进行计算,将各个层目录内容联合挂载到同一个目录下,最后生成一个文件系统。这就是联合挂载。
联合文件系统
是一种分层、轻量级并且高性能的文件系统,它把对文件系统的修改作为一次提交commit
,一层一层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。镜像可以通过分层来进行继承,基于基础镜像,可以制作各种具体的应用镜像。
比如,一个拥有3层的镜像。最底层创建/home/test1.txt文件。第二层创建/home/test2.txt,那么第二层只有test2.txt文件。第三层在/home/test1.txt中加了一句“CSDN”,那么第三层只有/home/test1.txt这个文件。在创建容器的时候,Docker会采用UnionFS技术,在“容器层”将test1.txt和test2.txt都挂载到/home目录下,并且test1.txt也根据修改记录进行整合(文件中有“CSDN”)。
Docker核心对象
1.Dockerfile
Dockerfile是一个文本文档,可以在其中定义生成镜像的步骤。编写Dockerfile的语言,称为Dockerfile语法。
Dockerfile构建命令
- 语法:
docker build [OPTIONS] PATH|URL|-
每个部分的含义:docker build
:Docker 命令,用于构建镜像。OPTIONS
:可选的命令参数,
如:
--build-arg <name>=<value>
:给ARG定义的变量传参。
-t <my-image>:<tag>
:定义构建镜像名称和标签。PATH|URL|-
: 三种Dockerfile读取方式。
PATH
表示包含Dockerfile
的目录。
URL
表示Git仓库的URL。
-
表示从标准输入流中读取Dockerfile
内容。- 示例:
docker build --build-arg USER_NAME=my_app --build-arg PRODUCT_VERSION=6.3.0 .
推荐使用这个方式。
docker build --build-arg USER_NAME=my_app - < Dockerfile
Dockerfile语法
首行注释:
- 格式:
# syntax=docker/dockerfile:1
- 作用:定义Dockerfile解释器。可选性。
1. FROM
- 格式:
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
- 作用:指定基础镜像,后面的所有指令都要基于此指令。
每个构建阶段都是从FROM开始。以CMD或者ENTRYPIONT结束。每个Dockerfile可以有多个FROM,称为“多阶段构建”。2. LABEL
- 格式:
LABEL <key>=<value> <key>=<value> ...
- 作用:以键值对的形式给镜像添加元数据信息。可选项。
3. MAINTAINER
- 格式:
MAINTAINER <name>
- 作用:指定镜像的作者。可选项。
4. RUN
- 格式:
RUN ["executable", "param1", "param2"]
- 作用:定义执行命令。
- 说明:每条RUN指令都将在当前镜像的基础上执行指定命令,并提交为新的镜像。也就是说,每执行一次RUN,提交一层镜像。每个Dockerfile可以有多个RUN指令。
5. CMD
- 格式1:
CMD ["executable","param1","param2"]
作用:定义容器启动docker run
时,运行的命令。
说明:如果docker run
后面定义了运行的命令参数,则会覆盖CMD定义的命令参数。- 格式2:
CMD ["param1","param2"]
作用:作为ENTRYPOINT的参数
说明:每个Dockerfile只能有一条CMD命令,若指定了多条,只有最后一条生效。6. ENTRYPOINT
- 格式:
ENTRYPOINT ["executable", "param1", "param2"]
- 作用:定义容器启动
docker run
时,运行的命令。- 说明:不会被
docker run
提供的参数覆盖,会把docker run
的参数传递给ENTRYPOINT
。
每个Dockerfile只能有一条ENTRYPOINT命令,若指定了多条,只有最后一条生效。7. CMD 和 ENTRYPIONT 异同点
- 相同点:两者都可以单独在
Dockerfile
中定义,而且功能相同。- 差异点:CMD定义的命令参数可以被
docker run
参数覆盖。ENTRYPIONT会接收docker run
的参数作为自己的参数。- 若
Dockerfile
中同时定义了CMD和ENTRYPIONT,则CMD定义的内容将作为ENTRYPIONT的参数。docker run
参数会覆盖CMD定义的参数。
推荐使用ENTRYPIONT
定义容器执行的第一条命令(主进程)。8. ENV
- 格式:
ENV <key>=<value> ...
- 作用:定义环境变量值。
- 说明:自定义后生效,可在Dockerfile中引用(
RUN USER COPY等
),也会被保存到镜像中,成为容器的环境变量。- 可在运行容器时,通过–env修改或者定义这些环境变量
docker run --env <key>=<value>
。9. ARG
- 格式:
ARG <name>[=<default value>]
- 作用:定义Dockerfile变量。可以通过外部参数传值。
- 说明:仅在构建阶段有效(从定义开始,到CMD/ENTRYPIONT结束),即
docker build
时生效。
可通过docker build --build-arg <name>=<value>
给Dockerfile中定义的键name
传值value
。10. ARG和ENV结合使用
- 相同点:都是定义变量值。都可以定义多个变量。都可在Dockerfile指令中被引用。
- 不同点:
ARG定义的是Dockerfile的变量,仅在Dockerfile构建阶段有效,不会保存到镜像中。可以通过外部参数传值。
ENV定义的是容器内的环境变量,即可在Dockerfile构建阶段被Dockerfile指令引用,也会被保存到镜像中,待容器创建时,作为容器的环境变量。不能通过外部参数传值,只能在Dockerfile中定义好。
ENV定义的环境变量会覆盖ARG定义的同名变量。- 应用:在定义需要灵活传参的环境变量时,我们可以通过ARG定义变量,然后赋值给ENV同名环境变量。
Dockerfile构建镜像时直接使用宿主机的环境变量:
构建时,docker build --build-arg USER_NAME=csdn .
,给USER_NAME
传值csdn
。11. WORKDIR
- 格式:
WORKDIR /path/to/workdir
- 作用:可以设置当前工作目录为任何路径。如果目录不存,则被创建。
如果Dockerfile中的第一个WORKDIR
定义的是相对路径,则是基于Dockerfile位置的相对路径。
如果之后的WORKDIR
定义的也是相对路径,则基于上一个WORKDIR
指定的路径。
如,依次定义WORKDIR a \n WORKDIR b \n WORKDIR c
,则当前的工作路径依次为./a ./a/b ./a/b/c
,最终工作路径./a/b/c
12. COPY
- 格式:
COPY [OPTIONS] <src> ... <dest>
或COPY [OPTIONS] ["<src>", ... "<dest>"]
- 作用:复制本地主机的
<src>
到镜像中的<dest>
<src>
:Dockerfile所在目录的相对路径。<dest>
:可以是绝对路径也可以是相对路径。相对路径是相对当前工作路径<WORKDIR>
而言。路径可以使用匹配字符串,*
匹配所有,?
匹配单个字符。- 如,
COPY test_*.txt relativeDir/
,将test_*.txt
复制到镜像<WORKDIR>/relativeDir/
目录下。OPTIONS
:
--chown
和 ---chmod
格式:COPY [--chown=<user>:<group>] [--chmod=<perms> ...] <src> ... <dest>
作用:复制到镜像中的目录文件,默认都是UID和GID为0创建的。可在COPY
时指定用户和组。
如,COPY --chown=myuser:mygroup --chmod=640 files* /somedir/
将files*
复制到镜像中的/somedir/
,并设置其属性myuser:mygroup
,权限640
。--from
格式:--from=<name>
或--from=<number>
作用:指定源镜像名称或者编号。在Dockerfile多阶段(多个FROM)构建的时候使用。
<name>
表示镜像名称或者别名。<number>
表示Dockerfile中的镜像编号,0表示第一阶段的镜像。--link
--parents
--exclude
13. ADD
- 格式:
ADD [OPTIONS] <src> ... <dest>
或ADD [OPTIONS] ["<src>", ... "<dest>"]
- 作用:将外部资源添加到镜像中。常用于拉取远程git仓代码。
<src>
:可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL;还可以是一个tar文件
(自动解压为目录)。<dest>
:同COPY
。
当<src>
为本地目录时,推荐使用COPY
。14. USER
- 格式:
USER <user>[:<group>]
或USER UID[:GID]
- 作用:指定运行容器时的用户名和组,后续的
RUN|ENTRYPOINT|CMD
也会使用指定用户执行。用户和组必须已经存在。- 组:默认为
root
,当指定group
时,用户将只有指定组的成员身份,其他已配置的组成员身份会被删除。- 应用:当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如:
RUN groupadd -r docker_group && useradd -r -g docker_group docker
。要临时获取管理员权限可以使用gosu
,而不推荐sudo
。15. SHELL
- 格式:
SHELL ["executable", "parameters"]
- 作用:定义容器执行的shell解释器。
- 应用:
SHELL ["/bin/bash", "-c"]
16. EXPOSE
- 格式:
EXPOSE <port> [<port>/<protocol>...]
- 作用:定义容器监听的端口(默认为tcp,如:10328/tcp)。
- 说明:当启动容器时,Docker会将该端口映射到宿主机端口上。
- 示例1:
docker run -p 80:10328/tcp
根据-p定义的映射关系,Docker将宿主机端口80映射到容器监听端口10328/tcp。外界就可以通过 “宿主机IP:80” 地址访问容器了。- 示例2:
docker run -P
-P表示Docker主机会自动随机分配一个主机端口,映射到容器监听端口10328/tcp。17. VOLUME
- 格式:
VOLUME ["/data"]
- 作用:在容器中创建一个挂载点,一般用来存放数据库和需要保存的数据等。
- 应用:
不指定宿主机目录时docker run
,在宿主机上随机创建一个容器卷path/_data
目录,挂载到容器中的/data
挂载点。
指定宿主机目录时docker run -v /home/data:/data
,根据指定的目录进行挂载。
Dockerfile与镜像层对应关系
- 在每个构建阶段
FROM
中,每次对镜像的修改,都会新建一层,层层叠加组成镜像的文件系统,涉及到的Dockerfile命令有COPY|RUN|ADD
等。
Dockerfile多阶段构建
- 每一条
FROM
指令都是一个构建阶段,多条FROM
就是多阶段构建,最后生成的镜像仍是最后一个构建阶段的结果。- 我们可以利用多阶段构建,将前置阶段中的文件拷贝到后面的阶段中,从而减少镜像的大小和层数。
-
示例1:利用多阶段构建,分离编译环境和运行环境。
# syntax=docker/dockerfile:1 # 第一构建阶段:编译环境 FROM golang:1.16-alpine AS builder WORKDIR /home/app # 从git仓上拉去代码,并保留项目目录mygo ADD --keep-git-dir=true https://github.com/mygo.git#v1.10.1 . # 进入mygo项目目录下 WORKDIR /home/app/mygo # 编译生成自己的app RUN go build -o myserver # 第二阶段:运行环境 # scratch镜像是一个精简的基础镜像,它仅包含Docker容器运行所需的最小文件系统和执行环境。 FROM scratch WORDDIR /home/app # 在基础镜像基础上,创建一层。若基础镜像为1层,则该阶段制作的镜像为2层。 COPY --from=builder /home/app/mygo/myserver . # 定义容器启动时运行的命令 ENTRYPOINT ["/home/app/myserver"]
示例1构建过程:
- 首先使用golang:1.16-alpine镜像作为编译环境,编译构建我们的应用程序myserver。然后,我们在第二构建阶段,使用scratch作为运行环境,并将应用程序myserver复制到其中。
- 第一阶段使用的golang:1.16-alpine镜像包含了所有的go库和编译工具,特别庞大,运行程序的时候又不需要这些编译工具。为了降低镜像的体积,在第二阶段我们以scratch为基础镜像,添加myserver应用程序。
Docker Scratch镜像具有以下优点:
- 引用方便:Docker镜像不基于任何其他镜像构建,只需要在Dockerfile中指定FROM scratch即可
- 极小的镜像大小:Scratch镜像非常小,因为它仅包含Docker容器运行所需的最小文件系统和执行环境。这使得它非常适合用于构建轻量级容器化应用程序,减小了镜像大小和传输时间。
- 更好的安全性:由于Scratch镜像非常精简,因此它具有更少的漏洞和攻击面,提高了容器的安全性。此外,由于Scratch镜像不包含任何额外的组件或库,因此它可以防止不必要的攻击。
- 更好的可移植性:由于Scratch镜像非常小,因此它非常适合构建微服务应用程序。每个微服务都可以使用自己的Scratch镜像作为基础镜像,从而获得更好的隔离和可移植性。
-
示例2:利用多阶段构建,减少镜像制作过程中产生的中间层。
# syntax=docker/dockerfile:1 # 第一构建阶段 ARG BASE_IMAGE ENV BASE_IMAGE=${BASE_IMAGE:-ubuntu:22.04} # 基础镜像:我们在此镜像基础之上,制作自己的镜像。 FROM ${BASE_IMAGE} as b_image # 定义变量,从外部传参。 ARG USER_NAME ARG PRODUCT_VERSION # 将ARG变量值复制给ENV,如果USER_NAME为空,则取默认值docker。 ENV USER_NAME=${USER_NAME:-docker} ENV PRODUCT_VERSION=${PRODUCT_VERSION:-6.3.0} # 维护者:Specify the author of an image MAINTAINER docker_user author_name@email.com # 镜像操作指令:Commands to update the image # 定义容器内的工作目录为/home/${USER_NAME}。跟宿主机目录无关。 WORKDIR /home/${USER_NAME} # 复制本地主机的install.sh到容器中的/home/${USER_NAME}/install/目录下。并将属主设置为9079 COPY --chown=9079 --chmod=750 install.sh install/ # 更新软件软并安装nginx软件。 RUN apt-get upgrate && apt-get install bc ksh # 定义工作目录为/home/${USER_NAME}/install/ WORKDIR install # 将宿主机上的软件安装包service_pkg.tar.gz复制到容器中的/home/docker/install/目录下。 COPY --chown=9079 --chmod=640 service_pkg.tar.gz . # 将软件包安装到镜像中。 RUN sh install.sh ${USER_NAME} && rm -rf service_pkg.tar.gz install.sh # 定义容器内的工作目录为/home/${USER_NAME}。 WORKDIR /home/${USER_NAME} # 将启动脚本复制到/home/${USER_NAME} COPY --chown=9079 --chmod=640 start_up.sh . # 第二构建阶段 FROM ${BASE_IMAGE} ARG USER_NAME ENV USER_NAME=${USER_NAME:-docker} WORKDIR /home/${USER_NAME} # 把第一个段构建的镜像中文件,拷贝到当前构建阶段。 # COPY --from=<src_image> <src_image_path> <当前镜像的路径> COPY --from=b_image /home/${USER_NAME} . # 定义ENTRYPIONT默认参数 CMD ["${USER_NAME}", "${PRODUCT_VERSION}"] # 定义start_up.sh为容器启动时执行的命令。 ENTRYPOINT [ "/bin/bash", "-c", "start_up.sh"]
示例2构建过程:
- 首先使用ubuntu:22.04镜像作为基础镜像,安装我们的应用程序软件包service_pkg.tar.gz。然后,我们在第二构建阶段,我们同样使用ubuntu:22.04作为运行环境,并将应用程序myserver复制到其中。
- 第一阶段执行了特别多的
COPY|RUN
命令,创建了很多中间层。这些中间层没有实际的业务需要,不利于镜像层的管理。为了屏蔽掉这些中间层,在第二阶段,我们以ubuntu:22.04镜像重新制作镜像,并将第一阶段安装的应用程序添加到当前镜像中,该阶段只有一个COPY创建了1层,加上基础镜像的1层,总共只有两层。
2.镜像(Image)
UnionFS
是镜像分层的基础。
镜像实际上由一层层的文件系统组成,这些层都是只读的(不会变,无状态)。镜像分层可以共享资源,方便复制迁移,可以被复用。
比如,基于基础镜像
构建了很多应用镜像,那么,Docker只需加载一份基础镜像
,就可以为所有容器服务。而且镜像的每一层都会被共享,不需要重复加载。复制迁移时,也只需要迁移一份基础镜像
即可。
镜像文件系统
镜像的文件系统是容器运行的基础,须包含运行应用程序所需的一切(所有依赖项、配置、脚本等),还应包含容器运行的文件系统,如linux文件系统,内核等。
bootfs
(boot file system)。
镜像的最底层是文件系统bootfs
,这层与典型的Linux系统一样,包含bootloader
和kernel
。rootfs
(root file system)
在bootfs
的上层是rootfs,就是各种文件系统(如:ext2/3/4、XFS、ZFS等),包含:/dev,/bin,/etc等标准目录和文件。业务层
就是我们在Dockerfile构建时,修改文件系统时创建的新层,每个镜像有很多业务层。每层都有目录文件。容器层
此层不是镜像文件的一部分,是Docker创建容器时,基于镜像的多层文件系统,利用UnionFS
联合挂载技术,创建的一个可写层,存在于容器中。
就文件系统而言,容器与镜像的区别就在于容器层
。
镜像加载机制(基于UnionFS)
- 容器刚启动时,Docker引擎会加载
bootfs
文件系统的bootloader
引导程序,然后bootloader
引导加载kernel
。- 当
bootfs
文件系统加载完成之后,容器的VFS
已经被创建成功(根挂载点
已就绪),整个内核也被加载到内存中了。此时内存的使用权已由bootfs转交给系统内核,系统内核会将bootfs卸载掉。- 在
bootfs
文件系统加载完成后,内核会加载rootfs
系统,并将其挂载到容器VFS
的根挂载点上。- 在
rootfs
加载完成后,系统内核会依次加载这些业务层,利用UnionFS
联合挂载技术,将所有层的文件系统整合,在“容器层”创建一个完整的linux文件系统(如:ext4)。- 此时,再将启动参数配置到“容器层”的文件系统中,一个运行的
容器
就加载完成了。- 此外,镜像被加载到内存后,会生成一个64位十六进制的字符串(镜像ID)。通常,前12位ID对镜像进行标识。
镜像分层
新镜像是从基础镜像一层层叠加生成的,每对镜像做一次修改,就在现有镜像基础上加一层。
镜像存储
Docker默认安装的情况下,将/var/lib/docker
作为存储目录,存放镜像、容器、网络和挂载卷等。执行docker info
可查看Docker安装信息。
master:~ # docker info
...
Storage Driver: overlay2 # 存储驱动,常见的存储驱动:devicemapper、btrfs、zfs。
Docker Root Dir: /var/lib/docker # 存储根目录
...
/var/lib/docker
目录结构
master:/var/lib/docker # ls -g
total 48
drwx--x--x 4 root 4096 Aug 14 2023 buildkit
drwx--x--x 3 root 4096 Aug 14 2023 containerd
drwx--x--- 2 root 4096 Aug 14 2023 containers
-rw------- 1 root 36 Aug 14 2023 engine-id
drwx------ 3 root 4096 Aug 14 2023 image
drwxr-x--- 3 root 4096 Aug 14 2023 network
drwx--x--- 3 root 4096 Mar 15 13:50 overlay2 # 根据驱动类型创建的目录。
drwx------ 4 root 4096 Aug 14 2023 plugins
drwx------ 2 root 4096 Mar 15 13:50 runtimes
drwx------ 2 root 4096 Aug 14 2023 swarm
drwx------ 2 root 4096 Mar 15 13:50 tmp
drwx-----x 2 root 4096 Mar 15 13:50 volumes
可以通过修改配置文件/etc/docker/daemon.json
,更改Docker Root Dir
和 Storage Driver
。
vi /etc/docker/daemon.json
{
"storage-driver": "devicemapper", # 修改默认的存储驱动
"data-root": "/opt/paaslib/docker" # 修改默认存储路径
}
sudo systemctl stop docker.service # 重启Docker服务
镜像仓库
我们都知道Git有远程仓库
和本地仓库
的概念,当我们使用git将远程仓库的代码拉取git pull
到本机时,会在本机创建一个关联分支
,通过这个关联分支,我们可以便捷的把commit
的代码推送到远程仓库
。通常把我们本机存储地址看做本地仓库
。
类似的,Docker也有远程仓库
,本机存储镜像的地址称为本地仓库
。Docker把镜像拉取docker pull
到本机后,可以进行修改和提交docker commit
,再把commit
后的镜像通过docker tag
创建与远程仓库
关联的关联镜像
,然后将其推送docker push
到远程仓库
上。
3.容器(Container)
容器
是镜像
的运行实例,是一组通过Namespace/Cgroups/UnionFS技术隔离的进程。镜像
为容器
提供隔离的文件系统。就像运行的应用程序和其静态的源代码之间的关系。
容器
拥有独立的命名空间和文件系统,他们之前相互独立,互不影响。容器
归根结底也是宿主机上运行的一个进程,他们共享操作系统内核。
当创建container时,存储驱动
程序会在原始的只读镜像上创建一个可写层(称为容器层
),所有的变更都发生顶层的容器层
。
因为每个容器
都会创建自己的容器层
,并且所有更改都是发生在容器层
中。所以,一个镜像
可以被多个容器
共享,容器
在共享镜像
基础上拥有自己的数据状态。当容器
被删除时,对应的容器层
也会被删除,不会被持久化。
Docker工作流
Docker工作流程汇总图:
1.查看容器ID:
docker ps
。
2.重启/启动/停止容器:docker restart/start/sotp/stats
。
3.运行容器:docker run/exec/attach
。
4.容器与宿主机间拷贝文件:docker cp <src> <dst>
。
5.删除容器:docker rm 容器ID
。6.查看镜像ID:
docker images
7.查看镜像原数据:docker inspect 镜像ID
8.删除镜像:docker rmi 镜像ID
。
命令参考:https://docs.docker.com/reference/cli/docker/
镜像制作
1.通过Dockerfile制作镜像
构建流程:
# -t:定义新建镜像的标签。
$ sudo docker build -t mycontainer/ubuntu1:22.04 - << EOF
# Dockerfile
FROM ubuntu:14.04
MAINTAINER rufei@LLM
RUN apt-get update
RUN pip install numpy
RUN pip install flask
ENTRYPIONT ["/bin/bash","-c","python"]
EOF
...
Successfully built 324104cde6ad
# 查看新创建的镜像
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
mycontainer/ubuntu1 22.04 324104cde6ad 1 hours ago 478.5 MB
ubuntu 22.04 3c59e02ddd1a 10 hours ago 446.7 MB
# 导出镜像到本地文件myubuntu1_22.04.tar.gz
$ sudo docker save -o myubuntu1_22.04.tar.gz mycontainer/ubuntu1:22.04
# 将归档的镜像文件myubuntu_22.04.tar.gz迁移到生产节点中, 并载入到本地镜像库。
$ sudo docker load --input myubuntu1_22.04.tar.gz
2.通过容器制作镜像
构建流程:
# 宿主机运行容器。
$ sudo docker run -it ubuntu:22.04 /bin/bash
# 进入到fe7fc4bd8fc9容器内, 安装python包。
root@fe7fc4bd8fc9:/$ pip install flask
# 退出容器fe7fc4bd8fc9
root@fe7fc4bd8fc9: exit
# 此时的容器已经被修改了, 使用docker commit命令来提交更新后的副本。-m提交信息。
$ sudo docker commit -m "install flask" ubuntu:22.04 mycontainer/ubuntu2:22.04
# 查看新创建的镜像
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
mycontainer/ubuntu2 22.04 5bc342fa0b91 1 hours ago 458.5 MB
mycontainer/ubuntu1 22.04 324104cde6ad 1 hours ago 478.5 MB
ubuntu 22.04 3c59e02ddd1a 10 hours ago 446.7 MB
# 导出镜像到本地文件myubuntu_22.04.tar.gz
$ sudo docker save -o myubuntu2_22.04.tar.gz mycontainer/ubuntu2:22.04
# 将归档的镜像文件myubuntu2_22.04.tar.gz迁移到生产节点中, 并载入到本地镜像库。
$ sudo docker load --input myubuntu2_22.04.tar.gz
3.通过文件系统导入镜像
上面讲述了使用Dockerifle
和本地容器
来创建镜像的方法,除此之外,还可以根据一个文件系统
来创建一个简单的镜像。常用于将本地文件系统快照转换为Docker镜像。不常用,了解即可。
构建流程:
命令格式:
docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
各部分含义:
file|URL
:系统文件来源,可以是本地目录|文件
URL资源
STDIN标准输入流
,资源类型必须是.tar/.tar.gz/.tgz/.bzip/.tar.xz/.txz。本地目录需包含完整的文件系统,如/
。- OPTIONS:
-c, --change
:将Dockerfile指令应用到创建的镜像。如--change "ENV DEBUG=true"
。
-m, --message
:设置提交消息。如--message "new image"
。[REPOSITORY[:TAG]]
:给新制作的镜像设置标签。如exmpimage1:new
。
# 通过URL资源创建镜像
$ docker import https://example.com/exampleimage.tgz exmpimage1:new
# 通过本地文件创建镜像 - STDIN
$ cat exampleimage.tgz | docker import --message "new image" - exmpimage2:new
# 通过本地文件创建镜像 - archive
$ docker import /path/exampleimage.tgz --change "ENV USER_NAME=docker" exmpimage3:new
# 通过本地文件创建镜像 - directory
$ sudo tar -c . | docker import --change "ENV USER_NAME=docker" - fsdir exmpimage4:new
docker load
:目标对象是Docker镜像归档文件。
docker import
:的目标对象是文件系统资源。
容器管理
- 新建并启动
docker container run [OPTIONS] IMAGE [COMMAND] [ARG…]
docker run 可选项 镜像 启动容器命令 命令参数
docker run --name mycontainer01 ubuntu:22.04 echo param1 param2
命令参考:https://docs.docker.com/reference/cli/docker/container/run/ - 运行容器
docker container exec [OPTIONS] CONTAINER COMMAND [ARG…]
docker exec 可选项 容器 启动容器命令 命令参数
docker exec -it mycontainer01 sh -c “echo a && echo b”
命令参考:https://docs.docker.com/reference/cli/docker/container/exec/
仓库管理
Docker镜像
远程仓库
是集中存放镜像的地方。以公共仓库Docker Hub为例。
-
注册Docker账号。
-
登陆和退出
docker login [OPTIONS] [SERVER]
docker login -u 用户名 -p 密码 仓库名称$ docker login --username=mydocker registry.cn-hangzhou.aliyuncs.com Password: $ docker logout registry.cn-hangzhou.aliyuncs.com
-
拉取镜像
# 在镜像仓中查找镜像 $ docker search ubuntu # 拉取镜像 $ docker pull ubuntu:22.04
-
推送镜像
# 打个tag标签 $ docker tag ubuntu:22.04 registry.cn-hangzhou.aliyuncs.com/ubuntu:22.04 # 推送到远程仓库 $ docker push registry.cn-hangzhou.aliyuncs.com/ubuntu:22.04
参考:
Docker官方文档:https://docs.docker.com/get-started/overview/
Docker中文文档:http://www.dockerinfo.net/document