跟着公司用了一段时间的 docker,一直感觉不明不白的。今天抽了点空从头梳理了一遍,过程记录如下。
概念
使用 docker 基础的概念有三个:镜像、容器 和 仓库。
镜像功能上和我们装系统时使用的 ghost 镜像类似,可以看成一个系统环境的导出文件。
而容器就是用某个镜像创建的系统。
比如我刻了一张 XX 特制 XP 系统盘,这张光盘就是一个镜像,我拿着这个镜像在 N 台服务器上装系统就等同于启动了多个容器。容器的本质是进程,因此这里有一点区别就是容器的启动成本比真实的操作系统要低得多。
仓库就是一个集中存放各种镜像的源,可以类比于 github,他们在概念设计上也很类似。比如都有地址和标签的概念。通常地址用于标记项目,标签用于标记版本。
仓库也分为公开和私有。官方的 dockerhub 是公开的源,如果你要部署闭源项目,需要自己搭建私有源,类似自己搞个 gitlab。
开始使用
安装 dockerd
容易想象,因为 docker 需要管理本地的很多镜像文件,又需要管理众多的容器进程,因此在部署端一定存在一个很可能名为 dockerd
的进程。
确实如此,使用 yum 安装好 docker-ce 后就可以添加服务并启动它:
$ sudo systemctl enable docker$ sudo systemctl start docker
这个管理进程使用的是 unix socket 来进行通信,而非 tcp。这个 unix socket 有访问权限控制,只接受 root 和 docker 组访问,因此你跑服务的用户需要进组
$ sudo usermod -aG docker $USER
关于仓库
如果是实验性质的使用 docker,可以在 dockerhub 注册个账号使用公共仓库。否则就需要通过 docker-registry
搭建私有仓库。以下全部基于 dockerhub。
拉取镜像
管理镜像就像管理 git 项目。基础命令是 docker image COMMAND
,参考: 。
一开始要使用的是 pull,用来从 dockerhub 拉取一个基础镜像。用法是
docker image pull [OPTIONS] NAME[:TAG|[@DIGEST](https://my.oschina.net/u/3392911)]
OPTIONS 可以暂时忽略,重点是后面的 镜像唯一标识。
NAME 的完整格式是包含仓库地址和用户的,比如 localhost:5000/test/hello
,/
分隔的三块分别是 仓库地址,用户名 和 项目名。可以看到,跟 git 地址很像:https://github.com/iqiyi/dpvs.git
。
TAG 或 DIGEST 是一个特殊版本的标志,TAG 通常是可读性更好的名字,如 1.0.0。而 DIGEST 更加精准。这个用法跟 git 也是一样的。
有时这个长长的唯一标识还可以简写为只包含项目名,这时默认使用 dockerhub 仓库,tag 为 lastest。比如对于 dockerhub.com/library/nginx
这个镜像,你可以使用 docker image pull nginx
来拉取。
创建容器
当你有了一个镜像以后就可以用它创建容器了,注意容器是持久化的一个概念,它的状态有 跑 也有 停。就像你 ghost 了 N 台电脑,这些电脑是有实体的,可以开机也可以关机。因为之前提到过容器的本质是进程,可能会让人误以为容器停下以后就被自动销毁了,其实没有。
创建一个新的容器使用 docker container COMMAND
子命令,因为我们刚才 pull 的是一个 nginx 镜像,所以要让他正确的跑起来的话需要加两个参数:
docker container run --name mynginx -d -p 80:80 nginx
-d 的意思很好猜,就是守护进程。-p 是做了一个端口映射。因为宿主机上的端口空间和容器的端口空间是独立的,所以需要配置这个转发(其实容器里的一切都是独立的)。因此宿主机的 80 端口只能被 dockerd 监听,然后转发给容器。这里的 -p 80:80
就是把宿主机的 80 转给了该容器。这时候在宿主机使用 curl -i http://localhost/
就可以看到 nginx 的欢迎页面了。
启动容器时使用 --name
是个好习惯。
端口映射的意义在于,多个容器可以各自监听自己端口空间里的同一个端口。用 nginx 来说明比较不直观,因为通常我们只会起一个 nginx,他对 80 是独占的。那么用 *GI 类的后台服务来说明吧:我们在制作映像的时候,可以指定其监听某个端口,比如 5000。那么不管你用这个镜像起几个容器,他们监听的都是自己的 5000 端口,然后我们只需要在 docker 端配置上端口转发,就可以对外提供多个端口的服务了,比如我们让 dockerd 监听 5001~5009 之间的 9 个端口,分别转发给 9 个容器。这样我们就把部署相关的某些配置从项目代码里面拿了出来,这有利于项目的标准化交付。在使用 docker 之前,我部署 wsgi 服务的时候,通常每个实例都会有一个不同的 "instance.py" 文件,里面描述了端口、线程等信息,这种文件其实是不太好维护的。
当然这些配置信息虽然拿出来了,依然需要在外层进行维护,但这个任务做起来要简单的多,而且还有 compose 这个工具,compose 的说明在下面独立成节。
可以看到 dockerd 对容器的管理在概念上非常类似 supervisord。做这样的类比有助于帮助理解。
容器管理
容器管理使用 docker container
的剩余子命令来完成,参考:
仍然要强调的是,容器有状态。stop 一个容器并没有删除他,如果你给 docker container ls
命令加上 -a
参数,就可以看到那些已经被停掉的容器。
因为容器的运行环境完全是独立的,有时候我们想 debug 的时候就需要进入容器里面去,这个需求可以通过 docker container exec -ti fc588f356589 bash/sh
来实现,他的意思是在某个运行中的容器内执行某个命令,这里我们选择开一个 shell,并通过 -ti 参数与当前终端连接起来。
数据持久化
容器运行于完全独立的文件系统有时候也是个麻烦,应用难免产生一些数据,我们希望这些数据可以存放在宿主机上而不是容器里,比如日志。
首先,把数据保存在容器的文件系统里是个不好的想法,刚才跑起来的 nginx 日志其实被重定向到了标准输出:
lrwxrwxrwx 1 root root 11 Nov 4 18:41 access.log -> /dev/stdoutlrwxrwxrwx 1 root root 11 Nov 4 18:41 error.log -> /dev/stderr
这些日志被 dockerd 存下来了,可以通过 docker container logs CONTAINER_ID
来查看,我们的目标是将其写入某个宿主文件,比如 /var/log/nginx/xx.log
。
就像刚才把宿主机端口映射到容器里一样,我们也可以把宿主机的文件系统的一部分(目录)映射进容器里。只要在创建容器时传入挂载选项即可:
--mount /var/log/nginx:/var/log/nginx
对于另一些静态内容,如果你希望容器对其只读,可以使用
--mount /var/log/nginx:/var/log/nginx:ro
在较早版本,比如 17 以前,可以使用 -v 代替 --mount 做同样的事情,-v 是 --volume 的缩写。这种文件系统的直接映射叫做 bind mount
,是 docker 早期的一种方式。
新版本更鼓励使用另一种名为 Volume
的方式,数据卷是一种被 docker 管理的对象,他们存放于 docker 的数据目录(var/lib/docker/
)下,即 /var/lib/docker/volumes
。在这种模式下,给一个 container 分配数据卷只需要(只能)指定该卷的名称,路径是固定的。如果要修改这个路径就需要修改 docker 的数据目录。
--mount source=ng-log,target=/var/log/nginx
--mount 的标准用法下参数是逗号分隔的键值对。这里 source 就是宿主机的数据卷名称,日志的路径是 /var/lib/docker/volumes/ng-log/_data/access.log
。
注意数据卷的数据不能被直接修改。
docker 还有一种存放数据的方法,这里不使用持久化这个术语是因为这次数据不再落盘了。tmpfs
用于提供一个基于内存的挂载点给容器使用。容器停止时便被销毁。
--mount type=tmpfs,destination=/var/log/nginx
Dockerfile
以上是关于使用镜像,Dockerfile 则是关于制作镜像。
Dockerfile 是一个文本的描述文件,描述了从一个基础镜像开始做哪些步骤可以得到一个新的镜像。也因此,镜像是分层的。比如基于 nginx 镜像,我给他添加一个配置文件就得到一个新的,结合了我的业务的镜像。
这个 Dockerfile 一般可以放在工程的最外层目录,或者接近最外层的目录(按你的习惯)。以配置 nginx 静态服务为例,工程目录为:
$ ll-rw-rw-r-- 1 xion xion 86 11月 27 16:08 Dockerfile-rw-rw-r-- 1 xion xion 13972 11月 27 16:01 hello.html
其中 Dockerfile 内容为:
FROM nginxADD hello.html /usr/share/nginx/html/
然后执行 docker build -t mynginx .
,-t 是加个标签的意思,支持 repo:tag
的格式,我只指定了名称,那么 tag 默认就是 latest。记得指定名称是个好习惯~
这时查看镜像列表就会发现本地多了一个镜像:
$ docker imagesREPOSITORY TAG IMAGE IDmynginx latest 70710e43a5a2 nginx latest 40960efd7b8f
把它跑起来就能访问 hello.html 了。
Compose
以上的部分已经涵盖了 docker 的基础使用。docker 还提供了一些更高级的面向集群的部署管理工具,但也要求和 docker 更高的集成度,部署配置使用一个 docker-compose.yml
文件来描述。使用这套部署系统部署的应用称为 Dockerized
。
非必须,是否使用看信仰程度吧。如果你已经有一套现成的部署逻辑和服务发现系统,可以不用。