闲谈 - 我如何看待 Docker
Docker 的启蒙是来源于 innei ,我从 Mix Space CMS 这个项目中学习到了很多。
Docker 的本质
首先,让我们来理清一下 Docker 的概念。官方宣传它是“构建一次,到处运行”(Build once, run anywhere),但这句话需要有所解读。如果你是用 Docker 23.x 版本构建的镜像,那么在 Docker 19.x 上可能会出现异常。这有点类似于 Java 的 JDK 版本兼容性问题,低版本无法运行高版本的 Class 文件,这是我们所熟悉的情况。而低版本 Docker 构建的镜像呢?Docker 提供了迁移流程,通常可以正常运行,这与 JDK 不同,高版本中废弃的方法在低版本中是无法使用的。
说了那么多,Docker 到底是个什么呢?
我给其定性为,Docker 是具有一套隔离环境的守护程序。当然这个定义就比较通俗了,如果用更深一点的话来说,Docker 是一种容器化平台,通过提供隔离环境,使应用程序能够在独立的容器中运行。
那么我为啥强调守护程序这个概念呢?那是因为 Docker 和 Linux 的相通之处,在 Unix 派别的操作系统,在内核初始化完成以后,会启动一个 PID = 1 的 init 进程,由它来处理用户态的后续进程启动以及依赖关系等操作。在 Linux 下也是如此,有的发行系可能会把守护程序链接为 init ,有的没选择链接。但是不管怎么样,这些程序承担的都是 init 程序的责任。也就是说,所有程序都是这种 init 进程 fork 出来的,你可以通过 htop 等查看进程关系树。
容器化的适用场景
那么什么样的程序适合使用 Docker 跑呢?我的实践经验告诉我,状态非自赋予的程序适合使用 Docker,比如常见的 Web 后端,PHP CMS 等等。
那么状态自赋予的程序是什么?比如数据库,它的状态是自己赋给自己的,不是由外部输入获得状态的。那么如果容器异常,自赋予需要写入到文件的东西就可能丢失。但是那些由外部输入状态的程序,就无需考虑这个问题。如果你能理解这个问题,那很好。接下来思考一个问题,数据库的容器化集群是否合适?按照 Docker 的理念来说,不太合适,但用起来又没啥问题。
容器化解决了什么问题
众所周知,技术的发展往往源于解决问题。这一点可以从我对 Docker 的定义中得知,它是一个具有隔离环境的守护程序。假设你需要在一个节点上运行多个相同类型的 Node.JS 服务,这些服务占用相同的端口。如果使用 pm2 或 systemd 等守护程序,你需要修改端口,才能让多个 Node.JS 服务正常运行。但在 Docker 中,你无需担心这个问题。它们可以共享相同的端口,并且可以使用“容器名+端口”的方式进行访问。此外,Dockerfile 和 docker-compose 的组合可以轻松实现单机容器编排。
Docker 的优点在于隔离性。此外,它也是一个守护程序。在容器中,也有一个 PID = 1 的程序。那么这个程序是守护程序吗?在容器内是守护程序,在容器外,由 Docker 管理。如果这个程序出现异常,Docker 就会判定整个容器异常。这也是很多人不知道如何解决 Docker 问题的原因之一,即不知道容器为何无法启动,或者为何 PID = 1 的进程异常。你可能会说,Docker 这种判断太过简单粗暴,只看 PID = 1 的程序是否异常。我只能说,作为守护程序,它只能如此。只要你能让这个程序持续运行,那么容器的状态就不会被 Docker 判定为异常。因此,Docker 不适合运行那些运行一段时间后自动退出的程序。
综上所述,隔离是 Docker 的核心,它解决了许多问题。通过进程隔离、网络隔离和文件系统隔离,帮助我们解决了一系列难题,例如部署多个相同服务的端口占用和计算资源分配。基于这种隔离性,我们还实现了容器集群这种更加灵活的东西。让我们的服务更加灵活。
代价
当然,使用容器化技术并非没有代价,一点点的 CPU 性能损失,额外的内存占用(因为 libc 不和宿主机共享,需要加载到容器),磁盘 IO 性能损失。