☰
CI/CD平台搭建:从GitLab到K8s全链路实践
2026/10/2 2:07:12 网站建设 项目流程

搞了这么多年的持续交付,我一直觉得“CI/CD平台搭建”是个被严重低估的工程活。很多团队手里的工具其实都不差,代码仓库、构建机、镜像仓库、编排系统样样都有,但把它们串成一条能自动跑的流水线,就总会卡在某个环节:Jenkins连不上GitLab、镜像推不进Harbor、K8s拉不到私有仓库的包、Rancher导入集群后节点全是NotReady。这些坑我基本都踩过一遍,所以每次看到有人问“GitLab+Jenkins+Docker+Harbor+K8s+Rancher怎么搭一套CICD”,我都想把完整的思路和细节直接扔过去。

这篇文章不是官方文档式的罗列,而是我实际从头搭建这套平台、并把一个Java微服务应用自动发布到K8s集群的经验复盘。文里会讲清楚每个组件为什么选它、全链路数据是怎么流转的、关键配置该怎么写、以及那些不翻文档根本发现不了的坑。目标读者很明确:想在公司内部落地一套自建DevOps平台,或者准备考K8s相关认证、需要亲手搭一套完整环境来练手的人。如果你是这种人,这篇文章应该能省下你至少一周的摸索时间。

1. 整链路设计与组件选型思路

1.1 为什么偏偏是这六个组件

先聊一个最容易被忽略的问题:为什么是GitLab、Jenkins、Docker、Harbor、K8s、Rancher这六样,而不是别的?

先说GitLab。代码仓库的选择其实很多,GitHub、Gitea、Bitbucket都行,但GitLab在企业内网落地有一个非常大的优势:它自带完整的DevOps生命周期管理,从代码托管、Merge Request评审到CI/CD、容器镜像仓库全都有。虽然我们这里不用它的内置CI,但代码托管、分支权限、Webhook这些能力非常成熟,社区版免费功能也够用。更重要的是,很多公司已经有现成的GitLab实例,这套链路可以无缝接上去。

Jenkins是我在这套链路里最坚持的选择。有人会问“GitLab自带CI,为什么还要单独搞Jenkins?”我的回答是:Jenkins的插件生态和Pipeline自由度目前依然是所有CI引擎里最强的。你可以用Groovy写任意复杂的构建逻辑,几百个插件覆盖你能想象到的所有构建场景。而GitLab CI更适合轻量级、以YAML为主的流水线,一旦遇到复杂的多分支策略、跨项目编排,或者需要和大量内部系统对接时,Jenkins的可控性会明显更好。

Docker不用多说,它是整个容器化链路的地基。需要特别区分的是Docker和K8s的关系:Docker负责把应用打包成标准化的镜像,并负责单台机器上容器的运行;K8s负责的是跨多台机器的容器编排、调度、伸缩和故障恢复。按生活化类比来说,Docker是“打包工”,K8s是“调度中心”,一个是生产标准件,一个是管理标准件怎么摆放和替换。

Harbor则是解决镜像存储和分发的问题。你可能觉得“Docker Registry不也够用吗?”但Harbor在企业场景下多提供了几个关键能力:基于角色的访问控制、镜像复制同步、漏洞扫描、审计日志。特别是多环境部署时,Harbor的镜像复制功能可以让你把镜像从测试环境同步到生产环境,这个在合规审计里几乎是刚需。

最后是Rancher。它在K8s之上做了一层统一管理面板,就像给你的K8s集群装了套“带界面的控制台”。对于团队里不习惯整天敲kubectl命令的运维和开发,Rancher的UI操作非常友好,而且它支持一个界面同时管理多套K8s集群,这在测试环境、预生产、生产多套集群并行时非常实用。引入Rancher不是为了替代K8s,而是为了降低K8s的使用门槛。

这套组合的本质是一条完整的“代码到应用发布”流水线,每个组件负责一个不可替代的环节,环环相扣。

1.2 全链路工作流程拆解

先把整条链路的数据流讲透,后续配置你才能理解“为什么这个参数要这么填”。

当一个开发者在本地把代码push到GitLab的指定分支后,GitLab会根据仓库里预先配置的Webhook,往Jenkins发送一个HTTP请求,内容大概是“某分支有新提交了”。Jenkins收到请求后,会根据触发规则找到对应的流水线任务,开始执行构建。

构建阶段通常包括:从GitLab拉取最新代码,在构建机或Jenkins容器内执行Maven或Gradle打包生成可执行文件,然后读取项目里的Dockerfile,把可执行文件打成Docker镜像。这里的镜像不是推送到Docker Hub,而是推送到公司内部部署的Harbor私有仓库。

镜像推送到Harbor之后,K8s集群并不会自动感知。实际上,还需要再走一步:Jenkins在镜像推送成功后,会执行kubectl命令,修改K8s集群里的Deployment配置里的镜像版本号,然后触发滚动更新。如果Harbor是私有的,K8s节点在拉取镜像时还需要预先配置一个imagePullSecret,否则会出现永远ImagePullBackOff的报错。

如果你问“为什么不让K8s那边定时去Harbor拉最新镜像?”这个问题问得很好。一个镜像tag如果永远是latest,你就永远不知道线上跑的到底是哪个版本。所以正确做法是给镜像打上唯一tag,比如build-20250317-001,然后通过修改Deployment的镜像tag来触发更新。这既保证了可追溯性,也让回滚变得简单:改回旧的镜像tag即可。

1.3 资源规划与部署架构

开始动手前,先规划机器资源。最低配置和推荐配置差别很大,我建议按下面的标准来:

  • 最小可用架构(个人学习/小型团队试用):一台8核16G的服务器,将所有组件通过Docker Compose方式部署在同一台机器上;K8s使用单节点集群(minikube或者kubeadm单节点都可以)。8G内存其实很紧张,实际跑起来后K8s系统组件大概要占2G多,Jenkins和GitLab各占1到2G,Harbor也要1G左右。所以16G是底线,再多几个微服务就很容易打满。
  • 推荐生产架构(正式环境):至少3台4核8G的节点组成K8s集群,再单独准备两台4核8G的机器,分别跑GitLab+Jenkins和Harbor。生产环境不建议把GitLab和Jenkins塞进K8s集群里,除非你已经有成熟的K8s运维能力,否则一旦集群出问题,CI系统也跟着挂,就违背了“稳定”的初衷。

架构上再强调一个容易犯的错误:不要在生产环境把Harbor和K8s集群混部。因为Harbor挂了,K8s节点虽然不会立刻停止运行,但一旦Pod需要重新调度到新节点、或者镜像需要重新拉取时,就会卡死。这是我在一次故障演练后深刻体会到的。

2. 环境准备与基础组件部署

2.1 系统初始化与前置配置

我实际部署用的操作系统是Ubuntu 22.04 LTS和CentOS 7.9都跑过。如果你用CentOS 7,需要注意内核版本对K8s的支持,建议先升级内核;Ubuntu则省心很多。开始前先做几项通用初始化:

  • 更新系统源并安装基础工具:yum install -y vim net-tools wget(或apt install -y vim net-tools wget)
  • 关闭swap:K8s默认要求关闭swap分区,否则kubelet无法启动。swapoff -a并注释掉/etc/fstab里的swap行
  • 加载内核模块并调整系统参数,/etc/sysctl.d/k8s.conf里至少要有:
    net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 vm.swappiness = 0
    这是K8s网络通信的基础,不配置的话Pod网络会有问题。
  • 修改主机名,并保证每台机器的主机名能互相解析。很多节点NotReady的坑就是主机名解析失败导致的。最简单的做法是编辑/etc/hosts,把三台机器的IP和主机名写进去。
  • 时间同步。K8s对时间同步要求很严格,证书校验、事件时间戳都依赖准确的系统时间。chronyc sources确认时间源正常。

2.2 GitLab部署与初始化

GitLab的部署方式,最简单的就是用官方Docker镜像跑单容器。虽然GitLab官方更推荐用Omnibus包直接装在宿主机上,但既然我们整套链路都容器化,用Docker部署也更便于迁移和备份。我的部署命令大致如下:

docker run -d \ --name gitlab \ --restart always \ -p 2222:22 -p 80:80 -p 443:443 \ -v /data/gitlab/config:/etc/gitlab \ -v /data/gitlab/logs:/var/log/gitlab \ -v /data/gitlab/data:/var/opt/gitlab \ -e GITLAB_OMNIBUS_CONFIG="external_url 'http://gitlab.example.com'; gitlab_rails['gitlab_shell_ssh_port'] = 2222" \ gitlab/gitlab-ce:latest

注意三个细节。第一,external_url必须设置为你希望用户访问的地址,如果后面用户通过git clone http://主机的IP/xxx.git去拉代码,这个URL就会显示成你配置的地址。热词里有人问“clone with http怎么clone设置为域名 不是机器id”,答案就在这里:在GitLab管理后台把External URL改成域名,或者改/etc/gitlab/gitlab.rb里的external_url配置后重新配置。第二,宿主机22端口如果被占用,就映射成2222,但GitLab克隆SSH地址时要记得带上这个端口,否则连不上。第三,GitLab容器首次启动耗时比较久,需要几分钟初始化,不能急。可以通过docker logs -f gitlab监控日志,看到“GitLab is ready”或类似提示后再访问。

首次访问时,浏览器打开GitLab地址,系统会要求设置root密码。很多人忽略了这个细节,导致后面积累了数据后忘了root密码。更推荐的做法是直接查看容器内生成的初始密码:

docker exec gitlab cat /etc/gitlab/initial_root_password

用这个密码登录后立刻修改。

日常使用中,开发者在GitLab上最好配置SSH Key,这样拉取和推送代码时不用每次输密码。在GitLab页面上,头像菜单里找到Settings → SSH Keys,把本地生成的~/.ssh/id_rsa.pub内容粘贴进去。这个动作看着简单,但在整个CI/CD链路里非常重要,因为后面Jenkins要拉取GitLab代码,用SSH凭据方式是最稳妥的。

2.3 Harbor私有镜像仓库部署

Harbor的部署方式和GitLab类似,但步骤会更繁琐一些,因为它还依赖PostgreSQL、Redis等组件。官方提供了在线和离线安装包,企业内部网络环境建议下载离线包,避免安装过程中拉取外部镜像超时。

下载并解压后,会得到一个harbor.yml文件,这个文件需要改几个关键项:

hostname: harbor.internal.com http: port: 8080 harbor_admin_password: Admin@12345 database: password: Db@12345

这里有一个特别容易踩坑的地方:Harbor对密码强度有要求,必须包含大小写字母和数字,否则启动时会在配置校验阶段报错。热词里有人遇到“harbor happened in config validation”,十有八九就是这个原因。我一开始就吃了这个亏,设置了一个太简单的密码,结果./install.sh跑起来后看到一堆validation error,翻了好一会儿日志才定位到问题。

hostname字段很关键,它会被写入镜像tag里。比如你配置的是harbor.internal.com,那么推送镜像时就必须写成harbor.internal.com/library/myservice:latest。如果你在/etc/hosts里把这个域名映射到Harbor所在机器的IP,那么本地测试也能正常推送。这里要提醒:如果Harbor不启用HTTPS,那Docker客户端必须在/etc/docker/daemon.json里配置:

{ "insecure-registries": ["harbor.internal.com:8080"] }

否则执行docker login harbor.internal.com:8080时会报“certificate signed by unknown authority”或“http: server gave HTTP response to HTTPS client”。配置好后重启Docker服务,登录Harbor才能成功。

安装时我通常会执行./install.sh --with-trivy,把漏洞扫描组件也带上,虽然会多占一些内存,但扫描镜像漏洞功能对生产环境非常重要,可以在镜像进入K8s前就拦截掉高危漏洞。在Harbor页面里,创建一个名为library的项目,把它设为公开或私有都行。建议公开,这样K8s集群拉取时就不用每次都配置凭据,但前提是Harbor只在内网可达。

3. Jenkins流水线实现CI

3.1 Jenkins安装与插件准备

Jenkins的安装方式我推荐直接用官方war包配合systemd管理,或者用Docker跑都行。我习惯用Docker方式,因为可以单独隔离环境,不污染宿主机。但要注意一点:如果用Docker方式跑Jenkins,而且你还想在Jenkins里直接用宿主机的Docker来构建镜像,就需要把宿主机/var/run/docker.sock挂载进Jenkins容器,否则Jenkins内部没法调用Docker命令。这一点很多人没注意到,导致流水线里执行docker build时报“Cannot connect to the Docker daemon”。

我的Jenkins启动命令大致是:

docker run -d \ --name jenkins \ -p 8080:8080 -p 50000:50000 \ -v /data/jenkins_home:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /usr/bin/docker:/usr/bin/docker \ --restart always \ jenkins/jenkins:lts

首次启动后,浏览器访问http://jenkins_ip:8080,会要求输入解锁密码。这个密码在Jenkins容器里的/var/jenkins_home/secrets/initialAdminPassword文件里,可以直接用docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword获取。

插件选择上,建议在安装向导里直接选“Install suggested plugins”,然后在系统管理里再额外安装几个关键插件:

  • Git plugin:拉取GitLab代码的基础插件,默认会带上
  • Docker Pipeline:在流水线里执行docker build、docker push需要使用
  • Kubernetes CLI:用于在流水线里执行kubectl命令,后面CD阶段会用到
  • GitLab插件:用于和GitLab Webhook集成,同时支持在pipeline中调用GitLab API

插件安装完成后,第一件事就是配置“系统管理 → Manage Credentials”里的凭据。Jenkins连接GitLab,我踩过的坑是在凭据类型上选错了。如果你用SSH方式拉代码,凭据类型选“SSH Username with private key”,填入GitLab部署用户的私钥。如果你用HTTP方式,那就用GitLab的Access Token作为密码,用户名可以写任意值。热词里有人遇到“login failed. check api token or gitlab version”,这个报错通常是GitLab的Access Token权限不足或已过期,需要在GitLab里重新生成一个具有api和read_repository权限的token,再回Jenkins更新凭据。

3.2 构建Java微服务Pipeline示例

这里给出一个典型的Java Maven项目流水线片段,用Jenkins Pipeline语法编写。这个示例涵盖了拉代码、构建、打镜像、推镜像四个阶段,是整条CI流水线的核心逻辑:

pipeline { agent any environment { DOCKER_REGISTRY = 'harbor.internal.com:8080' IMAGE_REPO = 'library/my-service' GITLAB_CRED = credentials('gitlab-ssh-key') } stages { stage('Checkout') { steps { checkout([$class: 'GitSCM', branches: [[name: '*/main']], userRemoteConfigs: [[url: 'git@gitlab.internal.com:dev/my-service.git', credentialsId: 'gitlab-ssh-key']] ]) } } stage('Build') { steps { sh 'mvn clean package -DskipTests' } } stage('Build Docker Image') { steps { script { def image = "${DOCKER_REGISTRY}/${IMAGE_REPO}:build-${BUILD_NUMBER}" sh "docker build -t ${image} ." sh "docker push ${image}" } } } } }

这段代码里值得注意的有几点。

第一,镜像的tag我用了build-${BUILD_NUMBER},而不是latest。这在前面已经解释过原因:为了可追溯和可回滚。BUILD_NUMBER是Jenkins内置的环境变量,每次任务执行都会自动递增,天然是唯一的。如果你想同时保留多个特征(比如分支名、Commit Hash),可以继续拼接,比如${BRANCH_NAME}-${GIT_COMMIT.take(7)}-${BUILD_NUMBER},但要注意tag里不能有斜杠,分支名如果带斜杠要处理一下。

第二,流水线里用了credentials('gitlab-ssh-key'),引用的是之前创建的凭据ID。Jenkins会用这个凭据去拉取GitLab代码。如果你遇到“Host key verification failed”的报错,说明Jenkins容器里没有把GitLab的主机公钥加入known_hosts。解决办法是在Jenkins容器里执行ssh-keyscan gitlab.internal.com >> ~/.ssh/known_hosts,或者更稳妥的方式是在流水线里加一行sh 'GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git pull'。

第三,执行docker build时有个经常被忽视的细节:Dockerfile里如果基于的base image也要从Harbor拉取,那构建时会提示找不到镜像。这时候的解决办法和K8s节点拉镜像一样,在Jenkins所在的宿主机Docker配置里加入insecure-registries,并且在构建前先执行docker login。

3.3 GitLab Webhook联动排查

CI配置好之后,还需要把GitLab和Jenkins联动起来:当代码push时,GitLab自动触发Jenkins构建。在GitLab项目页面的“Settings → Webhooks”里,填写Jenkins的构建触发地址,格式是:

http://jenkins_ip:8080/project/你的任务名

同时添加一个Secret Token,这个token就是之前我们创建的GitLab API Token。GitLab会把这个token放到请求头里,Jenkins确认后才执行。如果你用了反向代理或域名访问Jenkins,务必在系统管理里把Jenkins URL配置成对外可访问的地址,否则Webhook回调会失败。

我遇到过的一个典型问题是:Webhook配置时点“Test”显示成功,但代码push就是不会触发构建。后来发现是Jenkins任务配置里只选了“Build when a change is pushed to GitLab”,却没有勾选对应的分支过滤规则。在Jenkins任务配置的GitLab触发面板里,要明确填写触发分支名(如main),GitLab的push事件才会被正确匹配。

还有一次踩坑是出于安全考虑给Jenkins加了一层Nginx反向代理,导致GitLab的Webhook走HTTP回调到Jenkins时,Jenkins拿到的是代理IP,而不是GitLab请求的真实来源IP。这不影响构建触发,但影响Jenkins的权限控制日志。如果不想深究这个,直接用IP加端口的方式访问Jenkins是最省心的。

4. K8s集群搭建与Rancher接入实现CD

4.1 K8s集群搭建方式对比

从CI阶段进入CD阶段,核心就是把构建好的镜像真正跑起来。这一步的关键是K8s集群本身。

K8s集群的搭建方式,我用过kubeadm手动搭、也用过sealos一键部署。kubeadm是官方标准方式,适合学习原理,但步骤多、容易出错,尤其在网络组件和证书配置上。sealos则是傻瓜式操作,一行命令能拉起来一个高可用集群,适合生产快速部署。如果你的目的是搭建一套可用的CICD平台,而不是深入学习K8s集群原理,强烈建议用sealos或类似工具节省时间。

用kubeadm手动搭集群的大致步骤包括:

  1. 在所有节点安装kubelet、kubeadm、kubectl,版本要一致
  2. 在master节点执行kubeadm init --apiserver-advertise-address=主IP --pod-network-cidr=10.244.0.0/16
  3. 按输出提示配置kubectl的kubeconfig文件
  4. 安装Pod网络插件,我常用的Calico或Flannel
  5. 将worker节点用kubeadm join命令加入集群

这里需要强调“kubectl配置文件”是什么。很多新手会在热词里搜“gitlab 怎么设置kubectl 配置文件”,其实这个文件不是只给GitLab用的,而是K8s的访问凭据,默认路径是~/.kube/config。里面包含了集群地址、证书、用户信息。后面Jenkins要用kubectl操作K8s集群,就必须把这个config文件内容放到Jenkins的凭据里,或者直接挂载到Jenkins容器中。

热词里还有“单节点 k8s 上的若依微服务整套环境”,这说明很多人想在单机学习环境里跑完整的微服务。这里有个关键点:单节点集群默认control-plane节点是不允许调度业务Pod的,需要用一条命令去掉这个限制:

kubectl taint nodes --all node-role.kubernetes.io/master-

否则你部署应用时Pod会一直卡在Pending状态。

4.2 Rancher部署与集群导入

K8s集群起来后,我不会急着直接用命令行部署应用,而是先把Rancher装上。Rancher有两种部署方式:一种是docker run直接跑单容器,另一种是Helm方式部署到K8s集群里。如果你想用Rancher监控和管理现有集群,直接在任意一台有Docker的机器上跑:

docker run -d \ --name rancher \ --restart unless-stopped \ -p 80:80 -p 443:443 \ --privileged \ rancher/rancher:latest

Rancher首次启动后访问UI,会让你设置管理员密码和服务器URL。服务器URL填写Rancher服务对外访问的地址,整个后续的操作都基于这个地址做跳转。

Rancher UI里的核心操作是“导入集群”。点“添加集群 → 现有集群”,Rancher会生成一个用于导入的kubectl命令,在你的K8s master节点上执行这个命令,Rancher会自动安装一些管理组件,并在几分钟之内把集群状态同步到UI。导入后你可以在Rancher上直接看到集群的节点信息、工作负载、服务发现这些内容,完全不用再去记复杂的kubectl命令。Rancher本身就相当于K8s的一个“驾驶舱”。

但这里要特别提醒一个点:Rancher和K8s集群的版本兼容性问题。太新的Rancher版本可能不支持老的K8s版本,反之亦然。我第一次搭的时候Rancher用最新版,K8s用的却是1.20,结果导入后一堆组件状态异常。后来把K8s升级到和Rancher兼容的版本,问题才解决。建议在搭建前查一下官方兼容性表格,别用太古老或太新的组合。

4.3 应用部署与私有仓库拉取

应用部署到K8s,本质上就是创建Deployment和Service资源。以我们前面构建推送的harbor.internal.com:8080/library/my-service:build-123镜像为例,最小可用的Deployment YAML如下:

apiVersion: apps/v1 kind: Deployment metadata: name: my-service namespace: dev spec: replicas: 2 selector: matchLabels: app: my-service template: metadata: labels: app: my-service spec: containers: - name: my-service image: harbor.internal.com:8080/library/my-service:build-123 ports: - containerPort: 8080

如果你直接执行kubectl apply -f deploy.yaml,大概率会遇到ImagePullBackOff,因为K8s节点尝试去Harbor拉镜像,但Harbor是私有的,没有登录凭据。解决办法是先在K8s集群里创建一个docker-registry类型的Secret:

kubectl create secret docker-registry harbor-secret \ --docker-server=harbor.internal.com:8080 \ --docker-username=admin \ --docker-password='Admin@12345' \ --namespace=dev

然后在Deployment的模板里加上:

imagePullSecrets: - name: harbor-secret

加了这一段的Deployment才能成功从私有仓库拉取镜像。这是整个CD流程里最容易被忽略、也是报错率最高的一个步骤。很多人在Rancher的UI里直接部署,却忘了配置Image Pull Secret,结果页面一直显示拉取失败。如果你用Rancher UI部署,可以在工作负载页面里配置“镜像拉取凭据”,原理和命令行完全一样。

应用跑起来后,你可以用kubectl get pods确认状态,再用kubectl logs查看日志。如果要让外部访问,还需要创建Service,类型可以是NodePort、LoadBalancer或者Ingress,这个根据你的网络环境来选。在Rancher里操作更加直观,创建Service时选择端口映射,几秒钟就能完成。

4.4 Jenkins到K8s的CD打通

有了Deployment YAML和集群访问凭据后,最后一步就是把Jenkins构建完镜像之后的操作串起来:镜像推送到Harbor后,自动更新K8s里的Deployment镜像版本。

我用的方案是:Jenkins里新增一个“Deploy to K8s”阶段,用kubectl命令直接更新Deployment的镜像tag。

第一步,在Jenkins里配置K8s集群的kubeconfig凭据。系统管理 → Manage Credentials,凭据类型选择“Secret file”,上传~/.kube/config文件的内容。然后在Pipeline里引用这个凭据,把它写到工作目录:

stage('Deploy to K8s') { steps { withCredentials([file(credentialsId: 'k8s-kubeconfig', variable: 'KUBECONFIG')]) { sh """ export KUBECONFIG=${KUBECONFIG} kubectl set image deployment/my-service my-service=${DOCKER_REGISTRY}/${IMAGE_REPO}:build-${BUILD_NUMBER} -n dev """ } } }

这一步直接通过kubectl set image命令触发Deployment滚动更新,K8s会自动拉取新镜像并逐步替换旧Pod。好处是不用写复杂的YAML,一行命令就能完成发布。

如果你的Deployment配置比较复杂(比如有环境变量、配置映射、健康检查),更稳妥的方式是在GitLab仓库里维护一份deploy.yaml模板,然后在Jenkins里用sed或模板工具把镜像tag替换进去,再执行kubectl apply -f deploy.yaml。替换命令大概长这样:

sed -i "s|image: harbor.internal.com:8080/library/my-service:.*|image: ${DOCKER_REGISTRY}/${IMAGE_REPO}:build-${BUILD_NUMBER}|g" deploy.yaml kubectl apply -f deploy.yaml

这里的正则要点是:.*用来匹配旧的tag,而不关心它具体是什么,所以每构建一次,Deployment都会指向新镜像。而且这种方式能顺带更新Service、ConfigMap等其他资源,适合微服务数量多的情况。

还要注意权限问题:Jenkins通过kubeconfig访问K8s集群时,用的是kubeconfig里内置的用户,通常是admin用户。这么做在测试环境没问题,但生产环境强烈建议创建一个专用ServiceAccount,只授予需要的命名空间权限,避免Jenkins误操作其他资源。这个操作可以通过RBAC来实现:

kubectl create serviceaccount jenkins -n dev kubectl create rolebinding jenkins-dev-binding \ --clusterrole=edit --serviceaccount=dev:jenkins -n dev

然后把这个ServiceAccount对应的token配置到Jenkins的Kubeconfig里。权限最小化是生产安全的基本要求,我实际在正式环境就是这么做的。

5. 常见问题与排查技巧实录

5.1 高频报错速查表

整理了这套平台搭建过程中我遇到的和身边朋友问得最多的问题,做一个速查表,方便你按图索骥:

现象常见原因排查与解决思路
GitLab页面无法访问容器未启动完成或external_url配置错误docker logs看omibus日志,确认initial_root_password出现后再访问
推送Harbor报x509证书错误Docker未配置insecure-registries或Harbor证书问题检查/etc/docker/daemon.json,重启Docker,再执行docker login验证
Harbor初始化报config validation错误密码强度不够或hostname填写错误确保hostname不包含协议,密码包含大小写字母和数字
Jenkins无法连接GitLabToken权限不足或Jenkins插件版本过旧在GitLab生成新Token,勾选api和read_repository权限,更新Jenkins凭据
Jenkins执行docker build无权限Jenkins容器没挂载docker.sock启动Jenkins时挂载/var/run/docker.sock,并将jenkins用户加入docker组
K8s Pod一直Pending单节点master有污点未去除或资源不足kubectl describe pod看事件,执行去除taint命令或检查节点CPU/内存
Pod状态ImagePullBackOff没有配置imagePullSecrets或Harbor地址不通创建docker-registry Secret,在Deployment里引用;用docker pull在节点上验证地址可达性
Rancher导入集群后节点NotReady主机名解析失败或网络插件异常检查/etc/hosts、Pod网络Pod是否Running,查看kubelet日志
Webhook测试成功但Jenkins不触发任务没配置分支过滤规则或Webhook URL不正确检查GitLab Webhook里填的URL和Jenkins任务名是否精确一致,分支过滤里是否写了正确分支

5.2 低配置机器下的资源调优心得

很多朋友用一台8G内存的机器想跑完整套平台,经常卡到怀疑人生。我说说实际优化经验。

先说K8s集群本身的资源占用。单节点K8s跑起来后,光系统组件(kubelet、etcd、kube-apiserver、kube-scheduler、kube-controller-manager、Pod网络)加起来就要占2GB左右内存,这还没算分布式存储。如果你测试环境对存储要求不高,可以考虑不用StorageClass,减少相关组件的开销。

Jenkins也是个内存大户。默认JVM参数可能让它尝试吃满系统内存,必须在启动参数里限制:

JAVA_OPTS=-Xms512m -Xmx1024m

加入/etc/default/jenkins或Docker启动命令的环境变量里,否则Jenkins会拖垮整台机器。GitLab也是内存消耗大户,默认配置会使用Unicorn和Sidekiq,在docker启动命令里可以加GITLAB_OMNIBUS_CONFIG="unicorn['worker_processes']=2; sidekiq['max_concurrency']=5"来降低内存占用。

还有一个细节是Docker在Windows或macOS上启动报“virtualization support wasn't detected”。这个报错其实是宿主机BIOS里没开虚拟化,或者Windows的Hyper-V没有被启用。解决办法是进入BIOS开启VT-x/AMD-V,并在Windows功能里勾选Hyper-V或Windows Hypervisor Platform。在国内很多人用Docker Desktop时遇到这个问题,通常就是电脑的虚拟化被安全软件或系统策略关了。

5.3 提高流水线稳定性与团队协作

当整套平台能跑通之后,更进一步的问题是如何让流水线更稳定、更好用。这里分享几个我认为很有效的实践。

第一,Docker镜像构建一定要利用缓存。写Dockerfile时,把依赖下载层放在源码复制之前。比如Java项目常见的分层写法:

FROM openjdk:11 WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","app.jar"]

这个写法的缺点是每次代码变更,整个镜像层都重新构建,因为target/*.jar的内容变了。而更优的写法是先把pom.xml复制进去,执行mvn dependency:go-offline把依赖下载缓存成一两层,再复制源码打jar。这样依赖没有变更的情况下,Docker能直接复用缓存层,构建速度能快一倍以上。

第二,多环境部署时的镜像复用策略。我建议在测试环境构建出的镜像直接复用为预生产和生产环境的镜像,不要再各自构建一次。也就是说,同一个构建产物,通过修改Deployment里的配置(数据库连接、注册中心地址等)打在不同的环境里。这样能最大化保证“测试通过的产物就是上线运行的产物”。Harbor的镜像复制功能可以帮你把镜像同步到生产环境的Harbor仓库,避免不同环境之间网络不通的问题。

第三,团队协作上的一个建议:粗粒度流水线拆分成多个任务。如果所有微服务都放在一个Jenkins流水线里,每次构建都全量执行,会很慢也很乱。可以按服务维度拆分Jenkins多分支流水线任务,或者用GitLab的Group + Jenkins的Organization Folder来自动发现仓库。这个方案一开始配置起来麻烦,但长期维护会轻松很多。

5.4 热词背后的典型需求解读

浏览这组热搜词的时候,我发现几个出现频率很高的词,背后其实对应着不同的需求层次,这里集中回应一下。

“gitlab使用教程”“jenkins配置gitlab connection”“gitlab拉取代码到本地”这类词,说明很多人卡在最基础的账号接入和网络配置上。核心就是三类操作:SSH key配置、HTTPS克隆地址、Jenkins凭据配置。把这三件事理顺,GitLab这一环基本就通了。

“k8s和docker区别”“k8s部署教程”“k8s常用命令”这类词,说明不少人是第一次接触容器编排。我的建议是先用Docker Compose跑通单体应用,再学K8s跑同样的应用,最后再上微服务。跳过基础直接玩微服务,遇到问题时完全不知道是网络问题、存储问题还是编排问题,排查成本极高。热词里“单节点 k8s 上的若依微服务整套环境”就是这么个吃力不讨好的场景,不是不能跑,但一定要先弄懂单节点K8s的限制和调试手段。

“准不停服、不丢数据地迁移到阿里云ecs”这个词比较特别,它可能来自某个具体问题。但从DevOps平台的角度看,数据迁移和滚动更新本质上是一回事:你需要保证服务在替换节点或集群时,数据不丢、请求不断。在K8s里,这取决于你的应用是否做到无状态化(数据放到外部存储,实例不保存本地状态),以及Deployment的strategy是否配置了RollingUpdate而不是Recreate。所以别急着看迁移工具,先认识到底层的工作机制。停服迁移一定不是因为工具不行,而是架构没有支持滚动更新。

“dubbo mesh(k8s service mesh)”和“idea 打包docker镜像”这两个词很有趣,一个代表K8s生态的进阶方向(服务网格),一个代表开发环境的日常操作。前者说明有人已经在考虑微服务治理和流量治理的问题,建议先扎实掌握K8s原生能力再上Service Mesh。后者说明开发者希望IDE一体化操作,在IDEA里安装Docker插件,配置Docker Host地址后,就能直接右键镜像构建和推送,这对开发阶段做本地验证很有帮助,能有效减少和运维联调的等待时间。

最后说一个很实际的经验。每次搭建这套平台的过程,本质上是一次对企业软件交付流程的重新梳理。很多团队不是缺工具,而是缺一条让代码变更快速、安全、可靠地抵达生产环境的路径。单靠某一个组件解决不了这个问题,只有把GitLab的代码管理、Jenkins的自动构建、Docker的标准化打包、Harbor的镜像管理、K8s的容器编排、Rancher的集群管理这六层串起来,形成一个闭环,才能真正意义上实现“提交代码后,剩下的事情交给流水线”。

我踩过最大的坑就是在资源不足时硬上整套平台,结果排查定位问题时反而更混乱。如果你只有一台机器,先别上K8s,用Docker Compose把GitLab、Jenkins、Harbor跑通,先实现手动触发构建和推送;再单独找一台机器装K8s,手动kubectl apply部署;等这些都熟了,再引入Rancher和自动化CD。一步步来,比一次全上然后花一周排错要靠谱得多。

这一套平台搭建完之后,后续的演进方向也值得留个心眼:Jenkins的流水线可以逐渐标准化成模板库,微服务的部署文件可以搬进GitOps的仓库里管,镜像漏洞扫描和审批流也可以接进流水线。工具会不断迭代,但“代码、构建、镜像、部署”这条主线不会变。把主线想清楚,工具只是实现方式的选择问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询