如何在香港服务器中设置并结合 Kubernetes 与大带宽,为跨境研发团队实现分布式 CI/CD 环境?

夜里 1 点 40,我在香港葵涌的机房,45U 机柜从上到下排着刚上架的 R7525,前面板的指示灯一排排跳,ToR 的 Arista 7050 在做 MLAG 收敛,端口灯以太鼓点一样闪。纤缆托盘里,几根标着 “VLAN30/Ingress”“VLAN20/NAT” 的双芯跳纤刚压好卡扣,Mellanox CX5 的链路协商到 25G;我在控制台里跑了遍 iperf3,链路轻松过 9.4Gbps,可 Slack 依然在叮咚——北京同事说 iOS 的流水线卡死在拉镜像,Node 的依赖像黏在墙上的口香糖,怎么也下来。那一刻我意识到,问题不在于“管子够不够粗”,而是该把计算、镜像、依赖和缓存统统搬到香港,让数据就地解决:用 Kubernetes 把算力铺开,用大带宽把跨境的控制面与必要制品拉通,用 Harbor/Nexus/S3 缓存把 CI/CD 的“热路径”牢牢留在机房里。接下来的几个小时,我要做的不仅是点亮一个集群,而是把一条跨境研发的高速公路,在这片嘈杂里,按下真正的通车键
1. 目标与约束
目标
- 在香港机房落地一个 分布式 CI/CD 平台,支撑跨境团队(内地 + 海外)并行构建、自动化部署。
- 充分利用 1–10 Gbps 级别的专线/大带宽,把镜像拉取、依赖下载、制品上传的时间压到最低。
- 体系可扩、可观测、可回滚,新手能照做,老手能优化。
关键约束
- 业务 OS 偏好 CentOS 7(公司历史原因)。
- 构建任务偏重:Android/iOS/Node/Java/Go,镜像层多、依赖大。
- 跨境访问:内地办公网 ↔ 香港 DC;海外雇员 ↔ 香港 DC。
2. 目标架构(ASCII 拓扑)
结构:
海外开发者
│ HTTPS/VPN
▼
┌───────────────┐
│ 香港 DC (HK) │ 10/40G 上联
└───────────────┘
┌───────────────────────────────────────────────────┐
│ Kubernetes 集群 │
│ [ingress] NGINX | MetalLB VIP | egress NAT GW │
│ │ │ │
│ ┌──────────▼───┐ ┌─────────▼────────┐ ┌────▼─────┐
│ │ Harbor 镜像仓 │ │ GitLab + Runner │ │ MinIO │
│ │ S3 远端备份 │ │ ARC/GHA 备选 │ │ Cache/S3 │
│ └──────────────┘ └───────────────────┘ └──────────┘
│ │ (registry cache/buildx) │
│ ├───────────┬─────────────────────────────┤
│ ┌────▼────┐ ┌───▼────┐ ┌────────┐ ┌─────▼────┐
│ │ Nexus │ │ Squid │ │ Tekton │ ... │ Argo CD │
│ │ Maven/N │ │ APT/YUM│ │/GitLab │ │ 持续交付 │
│ └─────────┘ └────────┘ │ CI │ └──────────┘
│ └────────┘
└───────────────────────────────────────────────────┘
▲ ▲
│ WireGuard/IPSec │ Prometheus/Loki
内地办公室───┘ 备线:CN2/CMI └── Grafana/Alert
3. 硬件与网络选型(实配参数)
3.1 服务器/交换机/线路
| 角色 | 数量 | 机型/CPU | 内存 | 磁盘 | 网卡 | 备注 |
|---|---|---|---|---|---|---|
| Control Plane/etcd | 3 | Dell R6525 / AMD 7313 (16C) | 128GB | SATA SSD 480G ×2 (RAID1) | 2×10G (Intel X710) | kube-apiserver/etcd |
| Worker (构建型) | 6 | Dell R7525 / AMD 7443P (24C) | 256GB | NVMe U.2 3.2TB ×4 (RAID10/软阵列) | 2×25G (Mellanox CX5) | 高 IO/高网络 |
| 存储/缓存 | 2 | Supermicro 2U | 128GB | HDD 12TB ×12 + NVMe 1.6TB ×2 (cache) | 2×25G | MinIO/Nexus |
| ToR 交换机 | 2 | Arista 7050 系列 | — | — | 48×10/25G + 6×40/100G | MLAG |
| 出口带宽 | 1 | 1 Gbps 专线 + 10 Gbps 共享 | — | — | BGP(CN2/CMI/GIA) | 可升配 |
经验:构建 Worker 上 NVMe 的随机读写比你想象的重要,Android/Node 的依赖展开 + Docker 层缓存能吃满 IOPS。网卡尽量上 CX4/CX5,驱动稳定、RDMA/多队列好调。
3.2 IP/VLAN/MTU
| 网络 | VLAN | MTU | 用途 |
|---|---|---|---|
| K8s Pod 网 | 200 | 1450 | VXLAN/Calico,避免 1500 上的封装碎片 |
| Node 管理网 | 10 | 9000 | 机房内复制/对象存储加速 |
| 出口 NAT 网 | 20 | 1500 | 互联网访问 |
| 业务 Ingress | 30 | 1500 | HTTPS 对外/对内入口 |
4. 带宽与并发容量规划
估算公式(保守)
- 单 Job 平均镜像层 + 依赖传输量:D (GB)
- 并发 Job 数:N
- 链路利用率:η(0.6~0.8)
- 需要的出口带宽(Gbps):B = (D × 8 × N) / (Job 时长 / 60) × 1/1000 / η
示例
- Android/Node 混合构建:每 Job 平均 2.5 GB 进出,Job 时长 12 分钟,并发 20,η=0.7
- B ≈ (2.5×8×20)/(12/60)/1000/0.7 ≈ 2.86 Gbps
这就是我们为什么起步就要 1 Gbps 专线 + 10 Gbps 共享出口,并在仓库/依赖代理上尽量 “就地命中”,减少外网流量。
5. 系统与内核准备(CentOS 7,实操细节)
CentOS 7 自带 3.10 内核对 eBPF/容器网络支持一般,建议上 ELRepo 的 5.x LTS,BBR/FQ 改善长肥管道吞吐。
# 1) 升级内核(ELRepo)
yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install -y kernel-ml-5.10.219-1.el7.elrepo
grub2-set-default 0 && reboot
# 2) 关闭 swap(kubeadm 必需)
swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab
# 3) 基础内核优化
cat >/etc/sysctl.d/99-k8s.conf <<'EOF'
net.core.rmem_max=134217728
net.core.wmem_max=134217728
net.ipv4.tcp_rmem=4096 87380 67108864
net.ipv4.tcp_wmem=4096 65536 67108864
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
net.ipv4.ip_forward=1
fs.inotify.max_user_watches=1048576
fs.inotify.max_user_instances=8192
vm.max_map_count=262144
EOF
sysctl --system
# 4) 时间同步(极易踩坑)
yum install -y chrony && systemctl enable --now chronyd
timedatectl set-ntp yes
坑 1(血泪): NTP 漂移会让 etcd 选主反复超时、Runner 证书校验失败。上线前跑一遍 chronyc sources -v,确保抖动 <5ms。
6. 容器运行时与 kubeadm
选择 containerd,避免 Docker shim 的历史包袱。
# containerd 安装
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y containerd.io
mkdir -p /etc/containerd
containerd config default >/etc/containerd/config.toml
# Systemd cgroup,配合 kubelet
sed -ri 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl enable --now containerd
# kubeadm/kubelet/kubectl
cat >/etc/yum.repos.d/kubernetes.repo <<'EOF'
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/repodata/repomd.xml.key
EOF
yum install -y kubeadm-1.29.* kubelet-1.29.* kubectl-1.29.*
systemctl enable kubelet
HA 入口:使用 keepalived + haproxy 给 kube-apiserver 一个 VIP 10.0.30.10:6443。
kubeadm-config.yaml(示例):
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.29.6
controlPlaneEndpoint: "10.0.30.10:6443"
networking:
podSubnet: "10.244.0.0/16"
serviceSubnet: "10.96.0.0/12"
dnsDomain: "cluster.local"
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
nodeRegistration:
criSocket: "unix:///run/containerd/containerd.sock"
初始化与加入:
kubeadm init --config kubeadm-config.yaml
# worker
kubeadm join 10.0.30.10:6443 --token <...> --discovery-token-ca-cert-hash sha256:<...>
7. CNI 与网络(Calico,MTU 调优)
Calico VXLAN,MTU 1450,避免外网路径 1500 + 封装碎片。
# 安装 Calico(示例,需替换版本)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml
# MTU 调整(ConfigMap)
kubectl -n kube-system edit configmap calico-config
# 设置 veth_mtu: "1450"
坑 2: 只改 Pod MTU 不够,Node to Node 的 jumbo(9000)别乱开,否则跨网段 egress NAT 会碎包。统一在 K8s 层用 1450。
8. Ingress/Egress 与负载均衡
Ingress:ingress-nginx + MetalLB 提供裸机 VIP。
Egress:专设 NAT GW DaemonSet(或边界路由)统一出网,便于 QoS/Shaping/审计。
MetalLB(L2 模式)示例:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata: { name: prod-pool, namespace: metallb-system }
spec:
addresses: [ "10.0.30.100-10.0.30.120" ]
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata: { name: prod-l2, namespace: metallb-system }
spec: {}
9. 镜像仓库与缓存(Harbor + S3/远端备份)
为什么放香港? 大多数构建发生在香港集群内,本地命中拉取;跨境只走制品与少量元数据。
Harbor(Helm)values.yaml 关键点:
- 开启 notary/扫描(可选)
- externalURL 指向香港 VIP
- 后端 s3(MinIO 或公有云对象存储)
- GC 计划任务,保留最近 N 版本
persistence:
persistentVolumeClaim:
registry:
size: 2Ti
jobservice:
maxJobWorkers: 8
registry:
upload_purging:
enabled: true
age: 168h
interval: 24h
dryrun: false
storageService:
s3:
region: us-east-1
bucket: harbor-prod
accesskey: xxx
secretkey: yyy
secure: true
坑 3: Docker Hub 的 rate limit 经常打爆。Harbor 里配置 上游 proxy cache,并给 CI 用 registry.mydomain/hub-cache/library/node:18 这种前缀。
10. 依赖加速:Nexus + Squid + 语言代理
- Nexus:Maven、npm、PyPI、Gradle 代理仓统一命中。
- Squid:APT/YUM/ CocoaPods 私有镜像(配上 ACL + 缓存目录 SSD)。
- Android:Gradle ~/.gradle/gradle.properties 指向 Nexus;org.gradle.parallel=true。
- Node:.npmrc 指向 https://nexus/repository/npm-group/。
11. CI 平台落地:GitLab + Runner(或 GitHub ARC)
11.1 GitLab Runner on K8s(推荐参数)
values.yaml 片段(关键位):
gitlabUrl: https://gitlab.example.com/
runnerRegistrationToken: "<TOKEN>"
rbac:
create: true
runners:
privileged: true # BuildKit/DIND 需要
requestConcurrency: 40
concurrent: 80 # 集群总并发上限
cpuLimit: "8"
memoryLimit: "16Gi"
cache:
type: s3
s3ServerAddress: "minio.minio.svc.cluster.local:9000"
s3BucketName: "gitlab-cache"
s3BucketLocation: "us-east-1"
s3CacheInsecure: true
builds:
cpuRequests: "2"
memoryRequests: "4Gi"
services:
cpuRequests: "500m"
memoryRequests: "1Gi"
构建优化
用 BuildKit + buildx,开启 跨 Job 层缓存:
docker buildx create --use
docker buildx build \
--cache-from=type=registry,ref=harbor/cache/myapp:buildcache \
--cache-to=type=registry,ref=harbor/cache/myapp:buildcache,mode=max \
-t harbor/prod/myapp:${CI_COMMIT_SHA} .
iOS 打包 Runner 用 macOS 金丝雀机 通过 WireGuard 接入(ARC 也可)。
11.2 备选:GitHub Actions Self-Hosted(actions-runner-controller)
如果公司偏 GH,部署 actions-runner-controller,思路与缓存完全一致。
12. 持续交付:Argo CD 基线
- 每个服务一个 Application,GitOps 推进。
- 使用 kustomize overlays 区分 dev/stage/prod。
- 镜像标签锁定 SHA,回滚迅速。
示例 Application:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: { name: myapp-prod, namespace: argocd }
spec:
project: default
source:
repoURL: 'https://git.example.com/ops/myapp-deploy.git'
targetRevision: main
path: overlays/prod
destination:
server: 'https://kubernetes.default.svc'
namespace: myapp
syncPolicy:
automated:
prune: true
selfHeal: true
13. 跨境连接:WireGuard / IPSec + 分流
- 内地办公网 <-> 香港 DC:WireGuard 站点到站点,仅开发/CI 必需网段走隧道;其余走本地互联网。
- DNS Split-Horizon:harbor.prod.local 在内地解析到 北京边界 VIP,再由边界路由转发到 HK,或直接解析 HK VIP(看策略)。
- SNI 透传:避免中间代理插证书,移动端签名校验敏感。
WireGuard 端点(HK):
[Interface]
Address = 172.22.0.1/24
ListenPort = 51820
PrivateKey = <HK-PRIVATE>
[Peer]
PublicKey = <BJ-PUB>
AllowedIPs = 10.8.0.0/16, 10.0.0.0/16
PersistentKeepalive = 25
14. QoS 与大带宽利用(别“糟蹋”链路)
出口 GW 用 tc fq_codel 或 cake,避免队首阻塞。
为 CI 子网设置优先级(容许 80% 峰值),预留 20% 给线上服务/管理面。
# 简化示例:ifb 入向整形 + fq_codel
tc qdisc add dev eth0 root handle 1: htb default 10
tc class add dev eth0 parent 1: classid 1:10 htb rate 8gbit ceil 9gbit
tc class add dev eth0 parent 1: classid 1:20 htb rate 1gbit ceil 1gbit
# 把线上 VIP 段标到 1:20,CI 段到 1:10(配合 iptables --set-class)
15. 可观测性与告警
Prometheus + Grafana:关注 container_network_transmit_bytes_total、registry_request_latency、runner_job_duration_seconds。
Loki:Runner/Harbor/Ingress 的关键日志索引。
告警:
- Harbor proxy cache 命中率 < 70%
- Runner Pending > 20 (持续 5 分钟)
- 出口带宽 5 分钟均值 > 85%(考虑扩容)
16. 备份与演练
- etcd:etcdctl snapshot save 每日,异地存储。
- Harbor:镜像复制到二存(另一家对象存储/另一 Region)。
- MinIO:版本化 + 生命周期。
- 集群应用:Velero 备份命名空间级资源与 PV。
17. 验证与量化结果
上线后一周我们做了对比(真实统计口径):
| 指标 | 上线前(内地构建) | 上线后(HK 构建) | 变化 |
|---|---|---|---|
| 平均镜像拉取 | 14.8 分钟 | 2.3 分钟 | ↓ 84% |
| Node 构建(npm 依赖) | 9.5 分钟 | 3.1 分钟 | ↓ 67% |
| Android 构建 | 37 分钟 | 22 分钟 | ↓ 41% |
| 每日可交付构建数 | 180 | 520 | ↑ 188% |
| 出口外网峰值 | — | 2.6 Gbps | — |
| Harbor 命中率 | — | 78% | — |
关键并不在“更粗的管子”,而在“把数据留在香港”:镜像/依赖/缓存尽量本地命中,跨境只走必要的控制面与制品归档。
18. 常见坑与现场解决
NTP 漂移:etcd 反复选主、Runner TLS 报过期。
解决:chrony 对齐 3 家上游 + 边界 PTP;Prometheus 做抖动告警。
MTU 不一致:Pod 1450,Node 9000,跨网段时碎包重传,速度像“拉闸”。
解决:K8s 全面 1450;管理网/存储网保留 9000,但不穿越 egress。
Docker Hub 限流:白天集中爆仓。
解决:Harbor Proxy Cache + 项目白名单镜像预拉取;夜间预热脚本。
npm/maven 污染/不可达:上游被墙或变更。
解决:Nexus 组仓 + 双上游(国外/国内镜像),健康探针自动切换。
kube-proxy ipvs + conntrack 累积:大流量 Ingress 下连接表爆。
解决:提高 nf_conntrack_max,并调短 tcp_tw_reuse/tcp_fin_timeout;或迁移 Cilium/BPF(需核对内核)。
Runner DIND 慢:overlay2 白名单/元数据开销大。
解决:改 BuildKit,跨 Job 层缓存到 Harbor;NVMe 专盘挂载 /var/lib/docker。
证书链:海外同事偶发 TLS 验证失败。
解决:统一由 ACME 颁发,集团内 CA 仅用于内网域名;确保 fullchain.pem。
19. 关键配置清单(可直接抄)
19.1 kubelet(cgroup/systemd)
cat >/etc/sysconfig/kubelet <<'EOF'
KUBELET_EXTRA_ARGS="--cgroup-driver=systemd --node-status-update-frequency=10s"
EOF
systemctl daemon-reload && systemctl restart kubelet
19.2 Ingress NGINX(大连接优化)
controller:
config:
use-forwarded-headers: "true"
proxy-body-size: "0"
proxy-buffer-size: "32k"
proxy-read-timeout: "600"
keep-alive: "120"
19.3 Nexus(npm 代理组)
npm-group = npmjs(国外) + cnpm(国内),路由失败自动降级。
带宽大时,开启 maximum concurrent connections 限制到 500,避免吞吐抖动。
19.4 Prometheus 告警(示例)
- alert: RunnerPendingHigh
expr: sum(gitlab_runner_jobs{status="pending"}) > 20
for: 5m
labels: { severity: warning }
annotations: { summary: "Pending jobs > 20 for 5m" }
20. 运维手册里的“红线”
- 变更窗口:跨境路由/证书/Harbor GC 只在低峰执行。
- 配额:团队/项目级并发上限,避免一个仓库独占带宽。
- 一键回滚:Argo CD Sync Window + 镜像以 SHA 标识。
- 演练:每月随机抽项目从头构建一次,验证缓存失效路径。
21. 尾声:那杯凉掉的美式
一周后,我们把统计图发到群里,大家都在刷“这次真快”。北京的 iOS 同学开玩笑说:“再也不用喝完一杯奶茶等镜像了。” 我回到那台 R7525 前,摸了摸还是热乎的散热片,心里想的却是下一步:把 1 Gbps 专线升到 5 Gbps,把海外办公室的接入也并回来。
机房的风还是大,但这次我把咖啡喝完了。
22. 总结(给新手与老手)
- 新手照做:按本文硬件/网络/配置直接落地,优先把 Harbor + Nexus + Runner on K8s 跑起来。
- 老手优化:关注 链路利用率、MTU 一致性、缓存命中率、BuildKit 层缓存,再考虑 Cilium/eBPF、RDMA、分层存储等高级优化。
- 核心心法:跨境不等于“拉更粗的管子”,而是 把数据留在最近的地方,让链路只传真正必须的东西。
如果你也准备在香港落一套分布式 CI/CD,希望这份“带汗味儿”的实操能让你少踩几个坑。