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
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.enabled=true \
--set valkey.enabled=true
入口:
http://45.205.31.214:30022
如果没有把 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
完整链路成功标准:
- Gitea 能看到代码 commit。
- Jenkins build 成功。
- Harbor 有新镜像 tag。
- 部署 repo 的 image tag 被更新。
- ArgoCD Application
Synced+Healthy。 - 业务 Pod 使用 Harbor 镜像启动成功。