Atlantis
GitHub Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

5. 重构OCP安装

1. 前言

什么是 OCP?OCP 全称 OpenShift Container Platform(以下简称 OCP),它是红帽公司开发的开源企业级容器平台,提供了一套用于构建、部署、运行、管理运维容器化应用程序的工具和服务,遗憾的是 OCP 给笔者带来的精神伤害远比启迪多。

总得来说,私有云底座的团队将 OCP 适配了自研的信创系统,走通了部署流程,然后根据使用场景增加或者裁剪 Operator,输出了几个不同的版本,接着将它移交给各个团队维护。

OCP 有一套自己的设计模式和使用方法,可以解决许多复杂业务场景,如果已经存在一套容器引擎,那么可以参考 OCP 来丰富企业级功能,但我们接到的需求是先基于 OCP 做一个私有化的 PaaS 平台,然后再裁剪这个 PaaS 产品,只留下容器编排能力作为一个容器引擎。

开发过程犹如盲人摸象,功能改了又改,困难数不胜数,写这段文字时笔者只觉得 PTSD 在发作,也许这个定制化的 OCP 本身就是一个 bug,因为全程都在排查问题。

2. 原始需求

一个基于 K8s 的私有云大致是这么部署的:

  1. 组装硬件,连通网络
  2. PXE 装机,部署定制的操作系统
  3. 部署存储集群
  4. 部署管理网公共镜像仓库,上传容器镜像,后续的部署操作都依赖这个仓库的容器镜像
  5. 部署 Master 节点
  6. 部署 Worker 节点
  7. 部署基础云产品,如虚拟机、网络、存储
  8. 部署其他云产品,如容器引擎、镜像仓库、PAAS 平台等

而笔者拿到的 OCP 版本上述的流程类似,但有以下区别:

  1. 容器镜像与OS镜像耦合,容器镜像是打包嵌入在OS镜像中的
  2. 公共镜像仓库运行在 Master 上,Master 使用本机 IP 访问镜像仓库,Worker 通过 VIP 负载均衡(在云上使用 LB 替换 VIP)访问镜像仓库
  3. 集群层面存在版本管理,release 镜像中保存着发版时引用的所有镜像的 sha256 与工作负载 yaml 模板
  4. 基础组件使用 ClusterOperator 部署,这里应当只部署必须的组件,如虚拟机、网络、存储,高级组件使用 ApplicationOperator 部署,根据用户需求按需部署,如日志产品、安全产品等
  5. ClusterOperator 整合在基础包中,负责管理 CO 类型组件(即基础组件),确保 CO 的镜像版本、配置与预期一致,而基础组件 Operator 各自管理各自孵化的实例
  6. ApplicationOperator 单独出包,负责管理 AO 类型组件(即高级组件),与 CO 的功能类似,但可以按需安装和卸载

总得来说,原先很多云产品的手动上架、初始化操作改由云产品的团队自行通过代码实现,现在这样一个形态的容器集群,需要跑在私有云的 ECS 中。

老架构中使用标准版的 K8s 加一些附加组件,使用 Pod 运行 Master,托管在私有云底座,节点使用 ECS,参考老架构后带来了以下需求:

  1. 支持多种操作系统与双架构:我们需要支持不同的操作系统如CentOS、麒麟、龙蜥、统信,还有多架构如 AMD64、ARM64,以及双架构混部,这导致我们初期需要制作了许多 40G 以上qcow2 文件,维护难度极大
  2. 快速部署:老架构托管版的 K8s 集群部署时间在 5 分钟以下,而新架构 OCP 集群 3 Master + 1 Worker 需要两个小时,让人无语的时间长度,后期发现是脚本文件以及 CO 调谐耗时过长
  3. 精简:OCP 携带了太多的基础组件,这些组件既占用初始化时间,也占用 CPU 和内存,老架构托管版的 K8s 只含有Prometheus、NFS、CloudProvider 等必装附加组件,其余可按需安装
  4. 打补丁与升级:老架构中管理网与用户网都存在一个公共镜像仓库,无论是发版还是出补丁,都是通过出镜像包跟新到现场,而后现场升级服务镜像版本实现,但新架构中由于 OCP 使用 sha256 引用容器镜像且强制校验,暂无打补丁和升级的方案

因此在新架构 OCP 中,还需要做以下的工作:

  1. 解耦容器镜像与OS镜像:将容器镜像从 OS 镜像中分离,使用脚本来适配不同的操作系统,降低 qcow2 文件的更新频率
  2. 开发一个用户网内的公共镜像仓库存储公共镜像:新架构中尚不存在公共镜像仓库
  3. 设计容器镜像打包方案:容器镜像需要在公司内部打包,从公司内部的镜像仓库同步到客户现场的公共镜像仓库,且需要保持签名不变,并把双架构、补丁包、升级包等情况考虑在内
  4. 裁剪Operator:从底座团队拿到的 OCP 版本为了减少工作量,将所有组件都做成了 CO,导致初始化时间太长,需要精简
  5. 重构部署脚本:当前的部署脚本就像 ChatGPT 编写的,只走通一部分关键路径,执行流程在不同文件夹下的脚本间跳跃,没有操作时间、没有异常处理、没有逻辑,当然文档也是不存在的

3. 逐步推进

最开始的需求是降低集群初始化时间,由于无从下手就先定下 解耦容器镜像与OS镜像 的需求,解决虚拟机镜像维护问题,随着发现的问题越来越多,再加上中间插入的一些紧急任务,到笔者最终完成时大概花了两个多月。

3.1 解耦容器镜像与OS镜像

将所有东西耦合在一起是一个不得以而为之的办法,归根结底还是最初的开发人员对 OCP 不熟悉,导致需要以 qcow2 和 iso 的方式维护集群版本。

他们将所有的脚本、配置文件、容器镜像列表等保存在一个巨大的镜像仓库中,首先启动一个本地 Harbor,将所需的容器镜像从公司内部公共仓库同步到本地 Harbor,然后使用本地 Harbor 的镜像及其 sha256 生成一个 release 镜像 推送到本地 Harbor,接着停止 Harbor,将所有资源打包,然后执行目标操作系统的定制化修改,输出 iso 文件,最后将镜像包、脚本、二进制文件等拷贝进 iso 文件,再生成一个 qcow2 文件。

笔者在本地使用 registry 搭建了一个简单的镜像仓库,然后利用脚本与 skopeo 将所有镜像同步了出来,然后清理 qcow2 中硬编码依赖本地镜像仓库的脚本与代码,最后压缩 qcow2 得到一个 2G 左右的系统文件。

本地 registry 打包压缩后大概占据 13.45GB,如果是双架构,大概需要占用 25GB。

最后的最后,配置 hosts 将原本指向本地 IP 的镜像仓库改为外部的 registery,验证部署正常,完成了第一步。

3.2 公共镜像仓库与容器镜像打包方案V1

底座的团队没有使用 Harbor,而是直接使用 registry 作为镜像仓库,因此笔者只需要考虑如何将镜像同步到现场的方案,镜像打包与同步的整体逻辑如下:

镜像打包

alt text

镜像同步

alt text

总得来说,需要出一个压缩包保存双架构镜像,并且需要把出包、解包、同步镜像的操作自动化。

3.3 裁剪Operator

裁剪主要是针对 release 镜像 进行瘦身,但无法断定哪些 CO 应该保留,哪些 CO 应该剔除,只能通过测试验证,生成裁剪后的 release 镜像 验证是否可以正常启动集群。

由于新架构的私有云开发是齐头并进的,无论是计算、存储还是网络部门,都在同时开发功能,许多应当稳定的功能都不存在,比如云盘经常性失效、快照功能无法使用、VPC 异常,导致笔者在测试 release 镜像 上浪费了两三天时间,依旧没有收获。

幸好还有 PVE,笔者将手上的游戏主机重装了系统,作为一个虚拟化环境使用:Linux笔记 / Proxmox VE / 1. 硬件配置

首先使用一个 LXC 容器搭建了外部镜像仓库,并配置 DNS 将公共镜像仓库的域名指向该 LXC 容器的 IP,然后创建一个 LXC 容器安装 Nginx 模拟外置的 LB,最后创建了一台 KVM 虚拟机,完成基础配置后打了一个快照备份。

接下来就是不断地打快照、裁剪 CO、生成 release 镜像、部署单节点集群验证功能,经过几天的努力,将单节点的资源占用降低到 4C16G,集群的部署时间也压缩到了 15 分钟左右。

alt text

在测试过程中发现,第一台 Master 的部署时间确实比较长,OCP 的核心是 Operator,这些 Operator 在初始化阶段会不停失败重试,占用过多时间,不过一旦第一台 Master 运行稳定后,添加其余 Master 的耗时只有几十秒,Worker 的添加速度更快,只需几秒即可处于 Ready 状态。

3.4 重构部署脚本

在分析部署脚本时,笔者深刻感受到网上所说的:代码不知怎么地就运行起来了

原始的 OCP 部署脚本中存在几十个 sh 文件,莫名其妙地调用来调用去,配置文件也是随处生成随处引用,笔者可以肯定开发者写完代码一段时间后都会认不出来。

幸好笔者在裁剪 Operator 阶段搭建了一个复现操作的虚拟化环境,借助快照一步步完成了下面的操作:

  1. 添加注释:原始脚本中不存在任何注释,笔者投入大量时间验证脚本的每一步执行了什么操作,然后记录文档、添加注释
  2. 提取公共函数:原始脚本中存在许多就地检查配置文件、等待配置生效、下载文件、打印日志的操作,笔者将这些常用操作提取为一个 609 行的 utils.sh 文件,并格式化了日式输出(增加了时间戳、日志等级)
  3. 定义脚本格式:使用三个脚本分别用于初始化第一个 Master、其余 Matser、Worker,每隔脚本里固定执行安装前操作、安装操作、安装后操作
  4. 资源文件容器化:有许多资源文件如 脚本、二进制文件、yaml 模板、systemd service 文件等,需要在安装期间拷贝到虚拟机上,笔者将这些文件整合制作为 cluster-installer 镜像,这样可以在安装时通过 podman 或 docker 从公共镜像拉取下来拷贝到本地,无需内置到 qcow2 文件中,便于更新和升级

alt text

我们应该以编译型静态语言的要求来编写脚本,这样才不会陷入无法维护的状况。

3.5 公共镜像仓库与容器镜像打包方案V2及V3

完成 OCP 的安装脚本重构以及集群管控服务的适配后,笔者以为该结束,结果这个时候由于各种原因,底座的 registry 不给用了…不给用了…不给用了。

这下又冒出来一个新需求,容器集群服务的后台需要对接容器镜像仓库后台,调用自己团队的接口,创建一个镜像仓库作为容器集群服务的公共镜像仓库。

理论上是可行的,新架构中为了更好的性能与安全隔离,不再提供公共镜像仓库让用户通过项目共享一个 Harbor,而是创建自己的 Harbor 实例,所有实例都分配了内网 EIP,理论上可以被所有用户访问,只是其他用户不知道别的用户的 Harbor 示例 IP 与域名,这样形成了隔离,于是开发任务落在了笔者身上,对接服务账号、对接容器镜像服务接口、重新设计镜像打包与镜像同步方案。

又一次在完成开发后,由于各种原因,不允许将容器镜像包内置在私有云的基础包中,负责私有云发版的架构师拉上与私有云产品的 PO,一顿输出后否定了当前方案,最后经过 Leader 的讨价还价后,确定在运维侧开发一个页面上传镜像包,于是开发任务又落在了笔者身上,对接运维侧、对接前端、对接分片传输协议、重新设计镜像打包与镜像同步方案。

OCP 容器镜像最开始是 40G 左右(qcow2 + 容器镜像),经过一次裁剪降低到单架构 9.1G,V2 方案中双架构压缩包不到 20G,但是到了 V3 中因为无法压缩又变回了接近 40G,真是一个恶性循环。