基于Kubernetes的一体化Jenkins CI/CD平台部署与实战指南
1. 项目概述与核心价值
如果你正在寻找一个能一键部署、开箱即用的企业级CI/CD平台,并且希望它原生运行在Kubernetes上,那么这个名为jenkins-stack-kubernetes的项目绝对值得你花时间深入研究。我最近在为一个中型研发团队搭建自动化流水线时,就深度使用了这个方案,它帮我省去了大量繁琐的集成和配置工作。简单来说,这是一个将Jenkins及其完整的生态系统(包括代码仓库、镜像仓库和容器运行时)打包成Kubernetes清单的项目,通过一个脚本就能在K8s集群里拉起一套功能齐全的DevOps工具链。
这套栈的核心价值在于“一体化”和“生产就绪”。它不仅仅是把Jenkins丢进K8s,而是精心集成了Gitea作为轻量级Git服务,Docker Registry作为私有镜像仓库,并且在Jenkins Master容器内预装了几乎涵盖现代基础设施即代码(IaC)和配置管理所有主流工具的最新版本,如Terraform、Pulumi、Ansible、Helm等。这意味着,从代码提交、自动化构建、镜像打包到基础设施部署,整个闭环都可以在这一套环境中完成,非常适合中小团队快速搭建内部研发平台,或者个人开发者学习完整的云原生CI/CD实践。
2. 技术栈深度解析与设计思路
2.1 为什么选择这套技术组合?
作者选择Jenkins作为CI/CD核心,而非GitLab CI或GitHub Actions,我认为是基于Jenkins无与伦比的插件生态和灵活性。在需要深度定制、对接多种异构系统(如不同云厂商、内部监控)的场景下,Jenkins的Pipeline即代码和丰富的插件库依然是强大武器。而将其容器化并部署于Kubernetes,则是利用了K8s强大的调度、自愈和资源隔离能力,让Jenkins Master和构建节点(Agent)的管理变得异常轻松。
集成Gitea而非GitLab或Gitee,是一个兼顾轻量与功能的明智选择。Gitea消耗资源极少,功能却足够团队进行代码协作,完美契合了“All in One Stack”的轻量化理念。内置的Docker Registry则解决了镜像推送的“最后一公里”问题,使得构建出的镜像可以直接推送到集群内部的仓库,加速后续部署流程。这种设计将版本控制、持续集成、镜像托管三个关键环节紧密耦合在同一个网络域内,减少了公网拉取依赖和配置复杂度。
2.2 工具链的选型逻辑:从Ansible到Pulumi
项目预装的工具链堪称“DevOps全家桶”,其选型逻辑清晰反映了现代应用交付的全链路:
- 基础设施配置与管理层:同时集成了Terraform和Pulumi。Terraform是声明式IaC的事实标准,拥有最广泛的Provider支持;而Pulumi允许你用熟悉的编程语言(如Python、Go)来定义基础设施,为开发者提供了另一种选择。这种并存体现了对团队不同技术偏好的包容。
- 配置管理与应用部署层:Ansible负责服务器配置和应用程序部署,其“无代理”和“幂等性”特点非常适合做后续的配置漂移检查和批量操作。Helm则是Kubernetes的应用包管理标准,用于部署复杂的K8s应用。
- 构建与运行时环境:Docker和Kubernetes (kubectl)客户端是必备项,使得Jenkins Pipeline可以直接在容器内执行
docker build和kubectl apply。 - 特色工具:MAASTA和Tf2的引入显示了项目的深度。MAASTA用于集成物理机管理平台MAAS与Terraform、Ansible,适合混合云场景;Tf2(Terraform 2)可能指代特定的工作流或模块,增强了Terraform的协作能力。
这套组合拳确保了无论你的部署目标是公有云、私有云、Kubernetes还是裸金属,都能在Jenkins Pipeline中找到对应的工具链支持。
注意:工具全部安装“latest”版本虽然保证了新鲜度,但在生产环境中可能需要锁定特定版本以确保流水线的稳定性和可重复性。建议在深入使用前,评估这些工具版本与你们现有基础设施的兼容性。
3. 部署实操全流程与核心配置详解
3.1 环境准备与先决条件检查
部署前,必须确保你的Kubernetes集群满足以下三个硬性条件,否则部署过程会失败:
- LoadBalancer服务类型支持:这是为了对外暴露Jenkins、Gitea和Registry的Web界面。如果你在本地环境(如Minikube、Kind)或某些不支持外部负载均衡器的云环境中,需要替代方案。例如,在Minikube中,你可以使用
minikube tunnel命令来模拟LoadBalancer;或者,后续手动将Service类型改为NodePort。 - 动态PVC(PersistentVolumeClaim)支持:Jenkins、Gitea和Registry的数据都需要持久化存储。项目清单中使用了
PersistentVolumeClaim,这意味着你的集群必须配置有StorageClass并支持动态卷制备。你可以通过kubectl get storageclass命令检查是否有默认的StorageClass(标记为default)。 - OpenSSL客户端:用于生成自签名证书。部署脚本会调用OpenSSL为各个服务创建HTTPS证书。确保你的执行机器(通常是本地电脑或跳板机)上安装了OpenSSL。
一个快速的预检脚本可以这样写:
# 检查kubectl配置和集群版本 kubectl cluster-info kubectl version --short # 检查StorageClass kubectl get storageclass # 检查OpenSSL openssl version3.2 一键部署脚本背后的奥秘
项目的核心是一个名为./deploy的Shell脚本。直接运行它看似简单,但理解其内部步骤至关重要,尤其是在出现问题需要排查时。通常,这类脚本会按顺序执行以下操作:
- 生成自签名证书:在项目目录下创建
certs文件夹,并为jenkins.local、gitea.local、registry.local等域名生成密钥和证书。这是实现内部HTTPS访问的基础。 - 创建Kubernetes Secret:将上一步生成的证书文件,通过
kubectl create secret tls命令存入Kubernetes集群的Secret对象中。这样,Pod内的容器才能挂载并使用这些证书。 - 应用Kubernetes清单:按顺序执行
kubectl apply -f命令,部署Namespace、ConfigMap、PersistentVolumeClaim、Deployment、Service等资源。顺序很重要,例如PVC必须在Pod启动前创建好。 - 等待服务就绪:脚本可能会使用
kubectl wait命令,等待所有Pod进入Running状态,并检查Service是否分配了外部IP(对于LoadBalancer类型)。
在实际执行中,我建议不要直接./deploy,而是先分步运行,或者查看脚本内容。你可以:
# 1. 先看脚本内容 cat deploy # 2. 或分步执行(如果脚本是模块化的) ./deploy-certs.sh ./deploy-secrets.sh kubectl apply -f k8s/namespace.yaml kubectl apply -f k8s/pvc/ kubectl apply -f k8s/deployment/ ...这样做的好处是,当某一步出错时,你能精确定位问题。
3.3 访问服务与初始配置
部署成功后,你需要获取各个服务的外部访问地址。由于使用了LoadBalancer,你可以通过以下命令查看:
kubectl get svc -n jenkins-stack找到jenkins、gitea、docker-registry这几个Service,它们的EXTERNAL-IP列就是访问入口。如果显示<pending>,可能是云供应商的负载均衡器还在创建中,稍等片刻。
初始登录与安全配置:
- Jenkins:首次访问,你需要从Jenkins Pod的日志中获取初始管理员密码。执行
kubectl logs <jenkins-pod-name> -n jenkins-stack,在日志开头寻找一串随机密码。登录后,立即按照向导安装推荐的插件(虽然项目已预装很多,但一些基础插件可能仍需通过向导安装),并创建第一个管理员用户。 - Gitea:首次访问会进入安装页面。数据库已经配置为使用内置的SQLite(通过PVC持久化),你主要需要设置管理员账号、服务器域名(可先保持为
gitea.local,后续在Ingress中配置)和邮箱设置。 - Docker Registry:由于使用了自签名证书,你需要在本地的Docker客户端信任该证书,或者通过Jenkins Pod(内部网络已信任)来推送镜像。直接通过浏览器访问Registry的Service IP,你会看到一个简单的JSON响应,表明服务正常。
实操心得:将这三个服务的域名(如 jenkins.your-company.com)通过本地hosts文件或内部DNS,解析到对应的
EXTERNAL-IP,会极大方便日常使用。在生产环境,这正是配置Ingress和正式SSL证书(如Let‘s Encrypt)的前置步骤。
4. 核心组件配置与优化指南
4.1 Jenkins Master的定制与插件管理
项目通过Dockerfile构建了一个功能强大的Jenkins镜像。如果你需要定制,比如增加特定版本的JDK、Node.js或Python,应该修改项目的Dockerfile并重新构建镜像。但更常见的需求是管理插件。
项目声称安装了“50个最有用插件”,具体列表在Dockerfile中。Jenkins插件依赖关系复杂,升级时容易冲突。我建议的维护策略是:
- 导出当前插件列表:在Jenkins管理界面,“系统管理” -> “插件管理” -> “已安装”标签页,可以查看列表。但更程序化的方式是,进入Jenkins Pod,查看
/var/jenkins_home/plugins/目录,或者使用Jenkins Configuration as Code (JCasC)插件来声明式管理插件。 - 增量添加插件:如果需要新插件,最好在Jenkins的Web界面手动安装,并观察是否与现有插件冲突。稳定后,可以将插件名和版本回写到Dockerfile的插件安装列表中,以便后续重建镜像时能包含。
- 备份插件配置:Jenkins的核心是
/var/jenkins_home目录。定期备份这个目录对应的PVC,是恢复Jenkins状态最可靠的方式。
4.2 利用Kubernetes构建代理(Agent)
这是本方案最强大的特性之一。传统的Jenkins Agent需要单独维护虚拟机或容器,而在这里,你可以配置Jenkins使用Kubernetes插件来动态创建构建Pod。
项目可能已经预装了kubernetes插件。你需要配置“云”设置:
- 进入“系统管理” -> “节点管理” -> “配置云”。
- 添加一个“Kubernetes”云。
- 关键配置:
- Kubernetes地址:通常是
https://kubernetes.default.svc.cluster.local(集群内服务发现地址)。 - Jenkins地址:需要填写Jenkins Service在K8s集群内部的地址,如
http://jenkins.jenkins-stack.svc.cluster.local:8080。这是最容易出错的地方,如果填错,Agent Pod将无法连接回Master。 - Pod模板:定义你的构建Pod的默认镜像、资源限制(CPU/Memory)、卷挂载等。你可以指定一个包含项目所需所有工具的Docker镜像作为默认镜像,这样每个构建任务都能在一个干净、一致的环境中运行。
- Kubernetes地址:通常是
配置成功后,当你运行一个Pipeline任务并指定agent { kubernetes { ... } }时,Jenkins会自动在指定的Namespace中启动一个Pod来执行构建任务,任务完成后Pod自动销毁。这实现了极佳的资源利用率和环境隔离。
4.3 Gitea与Docker Registry的集成要点
Gitea:
- 仓库克隆地址:在Gitea中创建仓库后,其克隆地址是内部Service地址(如
http://gitea:3000)。在Jenkins Pipeline中,可以直接使用这个地址进行克隆,速度最快。 - Webhook配置:这是实现提交即触发构建的关键。在Gitea仓库的设置中,添加Webhook,Payload URL填写Jenkins的通用Git Hook地址:
http://jenkins:8080/gitea-webhook/post。确保Jenkins中安装了Gitea插件,并在任务中配置了“Gitea触发构建”。
Docker Registry:
- 内部推送:在Jenkins Agent Pod中,由于它们与Registry在同一个K8s集群网络,可以直接使用Service名作为地址进行推送和拉取,例如
docker push docker-registry:5000/myapp:latest。 - 外部访问与认证:项目默认可能未配置认证。对于生产环境,必须为Registry启用HTTPS和基础认证。你可以修改Registry的Deployment,添加环境变量(如
REGISTRY_AUTH)或挂载htpasswd文件来配置认证。同时,需要在Jenkins中配置对应的Docker Registry凭证。
5. 生产环境进阶考量与安全加固
5.1 网络暴露与Ingress集成
项目TODO列表中提到了通过Ingress暴露服务,这是生产环境的必经之路。使用Ingress(配合Nginx Ingress Controller或Traefik)的好处是:
- 统一的访问入口:一个公网IP和域名,通过不同路径(如
/jenkins,/gitea)或子域名来区分服务。 - 集中管理SSL/TLS:可以在Ingress层面统一配置SSL证书,无需每个服务各自处理。
- 更精细的路由规则:可以配置基于路径的重写、认证等。
配置示例(Nginx Ingress):
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: jenkins-stack-ingress namespace: jenkins-stack annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: tls: - hosts: - devops.your-company.com secretName: your-tls-secret # 指向存放正式证书的Secret rules: - host: devops.your-company.com http: paths: - path: /jenkins pathType: Prefix backend: service: name: jenkins port: number: 8080 - path: /gitea pathType: Prefix backend: service: name: gitea port: number: 3000部署Ingress后,你需要将LoadBalancer类型的Service改为ClusterIP类型,因为流量将通过Ingress Controller进入。
5.2 持久化存储与备份策略
数据是无价的。项目使用了PVC,但你需要确保底层存储的可靠性和性能。
- 存储类选择:根据你的集群环境,选择高性能的SSD存储类用于频繁读写的Jenkins工作空间,选择标准存储类用于Gitea和Registry的镜像存储。
- 备份方案:
- Jenkins:定期使用
thinBackup插件备份JENKINS_HOME到对象存储(如S3)。或者,直接对PVC进行快照(如果存储驱动支持)。 - Gitea:Gitea提供了完整的
dump命令,可以备份所有仓库、数据库和附件到单个压缩文件。可以创建一个CronJob定期执行备份并上传到远程存储。 - Registry:Registry的镜像数据存储在文件系统中。备份策略可以是定期将存储卷快照,或者使用Registry的垃圾回收机制清理旧镜像后,复制整个
/var/lib/registry目录。
- Jenkins:定期使用
5.3 监控、日志与资源限制
一个健康的平台离不开可观测性。
- 资源配额(Resource Quotas)和限制(Limits):务必为Jenkins、Gitea、Registry的Pod设置合理的CPU和内存资源请求(requests)与限制(limits)。特别是Jenkins Master,内存建议至少2Gi。对于动态创建的Jenkins Agent Pod,也需要在Pod模板中设置默认的资源限制,防止单个构建任务耗尽节点资源。
- 监控:为Namespace部署Prometheus Operator和对应的ServiceMonitor,采集各服务的指标(如Jenkins的队列长度、构建时间,Gitea的API响应时间)。使用Grafana进行可视化。
- 日志集中:部署EFK(Elasticsearch, Fluentd, Kibana)或Loki堆栈,将各容器的标准输出日志统一收集起来,便于故障排查和审计。
6. 常见问题排查与实战经验
在实际部署和使用过程中,我遇到并总结了一些典型问题及其解决方案:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
./deploy脚本执行失败,提示证书生成错误。 | 1. OpenSSL未安装或版本不兼容。 2. 脚本中的域名包含非法字符。 | 1. 运行openssl version确认安装。2. 检查脚本中用于生成证书的域名(如 jenkins.local)是否格式正确。可以尝试手动执行脚本中的openssl命令看具体报错。 |
Pod 一直处于Pending状态。 | 1. PVC无法绑定(StorageClass不可用或资源不足)。 2. 节点资源不足(CPU/内存)。 3. 节点Selector或污点(Taint)不匹配。 | 1.kubectl describe pod <pod-name>查看事件(Events),常见是“waiting for volume to bind”。2. kubectl get pvc查看PVC状态是否为Bound。3. kubectl describe node查看节点资源分配情况。 |
| Jenkins Web界面可以访问,但无法登录,或插件加载失败。 | 1. 初始密码获取错误。 2. /var/jenkins_home目录权限问题。3. 网络问题导致插件中心连接失败。 | 1. 重新从Pod日志中获取密码,注意可能是多行。 2. 进入Pod检查目录权限: kubectl exec -it <jenkins-pod> -- ls -la /var/jenkins_home。确保用户jenkins(通常是UID 1000)有读写权限。3. 在Jenkins的“插件管理”->“高级”中,更换更新站点为国内镜像(如清华镜像)。 |
Jenkins Pipeline中无法连接Kubernetes集群(kubectl命令失败)。 | 1. Pod内没有正确的kubeconfig文件或令牌。 2. ServiceAccount权限不足。 | 1. 确保Jenkins Master的Pod挂载了ServiceAccount的令牌卷(默认已挂载)。 2. 为Jenkins所在的ServiceAccount(如 default)绑定更高的ClusterRole,例如cluster-admin(仅限测试环境)或按需创建最小权限角色。 |
| 构建时无法从内部Docker Registry拉取基础镜像。 | 1. Docker守护进程未配置对私有Registry的信任(针对自签名证书)。 2. Registry服务本身未正常运行。 | 1. 在Jenkins Agent的Pod模板中,添加一个步骤或在容器内执行docker login,或者修改Docker守护进程配置(/etc/docker/daemon.json)添加"insecure-registries": ["docker-registry:5000"](不推荐生产)。2. kubectl get pods,svc -n jenkins-stack检查Registry服务状态。 |
| Gitea的Webhook无法触发Jenkins构建。 | 1. Webhook地址错误。 2. Jenkins Gitea插件未正确配置。 3. 网络策略(NetworkPolicy)阻止了跨Pod通信。 | 1. 确认Gitea中Webhook的URL是Jenkins Service的内部地址,并带/gitea-webhook/post路径。2. 在Jenkins系统配置中,正确设置Gitea服务器连接信息。 3. 检查是否部署了NetworkPolicy,确保 jenkins-stack命名空间内的Pod可以相互通信。 |
一个关键的实操心得:在Pipeline中,尽量使用container指令在特定的工具容器中执行步骤,而不是依赖宿主机(Agent Pod)上预装的工具。例如:
pipeline { agent { kubernetes { yaml ''' apiVersion: v1 kind: Pod spec: containers: - name: jnlp image: jenkins/inbound-agent:latest - name: docker image: docker:latest command: ['cat'] tty: true volumeMounts: - mountPath: /var/run/docker.sock name: docker-sock volumes: - name: docker-sock hostPath: path: /var/run/docker.sock ''' } } stages { stage('Build') { steps { container('docker') { // 明确在docker工具容器中执行 sh 'docker build -t myapp .' } } } } }这样做的好处是工具版本隔离,且与Jenkins Master镜像解耦,灵活性更高。
最后,这个项目是一个极佳的起点和参考模板,但它并非银弹。将其投入生产前,务必根据你的团队规模、安全要求和基础设施现状,在网络、存储、认证、授权和监控方面进行充分的加固和定制。它的真正价值在于提供了一个经过整合的、可工作的完整范例,让你能跳过从零集成的痛苦,直接进入优化和深度使用的阶段。
