【CICD】基于Docker Image的持续部署
持续部署的基本原理
在上一章我们学习了如何构建一个 Docker 镜像,Docker 镜像的特性决定了我们不论是怎么样的环境都可以得到一致的结果。确定了产物的一致性。
而除了产物的一致性以外,为了实现持续部署,我们还需要有一套分发机制。
在现代的 devops 实践中,我们经常会谈论到 google 推行的 kubernetes (简称 k8s),我们只需要告诉 kubernetes 集群的 manager 节点,告知我们想要的镜像/实例数等指标,kubernetes 会自动帮我们实现诸如滚动升级、一键扩容等比较常见的也是曾经会让很多维护者困扰的集群管理问题。kubernetes 作为编排工具组合上 docker 的容器一致性能力就可以帮助我们实现我们常说的持续部署。
但是,对于个人学习者来说,kubernetes 过于复杂,概因其设计之初就是面向上百上千个节点的统一管理设计的,因此对于需求简单的用户来说反而是一种负担,我们可能并不需要类似滚动升级,也不需要面对这么多节点,对于个人客户或者初创企业来说往往单节点已经能满足一定的需求。作为工具,既然带来了负担,那么我们就选择不用。那么有什么轻量级的替代品么?其实底层的原理非常简单。
我们把这个流程简化一下,换成单机持续部署:
其中Proxy 就是起到一个接收器的工作,而Signal 则是部署信号的发布者。当Proxy 接收到信号时,运行脚本对本地正在运行的容器进行一个镜像更新的操作。
Signal 可以有很多选择,最简单的我们可以是连接到远程终端,那么我们的信号就是一个shell 命令,如果我们想要实现每次提交都部署最新代码,那么我们的信号就可以是github /gitlab 的webhook 。如果我们想要实现每天凌晨自动部署一个新的版本,那么我们就可以设定为一个定时任务。不论是来自外部还是来自内部,我们需要一个触发器来触发部署的操作。
Proxy 阶段则是信号的消费者,在这个角色可以是任何方式,比如老牌的 CICD 软件Jenkins 就可以接受来自外部的 http 请求,来执行一段 shell 脚本。当然功能并不复杂,我们甚至可以自己写一个简单的 http 应用来接受来自外部的调用,触发命令来执行镜像的升级。
最简单的也是最原始的方式就是通过docker 手动进行镜像更新、停止容器、删除容器、创建新容器来实现更新。使用shell 命令来描述的话大概会是如下的操作:
不过因为我们在实际操作容器的时候需要更加复杂的如端口映射/目录挂载/修改环境变量等,如果简单的配置修改需要我们手动去维护命令参数的话也未免过于繁琐。因此我们可以考虑使用docker 自带的compose 管理工具。
用 Docker Compose 管理镜像
docker compose 是docker 内置的一个配置化管理工具,通过docker compose 可以实现更加规范且有序的容器管理。
一个简单的docker compose 文件可能会表现为以下形式:
一个docker compose 文件的配置并不复杂,定义docker compose 文件格式的版本号(version)、容器名(nginx)、镜像名(nginx:latest)即可,然后如果有网络服务可以再声明一下要映射的端口,如上所示的文件中:
表示把容器的80端口映射为宿主机的8080端口,这样,我们就可以通过访问 http://localhost:8080 访问镜像中提供的网络服务了。
使用docker compose 也非常简单,只需要记住两个命令即可:
以上命令需要在配置文件所在目录执行
通过 Docker compose 来完成最后一块拼图
当我们了解了 Docker compose 的基本操作以后,我们就能自己做一个简单的部署系统了。一个简单的部署系统无非就是接收到指令,然后执行命令,最后返回结果。
而一个最简单的持续部署就是 Signal(发送端) 、 Proxy(接收端) 、Worker(执行端) 组成,我们通过 github webhook 作为发信端,一个简单的http服务作为接收端,最后通过http服务执行命令来实现部署操作。
其中Proxy 和 Worker 是可以放在同一个服务器上的(当然也可以拓展到多个服务器,不过目前我们先学习比较简单的)。
这时,我们已经了解了这套体系的基本逻辑和设计,那么我们来开始实施一下:
需要再次强调的是,本文所示例的方式更多的是为了开拓读者的思路,期望大家学习的时候主要以原理为准,其底层原理放之四海而皆准,而无需拘泥于某个具体的系统
Signal
只需要在github的设置项中简单的创建一个 webhook 即可,当我们的代码有任何推送信息的时候就会发送一个回调到远程
当然等到我们需求开始复杂了以后可以逐步细化,比如通过github action对改动的分支/文件路径/tag做过滤和判断等。
Proxy
接收端我们可以用一些现成的CICD系统,当然因为实际上功能并不复杂我们自己写一下也很简单。为了大家能够了解细节这里手动实现一个简单的http服务,来帮助大家理解内部原理。
逻辑非常简单,启动一个http服务,监听来自网络的请求。当请求/deploy 路由的时候在当前目录执行docker compose 脚本命令,更新镜像,完成后用新的镜像启动服务。以Proxy 的角度来看,就是简单的向Worker 发送一个指令,起到一个转发命令的作用。具体的工作将会由Worker 来完成
Worker
docker compose 在这套流程中是作为worker 存在的,worker 顾名思义就是干活的角色。docker compose 接收到了来自上级的指令后就负责执行拉取最新的镜像与使用新的镜像重建容器的任务。当命令执行完毕后,我们的新的镜像也就被成功部署了。
总结
相信聪明的读者已经可以看出来了,当管理的实例多了,worker 的角色就可以自由的切换成 k8s 或者其他的角色,而上述的任何角色都可以被替换成任意的其他应用,但是其本身的职责是完全不会发生变化的,无非就是 发送端->接收端->执行端 这样的流程。只要掌握了其核心的架构,不论工具怎么发展迭代,其内核都是完全不变的。期望我们能够学会的是事物的本质而不是工具的使用。
通过本章内容,我们学会了如何使用 Docker Image 完成一个简单的部署系统。在下面的章节我们会深入学习如何将我们的编码工作与CICD相结合起来,真正的能够从中收益。
小思考: 学会把复杂的、具象化的东西抽象为统一的概念是学习任何事物都应当掌握的能力。除了编程,你还能举出哪些场景是可以把事情抽象的呢?(举个例子: 上学、报班、买课程、买书都可以抽象成用金钱换取知识的过程)
