CI / GitOps Runbook:Harbor / Gitea / Jenkins / Kaniko / ArgoCD
这篇讲从源码到部署的链路:
开发者 git push
-> Gitea 保存源码
-> Jenkins 拉代码
-> Kaniko 构建镜像
-> Harbor 保存镜像
-> ArgoCD 从 Git 拉部署 YAML
-> K8s 更新业务 Pod
1. 组件作用
| 组件 | 作用 |
|---|---|
| Harbor | 私有镜像仓库,存业务镜像和基础镜像 |
| Gitea | 集群内 Git 服务,保存源码和部署仓库 |
| Jenkins | CI 控制器,触发构建 |
| Kaniko | 在 K8s Pod 内构建镜像,不需要 Docker daemon |
| ArgoCD | CD/GitOps 控制器,从 Git 同步 K8s YAML |
为什么拆成源码 repo 和部署 repo:
- 源码 repo 改动频繁,触发 CI。
- 部署 repo 只记录期望部署状态,ArgoCD watch 它。
- 镜像 tag 或 digest 变更可审计、可回滚。
2. 安装 Harbor
helm repo add harbor https://helm.goharbor.io
helm repo update
helm install harbor harbor/harbor \
--namespace harbor --create-namespace \
--set expose.type=nodePort \
--set expose.tls.enabled=false \
--set expose.nodePort.ports.http.port=30002 \
--set persistence.persistentVolumeClaim.registry.storageClass=longhorn \
--set persistence.persistentVolumeClaim.registry.size=10Gi \
--set persistence.persistentVolumeClaim.jobservice.jobLog.storageClass=longhorn \
--set persistence.persistentVolumeClaim.jobservice.jobLog.size=1Gi \
--set persistence.persistentVolumeClaim.database.storageClass=longhorn \
--set persistence.persistentVolumeClaim.database.size=2Gi \
--set persistence.persistentVolumeClaim.redis.storageClass=longhorn \
--set persistence.persistentVolumeClaim.redis.size=1Gi \
--set persistence.persistentVolumeClaim.trivy.storageClass=longhorn \
--set persistence.persistentVolumeClaim.trivy.size=1Gi \
--set harborAdminPassword=bootcamp \
--set externalURL=http://10.0.24.31:30002
入口:
http://154.201.73.31:30002
验收:
kubectl get pods -n harbor -o wide
kubectl get pvc -n harbor
curl -sS http://154.201.73.31:30002/api/v2.0/systeminfo | jq
真实输出(装好后,本集群 chart 1.19.1 / app 2.15.1):
$ kubectl get pods -n harbor # 共 8 个:core/database/redis/registry/jobservice/portal/trivy/registryctl
全部 Running(其中 harbor-core 2/2)
$ curl -sS http://10.0.24.31:30002/api/v2.0/systeminfo
{"auth_mode":"db_auth","self_registration":false,...} ← API 通,Harbor 起来了
⚠️ 刚装时
harbor-core会先 CrashLoopBackOff 一两次(实测,正常)。core 启动要连 database(PostgreSQL) 和 redis,这两个还在初始化时 core 连不上就重启;等它们 Running 后 core 自愈。看到一个 CrashLoop 先别动,等 1-2 分钟再看是不是 8/8 Running。
3. 配 containerd 信任 HTTP Harbor
当前 Harbor 是学习环境 HTTP NodePort。每台节点要告诉 containerd:10.0.24.28:30002 是 HTTP registry。
for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
ssh root@$ip 'set -eu
mkdir -p /etc/containerd/certs.d/10.0.24.28:30002
cat > /etc/containerd/certs.d/10.0.24.28:30002/hosts.toml <<EOF
server = "http://10.0.24.28:30002"
[host."http://10.0.24.28:30002"]
capabilities = ["pull", "resolve", "push"]
skip_verify = true
EOF
'
done
为什么不重启 containerd:新 certs.d 模式会动态加载 registry 配置。老式写 config.toml 才需要重启。
4. 安装 ArgoCD
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd \
--namespace argocd --create-namespace \
--set server.service.type=NodePort \
--set server.service.nodePortHttp=30080 \
--set server.service.nodePortHttps=30443 \
--set configs.params."server\\.insecure"=true
入口:
http://154.201.73.31:30080
密码:
kubectl get secret -n argocd argocd-initial-admin-secret \
-o jsonpath='{.data.password}' | base64 -d; echo
验收:
kubectl get pods -n argocd -o wide
kubectl get svc -n argocd
kubectl get app -n argocd
5. 安装 Gitea
helm repo add gitea-charts https://dl.gitea.com/charts
helm repo update
helm install gitea gitea-charts/gitea \
--namespace gitea --create-namespace \
--set service.http.type=NodePort \
--set service.http.nodePort=30022 \
--set persistence.storageClass=longhorn \
--set persistence.size=5Gi \
--set gitea.admin.username=bootcamp \
--set gitea.admin.password=bootcamp \
--set gitea.admin.email=bootcamp@local \
--set postgresql-ha.enabled=false \
--set postgresql.enabled=true \
--set valkey-cluster.enabled=false \
--set valkey.enabled=true
⚠️ 新版 Gitea chart(12.x,实测踩坑)默认启用 HA 子 chart。如果只按老教程写
--set postgresql.enabled=true --set valkey.enabled=true,安装直接报错:Error: INSTALLATION FAILED: ... valkey and valkey-cluster cannot be enabled at the same time Error: INSTALLATION FAILED: ... Only one of postgresql or postgresql-ha can be enabled at the same time因为 chart 默认
postgresql-ha.enabled=true+valkey-cluster.enabled=true,和你手动开的单节点版冲突。必须显式把 HA 版关掉(上面已加postgresql-ha.enabled=false和valkey-cluster.enabled=false)。学习集群用单节点 PostgreSQL / Valkey 更省资源;要真 HA 才反过来留 HA 版、关单节点版。
入口:
http://45.205.31.214:30022
⚠️ Gitea 主 Pod 的
configure-giteainit 容器常会先报几次Failed to initialize ORM engine: dial tcp ...:5432: connect: connection refused然后Init:CrashLoopBackOff——这是启动竞速(gitea 比 postgres 接受连接早),init 容器自带重试(日志末尾will try again),postgres 一就绪就自动过,不用管。别急着删 Pod。
如果没有把 longhorn 设成默认 StorageClass,需要额外给 PostgreSQL / Valkey 子 chart 显式设置 storageClass;当前集群已经把 longhorn 设为默认,所以这里不重复写一堆子 chart 参数。
验收:
kubectl get pods -n gitea -o wide
kubectl get svc,endpoints -n gitea
kubectl get pvc -n gitea
如果某个节点公网 IP 访问 Gitea timeout,但另一个节点正常,先查云防火墙或节点到 NodePort 的转发,不要先删 Gitea Pod。
6. 安装 Jenkins
helm repo add jenkins https://charts.jenkins.io
helm repo update
helm install jenkins jenkins/jenkins \
--namespace jenkins --create-namespace \
--set controller.serviceType=NodePort \
--set controller.nodePort=30808 \
--set controller.admin.username=admin \
--set controller.admin.password=bootcamp \
--set persistence.storageClass=longhorn \
--set persistence.size=10Gi \
--set 'controller.tolerations[0].operator=Exists'
入口:
http://154.201.73.31:30808
验收:
kubectl get pods -n jenkins -o wide
kubectl get pvc -n jenkins
kubectl logs -n jenkins jenkins-0 -c jenkins --tail=100
7. Kaniko 推 Harbor 的 Secret
Jenkins agent 里的 Kaniko 需要 Harbor 凭据:
kubectl create secret docker-registry harbor-auth \
-n jenkins \
--docker-server=10.0.24.28:30002 \
--docker-username=admin \
--docker-password=bootcamp
为什么 Secret 要在 jenkins namespace:Jenkins agent Pod 也在 jenkins namespace,Secret 不跨 namespace。
8. 最小 Jenkinsfile
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:debug
command:
- /busybox/cat
tty: true
volumeMounts:
- name: docker-config
mountPath: /kaniko/.docker
volumes:
- name: docker-config
secret:
secretName: harbor-auth
items:
- key: .dockerconfigjson
path: config.json
'''
}
}
stages {
stage('Build and Push') {
steps {
container('kaniko') {
sh '''
/kaniko/executor \
--context "${WORKSPACE}" \
--dockerfile "${WORKSPACE}/Dockerfile" \
--destination "10.0.24.28:30002/bootcamp/app:${GIT_COMMIT}" \
--destination "10.0.24.28:30002/bootcamp/app:latest" \
--insecure \
--skip-tls-verify
'''
}
}
}
}
}
为什么 Kaniko 用 debug 镜像:标准镜像是 distroless,没 shell;Jenkins K8s agent 需要容器保持运行,debug 带 busybox。
生产不要长期使用 --insecure --skip-tls-verify,应给 Harbor 配 HTTPS。
9. ArgoCD Application 示例
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app
namespace: argocd
spec:
project: default
source:
repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/app-deploy.git
targetRevision: main
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: app
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
如果 Gitea 是 HTTP,ArgoCD repo secret 要允许 insecure:
kubectl create secret generic app-repo \
-n argocd \
--from-literal=type=git \
--from-literal=url=http://gitea-http.gitea.svc.cluster.local:3000/admin/app-deploy.git \
--from-literal=username=admin \
--from-literal=password=bootcamp
kubectl label secret -n argocd app-repo argocd.argoproj.io/secret-type=repository
kubectl patch secret -n argocd app-repo --type=merge \
-p '{"stringData":{"insecure":"true"}}'
10. 查看和排障
Harbor
kubectl get pods -n harbor -o wide
kubectl logs -n harbor deploy/harbor-core --tail=100
kubectl get pvc -n harbor
curl -sS http://154.201.73.31:30002/api/v2.0/systeminfo | jq
Gitea
kubectl get pods,svc,endpoints -n gitea -o wide
kubectl logs -n gitea deploy/gitea --tail=100
curl -sS -I http://45.205.31.214:30022
Jenkins
kubectl get pod -n jenkins -o wide
kubectl logs -n jenkins jenkins-0 -c jenkins --tail=100
kubectl describe pod -n jenkins jenkins-0
kubectl get events -n jenkins --sort-by=.lastTimestamp | tail -50
Jenkins 常见坑:
| 现象 | 原因 | 查法 |
|---|---|---|
| API POST 403 crumb | Jenkins CSRF | 用 crumb + cookie jar |
| agent Pod FailedMount Secret | SA 无权读 Secret 或 Secret 不在同 ns | describe pod |
| Kaniko push 失败 | Harbor 认证或 HTTP registry 配置 | Kaniko 日志、containerd hosts.toml |
ArgoCD
kubectl get app -n argocd
kubectl describe app -n argocd <app>
kubectl logs -n argocd deploy/argocd-application-controller --tail=100
kubectl logs -n argocd deploy/argocd-repo-server --tail=100
ArgoCD 常见坑:
| 现象 | 原因 | 处理 |
|---|---|---|
ComparisonError | repo 连不上或凭据错 | 查 repo-server 日志 |
OutOfSync | Git 和集群状态不同 | 看 diff,确认是否需要 sync |
ImagePullBackOff | Harbor pull secret 缺失 | 给业务 ns 创建 imagePullSecret |
11. 端到端验收
kubectl get pods -n harbor
kubectl get pods -n gitea
kubectl get pods -n jenkins
kubectl get pods -n argocd
curl -sS -I http://154.201.73.31:30002
curl -sS -I http://45.205.31.214:30022
curl -sS -I http://154.201.73.31:30808
curl -sS -I http://154.201.73.31:30080
真实输出(4 套全装好):
Harbor :30002 -> 200 ArgoCD :30080 -> 200
Gitea :30022 -> 200 Jenkins :30808 -> 200
namespace pod: harbor 8/8 · argocd 7/7 · gitea 3/3 · jenkins 1/1 全 Running
入口与账号速查:
| 组件 | 入口 | 账号 |
|---|---|---|
| Harbor | http://<任一节点>:30002 | admin / bootcamp |
| Gitea | http://<任一节点>:30022 | bootcamp / bootcamp |
| Jenkins | http://<任一节点>:30808 | admin / bootcamp |
| ArgoCD | http://<任一节点>:30080 | admin / kubectl get secret -n argocd argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d |
⚠️ Jenkins 首次启动慢(要装一堆插件),
jenkins-0会在1/2 Running停留几分钟,属正常;等2/2再访问。NodePort 是「任一节点 IP + 端口」都能访问(kube-proxy/Cilium 会转发到实际 Pod 所在节点),不必非用 runbook 里写的那个 IP。某个节点公网访问 timeout 而另一个正常时,先查云防火墙是否只放行了部分节点的该端口,别先删 Pod。
完整链路成功标准(platform 装好后,应用层接线 —— 建源码/部署仓库、配 Jenkins job、建 ArgoCD Application —— 可照 8/9 节示例手动做):
- Gitea 能看到代码 commit。
- Jenkins build 成功。
- Harbor 有新镜像 tag。
- 部署 repo 的 image tag 被更新。
- ArgoCD Application
Synced+Healthy。 - 业务 Pod 使用 Harbor 镜像启动成功。