一、前言
最近事情有些多,这篇短文写写停停,花费了很长时间。本来想着在五一假期结束前完成这一系列文章的,现在看来还有些困难呀。
这篇文章总结了自己使用 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 命令。但是 ENTRYPOINT
和 CMD
结合使用,可以实现可变参数的效果。
# 定参
ENTRYPOINT ["nginx", "-c"]
# 变参
CMD ["/etc/nginx/nginx.conf"]
如上所示,这里 ENTRYPOINT
和 CMD
并非两条指令,而是一条指令 nginx -c /etc/nginx/nginx.conf
。CMD
中的内容作为 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