一、前言

最近事情有些多,这篇短文写写停停,花费了很长时间。本来想着在五一假期结束前完成这一系列文章的,现在看来还有些困难呀。

这篇文章总结了自己使用 Docker 时用到的一些知识,可能内容不深,还请见谅。

二、镜像的构筑

(1)Docker 镜像简介

正如在“准备工作”那篇文章中所说的,镜像时静态的容器,容器是运行的镜像。我们在使用 Docker 时,需要做的就是像编写代码一样构建 Docker 镜像。

Docker 镜像中包含了一套文件系统,其所维护的文件包含了项目运行所需的环境以及项目本身。但是镜像和一般的文件系统不同,镜像由一系列层构成,每一层中存储了对之前镜像的修改信息。

这样做有一定的优点,在下载镜像时,我们不需要每次都下载完整的镜像;而只需要下载与本地镜像不相同的层就可以了。另一方面,容器基于镜像进行构筑,在容器运行时,分层设计也有利于不同容器的共享。采取写时复制技术可以减少内存的占用。

我们进行镜像的构筑时,也不需要从头开始,而可以选择在已有的镜像上添加新的层,通过修改已有环境来构建我们所需的项目运行环境。

(2)Dockerfile 简介

Docker 中使用 Dockerfile 来设置镜像构建的操作流程,这一点类似于 Makefile。这里借我的后端项目中的 Dockerfile 的内容来稍微解释一下。

# python alpine is the smallest python image
FROM python:3.9-alpine

COPY ./src /src
COPY ./start.sh ./
COPY ./config.ini ./
RUN pip install -r ./src/requirements.txt

# ash is shell for alpine
CMD ash start.sh 

Dockerfile 中使用一系列大写关键字作为指令,构建镜像时,从头到尾依次执行。

第一句使用了 FROM 指令。该指令指定了构筑时的基础镜像,接下来的指令都是对镜像的修改,会在基础镜像上增加新的层。这里 FROM python:3.9-alpine 指定了一个镜像,来自仓库 python,标签为 3.9-alpine。该镜像包含了 python3.9 的运行环境,底层的操作系统为 alpine,这也是 Linux 的一个发行版,以体积小著称。

接下来的三条指令为 COPY,该指令用于将本地的文件复制到镜像中,第一个参数为本地的路径,第二个为镜像中路径。对第一条指令 COPY ./src /src,本地路径为目录,这意味着复制该目录下的所有文件,到镜像中的 /src 路径下。后两条指令中本地路径为目录,则只将该文件复制到对应路径下。

之后使用了 RUN 指令,该指令用于运行一条 shell 命令。这里我们使用 pip 命令下载 python 依赖包。注意此处提供给 pip 的路径为镜像内路径。

最后 CMD 指令同样用于运行命令,只不过该命令在启动容器时才会调用。这里我们执行了 start.sh 脚本以启动服务。ash 类似于 bash,但是是属于 alpine 的 shell 程序。CMD 还有另外一种写法,CMD ["ash", "start.sh"]

除此之外 Dockerfile 中还可以使用其他许多指令。这里在列举几个在我看来较为重要的指令。

ENTRYPOINT

ENTRYPOINT 指令和 CMD 类似,都可以执行 shell 命令。但是 ENTRYPOINTCMD 结合使用,可以实现可变参数的效果。

# 定参
ENTRYPOINT ["nginx", "-c"]
# 变参
CMD ["/etc/nginx/nginx.conf"]

如上所示,这里 ENTRYPOINTCMD 并非两条指令,而是一条指令 nginx -c /etc/nginx/nginx.confCMD 中的内容作为 ENTRYPOINT 中命令的参数。

当我们运行容器时,想要修改 CMD 所设置的参数,只需要在命令中增加参数 -c /etc/nginx/new.conf 即可。

ENV 和 ARG

这两个指令都用于设定环境变量,只不过 ARG 设定的环境变量在完成镜像构筑后便被移除。设定环境变量的语法如下

ENV <key1>=<val1> <key2>=<val2>
ARG <key3>=<val3> <key4>=<val4>

VOLUME

Docker 中用 “卷” 来表示主机目录和容器目录的映射关系。当设定容器目录中的某个路径为卷后,该路径下的内容便会直接保存在主机上。这样可以避免重要的数据因为容器重启而丢失;也可以避免容器大小增大。

在 Dockerfile 中使用 VOLUME 指定哪些路径作为卷。但是此处只是指定某些路径被映射到主机,而并未指出要映射到主机的哪些路径。这是因为不同主机的文件系统本就不同,设定映射应该交由镜像的使用者根据自身情况设定。在这里进行设置,只是映射到默认的路径上。

VOLUME ["<path1>", "<path2>"]

EXPOSE

EXPOSE 指令设定了容器内运行的服务对外提供的端口,可以方便镜像的使用者进行设置。此指令只是用于声明而已,并不具有更多的作用。

(3)镜像构筑

在 Dockerfile 中我们已经完成了大部分镜像构筑时的配置操作。在最后,我们需要执行 build 命令完成镜像的构筑。

docker build -t myimage:v1 ./

这里 -t 参数为镜像命名。其中这里的 myimage 为镜像所属的仓库名,而 v1 表示该镜像的标签。仓库中常常保存同一个应用的不同镜像,而标签则用于区分仓库中不同镜像间的区别,因此常用版本号作为标签。

最后的 ./ 指定了 Dockerfile 所在的位置。这样 docker 就将按指定路径的 Dockerfile 中的指令进行镜像的构筑。

最后执行命令 docker images,就可以看到本地的所有镜像信息了,这其中包含了我们新创建的镜像。

三、容器的运行

(1)容器运行

我们以镜像作为模板创建容器并运行。要运行容器,需要使用 run 命令。

如果我们希望基于上一节构筑的镜像运行,需要如下命令

docker run -d --name mycontainer -p 5000:5000 myimage:v1

这里我们除了传入 myimage:v1 作为容器运行的镜像外,还设定了一些参数。

首先 -d 设定容器启动后移到后台运行。类似的有前台交互的方式运行的选项,-it,其中 -i 指交互式操作,-t 指终端,这两者常常同时使用

--name 设定了容器名为 mycontainer。不同的容器有容器 ID 作为标识符,但容器 ID 常常难以记忆,于是经常为容器起名作为第二种指代容器的手段

-p 参数设定了容器的端口映射方式。在上面的例子中 5000:5000 表示将容器内的 5000 端口映射到了容器外的 5000 端口。前者为本地主机的端口,后者为容器内的端口。这样的映射意味着对本机的 5000 端口的请求,会转为请求容器内的 5000 端口。

(2)操纵容器

当容器在后台运行时,我们可能需要得知其状态。可以使用 logs 指令获取容器内的标准输出结果

docker logs mycontainer

有时我们还需要进入到后台运行的容器内部。这可以使用 exec 指令实现。该指令用于执行一条 shell 命令。当我们选择执行 shell 终端,并附加 -it 参数时,就能进入到容器的命令行中。

docker exec -it mycontainer /bin/bash

如果想要暂停容器,可以使用 stop。执行该命令后容器暂停运行,但依旧存在。

docker stop mycontainer

我们可以使用 ps 查看容器信息。不加额外参数则只显示正在运行的容器。

docker ps

在执行 stop 之前,应该可以看到 mycontainer 的相关信息;而在之后就看不到 mycontainer 的信息了。

这时为 ps 增加参数 -a 就可以查看所有容器,包括正在运行和暂未运行的容器的信息。

docker ps -a

想要真正的移除容器需要 rm 命令。该命令将删除容器的一切信息,包括容器运行过程中产生的数据。

docker rm mycontainer