微服务CI/CD全链路落地:Docker+K8s+Jenkins与Nacos实践

发布时间:2026/10/6 5:06:22
微服务CI/CD全链路落地:Docker+K8s+Jenkins与Nacos实践
搞微服务的时间不算短了从最早的手工打包、SSH连服务器、停服务、替换 jar 包到后来用脚本一把梭再到现在这套 Spring Cloud Nacos GitLab Jenkins Docker Kubernetes 的 CI/CD 体系发布方式的变化带给团队效率的提升是肉眼可见的。这篇文章就把我搭建这套微服务 CI/CD 架构的完整过程、关键设计思路、踩过的坑和排查方法整理出来。做微服务开发、后端研发和运维的同学都可以参考尤其是团队里服务数量超过五个、发布频率已经开始变成负担的时候这套链路能解决“发布靠人肉”“回滚靠手速”“环境各不同”的核心痛点。这套架构本身不复杂复杂的是每个环节为什么这么选、哪些地方容易翻车。我会把 Docker、Jenkins、K8s 的安装陷阱、流水线脚本的写法、以及那些报错信息的背后原因都拆开讲清楚保证你照着能落地。1. 整体架构与设计思路拆解1.1 为什么是 Spring Cloud Nacos服务多了最怕两件事找不到服务、配置乱改。Spring Cloud 生态里注册中心加配置中心就是微服务的“通讯录”加“调节中枢”。Nacos 比传统的 Eureka 加 Spring Cloud Config 组合更轻一个组件同时把服务发现和配置管理都做了还支持命名空间隔离环境dev、test、prod 直接用 namespace 分开配错环境的问题少了一多半。为什么选 Nacos 而不是 Consul我的理由很朴素社区活跃度和中文资料足够多出了问题能搜到解决方案而且它自带控制台登录页面就能看服务健康状态、看配置推送记录排查成本低。Nacos 支持 AP 和 CP 两种模式临时实例用 AP持久化实例用 CP实际用下来就是省心。更关键的是Nacos 的配置管理还能解决微服务启动顺序的问题。服务启动后从 Nacos 拉配置并注册到服务中心即使 Jenkins 批量部署多个服务依赖方也能通过注册中心拿到最新地址不需要死等某个服务先起来。这个特性在流水线自动部署时非常值钱。1.2 CI/CD链条上每个角色都在干什么GitLab代码仓库也是整条链路的源头。开发者提交代码、发起合并请求GitLab 通过 Webhook 通知 Jenkins 触发流水线。这里提一句拼写不少笔记写成“gitalab”实际产品是 GitLab部署时镜像名用的是 gitlab/gitlab-ce。Jenkins流水线的总调度负责拉代码、执行构建、跑测试、构建镜像、推镜像、触发 K8s 部署。Jenkins 本身不“发布”它只是把每一步串起来。Docker负责把应用和运行环境打成一个不可变的标准交付物。微服务最怕“我本地是好的”镜像构建完就是构建完环境差异在镜像层面被直接抹平。Kubernetes负责让镜像跑起来、跑得好。滚动更新、故障自愈、副本扩缩都由它管Jenkins 只需要告诉 K8s“我要这个新镜像版本”剩下的交给控制器。整条链路的请求路径大致是代码 push - GitLab Webhook - Jenkins Job - 代码构建 - 镜像构建 - 推送镜像仓库 - kubectl set image - K8s 滚动更新 Pod。理清这个顺序后面排查问题就有一个主线可以抓。1.3 这样的组合解决了什么问题环境一致性同一个镜像走完全程测试环境与生产环境运行的是同样的构建产物“我本地是好的”这种话在流水线面前没有意义。可回滚K8s 的 rollout undo 一条命令回到上一个版本不用再靠备份目录手忙脚乱。发布频率上去了一天多次发布不再是负担因为每次发布都是流水线自动完成人工干预只发生在审批和验证环节。权限收敛以前人人拿服务器账号现在只有 Jenkins 有部署权限安全问题少了一大截。支持灰度K8s 天然支持多副本和流量切换灰度发布可以在代码不变的情况下通过调整 Deployment 实现。这套架构也让新同学很容易上手代码提交到指定分支剩下的交给流水线出问题看 Jenkins 日志定位效率高很多。2. 环境准备先把地基打牢2.1 Docker 的安装和镜像拉取加速Docker 是整个链路的第一个基础设施。社区版安装其实不复杂但如果是从零开始我建议先确认操作系统和内核版本。Ubuntu 用官方安装脚本最省事CentOS 7 系列需要注意内核版本太旧的内核会影响存储驱动和网络性能。安装完之后启动服务确认/var/run/docker.sock存在。很多新手一上来跑docker ps就报权限错误那是因为当前用户不在 docker 组里执行sudo usermod -aG docker $USER退出重新登录就正常了。关于镜像拉取加速由于不同网络环境访问 Docker Hub 的速度差异很大建议在/etc/docker/daemon.json里配置镜像加速地址然后重启 Docker。拉取 gitlab/gitlab-ce、jenkins/jenkins 这种体积非常大的镜像时有没有加速完全是两种体验。另外建议直接建一个 docker-compose.yml 统一编排基础设施组件方便一键起停后面每次初始化环境都能少敲很多命令。2.2 GitLab 和 Nacos 的基础部署GitLab 对内存要求不低至少得给 4GB 以上否则经常遇到 502。小团队可以直接用docker run挂载 data、config、logs 三个目录端口做映射。第一次启动要等两到三分钟初始化root 密码在容器里通过命令重置。Nacos 部署时可以单机模式跑设置 MODEstandalone 就行。配置数据库要提前建好用 MySQL 8 的时候注意鉴权方式和时区设置Nacos 连接串里加上allowPublicKeyRetrievaltrue这类参数可以避免一些连接报错。这两个组件最好不要和业务容器混在一个节点上端口冲突和资源争抢会影响日常调试。我习惯把基础组件放在单独的 docker-compose 项目里用到哪个直接docker compose up -d对应服务名。2.3 Jenkins 初始化与 K8s 插件Jenkins 我推荐用官方 jenkins/jenkins 镜像运行/var/jenkins_home必须挂到宿主机目录否则升级容器数据全没了。第一次启动后从日志里找初始化密码打开页面按提示安装插件。插件安装优先级Pipeline、GitLab、Docker Pipeline、Kubernetes、Blue Ocean。其余插件等用到了再装插件装多了不仅启动慢版本冲突也烦人尤其是不同插件对 Jenkins 版本的要求不一致很容易踩坑。连接 Docker 做构建时有两条路。一条是在 Jenkins 容器里挂载宿主机的/var/run/docker.sock坏处是 Jenkins 容器权限过大生产环境不建议。另一条是给 Docker daemon 开启 TCP 2376 端口并配 TLS 证书Jenkins 里配置 Docker Host URI 为tcp://宿主机IP:2376。我个人的实践是内网环境用 TLS 方式安全性和管理上都更清晰。2.4 K8s 集群的搭建要点K8s 这块是很多人卡住最久的地方。控制节点初始化命令大致是kubeadm init --control-plane-endpointmasterIP --pod-network-cidr10.244.0.0/16。注意先把 swap 关掉把容器运行时配置好否则初始化必挂。初始化完成后把 kubeconfig 复制到.kube目录。网络插件选了 Calico 的话直接 apply 官方 manifest。Pod 网络 CIDR 一定要和集群初始化时保持一致不然跨节点 Pod 互通会出问题。工作节点通过kubeadm join加入token 有效期只有 24 小时过期就重新生成。有一点要提醒kubeadm 初始化需要拉取一系列 K8s 组件镜像这些镜像托管在公共镜像仓库网络条件不好时拉取很慢。可以在kubeadm init时通过--image-repository参数指定一个网络可达性更好的镜像仓库地址这样能省下大量等待时间。3. 流水线设计从代码提交到 Pod 启动3.1 触发源与 Webhook 配置流水线设计的第一步是确定触发方式。最简单的就是分支触发开发 push 到 dev 分支触发测试环境部署打 tag 或者 push 到 master 分支触发生产环境部署。GitLab 里进入项目 Settings - Webhooks填上 Jenkins 的 webhook 地址格式是http://jenkinsIP:端口/project/任务名再配上 Secret Token这个 token 要和 Jenkins 任务里配置的保持一致。很多团队网络环境比较特殊能看到 Jenkins 收到请求但任务没跑这时候先看 GitLab Webhook 的最近投递记录状态是不是 200再看 Jenkins 系统日志里有没有鉴权报错。Webhook 通了流水线才谈得上自动化。3.2 Jenkinsfile 的核心逻辑强烈建议把流水线写进 Jenkinsfile而不是在界面上点出一堆步骤。Jenkinsfile 即配置即文档代码评审还能顺带 review 发布流程比在网页上瞎点靠谱得多。一个标准的 Jenkinsfile 大致长这样pipeline { agent any environment { APP_NAME order-service IMAGE_REPO registry.example.com/microservice NAMESPACE dev } stages { stage(拉取代码) { steps { checkout scm } } stage(Maven构建) { steps { sh mvn clean package -DskipTests } } stage(构建镜像) { steps { script { def tag ${GIT_COMMIT_SHORT}-${BUILD_NUMBER} docker.build(${IMAGE_REPO}/${APP_NAME}:${tag}) docker.withRegistry(, registry-credentials) { docker.image(${IMAGE_REPO}/${APP_NAME}:${tag}).push() } } } } stage(部署到K8s) { steps { sh kubectl set image deployment/${APP_NAME} ${APP_NAME}${IMAGE_REPO}/${APP_NAME}:${tag} -n ${NAMESPACE} } } } }拉代码阶段注意用 GitLab 插件时通过 SCM 方式拉取不要用git clone硬拉否则分支切换、凭证管理都是坑。构建阶段 Maven 项目执行mvn clean package -DskipTests这个参数其实有争议建议测试环境还是跑跑单测生产环境再压缩时间。部署阶段调用 kubectl 有两种做法在 Jenkins 服务器上预装 kubectl 并配好 kubeconfig或者通过 Kubernetes 插件动态起一个 Pod 执行。动态 Pod 对集群资源有要求小团队预算有限就用第一种方式最直接。3.3 镜像仓库与版本管理策略镜像必须私有化。团队如果不想维护私有仓库云厂商的镜像服务也够用但 Jenkins 要有推送凭证。Docker Pipeline 推送时withRegistry里指定的 credentialsId 要和 Jenkins 里新增的 username-password 凭证对应。版本号里我建议用短 commit SHA 加构建序号而不是连续递增的数字。因为短 SHA 可以直接对应 GitLab 的 commit回滚时不需要查“版本号 x 是哪个包”。还可以用 A-B 镜像部署法先推 A 版本的镜像到仓库K8s 滚动更新到 A确认没问题再切 B旧镜像留一份随时兜底。这里要特别提醒一个坑同一个 tag 被反复覆盖推送到镜像仓库K8s 的imagePullPolicy如果设置成IfNotPresent部分节点会复用旧镜像导致“发布了新版本但代码没变”。妥善做法是每次构建生成唯一 tag从根上避免这个问题。3.4 用 Deployment 管理微服务K8s 资源文件建议直接随代码仓库维护而不是放在 Jenkins 里手写。每个微服务项目下面放一个k8s/目录包含 deployment.yaml 和 service.yaml这样每个版本的资源配置都跟着代码走审计也方便。apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: dev spec: replicas: 2 strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25% selector: matchLabels: app: order-service template: metadata: labels: app: order-service spec: containers: - name: order-service image: registry.example.com/microservice/order-service:latest ports: - containerPort: 8080 resources: requests: memory: 512Mi cpu: 250m limits: memory: 1Gi cpu: 500m readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 90 periodSeconds: 20Deployment 里一定要写 resource 的 requests 和 limits否则 Pod 可能挤爆节点。探针readinessProbe和livenessProbe对微服务尤其重要JVM 启动慢的服务initialDelaySeconds设置太短会导致一直重启这是极常见的翻车点。滚动更新策略maxSurge和maxUnavailable建议先按 25% 起步。服务数量少时也可以设maxSurge0先缩旧再起新避免资源不够导致发布卡住。3.5 Nacos 在流水线里的联动微服务配置在 Nacos 里管理流水线构建时不需要针对每个环境改代码里的配置地址。可以通过 Jenkins 参数化构建传入SPRING_PROFILES_ACTIVE环境变量容器启动时就能从对应 namespace 拉配置。如果配置改动了直接在 Nacos 控制台发布配置服务侧用了RefreshScope自动刷新不需要重新走流水线。这样代码发布和配置发布解耦发布窗口大大缩短。实际操作中把 Nacos 地址、命名空间 ID、配置分组这组参数统一沉淀到 Jenkins 的 environment 里每个服务复用同一套模板维护成本非常低。4. 常见问题与排查实录4.1 K8s Master 初始化提示 API Server 不健康这个报错是kubeadm init执行后最常看到的the api server is not healthy after 4m0.00747357s。很多时候并不是 API Server 真的崩了而是 kubelet 没有正常启动或者网络插件没就绪。排查顺序很重要先看 kubelet 日志journalctl -u kubelet确认没有证书、镜像缺失这类关键错误。再看容器运行时crictl ps能否列出容器如果不能先解决运行时问题。确认 6443 端口有没有被占用etcd 那侧有没有报错。如果 kubelet 因为某个镜像拉不下来一直等可以先把所需镜像手动拉全再执行kubeadm reset重新安装。初始化命令如果改过参数reset 后旧配置不会自动清干净必须清理/etc/kubernetes目录再重试。这个报错通常不是一次就能过的耐心按日志一段段看比反复 reset 有效。4.2 Docker 权限问题与守护进程起不了docker ps报permission denied while trying to connect to the Docker daemon socket十有八九是用户不在 docker 组。执行sudo usermod -aG docker $USER登出再登录生效。如果启动 Docker 卡住多半是 daemon.json 里加速地址写错或不可达先把配置临时挪走再启动等能跑了再改回来。CI 场景里还有一个容易踩的Jenkins 容器内执行 docker 命令报 socket 权限错误原因是挂载的 docker.sock 属主是 rootJenkins 用户不在 group 里。要么把 Jenkins 容器以 root 跑要么用官方镜像并配合 Docker Pipeline 插件的 staging 机制不建议长期以 root 跑构建节点。4.3 Docker Desktop 在 Windows 下的虚拟化报错Windows 上装 Docker Desktop 常报Failed to start because virtualization support wasnt detected。先确认 BIOS 里 Intel VT-x/AMD-V 已开启Windows 功能里勾选“虚拟机平台”和“适用于 Linux 的 Windows 子系统”然后通过 WSL2 跑 Docker。安装完成后如果还是报错检查 Hyper-V 是否与其他虚拟机软件冲突有些旧版本 VMware 会占用 VT 导致 Docker Desktop 起不来。还遇到过 WSL 发行版太多导致 Docker Desktop 绑定错误的情况可以在 PowerShell 里执行wsl --shutdown清一下再重新启动 Docker Desktop。这类问题属于开发环境坑但团队里一半人卡在这一步效率影响很大。4.4 Jenkins 连接 Docker Host 失败配置 Kubernetes 或 Docker 插件时控制台报new cloud docker host uri root相关错误通常是没有正确指定 Docker Host URI或者直接把宿主机 socket 暴露了但没有做 TLS。Jenkins 侧填tcp://ip:2376之前先在宿主机上确认 docker daemon 配置了 hosts 和 tlsverify客户端证书要放进 Jenkins credentials。有个细节要注意Docker 默认 2375 端口是无认证的主要用于调试一旦暴露到外网风险极高内网也建议套 TLS。把证书文件放到 Jenkins 的指定目录下再补上 Docker Host URI 里的证书参数测试连接就能看到 nodes 列表。如果只是测试环境用unix:///var/run/docker.sock能跑就先跑别在生产环境沿用。4.5 Docker 容器网络不通容器网络不通是个万金油问题但常见场景无非三类宿主机防火墙拦截、CNI 和 bridge 冲突、容器间所属 network 隔离。先用docker network ls和docker inspect确认容器挂在哪个网络再确认两个服务是不是在同一个 bridge 下跨网络需要 attach 到同一个自定义网络。和 K8s 部署在同一宿主机时要注意 docker0 网段和 pod CIDR 别冲突否则会出现诡异的路由问题。排查手段docker exec进容器 ping 网关和对方 IP宿主机上ip route看路由表必要时tcpdump抓包确认 ICMP 有没有到达。多数场景最后定位到的是防火墙 drop 规则而不是 Docker 本身。4.6 数据库与中间件镜像安装失败拉取 MySQL、Redis 这类镜像失败大部分是网络拉取问题加上 tag 不存在很容易让人误判。确认镜像 tag 存在的最稳妥方式去镜像仓库页面看 tag 列表。MySQL 8 官方镜像 tag 是mysql:8.0别盲目写latest出了问题再回头查就浪费时间了。容器启了但连不上时先看端口映射和容器内部监听地址。MySQL 容器如果不做特殊配置容易被外部连接拒绝需要在启动时加--bind-address0.0.0.0或使用对应的配置文件。Redis 主从的话注意 masterauth 配置还有容器 IP 变化问题关联的 slave 要写可解析的主节点地址而不是固定 IP。这套架构从零搭到稳定跑起来我最大的体会是CI/CD 不是把工具装齐就行难的是把每个版本的流转路径理清楚让代码、镜像、配置、运行状态可以一一追溯。现在团队发布一个服务基本就是 push 代码泡杯茶的功夫流水线就自动完成了回滚也就是一条命令的事。如果你们还在手工发布我建议先不要把整套 K8s 一步到位可以先从 GitLab Jenkins Docker 开始把镜像流转理顺了再往 K8s 迁移会平滑很多。最后再提醒一句Jenkinsfile 和 K8s 资源文件一定要跟代码一起走这才是真正的 Infrastructure as Code。