
简介微服务架构将单体应用拆分为众多独立服务随之而来的服务发现、负载均衡与集群管理问题需要依赖Kubernetes等容器云平台加以解决。方案文档面向企业架构师、运维人员及K8S实践者系统梳理了基于K8S容器云平台的微服务部署方案重点介绍在DMZ与内网分别部署两套相互隔离的OpenShift环境并细化权限管理、基于Project的多租户隔离、Router隔离、物理资源池隔离以及日志监控等关键设计。内容还涵盖OCP的认证与鉴权机制、SCC安全上下文限制、EFK日志管理平台以及基于cAdvisor、Heapster和Hawkular的监控组件贴合企业级真实落地场景。资源共1个docx文档大小约417KB轻量精炼。目前已有497人学习适合需要规划容器云部署方案或优化现有微服务架构的读者参考。1. 微服务部署到K8S容器云平台先想清楚它解决什么把几十个微服务直接扔进K8S和用脚本在几台ECS上拉起来最大的区别不在启动方式而在故障恢复与流量路由。K8S容器云平台真正解决的是让每个微服务都有一套标准的生命周期管理镜像拉不下来会重试Pod被杀掉会自动重建流量只在健康实例之间分发。很多团队从Spring Cloud切到K8S后最直观的感受是部署脚本从几百行收敛成几个yaml文件代价是先搞懂控制器、服务访问、配置注入和流量入口四件事。这套方案不绑定某个特定微服务框架RuoYi、Spring Cloud或go-micro都适用落地路径是一致的。定位上看它适合已经完成微服务改造、正在选部署形态的团队也适合刚接管一套K8S集群、需要给几十个服务定编排规范的一线开发。2. 基于K8S的微服务部署先搞懂四类核心对象先划清一个边界Docker解决的是单台机器上镜像的打包与进程隔离K8S解决的是跨机器的编排、调度与服务发现。微服务架构图里那些网关、业务服务、基础设施服务落到K8S容器云平台上对应的其实是同一套对象模型。业务实例交给Deployment管服务之间的调用入口交给Service管非敏感配置和敏感凭证分别交给ConfigMap和Secret管外部流量统一走Ingress。把这四类对象想清楚剩下的就是写yaml和调参数。2.1 用Deployment管理无状态服务而不是裸Pod微服务绝大多数是无状态的用户会话、缓存数据都外置到了Redis或数据库因此最常用的工作负载是Deployment。它保证指定数量的Pod永远在运行版本更新时按策略滚动替换Pod崩溃时按restartPolicy重建。我见过从Docker Compose转过来的团队第一个yaml直接写Pod这是最典型的误区# 错误示例直接创建Pod节点故障后不会自愈 apiVersion: v1 kind: Pod metadata: name: user-service spec: containers: - name: user-service image: registry.example.com/ms/user-service:1.0.0这种写法下kubelet只能保证容器挂了以后重启节点宕机或者Pod被驱逐服务就彻底没了。正确做法是把Pod定义交给Deployment控制器apiVersion: apps/v1 kind: Deployment metadata: name: user-service namespace: ms-prod spec: replicas: 2 selector: matchLabels: app: user-service template: metadata: labels: app: user-service spec: containers: - name: user-service image: registry.example.com/ms/user-service:1.0.0 ports: - containerPort: 8080这里的关键是spec.selector.matchLabels必须和spec.template.metadata.labels里的标签完全一致Deployment靠这个标签选择器管理它创建的Pod。replicas: 2只是起步值。正常生产的微服务考虑单实例QPS和节点故障容错一般取3到5个副本单节点K8S环境做验证时副本数设1就够了多副本在单节点上没有实际容错意义。2.2 Service负责服务发现微服务之间的调用就靠它Pod IP在滚动更新之后一定会变微服务A要调用服务B不能把Pod IP写死在配置里。K8S用Service对象固定访问入口apiVersion: v1 kind: Service metadata: name: user-service namespace: ms-prod spec: type: ClusterIP selector: app: user-service ports: - name: http port: 8080 targetPort: 8080Service创建后K8S内置DNS会生成一条记录user-service.ms-prod.svc.cluster.local同命名空间内直接用user-service这个短域名就能发起调用。port是Service对外暴露的端口targetPort是Pod内容器实际监听的端口两者可以不一样比如Service走80、容器监听8080。Dubbo这类RPC框架迁移到这里时注册中心换成K8S服务发现或者Dubbo Mesh的Service Mesh能力Service对象本身仍然承担固定虚拟IP的职责。2.3 ConfigMap与Secret分离配置环境切换才不慌把数据库地址、Redis密码直接写进镜像改配置就得重新打镜像密码还会留在镜像仓库里安全隐患很大。容器云平台上的标准做法是非敏感配置放ConfigMap敏感内容放Secret。apiVersion: v1 kind: ConfigMap metadata: name: user-service-config namespace: ms-prod data: SPRING_PROFILES_ACTIVE: prod LOG_LEVEL: INFO --- apiVersion: v1 kind: Secret metadata: name: user-service-secret namespace: ms-prod type: Opaque stringData: db-password: Prod2024ConfigMap和Secret在Deployment里用envFrom注入或者以卷的形式挂载成文件。使用stringData写Secret最方便K8S会在持久化时自动做Base64编码。但要明确一点Base64不是加密有权限的人用kubectl get secret -o yaml照样看得到明文生产环境需要配合云上的KMS加密或者sealed-secrets这类工具。2.4 Ingress在集群入口收敛流量统一网关几十个微服务每个都开一个NodePort既不安全也难维护。Ingress作为K8S的七层入口按host和path把外部请求路由到不同的Service对应微服务架构图里的网关层apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ms-gateway namespace: ms-prod spec: rules: - host: api.example.com http: paths: - path: /user pathType: Prefix backend: service: name: user-service port: number: 8080 - path: /order pathType: Prefix backend: service: name: order-service port: number: 8080pathType: Prefix是前缀匹配/user和/order分别打到两个Service。Ingress规则本身只是一个声明集群里必须装了nginx-ingress或traefik这类Ingress Controller规则才会被同步到代理进程里。像RuoYi微服务这种自带Knife4j接口文档的应用可以再给/doc开一条前缀规则把接口文档单独暴露给开发环境。3. 在K8S容器云平台手动部署微服务的完整操作路径前面四类对象理清了下面按真实操作顺序走一遍。这套路径在kubeadm自建集群和阿里云ACK这类托管容器云平台上通用Deployment和Service的yaml基本原样迁移涉及的改动主要在Ingress Class和存储类。我一般用kubectl做日常管理集群规模大了以后换k9s看Pod分布和日志定位问题更快。3.1 创建命名空间和私有镜像仓库凭证第一步划分环境边界用命名空间隔离prod和test避免两套环境互相污染。然后创建拉取私有镜像的凭证。大多数团队的镜像在私有仓库Deployment要从仓库拉镜像没有凭证会一直停在ImagePullBackOff。kubectl create namespace ms-prod kubectl create secret docker-registry registry-auth \ --namespacems-prod \ --docker-serverregistry.example.com \ --docker-usernamedeploy \ --docker-password密码写在这里 \ --docker-emaildeployexample.com第一行创建命名空间第二行创建类型为kubernetes.io/dockerconfigjson的Secret。Deployment里要在spec.template.spec.imagePullSecrets小节引用这个Secret否则新节点上没有缓存镜像时拉取会失败。提示--docker-password在shell历史里会留着明文脚本化执行时建议从环境变量读取避免泄露。3.2 编写最小可用的微服务编排文件一个完整的微服务部署清单至少包含Deployment、Service、ConfigMap、Secret四类资源。以订单服务为例Deployment是核心下面这份是加了探针并引用外部配置的最小版本apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: ms-prod spec: replicas: 3 selector: matchLabels: app: order-service template: metadata: labels: app: order-service spec: imagePullSecrets: - name: registry-auth containers: - name: order-service image: registry.example.com/ms/order-service:2.1.3 imagePullPolicy: IfNotPresent ports: - containerPort: 8080 envFrom: - configMapRef: name: order-service-config - secretRef: name: order-service-secret readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 20 resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1 memory: 1GireadinessProbe决定Pod何时进入Ready状态初始化还没完成时Pod会从Service的Endpoints里摘掉流量不会打过来livenessProbe决定进程是否假死连续多次失败后kubelet会重启容器。RuoYi微服务这类Spring Boot应用健康端点默认是actuator暴露的/actuator/health要在pom里引入spring-boot-starter-actuator。resources段里的requests是调度依据limits是运行约束只写limits不写requests调度器会把实例堆到同一台机器峰值期互相争抢CPU。3.3 用kubectl apply完成部署和滚动更新文件放在conf/ms-prod/目录后部署操作收敛为两条命令kubectl apply -f conf/ms-prod/ kubectl rollout status deployment/order-service -n ms-prod --timeout300skubectl apply是声明式操作K8S对比当前状态和期望状态的差异只做增量更新rollout status阻塞等待滚动完成超时300秒后自动失败退出CI里能直接捕获。发新版本时流水线里我喜欢用kubectl set image只替换镜像标签不重写整个文件kubectl set image deployment/order-service \ order-serviceregistry.example.com/ms/order-service:2.1.4 \ -n ms-prod第二个参数order-service是容器名Deployment里有多个容器时必须写清楚替换哪一个。滚动更新默认策略是RollingUpdate新Pod Ready后旧Pod才摘流量这个过程能做到不停服。注意如果复用一个旧tag镜像不会重新拉取版本号最好每次递增。3.4 服务访问不通时的三个排查命令部署完发现服务间调用不通先别怀疑代码。我一般按下面顺序查kubectl get endpoints -n ms-prod user-service kubectl get pod -n ms-prod -l appuser-service -o wide kubectl describe svc user-service -n ms-prod第一条看Service后面有没有挂上Pod IPEndpoints为空就是标签选择器没匹配上第二条确认Pod是Running且READY是1/1过滤条件-l appuser-service要和Service选择器一致第三条看Service的端口映射和后端列表。这三个命令能覆盖九成的服务发现问题剩下的再看网络策略和节点安全组。4. K8S微服务部署的资源配额、探针与HPA参数这样设yaml能跑起来只是第一步参数设得不对流量一上来就出问题。这一章把三个最常踩坑的参数讲透。4.1 给每个微服务设requests和limits避免资源争抢容器云平台是共享的多个微服务跑在同一个节点池里。不设资源限制一个服务的内存异常增长能拖垮整台节点。我给不同类型微服务定的参考值微服务类型requests.cpulimits.cpurequests.memorylimits.memory网关类gateway500m2512Mi1Gi业务类user/order250m1512Mi1Gi定时任务类100m500m256Mi512Mi异步消费者250m1512Mi1GiCPU是可压缩资源250m的requests允许突发到1核节点资源紧张时会被限制回250m内存不可压缩达到limits上限容器直接被OOM Kill。所以内存limits要给JVM堆外留出余量堆内、元空间、线程栈加上堆外缓存总量通常比上限低20%到30%。Java里一个典型的坑是JVM不认识容器配额。JDK 8u131以前的版本默认按宿主机物理内存计算堆上限宿主机32G时最大堆接近8G容器limits.memory只有512MiPod必然被杀。8u191之后的版本默认开启容器支持配合-XX:MaxRAMPercentage50.0JVM会按容器内存配额计算堆大小这个参数我一般直接写进镜像启动命令。4.2 存活探针与就绪探针怎么区分、怎么调参探针参数的调整原则是给应用留足启动时间。initialDelaySeconds设置太短服务还没初始化完成就探测失败后K8S重启Pod然后就陷入无限重启循环。这个值一般取应用从启动到能应答健康检查的真实耗时的1.5到2倍。periodSeconds决定探测频率。livenessProbe建议20秒一次太密集的探测在高并发下会消耗线程readinessProbe可以调到10秒让新Pod尽快接流量。failureThreshold默认3次服务启动时要做缓存预热的话调到5次更稳妥。readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 5timeoutSeconds容易被忽略。如果健康检查端点里加了数据库连接检查数据库慢查询会让探针超时Pod被摘流量甚至重启。我建议健康检查只做进程自身存活检查外部依赖的健康度单独走另一套链路。4.3 HPA自动扩缩容应对秒杀这类突发流量秒杀和活动场景的流量曲线陡峭人工扩副本永远来不及。HPA根据CPU、内存或自定义指标动态调整副本数运行中的K8S集群一行命令就能开启kubectl autoscale deployment seckill-service \ --cpu-percent70 \ --min3 \ --max20 \ -n ms-prodHPA的计算公式是期望副本数 当前副本数 × (当前指标值 / 目标指标值)。--cpu-percent70表示期望所有Pod的CPU使用率达到70%超过就扩容回落后按冷却时间逐步缩容默认缩容冷却5分钟。注意这里对比的基线是Pod的requests.cpu不是节点物理CPU。requests设得太大会让扩容不敏感设得太小单个Pod容易被流量打满。CPU指标在Java应用上经常滞后流量上来后的一两分钟GC和类加载才把CPU顶起来。如果业务对突发流量敏感建议走Prometheus的QPS自定义指标配置custom.metrics.k8s.io后HPA按请求数扩缩容反应速度比纯CPU快一个量级。单节点K8S集群里HPA的意义不大min和max都设1到3即可没有跨节点扩展的实际效果。5. 微服务在K8S上部署完用可观测性收尾验证5.1 用kubectl events和日志确认部署状态压测之前先看三样东西事件、日志、容器内健康状态。kubectl get events -n ms-prod --sort-by.lastTimestamp | tail -20 kubectl logs deployment/seckill-service -n ms-prod --tail200 kubectl exec -it deployment/user-service -n ms-prod -- sh第一条把最近20条集群事件倒序打印FailedScheduling和BackOff会直接显示调度失败与重启原因比逐个看Pod状态直观第二条看启动日志里有没有异常堆栈定位启动崩溃最直接第三条进入容器执行curl localhost:8080/actuator/health确认容器内部健康检查真实可用。有些精简镜像里没装curl改用wget -qO-或python3 -c。5.2 接入Prometheus与链路追踪拿数据说话微服务排障比单体难在链路长。A调用B、B调用C任何一环变慢用户侧就是一个超时。部署完成后的下一步是把指标、日志、链路三件套补齐。可观测性维度常用选型接入方式指标Prometheus GrafanaServiceMonitor自动发现Pod日志Loki或EFKDaemonSet采集按namespace聚合链路SkyWalking或JaegerJava agent或OpenTelemetry SDK指标采集推荐Prometheus Operator。它在集群里创建ServiceMonitor按标签自动发现目标PodapiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: ms-monitor namespace: ms-prod spec: selector: matchLabels: app: user-service endpoints: - port: http path: /actuator/prometheus interval: 15sSpring Boot微服务引入micrometer-registry-prometheus后actuator的/actuator/prometheus端点会自动暴露JVM、线程池和HTTP调用指标。链路追踪方面Java应用用SkyWalking agent接入成本最低Nest.js这类Node微服务走OpenTelemetry SDK自动埋点上报到Jaeger后按traceId串起整条调用链。压测人员拿着JMeter脚本打Ingress网关时我会同时盯两个地方Grafana上每个微服务的P99延迟以及HPA的扩容事件。扩容滞后时间如果超过30秒优先检查HPA指标来源是CPU还是QPS以及requests值是否偏大。每个服务的探针参数和HPA阈值建议每压测一轮就回看一次指标再调整Xmx比例、探针延迟、副本基数这些值往往会随业务迭代继续变化。本文还有配套的精品资源点击获取