我们团队推进GitOps实践已经有一段时间了从最开始的简单把KubernetesYAML文件放到Git仓库用ArgoCD自动同步到现在已经形成了一套比较完整的GitOps工作流,但是随着项目越来越多配置越来越复杂我们的GitOps代码仓库也变得越来越混乱越来越难维护各种YAML文件散落在不同目录重复代码到处都是命名不规范结构不清晰改一个配置要找半天还容易改错出问题已经到了不得不重构的地步。

于是最近我们花了两周时间对整个GitOps代码仓库进行了一次彻底的重构从目录结构到命名规范从模板抽象到配置管理全面梳理和,优化最后把原来那堆烂代码重构成了结构清晰易于维护的优雅代码今天就来分享一下这次重构的过程思路和经验。

一、重构前的状况:烂代码有多烂

先说说重构前我们的GitOps代码仓库有多烂有多难维护让大家有,个直观的感受也看看你们的项目有没有中枪。

1. 目录结构混乱文件散落各处

重构前我们的GitOps仓库目录结构非常混乱完全是想到什么就建什么目录没有统一的规划最开始,只有几个项目就随便建了几个目录后来项目越来越多就各种临时目录测试目录废弃目录都堆在一起根目录下有几十个目录和文件根本看不出来哪个是有用的哪个是废弃的哪个是测试的找一个项目的配置要翻好几个目录才能找到,而且经常找错,因为有好几个类似名字的目录不知道哪个是最新的在用的。

而且同一个项目的配置文件也散在不同地方,比如Deployment在一个目录Service在另一个目录ConfigMap在又一个目录Ingress在别的地方改一个项目的配置要在好几个目录之间,跳来跳去非常不方便也容易漏改,或者改错文件。

2. 大量重复代码复制粘贴满天飞

这是最严重的问题我们的YAML文件里有大量的重复代码很多项目的DeploymentServiceConfigMap等配置结构都差不多只是名字镜像端口环境变量等几个地方不一样,但是我们都是直接复制粘贴一份,然后改几个地方结果就是同样的配置结构重复了几十上百遍散在各个项目的文件里一旦需要改一个公共的配置(比如加一个公共的环境变量,或者改一下资源requests/limits的默认值)就要去几十个文件里挨个改非常麻烦,而且很容易漏改导致有的项目改了有的没改配置不一致出问题。

比如我们之前,要给所有服务加一个公共的环境变量LOG_LEVEL=info结果,因为有几十个项目每个项目的Deployment都要改我们改了整整一天还漏了好几个项目后来线上出问题排查了半天才发现是那几个项目没加环境变量日志级别不对导致日志太多把磁盘打满了这种问题完全是,因为代码重复没有抽象导致的非常低级,但是又很常见。

3. 命名不规范看不懂也记不住

重构前我们的文件和资源命名也非常不规范完全是每个人按自己的习惯来想怎么命名就怎么命名有的人喜欢用下划线有的人喜欢用中划线有的人喜欢用驼峰有的人喜欢全小写有的人喜欢加前缀后缀有的人不加结果就是各种命名风格混杂看起来非常乱,而且很多命名根本看不懂是什么意思,比如有的文件叫app.yaml有的叫deploy.yaml有的叫deployment-prod.yaml有的叫myapp-deploy-v2.yaml根本不知道哪个是哪个也记不住。

资源命名也一样有的Deployment叫myapp有的叫myapp-deployment有的叫myapp-prod有的叫myapp-v1Service有的叫myapp-svc有的叫myapp-service有的就叫myappConfigMap有的叫myapp-config有的叫myapp-cm有的叫myapp-env非常混乱,而且经常出现命名冲突,或者找不到对应资源的情况维护起来非常痛苦。

4. 环境管理混乱配置漂移严重

我们有多个环境(开发测试预发布生产)每个环境的配置有一些差异(比如镜像版本资源配置环境变量副本数等)重构前我们的环境管理非常混乱有的是每个环境一个分支有的是每个环境一个目录有的是同一个文件里用注释区分环境有的甚至是直接在生产分支上改,然后手动同步到其他环境结果就是各个环境的配置差异很大,而且经常出现配置漂移(Configuration Drift)也就是某个环境的配置和其他环境不一致,而且不知道为什么不一致谁改的什么时候改的出了问题排查起来非常困难。

比如有一次测试环境的一个服务出了问题,但是生产环境没问题我们对比了半天配置才发现测试环境的某个环境变量和生产不一样,但是不知道是谁什么时候改的Git历史里也查不到(因为是直接在测试环境目录里改的没走PR也没写commit message)最后也没搞清楚为什么改只能先改成和生产一致这种配置漂移问题在重构前经常发生非常头疼。

5. 没有文档和注释新人上手困难

最后重构前我们的GitOps仓库几乎没有什么文档和注释YAML文件里很少有注释说明这个配置是干什么的为什么这么配有什么注意事项仓库根目录也没有README说明仓库的结构怎么用怎么加新项目怎么改配置规范是什么结果就是新人来了根本看不懂这个仓库怎么回事要花很长时间问老人才能慢慢上手,而且很容易犯错,因为没有规范和文档指导全靠口口相传老人一走很多东西就没人知道了维护成本非常高。

二、重构目标与原则

在开始重构之前,我们先明确了重构的目标和原则避免为了重构而重构,或者重构到一半方向偏了目标不清晰最后越改越乱。

重构目标

  1. 结构清晰:目录结构清晰合理文件组织有序找东西方便一眼能看明白。
  2. 减少重复:抽象公共配置和模板减少重复代码改公共配置只需要改一个地方。
  3. 命名规范:统一命名规范文件和资源命名一致易懂好记。
  4. 环境一致:规范环境管理减少配置漂移各个环境配置差异可追溯可管理。
  5. 易于维护:有文档和注释规范明确新人能快速上手维护成本低。
  6. 安全可靠:重构过程不影响线上服务重构后配置和,原来功能一致不出问题。

重构原则

  1. 小步快跑逐步推进:不求一次性全部重构完而是分阶段分模块逐步重构每重构完一部分就验证没问题再继续下一部分降低风险。
  2. 保持功能不变:重构只是改代码结构和组织方式不改功能和配置内容重构后部署到集群的配置和原来应该完全一致(或者,只有预期内的变化)不影响线上服务。
  3. 向后兼容平滑过渡:重构过程中保持向后兼容旧的目录和文件不立即删除先保留一段时间等新的结构稳定了再清理避免ArgoCD或者其他工具找不到配置出问题。
  4. 团队共识统一规范:重构的方案和规范要团队一起讨论达成共识不能一个人拍脑袋决定,不然重构完其他人不认可不遵守还是会乱回去。
  5. 文档先行同步更新:重构的,同时同步更新文档和注释不能重构完代码,但是文档还是旧的那样还是会让人困惑维护成本还是高。

三、重构实施:一步步把烂代码变优雅

明确了目标和原则之后,我们就开始动手重构了整个过程分了几个阶段下面详细说说每个阶段做了什么。

阶段1:梳理现状建立清单

首先,我们花了两天时间对现有的GitOps仓库进行了全面的梳理和盘点搞清楚到底有多少东西哪些是有用的哪些是废弃的哪些是重复的具体做了以下事情:

  1. 盘点所有项目和环境:列了一个清单把所有在用的项目(服务)都列出来每个项目对应哪些环境(开发测试预发布生产)每个环境有哪些配置文件在什么位置都记录清楚。
  2. 识别废弃和重复文件:把那些已经不用的项目的配置测试用的临时文件废弃的旧版本配置都标记出来准备后面清理把重复的配置文件也标记出来准备后面合并,或者抽象。
  3. 分析公共配置和差异:把所有项目的配置都看了一遍分析哪些是公共的配置(所有项目都一样的部分)哪些是,每个项目特有的配置(名字镜像端口环境变量等)为后面抽象模板做准备。
  4. 绘制依赖关系图:把项目之间,的依赖关系(比如哪个服务依赖哪个ConfigMap哪个Secret哪个PVC等)都梳理清楚画了一个简单的依赖关系图避免重构的时候,漏了什么依赖导致出问题。

这个阶段,虽然不写代码,但是非常重要,只有把现状梳理清楚了后面的重构才能有的放矢不会盲目乱改我们也是在这个阶段才发现原来仓库里有将近三分之一的文件是废弃的,或者重复的根本没人用只是一直没清理堆在那里占地方还干扰视线。

阶段2:设计新的目录结构和命名规范

梳理清楚现状之后,我们团队一起讨论设计了新的目录结构和命名规范这是重构的基础结构和规范定好了后面的工作就好做了。

新的目录结构

我们参考了社区里比较好的GitOps仓库实践(比如ArgoCD官方的example仓库和,一些大厂的开源GitOps仓库)结合我们自己的情况设计了如下的目录结构:

gitops-repo/
├── README.md                    # 仓库说明文档
├── apps/                        # 所有应用的配置
│   ├── app-a/                   # 应用A
│   │   ├── base/                # 基础配置(所有环境共用)
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   ├── configmap.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/            # 环境差异化配置
│   │       ├── dev/             # 开发环境
│   │       │   ├── kustomization.yaml
│   │       │   └── patch.yaml
│   │       ├── test/            # 测试环境
│   │       ├── staging/         # 预发布环境
│   │       └── prod/            # 生产环境
│   ├── app-b/
│   └── ...
├── infrastructure/              # 基础设施配置(集群级别的资源)
│   ├── namespaces/
│   ├── rbac/
│   ├── storage/
│   ├── monitoring/
│   └── ...
├── argocd/                      # ArgoCD Application 配置
│   ├── app-a-prod.yaml
│   ├── app-a-test.yaml
│   └── ...
├── scripts/                     # 工具脚本
└── docs/                        # 文档

这个目录结构的核心思想是按应用组织每个应用一个目录下面分base(基础配置所有环境共用)和overlays(环境差异化配置)用Kustomize来管理配置的继承和覆盖这样同一个应用的所有配置都在一个目录里找起来很方便,而且公共配置只需要写一遍在base里各个环境只需要写差异的部分(用patch)大大减少了重复代码。

基础设施的配置(比如NamespaceRBACStorageClass监控组件等集群级别的资源)单独放在infrastructure目录下和应用配置分开,因为这些是集群级别的不是某个应用的ArgoCD的Application配置单独放在argocd目录下用来定义每个应用在每个环境的同步配置(从哪个目录同步到哪个集群哪个Namespace等)工具脚本和文档也单独放目录清晰明了。

命名规范

同时我们也制定了统一的命名规范主要有以下几条:

  1. 文件命名:全部小写用中划线分隔(kebab-case)比如deployment.yamlservice.yamlconfigmap.yamlingress.yaml不用下划线不用驼峰不用大写资源类型的文件就用资源类型的小写命名,比如Deployment就叫deployment.yamlService就叫service.yaml一目了然。
  2. 资源命名:Kubernetes资源的name统一用<应用名>-<资源类型缩写>的格式,比如应用叫user-serviceDeployment就叫user-service-deployService就叫user-service-svcConfigMap就叫user-service-cmIngress就叫user-service-ingress这样看资源名字就知道是哪个应用的什么资源非常清晰也不会冲突。
  3. 应用命名:应用名统一用小写中划线分隔简洁明了能表达应用的功能,比如user-serviceorder-apipayment-gateway等不用太长,或者太抽象的名字。
  4. 分支命名:Git分支统一用<类型>/<描述>的格式,比如feature/add-new-appfix/fix-config-errorrefactor/restructure-dir等类型包括featurefixrefactorchoredocs等清晰明了。
  5. 标签和注解:统一给所有资源加标准的标签(Labels)比如app.kubernetes.io/nameapp.kubernetes.io/instanceapp.kubernetes.io/versionapp.kubernetes.io/componentapp.kubernetes.io/part-ofapp.kubernetes.io/managed-by等用社区标准的标签方便查询和管理注解(Annotations)也统一规范不乱加。

这些命名规范团队一起讨论确认后写到了仓库的README和docs里作为团队的规范所有人都要遵守Code Review的时候,也会检查命名是否符合规范不符合的要求修改才能合并这样就能保证命名的一致性不会再乱回去。

阶段3:引入Kustomize抽象公共配置减少重复

这是重构最核心的部分也是技术含量最高的部分我们引入了Kustomize来管理Kubernetes配置解决重复代码和环境差异的问题。

Kustomize是Kubernetes官方的配置管理工具(现在已经内置在kubectl里了kubectl apply -k)它的核心思想是"无模板的配置定制"通过base+overlay的方式来管理配置base里放基础的配置(所有环境共用)overlay里放各个环境的差异化配置(用patch或者替换的方式覆盖base里的配置)最后通过kustomize build把base和overlay合并生成最终的配置这样公共配置只需要写一遍在base里各个环境只需要写差异的部分大大减少了重复代码,而且配置的继承和覆盖关系非常清晰容易理解和维护。

具体来说,我们做了以下事情:

  1. 为每个应用创建base目录:把每个应用的公共配置(DeploymentServiceConfigMap等所有环境都一样的部分)都放到apps/<应用名>/base/目录下,并且创建kustomization.yaml文件把这些资源都列进去作为基础配置。
  2. 为每个环境创建overlay目录:在apps/<应用名>/overlays/<环境名>/下创建各个环境的差异化配置,比如生产环境的副本数是6测试环境是2生产环境的镜像版本是v1.2.3测试环境是latest生产环境的资源requests/limits更大等等这些差异都通过Kustomize的patch(strategic merge patch或者JSON patch)来实现只写差异的部分不用复制整个文件。
  3. 抽象公共组件到base:对于很多应用都用的公共配置(比如公共的环境变量公共的资源配置公共的标签和注解等)我们还抽象了一个公共的base(apps/_common/base/)各个应用的base可以通过Kustomize的components或者resources引用这个公共base这样公共配置只需要写一遍所有应用都能继承改公共配置只需要改一个地方所有应用都生效彻底解决了之前,改公共配置要改几十个文件的问题。
  4. 用ConfigMap Generator管理配置:对于ConfigMap和Secret我们用Kustomize的configMapGenerator和secretGenerator来管理从属性文件,或者env文件生成ConfigMap/Secret并且自动加hash后缀这样配置内容变了ConfigMap的名字就会变Deployment就会自动滚动更新不用手动改annotation来触发更新非常方便。

引入Kustomize之后,我们的配置代码量减少了将近60%原来几十个文件的重复配置现在只需要几个base文件加几个overlaypatch就搞定了,而且结构清晰改公共配置只需要改一个地方再也不会出现漏改的情况维护成本大大降低这是这次重构最大的收获之一。

阶段4:规范环境管理消除配置漂移

接下来我们规范了环境管理解决之前,配置漂移严重的问题主要做了以下事情:

  1. 统一用base+overlay管理环境:所有应用的所有环境都统一用base+overlay的方式管理在同一个Git仓库的同一个分支(主分支)里不再用不同分支管理不同环境也不再用注释区分环境所有环境的配置都在一个地方清晰可见差异也通过overlay明确定义不会出现不知道为什么差异的情况。
  2. 所有变更走PR禁止直接改主分支:严格执行Git工作流所有配置变更都必须走PR(Pull Request)经过Code Review通过才能合并到主分支禁止直接在,主分支上提交更禁止直接在某个环境目录里偷偷改配置不走PR这样所有变更都有记录有审核可追溯谁改的为什么改什么时候改的都能在Git历史里查到不会再出现配置莫名其妙变了的情况。
  3. 用ArgoCD自动同步保证配置一致:所有环境的配置都通过ArgoCD自动从Git仓库同步到集群禁止手动在集群里改配置(kubectl edit等)如果需要改配置必须改Git仓库里的文件走PR合并后ArgoCD自动同步这样就能保证集群的实际状态和Git仓库的声明一致不会出现配置漂移(有人手动改了集群配置没同步到Git)如果有人手动改了集群配置ArgoCD会检测到不一致自动同步回Git声明的状态,并且发告警通知我们这样就能及时发现和纠正配置漂移。
  4. 定期做配置审计:我们还建立了定期的配置审计机制每周,或者每两周自动对比各个环境的配置差异生成报告看看有没有意料之外的差异,或者不符合规范的配置及时发现问题及时纠正把配置漂移的风险降到最低。

通过这些措施我们基本消除了配置漂移的问题各个环境的配置差异都是明确定义的可追溯的可管理的再也不会出现之前,那种测试环境和生产环境配置莫名其妙不一样的情况出问题排查也方便很多。

阶段5:补充文档和注释降低维护成本

最后我们补充了文档和注释让仓库更容易理解和维护主要做了以下事情:

  1. 完善README:在仓库根目录写了详细的README.md说明仓库的用途目录结构命名规范使用方法怎么加新应用怎么改配置怎么部署Code Review规范等让新人看了README就能基本明白这个仓库怎么回事怎么用。
  2. 补充YAML注释:在关键的YAML文件里加了必要的注释说明这个配置是干什么的为什么这么配有什么注意事项,比如在Deployment的资源配置旁边注释说明为什么CPUrequests是这个值是根据什么压测结果定的在环境变量旁边注释说明每个环境变量的作用这样别人看配置的时候,就能明白为什么这么配不用去问写的人。
  3. 写操作手册:在docs目录下写了各种操作手册,比如《如何添加一个新应用》《如何修改应用配置》《如何做环境配置变更》《如何处理ArgoCD同步失败》《常见问题排查指南》等把常见的操作和问题都写成文档步骤清晰可操作大家遇到问题先查文档大部分问题都能自己解决不用总是问老人。
  4. 架构图和流程图:画了仓库的目录结构图和GitOps工作流的流程图(从提交PR到ArgoCD同步到集群的整个流程)放在docs里直观易懂帮助大家理解整体架构和工作流程。

通过这些文档和注释我们的仓库可维护性大大提升新人上手时间从原来的一两周缩短到了一两天看完文档和,几个例子就能自己加应用改配置了老人也不用总是被问各种基础问题能专注做更重要的事情。

四、重构后的效果

经过两周的重构我们的GitOps代码仓库发生了翻天覆地的变化从原来的混乱不堪难维护的烂代码变成了结构清晰易于维护的优雅代码具体效果如下:

  1. 代码量减少60%:通过引入Kustomize抽象公共配置和模板我们的配置代码量减少了将近60%原来几千行的重复YAML现在只需要几百行就搞定了仓库更简洁更清爽。
  2. 目录结构清晰找东西快:新的目录结构清晰合理按应用组织base和overlay分离找一个应用的配置直接去apps/<应用名>/下找就行不用再翻好几个目录找半天效率提升很多。
  3. 改公共配置只需要改一个地方:公共配置抽象到公共base后改公共配置只需要改一个文件所有,应用都自动生效再也不用去几十个文件里挨个改也不会漏改效率和准确性都大大提升。
  4. 命名规范统一易懂好记:所有文件和资源命名都统一了规范一致易懂好记看名字就知道是什么不用再猜也不会搞混Code Review的时候,也能快速发现不规范的命名及时纠正。
  5. 配置漂移基本消除:通过规范环境管理所有变更走PRArgoCD自动同步定期审计我们基本消除了配置漂移的问题各个环境的配置差异都是明确可追溯的出问题排查也方便很多。
  6. 新人上手快维护成本低:完善的文档和注释让新人能快速上手维护成本大大降低团队的整体效率提升了很多也不用再担心老人走了没人懂仓库的问题。
  7. 线上零事故:整个重构过程我们严格遵循小步快跑逐步推进的原则每改一部分就验证没问题再继续,而且保持功能不变重构后的配置和原来完全一致(通过kustomize build生成最终配置和原来的配置diff对比确认没有意外变化)所以整个重构过程线上零事故没有影响任何服务非常平稳。

五、经验与心得

这次GitOps代码重构给我们团队带来了很多经验和心得这里也分享给大家希望对正在做,或者准备做类似重构的团队有帮助。

1. 技术债务要及时还不要拖到不得不重构

我们这次重构的根本原因是之前,技术债务欠太多了最开始项目少的时候,没注意规范和结构怎么方便怎么来后来项目越来越多债务越滚越大最后到了不得不重构的地步花了两周时间才还清其实,如果最开始就注意规范和结构及时抽象和整理根本不需要花这么大力气重构,所以技术债务一定要及时还不要拖越拖越难还成本越高平时写代码的时候,就要注意质量和规范定期做代码整理和重构不要等烂到不行了才想起来重构。

2. 重构前一定要先梳理现状不要盲目动手

这次重构我们最正确的决定之一就是先花两天时间梳理现状建立清单搞清楚有多少东西哪些有用哪些废弃哪些重复依赖关系是什么而不是上来就盲目改目录改文件,如果没有先梳理清楚就动手很可能改到一半发现漏了什么东西,或者改错了什么依赖导致出问题返工成本更高,所以重构前一定要先梳理现状做到心中有数再动手磨刀不误砍柴工前期花时间梳理是值得的。

3. 好的工具能大大提升重构效率和质量

这次重构我们引入了Kustomize来管理配置这个工具真的帮了我们大忙之前,觉得Kustomize学习成本有点高一直没用还是用复制粘贴的原始方式这次重构逼着我们学了Kustomize用了之后,才发现真的太香了配置管理的效率和质量提升了不止一个档次,所以不要害怕学新工具好的工具,虽然有一定学习成本,但是学会之后,能大大提升效率和质量长远来看是非常值得的做技术的就是要保持学习新工具新技术的热情不断提升自己的生产力。

4. 规范和文档和代码一样重要甚至更重要

这次重构我们,不仅改了代码结构还花了不少时间写规范和文档最开始有同事觉得写文档是浪费时间不如多写点代码,但是后来发现文档和规范真的很重要甚至比代码本身还重要,因为代码是给机器看的文档和规范是给人看的,只有人理解了规范和结构才能正确地维护和扩展代码,不然代码写得再好没人懂怎么维护还是会慢慢烂回去,所以一定要重视规范和文档把它们当作和代码一样重要的东西来写来维护,而且要同步更新不能代码改了文档还是旧的。

5. 重构要小步快跑不要追求一次到位

这次重构我们没有追求一次性全部重构完而是分阶段分应用逐步推进先重构几个非核心应用试试水验证方案没问题再逐步推广到所有应用,而且每重构完一个应用就验证部署没问题再继续下一个这样风险很小,即使出问题也只影响一个应用容易回滚和修复,而且在重构过程中我们也不断调整和优化方案最开始的方案有一些不完善的地方在重构过程中逐步改进了最后的方案比最开始设计的更合理,所以重构一定要小步快跑逐步推进不要追求一次到位大爆炸式的重构风险很高很容易出问题,而且方案也不一定一开始就完美需要在实践中逐步完善。

六、写在最后

以上就是我们团队这次GitOps代码重构的完整过程和经验分享从最开始的混乱不堪的烂代码到最后结构清晰易于维护的优雅代码我们花了两周时间,但是带来的收益是长期的维护成本大大降低效率大大提升团队的技术能力和工程规范也提升了一个档次这次重构非常值得。

当然重构不是一劳永逸的重构完之后,还需要团队持续遵守规范定期做代码整理和小范围重构及时还技术债务才能保持代码的优雅和可维护性,不然过一段时间又会慢慢烂回去,所以我们也把代码质量和规范纳入了Code Review的检查项,并且定期做技术分享和代码走查保证大家都重视代码质量遵守规范持续维护好我们的代码仓库。

最后想说的是作为工程师我们,不仅要会写代码实现功能还要有代码质量和工程规范的意识写优雅的可维护的代码而不是能跑就行的烂代码烂代码短期看好像省了时间,但是长期来看维护成本非常高会拖慢整个团队的效率甚至导致线上故障而优雅的代码,虽然写的时候,多花一点时间,但是长期来看维护成本低效率高能给团队带来长期的收益,所以希望大家都能重视代码质量写优雅的代码做有追求的工程师。

愿大家都能远离烂代码写出优雅的代码也能在重构的过程中不断成长不断进步加油!