DevOps全链路实战 | 第 18 天:生产化加固要点:Harbor/GitLab/Argo CD/SonarQube 高可用方案

第 18/19 天

引言:从”能跑”到”扛得住”的距离

K8s

走过前面十七天,我们的 DevOps 管道已经能完成「代码提交 → 构建 → 静态检查 → GitOps 部署 → 灰度 → 监控 → 告警」的完整闭环。但如果你把这套环境直接搬到生产,第一个上班日大概率会收到 PagerDuty 的连环夺命 call——单点 Harbor 重启后镜像拉不下来、GitLab 的内置 PostgreSQL 卡在 I/O 饱和、Argo CD 的 Redis OOM 导致全集群同步停滞……这些坑的共同点是:组件本身没问题,是部署形态不够生产化。

今天这一篇不引入新组件,只回答一个问题:怎样把 Harbor、GitLab、Argo CD、SonarQube 四个核心平台,从 PoC 级别改造到生产级的高可用(HA)形态。内容覆盖存储后端、数据库高可用、副本扩缩、备份恢复、证书与域名、性能调优六条主线,最后给出一套可直接套用的 Helm values 与运维 runbook。

核心概念:高可用不是”多副本”四个字

可用性的三个层次

生产化加固要分层来看,不能一把梭:

  • 接入层高可用:负载均衡 + 多实例,避免单 Pod 宕机导致服务不可用;
  • 数据层高可用:数据库主从、对象存储多副本、Redis 哨兵或集群模式,避免数据丢失;
  • 运维层高可用:定时备份、异地容灾、DNS 故障切换,保证故障发生后能快速恢复。

很多同学只做了第一层(多副本),就以为生产化了,结果数据库一崩直接回到解放前。今天每个组件我们都会同时关注三层。

通用加固 Checklist

在动手前,记住这张通用的加固清单,后面四大组件都按这个顺序推进:

  1. 有状态组件(数据库、Redis、存储卷)优先做高可用;
  2. 无状态组件(API、Web 前端)扩副本数 + 设置 PDB;
  3. 配置与密钥脱离镜像,走 Secret/ConfigMap + 外部 KMS;
  4. 启用 TLS 与 RBAC,控制面与数据面分离;
  5. 备份策略 + 演练,没演练过的备份等于没备份。

实战一:Harbor 高可用方案

瓶颈分析

Harbor 的核心服务(core、jobservice、registry、portal)是无状态的,可以多副本。真正的瓶颈在两个地方:

  • Redis:存储会话与任务队列,单点 Redis 一挂,登录态全丢;
  • 存储后端:默认用 PVC 存镜像层,单 PVC 绑死单节点,节点故障即不可用。

所以 Harbor 高可用的关键是:Redis 走外部集群 + 存储走对象存储(S3/OSS/MinIO)。

Helm values 高可用配置

💻 代码示例

# harbor-ha-values.yaml

expose:

type: ingress

tls:

enabled: true

certSource: secret

secret:

secretName: harbor-tls

ingress:

className: nginx

host: harbor.stellardata.top

 

externalURL: https://harbor.stellardata.top

 

# 核心服务多副本

core:

replicas: 2

resources:

requests: { cpu: 500m, memory: 512Mi }

limits: { cpu: 1, memory: 1Gi }

jobservice:

replicas: 2

resources:

requests: { cpu: 250m, memory: 256Mi }

registry:

replicas: 2

# 关键:改用 S3 后端,避免单 PVC

storage:

s3:

bucket: harbor-registry

region: us-east-1

accesskey: "${S3_ACCESS_KEY}"

secretkey: "${S3_SECRET_KEY}"

 

# Redis 走外部集群,不再用内置单点

redis:

type: external

external:

addr: redis-ha.stellardata-top.svc.cluster.local

port: 26379

sentinelName: mymaster

sentinelAuth: "${REDIS_SENTINEL_AUTH}"

password: "${REDIS_PASSWORD}"

 

# 数据库走外部高可用 PG

database:

type: external

external:

host: pg-ha.stellardata-top.svc.cluster.local

port: 5432

username: harbor

password: "${PG_PASSWORD}"

coreDatabase: registry

 

# Trivy 与 ChartMuseum 也走外部存储

trivy:

replicas: 2

storageClass: nfs-client

persistence:

persistentVolumeClaim:

registry:

size: 50Gi

storageClass: ""

部署命令:

💻 代码示例

# 添加并更新 Harbor 仓库

helm repo add harbor https://helm.goharbor.io

helm repo update

 

# 先注入密钥到 Secret,避免明文落盘

kubectl create secret generic harbor-secrets

–from-literal=S3_ACCESS_KEY=AKIAxxxxx

–from-literal=S3_SECRET_KEY=$(openssl rand -hex 16)

–from-literal=PG_PASSWORD=$(openssl rand -hex 16)

-n harbor

 

# 部署

helm upgrade –install harbor harbor/harbor

-f harbor-ha-values.yaml

-n harbor –create-namespace –version 1.14.0

 

# 验证副本数

kubectl get deploy -n harbor -o wide

PDB 与反亲和

光扩副本还不够,升级时两个 Pod 一起被驱逐会瞬间不可用。务必加 PodDisruptionBudget:

💻 代码示例

# harbor-pdb.yaml

apiVersion: policy/v1

kind: PodDisruptionBudget

metadata:

name: harbor-core-pdb

namespace: harbor

spec:

minAvailable: 1

selector:

matchLabels:

component: core

—

apiVersion: policy/v1

kind: PodDisruptionBudget

metadata:

name: harbor-registry-pdb

namespace: harbor

spec:

minAvailable: 1

selector:

matchLabels:

component: registry

配合 topologySpreadConstraints 把副本打散到不同节点:

💻 代码示例

topologySpreadConstraints:

– maxSkew: 1

topologyKey: kubernetes.io/hostname

whenUnsatisfiable: DoNotSchedule

labelSelector:

matchLabels:

component: core

实战二:GitLab CE 高可用方案

架构取舍

GitLab 官方提供了 Omnibus 一体包,部署简单但天花板是单机。要做真正的 HA,必须切到 GitLab Helm Chart + 外部组件 架构:

  • GitLab Rails(Webservice/Sidekiq)→ 多副本无状态;
  • Gitaly(Git 仓库存储)→ 存储层,需配合 NFS 或 Gitaly Cluster;
  • PostgreSQL → 外部 CloudNativePG 主从;
  • Redis → 外部 Redis Sentinel;
  • Runner → 独立 Chart,与 GitLab 解耦。

Gitaly Cluster 是关键

Gitaly 是唯一真正有状态的组件,单机 Gitaly 一旦磁盘损坏,仓库数据全没。生产环境必须上 Gitaly Cluster:

💻 代码示例

# gitaly-cluster.yaml(简化示意)

gitlab:

gitaly:

persistence:

storageClass: ceph-rbd

size: 200Gi

# 三副本 + 故障自动切换

replicas: 3

topologySpreadConstraints:

– maxSkew: 1

topologyKey: topology.kubernetes.io/zone

whenUnsatisfiable: DoNotSchedule

labelSelector:

matchLabels:

app: gitaly

# 通过 Praefect 代理实现透明故障切换

praefect:

enabled: true

replicas: 3

database:

host: pg-ha.stellardata-top.svc.cluster.local

name: praefect

Gitaly Cluster 通过 Praefect 这一前置代理,把写请求路由到主副本,读请求可分散到副本,主节点故障时自动切换。这是 GitLab HA 的真正底气。

Webservice 与 Sidekiq 横向扩展

💻 代码示例

gitlab:

webservice:

minReplicas: 3

maxReplicas: 10

hpa:

enabled: true

targetCPU: 70

workerTimeout: 60

resources:

requests: { cpu: 900m, memory: 2Gi }

limits: { cpu: 2, memory: 4Gi }

sidekiq:

replicas: 3

# 不同队列用不同 Sidekiq 进程池隔离

queues:

– name: default

workers: 2

– name: mailers

workers: 1

备份策略

GitLab 备份要走对象存储而非本地 PVC:

💻 代码示例

# 1. 备份配置

kubectl exec -n gitlab deploy/gitlab-toolbox —

backup-utility –env-file /etc/gitlab-backup/env

–backend s3 –parameters "Bucket=gitlab-backup"

 

# 2. 通过 CronJob 每天 02:00 自动备份

cat > /tmp/gitlab-backup-cron.yaml << 'EOF'

apiVersion: batch/v1

kind: CronJob

metadata:

name: gitlab-backup

namespace: gitlab

spec:

schedule: "0 18 * * *" # UTC 18:00 = 北京 02:00

jobTemplate:

spec:

backoffLimit: 2

template:

spec:

restartPolicy: OnFailure

containers:

– name: backup

image: registry.gitlab.com/gitlab-org/build/cng/gitlab-toolbox

command:

– /bin/sh

– -c

– |

backup-utility –backend s3

–parameters "Bucket=gitlab-backup,Region=us-east-1"

EOF

实战三:Argo CD 高可用方案

内置 HA vs 外部化

Argo CD 是四个组件里 HA 改造最省心的——官方 Helm Chart 自带 server HA 模式,开启一个开关就能多副本。但同样要注意 Redis 这一层。

💻 代码示例

# argocd-ha-values.yaml

server:

replicas: 3

# 自动水平扩缩

autoscale:

enabled: true

minReplicas: 3

maxReplicas: 8

targetCPUUtilizationPercentage: 70

# 反亲和避免同节点

affinity:

podAntiAffinity:

preferredDuringSchedulingIgnoredDuringExecution:

– weight: 100

podAffinityTerm:

labelSelector:

matchLabels:

app.kubernetes.io/name: argocd-server

topologyKey: kubernetes.io/hostname

 

repoServer:

replicas: 3

 

applicationSet:

replicas: 2

 

# Redis 走外部 HA 集群,禁用内置单点

redis:

enabled: false

externalRedis:

host: redis-argocd-ha.stellardata-top.svc.cluster.local

port: 26379

sentinel: true

sentinelName: argocd

password: "${ARGOCD_REDIS_PASSWORD}"

 

# 控制器也要多副本(leader 选举)

controller:

replicas: 3

# 多副本时通过 leader election 保证只有一份工作

env:

– name: ARGOCD_CONTROLLER_REPLICAS

value: "3"

 

# 关键:开启 PDB,升级时不被一锅端

server:

pdb:

enabled: true

minAvailable: 2

ApplicationSet 与 Git Generator

生产环境最大的痛点是「几十上百个微服务要同步」——纯 Application 资源手写到崩溃。改用 ApplicationSet 的 Git Generator 可以一条配置生成 N 个 Application:

💻 代码示例

# appset-all-services.yaml

apiVersion: argoproj.io/v1alpha1

kind: ApplicationSet

metadata:

name: all-services

namespace: argocd

spec:

generators:

– git:

repoURL: https://gitlab.stellardata.top/infra/manifests.git

revision: main

directories:

– path: services/*

template:

metadata:

name: '{{path.basename}}'

spec:

destination:

namespace: '{{path.basename}}'

server: https://kubernetes.default.svc

project: default

source:

repoURL: https://gitlab.stellardata.top/infra/manifests.git

targetRevision: main

path: '{{path}}'

syncPolicy:

automated:

prune: true

selfHeal: true

syncOptions:

– CreateNamespace=true

这样仓库目录每加一个服务,Argo CD 自动生成对应 Application,无需人工干预,这才是 GitOps 的真正生产力。

实战四:SonarQube 高可用方案

两个真正的瓶颈

SonarQube 在生产里最容易出问题的两个地方:

  • Elasticsearch:SonarQube 把代码扫描结果、Issue、安全热点全塞进 ES,单节点 ES 内存一满就 OOM;
  • Compute Engine:扫描分析后台任务,单线程跑会积压。

SonarQube 官方从 9.9 起提供 Data Center Edition 才支持真正的多节点,社区版只能做”伪 HA”——通过外部化存储和数据库来降低单点风险。

Helm values 高可用配置

💻 代码示例

# sonarqube-ha-values.yaml

edition: "community" # 企业版才支持 datacenter

 

# 数据库走外部高可用 PG

postgresql:

enabled: false

externalPostgresql:

host: pg-ha.stellardata-top.svc.cluster.local

port: 5432

database: sonar

username: sonar

password: "${PG_PASSWORD}"

 

# 关键:给 ES 足够内存,避免 OOM

sonarQube:

resources:

requests:

cpu: 2

memory: 4Gi

limits:

cpu: 4

memory: 8Gi

env:

– name: SONAR_SEARCH_JAVA_OPTS

value: "-Xms2g -Xmx2g -XX:+UseG1GC"

– name: SONAR_CE_JAVA_OPTS

value: "-Xms1g -Xmx2g"

# 关闭内置 ES 的 swap,避免被驱逐

podAnnotations:

cluster-autoscaler.kubernetes.io/safe-to-evict: "false"

 

# PVC 改用 Ceph RBD,提供块级别冗余

persistence:

enabled: true

storageClass: ceph-rbd

accessMode: ReadWriteOnce

size: 20Gi

用 Webhook 把扫描结果回推

光部署高可用还不够,要保证 GitLab CI 能拿到扫描结果。配置 SonarQube 的 Webhook:

💻 代码示例

# 在 SonarQube 管理后台配置 Webhook

# Administration → Configuration → Webhooks

# URL: https://gitlab.stellardata.top/api/v4/projects/<id>/pipelines/<pipeline_id>

 

# 通过 GitLab CI 配合质量门禁

cat > .gitlab-ci.yml << 'YAML'

sonarqube-check:

stage: analyze

image: sonarsource/sonar-scanner-cli:latest

script:

– sonar-scanner

-Dsonar.projectKey=$CI_PROJECT_PATH

-Dsonar.sources=src

-Dsonar.qualitygate.wait=true # 关键:等质量门结果

rules:

– if: $CI_PIPELINE_SOURCE == "merge_request_event"

YAML

qualitygate.wait=true 会让 CI 任务阻塞,直到 SonarQube 把这次扫描结果跟门禁规则比对完,门禁不过则 CI 失败,把低质量代码挡在合并之前。

常见问题与踩坑

问题 1:升级后 Harbor 启不来,报 “schema mismatch”

根因:内置 PG 在升级时没执行 migration。解法:要么走外部 PG 让运维统一管理 migration,要么升级前先 helm rollback 回到上一版本,再用 migrate.sh 单独跑 schema 升级。永远不要跨大版本直接升级 Harbor,先在 staging 走一遍。

问题 2:Argo CD 同步卡在 OutOfSync 死循环

根因:用了 selfHeal: true 但 Helm values 里有 {{ }} 模板变量没渲染。解法:把模板变量改成 Kustomize 的 patches 或 Argo CD 的 ignoreDifferences:

💻 代码示例

syncOptions:

– RespectIgnoreDifferences=true

ignoreDifferences:

– group: apps

kind: Deployment

jsonPointers:

– /spec/replicas # HPA 在管副本数,忽略差异

问题 3:GitLab Sidekiq 任务堆积,Webhook 不触发

根因:mailers 队列挤占 default 队列。解法:按前面配置把 mailers 独立成 worker pool,再给 default 单独分配 workers。同时给 Sidekiq 加上 maxConcurrency 上限避免线程饿死:

💻 代码示例

# 监控队列长度

kubectl exec -n gitlab deploy/gitlab-sidekiq —

sidekiq-stats -r redis://redis-ha:6379

问题 4:SonarQube 扫描 OOM,Compute Engine 重启

根因:扫描大仓库时 CE 堆内存不够。解法:调大 SONAR_CE_JAVA_OPTS 到 2-4g,并在 CI 里把大仓库拆成多 module 扫描,避免一次性加载。

问题 5:备份恢复了,但镜像拉不下来

根因:Harbor 的 registry 数据在 S3,但 core 的 ConfigMap 里有缓存。解法:恢复后强制 kubectl rollout restart deploy/harbor-core -n harbor 清空缓存,再重新登录验证。

总结:高可用是一条流水线,不是一次动作

今天的四个组件看起来各自独立,但它们的高可用改造其实是一条流水线:

  1. 数据层:PG 走 CloudNativePG、Redis 走 Sentinel、对象存储走 MinIO/S3;
  2. 接入层:每个组件 replicas ≥ 2 + PDB + topologySpread;
  3. 运维层:CronJob 定时备份 + 异地副本 + 每月演练。

最容易忽视的是第三层。备份脚本写好了,但只有真演练过才知道能不能恢复。没演练过的备份等于没备份——这句话送给每一个做过 HA 的同事。

明天是系列的收官篇,我们用 Trivy 做镜像漏洞扫描、用 cosign 给镜像签名、用 Kyverno 在 K8s 准入层验签,把整条 DevOps 管道的安全闭环补上最后一环——供应链安全。

下期预告

明天将发布 第 19 天:供应链安全闭环:Trivy 扫描 + cosign 签名 + Kyverno 验签,给今天的四大平台画上安全句号,把容器供应链从「能跑」推到「可信」。

系列大纲

  1. 全链路架构总览:从代码提交到灰度发布的完整管道设计
  2. K8s 集群与网络基础:kubeadm 离线部署与 Cilium 网络插件
  3. Harbor 私有镜像仓库部署与验证:Helm 安装与镜像推送
  4. GitLab CE 自托管部署:代码仓库与项目管理平台
  5. GitLab Runner 配置:K8s Executor 与 RBAC 权限设置
  6. GitLab CI 流水线搭建:.gitlab-ci.yml 与 Kaniko 无守护进程构建
  7. Harbor 镜像版本管理与 Tag 策略配置
  8. Argo CD 部署与 GitOps 配置仓库创建
  9. CI 与 CD 联动:从代码提交到部署的全自动闭环
  10. SonarQube 代码质量检查:SonarQube 部署与 GitLab CI 集成
  11. Prometheus 部署:kube-prometheus-stack 与 ServiceMonitor 指标采集
  12. Grafana 看板搭建:K8s 标准面板与自定义业务看板
  13. 告警规则与 Alertmanager:钉钉/邮件通知渠道配置
  14. Argo Rollouts 安装与基础概念:CRD 与 Rollout 资源定义
  15. 金丝雀发布实战:setWeight 流量分割与 pause 步骤
  16. AnalysisTemplate 指标分析与自动回滚机制
  17. 全链路端到端演示:代码提交→构建→代码检查→灰度→监控→告警完整流程
  18. 生产化加固要点:Harbor/GitLab/Argo CD/SonarQube 高可用方案(今天)
  19. 供应链安全闭环:Trivy 扫描 + cosign 签名 + Kyverno 验签
微信二维码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像 - 恒星
欢迎您留下宝贵的见解!
提交
头像 - 恒星

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容