自动化部署: 从脚本到 K8s
如果公司有专业运维,项目的部署上线过程一般来说开发者都不会接触到。但是很不幸,我所在的团队没有独立的运维团队,所以一切都得靠自己(与同事)。
以下都只是工作中逐步优化得到的经验总结,并且只以 Node.js 程序部署为例。
部署上线的原始版本
流程图
举例:服务器使用 PM2 管理部署。纯手工操作:
总结
整个过程耗时 10~30 分钟不等。
优点:
- 不依赖任何工具/系统
- 适用于任何分支
缺点:
- 麻烦、耗时
- 易出错
- 无法持续部署
- 多节点怎么办?
基于 CI 系统的全自动版本
一般来说企业都会有一套 CI 系统,可能是传统的 Jenkins,也可能是 Gitlab CI / Github Actions / Travis CI / Circle CI 等等。它们之间大同小异,都是通过某种方式写好若干份配置文件,当某些操作(如 git push)触发时以及满足某种条件(如当前分支为发布分支,或提交了 tag 等)执行某些任务。
这里使用 Gitlab CI 举例,CI/CD 通过 Gitlab Runner 完成,服务器使用 PM2 管理部署。
流程图
技术细节
配置文件 .gitlab-ci.yml
:
image: node |
总结
优点:
- 全过程仅依赖 Gitlab 与 Gitlab Runner (基于或不基于容器)
- 全自动测试,提交到发布分支则全自动部署,测试失败的代码不会被部署
缺点:
- 需要在 Gitlab 上配置远程机器的登录凭证(账号/密码),或在 Runner 机器上配置 ssh key
- Runner 会拥有部署机器的访问权限
- 多节点?运维?
Gitlab 与 Agent 平台结合的半自动版本
为了解决上述 Runner 机器权限过高的问题,这个版本引入了 Agent 平台的概念。每个企业使用的平台可能有所区别,有可能是自研的(如我司),也有可能是外部提供的的(如「宝塔」)。但大体功能基本一致。
该版本中:
- CI 通过 Gitlab Runner 完成。任务完成后会将代码打包,并放置于服务器上的某个位置,该位置通过 Nginx 暴露(仅对内)
- CD 通过 Agent 平台完成。Agent 从上一步暴露的地址中下载代码,解压缩并放置到指定位置,重启 PM2 服务
流程图
总结
这一个版本中,CI 系统的配置简化了,去除部署部分的任务即可。至于 Agent 平台的配置方式,可能是一个完整的 bash 脚本,也可能是其它配置,就不在此展开了。
优点:
- CI 过程全自动
- CI/CD 权限解耦
- 适用于各种分支
缺点:
- 非线上发布过程也需要手动完成,麻烦
- 严重依赖 Agent 平台
- 运维?
Gitlab 与 k8s 结合的全自动版本 v1
k8s (kubernetes) 是一个容器集群部署管理系统。
容器基础知识:
k8s 基础知识:
流程图
技术细节
Dockerfile
:
FROM alpine |
start.sh
:
|
kill 实现:
// koa |
总结
优点:
- CI 过程全自动
- 非线上环境 CD 全自动,线上环境 CD 手动指定版本,兼顾方便与安全
- 无需配置远程机器权限
缺点:
- k8s 使用原始镜像启动 pod,拉取代码与安装依赖的过程非常耗时(每次启动都是全新镜像,无缓存)。
- CD 过程不确定性较多,存在代码文件服务器故障、依赖安装故障等风险。
- kill 指令发出后服务会暂时不可用。
- 多节点?
Gitlab 与 k8s 结合的全自动版本 v2
为了解决上面的问题 3/4,引入 Consul
对 CI/CD 过程做出了改进。
Consul 是为基础设施提供服务发现和服务配置的工具,包含多种功能,这次用到了其中两个功能:
- 服务发现
- 健康检查
流程图
技术细节
这个流程里面涉及到几个问题:
关于“逐个发送 kill 指令”
虽然通过 Consul 可以获取到所有运行中 pod 的 ip 及端口,但是如果集中发送 kill 命令仍然会造成服务不可用。目前我司服务端的 CI 就有这个问题,他们虽然每个 kill 会有一段固定时间的 sleep 间隔,但无法保证下一个 kill 发出时上个服务时候已重启完毕。
为了解决这个问题,我写了一个脚本。
流程:
代码:
|
在 gitlab-ci 的最后一步执行此脚本:
.gitlab-ci.yml
:
# ... |
关于“解注册所有同名服务”与“注册自己”
项目内部使用 https://www.npmjs.com/package/consul 来与 Consul 通信。
需要“解注册所有同名服务”的原因:
Consul 在注册服务时并没有类似“主键”的概念,一个 Consul 有多个 Agent,也就是说相同 ip、相同 id 的服务可能会在不同 Agent 上被注册多次,并且由于 pod 的 ip 是短暂的,每次重启 pod 获得的 ip 可能会有差异,因此如果不进行解注册就会导致从 Consul 上获得的服务与现实正在运行的不一致。
流程:
总结
优点:
- CI 过程全自动
- 非线上环境 CD 全自动,线上环境CD手动指定版本,兼顾方便与安全
- 无需配置远程机器权限
- 支持多节点
- 高可用性的重启
缺点:
- k8s 使用原始镜像启动 pod,拉取代码与安装依赖的过程非常耗时(每次启动都是全新镜像,无缓存)。
- CD 过程不确定性较多,存在代码文件服务器故障、依赖安装故障等风险。
为什么会做成这个样子呢,因为我司的服务端使用 golang 是走的这一套流程,但是使用 golang 打包编译出的是一个二进制文件,镜像直接拉取就可以启动,不需要依赖安装等步骤。因此他们使用这种方式的缺点并不明显。但是对于 Nodejs 程序来说,这依然是一个较大缺陷。
Gitlab 与 k8s 结合的全自动版本 v3
为了解决上面的问题 1/2,这个版本更改了代码部署到 k8s 的方式:使用完整的预构建镜像,而不是空白镜像。
流程图
技术细节
build.sh
:
echo "Process: 构建 Docker 镜像..." |
Dockerfile
:
FROM alpine |
总结
优点:
- CI过程全自动
- 非线上环境CD全自动,线上环境CD手动指定版本,兼顾方便与安全
- 无需配置远程机器权限
- 支持多节点
- 高可用性的重启
- 预构建的镜像,CD 阶段开箱即用,无任何依赖
缺点:
- 为了使每个 pod 能够获得一个稳定的名称(用于在 Consul 中注册、解注册),部署类型使用了 Statefulset,因此带来了一些本来不需要的特性。