简介:这份文档面向正在推进微服务架构落地的架构师、运维工程师与技术决策者,围绕基于K8S容器云平台的微服务部署方案展开,重点解决服务依赖、服务发现、负载均衡、集群管理与有状态数据管理等落地难题。内容涵盖容器云部署框架、权限管理、多租户管理以及日志与监控四大关键维度,并具体说明DMZ与内网两套Openshift环境彼此隔离的部署思路、OCP基于OAuth的认证与细粒度鉴权、project租户隔离机制,以及EFK日志方案与Heapster、Hawkular、Cassandra监控链路。资源包为1个docx文档,约417KB,结构紧凑,适合作为企业级容器云部署的参考方案。目前已有497人学习,可帮助读者快速理解K8S与Openshift在微服务场景下的部署要点与设计取舍。
1. 从一份社区问答整理稿说起:K8S 微服务部署到底在解决什么
如果你手上正拿着一份叫《基于K8S容器云平台的微服务部署方案》的文档,大概率会先愣一下——它不像官方手册那样从架构图讲起,也不像博客那样只贴几段 YAML,而是一份由社区专家顾文俊根据线上交流活动整理、多位会员贡献的问答合集。这种“问答体”文档的价值恰恰在于:它记录的是真实落地时被反复追问的问题,而不是产品宣传页上那些漂亮话。K8S 是第一个把“一切以服务为中心,一切围绕服务运转”当指导思想做出来的容器编排产品,构建在它上面的系统可以跑在物理机、虚拟机集群或企业私有云上,也能托管在公有云里。微服务架构把一个巨大的单体应用拆成很多小的、互相连接的服务,一个服务背后可能有多个实例副本在支撑,服务之间必然产生依赖关系。发布时如果每个服务都单独启动,登录服务、支付服务一个个手动拉起来,那运维基本不用干别的了,编排动作必不可少。这份文档要解决的,就是“基于 K8S 的容器云平台到底怎么部署微服务”这个从选型到落地的完整链路问题,适合正在做容器化改造的运维和架构人员,也适合想搞清楚 OCP 与原生 K8S 差异的开发。
2. 部署框架与权限底座:DMZ/内网双区隔离怎么落地
2.1 双 Openshift 集群的物理隔离逻辑
文档里给出的部署框架很明确:在 DMZ 和内网分别部署彼此独立的 2 套 Openshift,分别对应内网和 DMZ 区两个网段,两套环境彼此隔离。DMZ 区的 Openshift 部署对外发布的应用,负责处理外网访问;内网的 Openshift 部署针对内网的应用,仅负责处理内网访问。这种做法的核心动机是安全边界——对外暴露的服务和内网核心服务不共享同一个集群的控制面,即使 DMZ 区被攻破,内网集群的 API Server、etcd 和业务 Pod 仍然独立。常见做法是两套集群各自维护独立的镜像仓库和存储后端,DMZ 区集群的节点不挂载内网数据库的直连权限,所有跨区数据访问走防火墙白名单。
落地时第一步是给计算节点打标签,让应用部署时能精确落到指定节点。比如在 DMZ 网段对某应用使用的 2 台计算节点打上标签,部署时 nodeSelector 指明使用的节点标签。命令如下:
# 给 DMZ 区的两台计算节点打标签 oc label node dmz-node-01 zone=dmz app=xxx oc label node dmz-node-02 zone=dmz app=xxx # 查看标签是否生效 oc get nodes --show-labels | grep dmz逻辑说明:oc label node是 OpenShift 对 K8Skubectl label node的封装,zone=dmz用于区域标识,app=xxx用于应用级绑定。参数上,标签键值对一旦写入节点对象,调度器就会在 Pod 的 nodeSelector 匹配时使用。失败时先看oc describe node的 Labels 字段是否包含目标键值,再检查节点是否处于 Ready 状态。注意标签是覆盖式操作,重复执行同一键会更新值,不会报错。
2.2 认证、鉴权与 SCC 三层权限模型
企业级平台会有来自内外不同角色的用户,灵活的、细粒度的、可扩展的权限管理必不可少。OCP 从设计初期就集成了标准化的认证服务器,定义了详细的权限策略和角色。认证层面,OCP 平台的用户是基于对 OCP API 的调用权限来定义的,所有操作都基于 API,用户可以是一个开发人员或者管理员,和 OCP 进行交互。OCP 内置了一个基于 OAuth 的通用身份认证规范的服务器,可以通过多种不同类型的认证源对用户进行认证。鉴权层面,权限策略决定了一个用户是否具有对某个对象的操作权限,管理员可以设置不同规则和角色,对用户或用户组赋予一定角色,角色包含一系列操作规则。
除了传统的认证和鉴权,OCP 还提供了针对 Pod 的细粒度权限控制 SCC(Security Context Constraints),可以限制 Pod 具备何种类型的权限,比如容器是否可以运行在特权模式下、是否可以挂载宿主机的目录、是否可以使用宿主机的端口、是否可以以 root 用户运行等。配置 SCC 的常见做法是:
# 创建一个限制性 SCC,禁止特权模式和宿主机目录挂载 apiVersion: security.openshift.io/v1 kind: SecurityContextConstraints metadata: name: restricted-custom allowPrivilegedContainer: false allowHostDirVolumePlugin: false allowHostPorts: false runAsUser: type: MustRunAsRange seLinuxContext: type: MustRunAs逻辑说明:allowPrivilegedContainer: false禁止特权容器,allowHostDirVolumePlugin: false禁止挂载宿主机目录,runAsUser设为MustRunAsRange强制容器以指定范围内的非 root 用户运行。参数上,MustRunAsRange需要配合 namespace 的openshift.io/sa.scc.uid-range注解使用。失败时看 Pod 事件里的unable to validate against any security context constraint,说明 SCC 与 Pod 的 securityContext 冲突,需要调整 SCC 或 Pod 的 runAsUser。注意 SCC 是集群级资源,绑定到 ServiceAccount 才生效,直接改 default SCC 会影响所有未指定 SCC 的 Pod。
2.3 多租户隔离的四个层面
租户是指多组不同的应用或者用户同时运行在一个基础资源池之上,实现软件、硬件资源的共享,为了安全需求,平台需要提供资源隔离的能力。在 OCP 中,project 是一个进行租户隔离的概念,来源于 K8S 的 namespace 并做了功能扩展。利用 Project,OCP 从多个层面提供多租户支持。权限控制上,管理员可以对不同的用户和组设置不同 project 的权限,不同用户登录后只能操作和管理特定的 project。网络隔离上,OCP 使用 openvswitch 管理内部容器网络,提供两种网络模式:一种是集群范围内互通的平面网络,另一种是 project 级别隔离的网络。每个 project 都有一个虚拟网络 ID(VNID),不同 VNID 的流量被 openvswitch 自动隔离,不同项目之间的服务在网络层不能互通。Router 隔离上,OCP 提供 Router 分组功能,不同 project 可以使用独立的 Router,不互相干扰,避免某些应用流量过大时对其他应用造成干扰。物理资源池隔离上,OCP 利用 nodeSelector 功能将基础设施资源池划分给特定 project 独享,实现从物理层面的隔离。
安装时启用多租户插件的参数是os_sdn_network_plugin_name='redhat/openshift-ovs-multitenant',这样 OpenShift 将使用 ovs-multitenant 多租户插件实现租户之间的安全隔离。在 OpenShift 的多租户和容器中心化日志实现中,每个租户都只能查看属于自己项目的日志。除了 OVS 插件,OpenShift 完全支持 CNI 标准,符合 CNI 标准的三方 SDN 插件都可以在 OpenShift 中使用,目前支持的包括 Cisco Contiv、Juniper Contrail、Nokia Nuage、Tigera Calico、VMware NSX-T。如果使用 OVS 插件且 OpenShift 部署在已有公有云或私有云上,可能出现 overlay on overlay 的情况,此时借助三方 SDN 插件是不错的选择,比如 flannel+hostgw 在性能上优于默认的 ovs-multitenant。
3. 日志监控与负载均衡:EFK、Heapster 和 Router 的配合
3.1 传统应用日志与新应用日志的分治
传统应用日志有别于当前流行的容器应用,传统应用同时一个中间件会运行多个应用,且应用通过 log4j 等机制保存在文件中方便查看和排错。因为容器运行的特性,这部分日志需要持久化到外置存储中。日志分类包括中间件日志、dump 文件、应用日志,日志保存在计算节点上挂载的 NFS 存储,按 OCP 平台中的 namespace 建立目录进行划分。新应用日志面对分布式环境下日志分散的问题,解决办法是收集日志集中到一个地方,收集到的海量日志经过结构化处理,交给需要的人员分析。不同人员对日志的需求不一样:运营人员关注访问日志,运维人员关注系统日志,开发人员关注应用日志。这就需要一种足够开放、灵活的方法让所有关心日志的人在日志收集过程中对其定义、分割、过滤、索引、查询。
OpenShift 使用 EFK 来实现日志管理平台,EFK 是 Elasticsearch + Fluentd + Kibana 的简称。ES 负责数据的存储和索引,Fluentd 负责数据的调整、过滤、传输,Kibana 负责数据的展示。Fluentd 无论在性能上还是在功能上都表现突出,尤其在收集容器日志领域更是独树一帜,成为众多 PaaS 平台日志收集的标准方案。部署 EFK 时,常见做法是通过 Ansible playbook 自动化完成,而不是手工逐个组件安装。手工部署 ELK 在任何环境下都不推荐,通过 Ansible 可以自动实现。至于分布式存储、本地存储还是集中存储,没有既定答案,可以参考行业实现。不建议 Elasticsearch 采用分布式存储,日志量大的情况下分布式存储 ES 写会成为瓶颈。ES 的后端存储选择上,集中存储目前用得最多,不管是 FCSAN 还是 IPSAN,其稳定性和安全性都能满足要求,但在性价比和可扩展性方面存在很大问题。分布式存储随着云计算兴起,优势是无中心节点、弹性伸缩,适合云应用,但还处于发展阶段,技术有待成熟。本地存储一般使用较少,主要是数据复制同步方面的问题。
3.2 监控技术栈的选型与演进
PaaS 平台的监控包括系统监控、容器监控等,监控流程由信息收集、信息汇总和信息展示几个部分组成。在 OpenShift 中默认使用 K8S 的监控信息收集机制,在每个节点上部署 cadvisor 的代理,负责收集容器级别的监控信息,然后将所有信息汇总到 heapster,heapster 后台的数据持久化平台是 Cassandra,最后由 hawkular 从 Cassandra 获取信息进行统一展示。组件说明上,Heapster 用于监控数据的采集,Hawkular Metrics 属于开源监控解决方案 Hawkular,基于 JSON 格式管理、展示监控数据,Cassandra 是 Apache 的开源分布式数据库,专门用于处理大数据量业务。
K8S 节点的 kubelet 服务自带 cadvisor 用来收集各节点容器相关监控信息,然后通过 heapster 收集,这样在 dashboard 上可以看到容器使用 CPU 和 Memory。为了长期监控,可以采用 Prometheus 监控方案,nodeExporter 收集主机监控信息,cadvisor 收集容器监控信息。K8S 中需要给 kubelet 配合 kube-reserved 和 system-reserved 相关参数给系统预留内存。Prometheus 作为一个时间序列数据收集、处理、存储的服务,能够监控的对象必须直接或间接提供 Prometheus 认可的数据模型,通过 HTTP API 的形式发出来。cAdvisor 支持 Prometheus,同样包含了 cAdvisor 的 kubelet 也支持 Prometheus,每个节点都提供了供 Prometheus 调用的 API。Prometheus 获取监控端点的方式有很多,其中就包括 K8S,Prometheus 会通过调用 master 的 apiserver 获取到节点信息,然后去调取每个节点的数据。就目前来看,Prometheus 应该是最具前景的监控工具,在 OpenShift 3.12 里面 heapster 将由 Prometheus 替换。
3.3 负载均衡与高可用的四个层次
高可用主要分为几个层面。外部镜像仓库高可用方面,外部镜像仓库独立于 OCP 平台之外,用于存储平台构建过程中所使用的系统组件镜像。因为外部无法直接访问 OCP 平台的内部镜像仓库,所以由 QA 环境 CD 推送到生产环境的镜像也是先复制到外部镜像仓库,再由平台导入至内部镜像仓库。为了保证外部镜像仓库的高可用,使用了 2 台服务器,前端使用 F5 进行负载均衡,所有请求均发至 F5 的虚拟地址,由 F5 进行转发,后端镜像仓库通过挂载 NFS 共享存储。Master 主控节点高可用方面,OpenShift 的 Master 主控节点承担了集群的管理工作。计算节点高可用方面,一个计算节点异常停机后,其上的容器将会被逐步迁移到其他节点上,从而保证高可用。同时可以通过标签的方式管理计算节点,在不同的计算节点划分为不同的可用区或组,在部署应用时使用节点选择器将应用部署至带有指定标签的目标计算节点上。为了保证高可用,标签组合的目标计算节点数要大于 1,这样可以避免一台目标节点宕机后调度器还能找到满足条件的计算节点进行容器部署。
应用高可用方面,基于软件 HAproxy 负载均衡服务,容器服务弹性伸缩时无需人工对负载均衡设备进行配置干预,即可保证容器化应用的持续、正常访问,可通过图形界面自定义负载均衡会话保持策略。由于平台内部通过软件定义网络为每个应用容器分配了 IP 地址,而此地址是内网地址,因此外部客户无法直接访问到该地址,所以平台使用路由器转发外部的流量到集群内部具体的应用容器上,如果应用有多个容器实例,路由器也可实现负载均衡的功能。路由器会动态检测平台的元数据仓库,当有新的应用部署或者应用实例发生变化时,路由器会自动根据变化更新路由信息,从而实现动态负载均衡的能力。简单来说,内部服务的动态发现、负载均衡、高可用和外部访问的路由通过 Service 解耦动态变化的 IP 地址,Pod 可以随意关停,IP 可以任意变,只要 DNS 正常,服务访问不受影响,但这里面要随时保证有个可用的 Pod,这个时候就需要 LB 了。内部服务之间访问通过 Service 解决,外部访问集群内服务则通过 Router 解决,外网访问要不要负载均衡,大规模高并发情况下是肯定的,外部负载均衡通常需要用户自己搞定,F5 或者开源的 HAproxy 都行。
4. 微服务拆分与 CI/CD:从 SVN 到镜像仓库的流水线
4.1 按业务能力拆分的粒度控制
微服务架构按照什么细粒度拆分,这个问题没有标准答案。既然理解微服务是用来重构业务应用的,那就以业务应用为核心,构建业务服务。业务服务需要数据服务、计算服务、搜索服务、算法服务,以及基本的日志、监控、配置、注册发现、网关、任务调度等组件。至于数据服务怎么实现,看团队能力,这才涉及数据分拆、模型重构。服务通信可以考虑事件驱动机制,也是后期业务数据处理、态势感知、智能风控、智能营销、智能运维等的基础。如何拆、按什么套路来拆,回答这两个问题的基础是一定要十分熟悉业务逻辑才行。微服务这东西,尤其是那种已经运行多年的老系统,一不小心就能拆出问题。
如果对云计算、对 OpenStack 有了解,建议以 OpenStack 中的 Kolla 项目为微服务入门学习对象。Kolla 干的事情就是把 OpenStack 服务拆分成微服务的形式跑在容器中,OpenStack 号称全球最大开源 Python 项目,由几十个开源子项目组成,如果能把这样复杂的集群项目都拆分成微服务,那么一定会得到很多别人给不了的心得体会。以 OpenStack 为例,Kolla 这个项目对 OpenStack 的拆分大概如下:先按服务功能划分,得到粗粒度,如计算服务、网络服务、存储服务,这些粗粒度模块通常会共享同一个 base 镜像,这个 base 镜像中预置了服务模块的共性依赖;然后基于服务模块的“原子性”拆分,如把计算服务 Nova 拆分为 nova-api、nova-scheduler、nova-compute、nova-libvirt 等等,所谓原子性拆分,就是拆分到不能再往下拆为止,原子拆分后通常就是彼此独立的单进程了,也可以把它们称为叶子节点,它们的镜像都是针对自己依赖的“个人”镜像,不能被其他进程共享了。从镜像的角度来看,继承关系是 centos-base -> centos-openstack-base -> centos-nova-base -> centos-nova-api。先将系统模块化解耦,别的微服务还是一体都只是部署的问题。常见的耦合方式有逻辑耦合、功能耦合、时间耦合等,从码农的角度来分析解决耦合是基于微服务还是 SOA 化的最大区别。SOA 化的系统更多的是业务系统、领域模型级别的,在分布式系统中远远不够,需要考虑性能、安全、事务等,最起码的 CAP 原则还是要把控的。码农解耦的角度有接口化、动静分离(查询和修改等)、元数据抽取等等,更多的是代码上、设计模式上的真功夫。
4.2 SVN 环境下的 CI/CD 流水线搭建
SVN 环境下实现 CI/CD,可以使用 hook(post commit)的方式来实现,但是需要编写 hook 脚本,灵活度存在问题,这在 svn-repo 的粒度较细的情况下还可行,如果一个大的 repo,管理起来较复杂,不建议使用。建议使用 Jenkins 轮询 SCM 的方式触发 pipeline/job。能不能实现 CI/CD 与 SVN 无关,关键是如何构建 pipeline,微服务理念下大致流程是:gitlab/svn -> Jenkins -> build images -> push images -> docker-registry -> pull images -> containers。具体落地时,Jenkins 侧配置轮询触发:
// Jenkinsfile 片段:轮询 SVN 并构建镜像 pipeline { agent any triggers { pollSCM('H/5 * * * *') // 每 5 分钟轮询一次 SVN } stages { stage('Checkout') { steps { checkout([$class: 'SubversionSCM', locations: [[remote: 'svn://svn.example.com/repo/app', local: '.']]]) } } stage('Build Image') { steps { sh 'docker build -t registry.example.com/app:${BUILD_NUMBER} .' } } stage('Push Image') { steps { sh 'docker push registry.example.com/app:${BUILD_NUMBER}' } } } }逻辑说明:pollSCM('H/5 * * * *')让 Jenkins 每 5 分钟检查一次 SVN 是否有新提交,H表示哈希散列避免整点并发。checkout步骤拉取 SVN 代码,docker build和docker push完成镜像构建与推送。参数上,BUILD_NUMBER作为镜像 tag 保证每次构建唯一。失败时先看 Jenkins 的 SVN 轮询日志,常见问题是 SVN 凭据未配置或仓库 URL 变更。注意轮询频率不宜过高,否则 SVN 服务器压力大,生产环境建议改用 webhook 或 post-commit 触发。
4.3 K8S DNS 与服务发布的配合
配置 K8S DNS,DNS(Domain Name System)提供域名解析服务,解决了难于记忆的 IP 地址问题,以更人性可读可记忆可标识的方式映射对应 IP 地址。Cluster DNS 扩展插件用于支持 K8S 集群系统中各服务之间发现与调用。组件包括 SkyDNS 提供 DNS 解析服务,Etcd 存储 DNS 信息,Kube2sky 监听 Kubernetes,当有 Service 创建时生成相应的记录到 SkyDNS。如访问外部 DNS,可以设置 external_dns 到 configmap 实现。K8S 分配给 Service 一个固定 IP,这是一个虚拟 IP(也称为 ClusterIP),并不是一个真实存在的 IP,而是由 K8S 虚拟出来的。虚拟 IP 的范围通过 K8S API Server 的启动参数--service-cluster-ip-range=19.254.0.0/16配置,虚拟 IP 属于 K8S 内部的虚拟网络,外部是寻址不到的。在 K8S 系统中,实际上是由 K8S Proxy 组件负责实现虚拟 IP 路由和转发的,所以 K8S Node 中都必须运行了 K8S Proxy,从而在容器覆盖网络之上又实现了 K8S 层级的虚拟转发网络。
服务代理在逻辑层面上,Service 被认为是真实应用的抽象,每一个 Service 关联着一系列的 Pod。在物理层面上,Service 是真实应用的代理服务器,对外表现为一个单一访问入口,通过 K8S Proxy 转发请求到 Service 关联的 Pod。Service 同样是根据 Label Selector 来筛选 Pod 进行关联的,实际上 K8S 在 Service 和 Pod 之间通过 Endpoint 衔接,Endpoints 同 Service 关联的 Pod 相对应,可以认为是 Service 的服务代理后端,K8S 会根据 Service 关联到 Pod 的 PodIP 信息组合成一个 Endpoints。Service 不仅可以代理 Pod,还可以代理任意其他后端,比如运行在 K8S 外部的服务。假设现在要使用一个 Service 代理外部 MySQL 服务,不用设置 Service 的 Label Selector。微服务化应用的每一个组件都以 Service 进行抽象,组件与组件之间只需要访问 Service 即可以互相通信,而无须感知组件的集群变化,这就是服务发现。K8S 提供了 NodePort Service、LoadBalancer Service 和 Ingress 可以发布 Service。NodePort Service 是类型为 NodePort 的 Service,K8S 除了会分配给 NodePort Service 一个内部的虚拟 IP,另外会在每一个 Node 上暴露端口 NodePort,外部网络可以通过 [NodeIP]:[NodePort] 访问到 Service。LoadBalancer Service 需要底层云平台支持创建负载均衡器,比如 GCE,它是建立在 NodePort Service 集群基础上的,K8S 会分配给 LoadBalancer Service 一个内部的虚拟 IP,并且暴露 NodePort,除此之外,K8S 请求底层云平台创建一个负载均衡器,将每个 Node 作为后端,负载均衡器将转发请求到 [NodeIP]:[NodePort]。
5. 避坑与排查:双区部署、多租户和数据库容器化的血泪经验
5.1 DMZ 区计算节点访问数据库的两种方案怎么选
现象:DMZ 区计算节点访问内网数据库时,直接开通防火墙后仍然连接超时。原因:DMZ 区节点和数据库不在同一网段,且没有配置 Outbound 路由,流量默认走默认网关而非内网防火墙。解决:内网计算节点可以直接访问数据库,DMZ 区计算节点访问数据库有 2 种方案。方案一是计算节点直接通过内网防火墙访问该应用数据库,内网防火墙仅开通应用所在节点访问内部数据库的端口,例如本期项目,某应用仅使用 2 个节点,则防火墙仅开通这 2 个节点访问该数据库的权限。方案二是计算节点经 Outbound 路由通过内网防火墙访问内网数据,Outbound 路由在 OpenShift 中称之为 Egress Router,因此内网防火墙仅开通应用所在节点访问内部数据库的端口,例如应用 A 仅通过路由节点 A 和 B 访问内部数据库,则防火墙仅开通这 2 个节点访问 A 数据库的权限。选择时看节点数量和防火墙策略复杂度,节点少用方案一,节点多且需要统一出口用方案二。
5.2 多租户网络隔离后服务间调用失败
现象:两个不同 project 的微服务互相调用时,DNS 能解析但 TCP 连接被拒绝。原因:启用了 ovs-multitenant 插件后,不同 VNID 的流量被 openvswitch 自动隔离,不同项目之间的服务在网络层不能互通。解决:如果确实需要跨 project 通信,常见做法是使用oc adm pod-network join-projects将两个 project 的网络合并,或者通过 Router 暴露服务后走外部路由。注意合并网络会削弱租户隔离,生产环境慎用。排查时先用oc get netnamespace查看各 project 的 VNID,再用oc exec进入 Pod 测试curl目标 Service 的 ClusterIP,确认是网络层不通还是应用层拒绝。
5.3 Elasticsearch 在 K8S 中部署的存储选型翻车
现象:ES Pod 频繁重启,日志显示写入超时,集群状态 yellow 或 red。原因:ES 采用了分布式存储作为后端,日志量大的情况下分布式存储 ES 写成为瓶颈。解决:不建议 Elasticsearch 采用分布式存储,日志量大的情况下分布式存储 ES 写会是瓶颈。集中存储目前用得最多,不管是 FCSAN 还是 IPSAN,其稳定性和安全性都能满足要求。如果必须用分布式存储,需要评估 IOPS 和延迟,Ceph 等开源分布式存储需要调优后才能承载 ES 写入。排查时看 ES 的cluster.stat和nodes.stats中的fs.total和write指标,确认磁盘 IO 是否饱和。
5.4 Dubbo + Zookeeper 环境下 K8S Provider 注册地址问题
现象:K8S 上的应用作为 Provider 注册到 Zookeeper 时,注册的是容器地址,Consumer 拿到后无法连接。原因:容器 IP 是集群内部地址,外部 Consumer 或不同网络的 Consumer 无法路由到该地址。解决:如果 K8S 上的应用仅仅是 Consumer,应该是没问题的,不管 Provider 是在 K8S 集群内部还是外部。如果 K8S 上的应用是 Provider,注册到 ZK 时是容器地址,这时如果 Consumer 不在同一集群网络内就会失败。常见做法是让 Provider 注册宿主机 IP 或 NodePort 地址,或者使用 Service 的 ClusterIP 并确保 Consumer 也在集群内。排查时先在 ZK 中get /dubbo/com.example.Service/providers看注册的 URL,再确认 Consumer 能否telnet该地址和端口。
5.5 监控数据持久化后查询缓慢
现象:Heapster + Cassandra 方案运行一段时间后,Hawkular 查询监控数据越来越慢。原因:Cassandra 作为 Heapster 的后端存储,数据量增长后未做 compaction 和 TTL 调优,导致查询扫描过多 SSTable。解决:定期检查 Cassandra 的nodetool tablestats,确认SSTable count和read latency。如果延迟高,调整compaction策略为LeveledCompactionStrategy,并设置合理的 TTL 让过期监控数据自动清理。长期方案是迁移到 Prometheus,Prometheus 的本地 TSDB 在监控场景下查询性能更好,且 OpenShift 3.12 已计划用 Prometheus 替换 Heapster。
6. 进阶技巧:用 nodeSelector + 标签把高可用真正做扎实
高可用这件事,文档里讲了很多层面,但真正落地时最容易翻车的是计算节点标签和目标节点数量的配合。计算节点高可用指计算节点上运行的容器应用的高可用,一个计算节点异常停机后,其上的容器将会被逐步迁移到其他节点上,从而保证了高可用。同时可以通过标签的方式管理计算节点,在不同的计算节点划分为不同的可用区或组,在部署应用时使用节点选择器将应用部署至带有指定标签的目标计算节点上。为了保证高可用,标签组合的目标计算节点数要大于 1,这样可以避免一台目标节点宕机后,调度器还能找到满足条件的计算节点进行容器部署。我一般会强制走一遍这个检查:先确认目标标签对应的节点数,再确认这些节点分布在不同的物理机或可用区,最后用反亲和性把同一应用的多个副本打散。
# Deployment 中同时使用 nodeSelector 和 podAntiAffinity apiVersion: apps/v1 kind: Deployment metadata: name: payment-service spec: replicas: 3 selector: matchLabels: app: payment template: metadata: labels: app: payment spec: nodeSelector: zone: dmz app: payment affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - payment topologyKey: kubernetes.io/hostname containers: - name: payment image: registry.example.com/payment:1.0 resources: requests: cpu: "500m" memory: "512Mi" limits: cpu: "1" memory: "1Gi"逻辑说明:nodeSelector把 Pod 限定在zone=dmz且app=payment的节点上,podAntiAffinity的requiredDuringSchedulingIgnoredDuringExecution确保同一app=payment的 Pod 不会调度到同一台主机(topologyKey: kubernetes.io/hostname)。参数上,replicas: 3配合反亲和性要求至少 3 台满足 nodeSelector 的节点,否则会有 Pod 处于 Pending。resources的 requests 和 limits 用于调度和限制,requests 影响调度决策,limits 影响运行时上限。失败时先看oc describe pod的 Events,如果出现0/8 nodes are available: 3 node(s) didn't match node selector, 5 node(s) didn't match pod anti-affinity rules,说明节点数不够或标签不匹配。注意反亲和性用required时是硬约束,节点不足会直接 Pending,生产环境可以先用preferred软约束过渡。
另一个容易忽略的点是 Router 隔离和物理资源池隔离的配合。Router 是 OCP 平台一个重要软件资源,它提供了外部请求导入 OCP 集群内部的能力。OCP 提供了 Router 分组的功能,不同的 project 可以使用独立的 Router,不互相干扰,这样就避免了由于某些应用流量过大时对其他应用造成干扰。物理资源池隔离方面,在多租户的环境中,为了提高资源的利用率一般情况下物理资源池是共享的,但是有些用户也会提供独占资源池的需求,针对这种类型的需求,OCP 平台利用 nodeSelector 的功能可以将基础设施资源池划分给特定的 project 独享,实现从物理层面的隔离。我一般会在给 project 分配独占节点后,再给这些节点打上project=xxx的标签,并在 project 的 ResourceQuota 中限制 CPU 和内存总量,防止单个租户耗尽节点资源。从那以后我每次做多租户交付,都强制走一遍“标签数 > 副本数、反亲和性拓扑键正确、ResourceQuota 已设置”这三步检查,希望帮到你。
本文还有配套的精品资源,点击获取