2年云原生落地实践在运维和开发侧踩过的6个坑

2023-10-25 19:59

本文主要是介绍2年云原生落地实践在运维和开发侧踩过的6个坑,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

应阔浩 李建宇 等

读完需要

11

分钟

速读仅需 4 分钟

摘要:云原生落地之路并不是一帆风顺的,无论是在运维侧还是研发侧,我们都走过不少冤枉路,下面把曾经遇到的“坑”汇总到一起,分享给在云原生之路探索的你。

1

   

运维侧的教训

运维侧最核心的目标就是保障 Kubernetes 集群的稳定性,在搭建 Kubernetes 集群的过程中,我们遇到了 2 个比较严重的问题,一个是容器产生僵尸进程,另一个是内核 Bug 引起的 Kubelet 负载飙升。

1.1

   

容器产生僵尸进程

Web 终端僵尸进程是困扰我们很久的问题,表现为当研发人员重启 Pod 时,发现集群中存在偶发的一些状态为 Not Ready 的节点,非常诡异,百思不得其解。后来发现原来是过多的 Bash 进程触发了 containerd-shim 的一个 Bug 所致。让我们一起来剖析问题的前因后果。

问题描述

在集群正常运行过程中,运维人员隔一段时间就会收到集群节点 Not Ready 的告警,当 Not Ready 状态持续一段时间后,Kubernetes 集群为了保持服务的高可用就会自动触发保护机制,进行整机迁移。

导致节点状态变为 Not Ready 的原因有很多,经排查发现,状态变为 Not Ready 的 Node 上存在一些处于 terminating 状态的 Pod,这些 Pod 的状态一直停留在 terminating,无法正常终止。同时,在 Node 上执行 docker ps 命令,发现命令无法正常输出,一直在等待 Docker 返回结果。由于 Kubelet 同样依赖 Docker 相关命令探测容器状态,就会导致 Kubelet 内部的健康检查超时,进而引发 Node 状态被标记为 Not Ready。为什么 docker ps 命令会卡死呢?经过进一步排查发现,所有出现问题的 Node 上均存在僵尸进程,并且这些僵尸进程均是由这些持续处于 terminating 状态的 Pod 所创建。

问题原因

为了便于开发人员调试和实时查看容器日志,发布平台提供了 Web 终端功能,让研发人员可以在浏览器中直接进入容器,并执行各种命令。

Web 终端通过 WebSocket 技术连接一个后端服务,该后端服务会调用 API Server 提供的 exec API(通过 client-go 实现),在容器中启动一个 Bash 进程,并将该 Bash 进程的标准输入、输出流与 WebSocket 连接到一起,最终实现了基于 Web 技术的终端。

问题就出现在这个 Bash 进程上,由于该进程并不是由容器父进程(Pid 0)派生的,而是由 containerd-shim 派生出来的,如图 17-1 所示,因此当容器被关闭时,Bash 进程无法收到容器父进程发送的退出信号,需要等待 Kubelet 通知 containerd-shim,由 containerd-shim 发送 killall 指令给容器内的所有进程,并等待这些进程退出。

23b73999b8dc4f2c51c52ae4579d9e30.png

图 17-1 Bash 僵尸进程原理图

containerd-shim 在实现这一步时,使用了一个长度为 32 的阻塞 Channel(Golang 的一种数据结构)来侦听子进程的退出事件,如果子进程较多,触发的退出事件会充满整个 Channel,进而触发 containerd-shim 出现死锁,无法正确回收子进程,从而导致产生了僵尸进程。而在 containerd-shim 发生死锁后,Kubelet 一旦运行 docker inspect 命令查看该容器状态,对应的线程便会挂起,最终导致 Node 进入 Not Ready 状态。

解决方案

定位到问题后,解决问题的核心思路是减少 containerd-shim 下派生的子进程 Bash 的数量上,我们通过 4 步来解决该问题。

  1. 优化 Web 终端代码,在用户关闭浏览器窗口后(WebSocket 连接断开),模拟发送 CTRL+C 和 exit 命令给 Bash 进程,触发 Bash 进程主动退出,如图 17-2 所示。

  2. 设置 Web 终端的超时时间,在 Web 终端空闲 10 分钟后(WebSocket 上没有数据传输),触发其主动退出。

  3. 如果用户使用了 Vim 等会抢占终端输入流的命令,便无法使用第 1 步的方法退出 Bash 进程,我们在每台 Node 上添加了定时任务,主动清理存活 30 分钟以上的 Bash 进程。

  4. 尽量避免使用 exec 类型的探针,而是使用 HTTP 探针替代,exec 探针同样是在 containerd-shim 下派生子进程,也容易造成子进程过多。

6577cbec109f63345120912f937e6613.png

图17-2通过exit命令主动关闭终端进程

1.2

   

内核 Bug 引起的 Kubelet 负载飙升

问题描述

在测试阶段发现,当集群运行一段时间后,研发人员在发布新应用时 Pod 的创建非常缓慢,Web 终端连接超时,Prometheus 获取数据经常丢失,集群宿主机 CPU 负载飙升。

问题分析

从 Pod 创建的过程开始排查,我们使用 kubectl describe 命令查看 Pod 的创建过程,发现从 Pod 资源对象的创建到调度到具体 Node 的耗时很短,说明调度器没有问题。而在 Pod 调度完成后,Kubelet 拉取镜像和创建容器等动作耗时较长,我们怀疑是 Kubelet 的问题,经查看发现 Kubelet 使用的 CPU 时间片偶尔会达到 400%左右,系统调用占比较高,最高达到 40%,随后开始对 Kubelet 进行 CPU 性能分析。

GitHub 上有相同的问题, https://github.com/google/cadvisor/issues/1774

红帽官方也有此 Bug 的讨论地址为 https://bugzilla.redhat.com/show_bug.cgi?id=1795049

博文很长,总结要点如下。

在 Kubernetes 集群中,网络延迟会升高到 100ms。这是由于内核做了太多的工作(在 memcg_stat_show 中)导致网络依赖出现软中断。文章中的例子和我们遇到的场景类似,都

是因为 cAdvisor 从/sys/fs/cgroup/memory/memory.stat 中获取监控数据引发的。

解决方案

  1. 使用 shell 命令检查耗时,如果耗时大于 1s,甚至超过 10s,那么可以判定当前系统的内核存在上面描述的 Bug。

  2. 使用 shell 命令/proc/sys/vm/drop_caches>可以减缓网络延时,但治标不治本。

  3. 禁止 Kubelet 使用 cAdvisor 收集 Cgroup 信息,这样会失去部分监控数据。

  4. 升级内核版本。

其中方案 1、2、3 属于临时方案,推荐使用方案 4,升级内核版本,我们将内核版本升级到 4.19.113-300.el7.x86_64 后,就避免了这个问题。

2

   

开发侧的教训

除了运维侧踩了很多坑,开发侧同样也遇到了不少棘手的问题。

2.1

   

运营问题(使用方式和习惯的改变)

在平台推广初期,尽管新平台相较老平台在各方面都有了很大程度的提升,但平台的推广并没有收到令人满意的效果。这里主要存在开发习惯、迁移成本以及对于一个新产品的不信任等因素,因此,基础架构团队经过深入的用户调研和分析后,决心大力运营平台推广,主要从技术手段和人力手段两个维度并行展开。

在技术层面,针对老平台提供一键迁移代码仓库、一键托管配置文件等效率工具,帮助用户低成本地从老平台迁移到新平台,避免因为烦琐的操作而耗费用户的耐心。

在人力层面,为公司的每个业务技术团队分配专人进行推进,手把手协助业务团队做应用迁移。这一步看似低效,在实际执行中效果非常好。人与人、面对面的沟通,更容易建立业务线技术团队对新平台的信任,帮助他们初步迁移几个应用后,后续的迁移均是由各个业务线研发人员自主进行的,实际消耗时间成本并不高。同时,在手把手帮助业务线迁移的过程中,可以从用户视角观察产品交互效果,这个过程也帮助我们找到了很多平台存在的缺陷,大大促进了平台的进一步优化。

2.2

   

IP 白名单访问控制

在应用进行容器化部署的过程中,也暴露出了现有架构不合理的地方,比如在解决访问鉴权问题时,过度依赖 IP 白名单。IP 白名单是一种低成本且能初步满足需求的鉴权机制,但存在不灵活、不易扩展等问题,例如很容易出现上游应用部署实例进行变更或者扩容时,下游应用没有及时修改 IP 白名单,导致访问被拒绝,从而引发线上故障。同时 IP 白名单也不是一种足够安全的鉴权机制,尤其是 IP 可能会被回收并重新分配给其他应用使用,这时如果没有及时回收原有 IP 上的授权,很有可能引发权限泄漏。

在应用进行容器化改造后,由于我们直接使用的是原生的网络方案,在容器被重新调度后,容器的 IP 会发生改变,这样便无法沿用 IP 白名单鉴权机制。为此我们从访问方以及服务提供方两个方向入手,制定了 3 个阶段的改造方案。

第一阶段,添加代理层。我们在访问方与服务提供方二者之间,部署了一套 Nginx 代理服务器,将 Kubernetes Ingress 的 IP 作为服务提供方的上游客户端地址,配置进入 Nginx 相应域名的上游客户端。对访问方进行改造,将原始服务提供方的域名替换为代理服务器的域名。改造后,从服务提供方视角观察到的访问方 IP 变为代理服务器的 IP,这时将代理服务器的 IP 配置进服务提供方的 IP 白名单后,无论访问方的 IP 如何变化,都不会被服务提供方的 IP 白名单限制了。

第二阶段,提供服务方改造。在第一阶段实施完成后,虽然访问方实现了容器化改造,但在服务提供方留下了安全漏洞,只要获取新加入的代理层 IP,即可将自己伪装成刚刚完成容器化改造的应用,并以该应用的身份调用服务提供方。在第二阶段,我们要对服务提供方进行改造,让其支持 API Key、对称签名等与 IP 无关的访问控制机制,以抵御代理层引入的安全风险。

第三阶段,访问方改造。在服务提供方完成与 IP 无关的访问控制机制后,访问方应随之做改造,以支持新的访问控制方式。这时访问方就可以将服务提供方的地址从代理服务器切换回服务提供方的真实地址了。在经过一段时间的验证后,访问方即可将代理服务器的 IP 从 IP 白名单列表中移除,最终完成整个改造方案。

2.3

   

流量平滑迁移

在平台推广初期,除去试水的新应用,很大一批应用是从老发布平台迁移到容器化平台上的。在迁移过程中,我们必须帮助用户将老平台的流量平滑、逐步迁移到新平台上。

我们通过在原有 Nginx 代理中,添加 Kubernetes Ingress IP 的方式,进行一段时间的灰度发布,逐渐调高 Kubernetes 流量的权重,直至将 100%的流量迁移至 Kubernetes。在切换初期,我们并不会直接调整 DNS,而是将 Ingress IP 加入域名的上游客户端,例如以下 Nginx 配置代码片段。

3017fae5fd16838eaa3f4fcbde7eb4f1.png其中 10.16.55.*段是应用的原始服务节点,而 10.216.15.96 与 10.216.15.196 是 Ingress 的 IP 地址,将 Ingress IP 加入域名的上游客户端中后,流量就可以根据我们配置的 weight 参数再均摊到多个节点中。通过逐步增大 Ingress 节点的 weight 值,将应用原节点的 weight 值降为 0,再持续运行一段时间后,即可将 DNS 直接配置解析到 Ingress 中,完成流量的最终切换。

在切换过程中,有以下需要注意的问题。

  • 在迁移过程中,新需求上线时,业务线研发需要在新/老两个平台上同时上线,切记不能忘记更新原有平台的应用代码。

  • 由于额外添加了一层代理,外部观察到的容器响应时间会比实际的高,这时需要从应用本身去观测性能指标,并与老平台的应用进行对比。

  • 额外的代理层除了会对响应时间造成影响,还会额外记录一份响应日志,即访问会在原 Nginx 服务器上记录一次,同时在 Ingress 服务器上也记录一次。在迁移过程中,很难完全排查这部分统计误差。

2.4

   

上线后的分支要不要删除?

Aone Flow 分支模型虽然灵活,但会在代码仓库中创建大量短生命周期的分支,有两种分支需要进行定期清理。

第一种是发布分支。每次有特性分支从发布分支退出时,就会导致相应环境的发布分支被重新创建,如图 17-3 所示。

ea4d5f53f052c5e3f6b2465492a1730d.png

图17-3特性分支退出并重建发布分支

在特性分支(feature/001)退出后,我们会创建新的发布分支(release/daily/20211105),这时旧发布分支(release/daily/20211102)的生命周期便结束了,特性分支(feature/002)的后续提交均会合并到新的发布分支(release/daily/20211105)上,此时旧发布分支便可以被安全地删除。

注意,虽然删除发布分支一般不会导致代码丢失,但如果在该发布分支上解决过代码合并冲突,这部分解决合并冲突的工作需要在新的发布分支上重新进行一次。如果之前发生冲突时,是在特性分支上解决的,就无需重复进行冲突的解决了。这也是遇到分支冲突时,推荐在特性分支上解决冲突的原因。

第二种需要清理的分支是特性分支。在特性分支被发布到正式环境,并且合并到主干分支后,即可标记特性分支为可以被删除。与发布分支重建后,老发布分支可被立刻删除不同,我们对特性分支采取了延迟删除的策略,每周将历史上已经处于冻结状态的特性分支进行清理。

为什么不立刻删除已经上线的特性分支呢?这是因为虽然特性分支已经被合并到主干分支,但可能在上线后发现代码存在 Bug,需要进行及时修复,这时最高效的方式是在引入 Bug 的特性分支上进行代码修改,并立刻进行回归测试,然后重新上线修复。但特性分支在上线后,状态会被标记为已冻结,无法重新上线,这时发布平台可以从已冻结的特性分支中快速复制并得到一个新的分支,进行上线修复。如果我们在特性分支上线后,立刻删除该特性分支,就无法做到这一点了。

更多 Q&A 集锦,请参阅《云原生落地:企业级 DevOps 实践》一书4cfc2780a9db307c39a1e1f756481450.png

《云原生落地:企业级DevOps实践》

自如官方深度复盘云原生落地全过程,云原生和DevOps实践标准读物,沈剑、孙玄、乔新亮、史海峰等17位专家力荐.

会额外记录一份响应日志,即访问会在原Nginx服务器上记录一次,同时在Ingress服务器上也记录一次。在迁移过程中,很难完全排查这部分统计误差。

4.上线后的分支要不要删除?

Aone Flow分支模型虽然灵活,但会在代码仓库中创建大量短生命周期的分支,有两种分支需要进行定期清理。

第一种是发布分支。每次有特性分支从发布分支退出时,就会导致相应环境的发布分支被重新创建,如图17-3所示。

8250f0740aed4214e72eec4b50ebe253.png

图17-3特性分支退出并重建发布分支

在特性分支(feature/001)退出后,我们会创建新的发布分支(release/daily/20211105),这时旧发布分支(release/daily/20211102)的生命周期便结束了,特性分支(feature/002)的后续提交均会合并到新的发布分支(release/daily/20211105)上,此时旧发布分支便可以被安全地删除。

注意,虽然删除发布分支一般不会导致代码丢失,但如果在该发布分支上解决过代码合并冲突,这部分解决合并冲突的工作需要在新的发布分支上重新进行一次。如果之前发生冲突时,是在特性分支上解决的,就无需重复进行冲突的解决了。这也是遇到分支冲突时,推荐在特性分支上解决冲突的原因。

第二种需要清理的分支是特性分支。在特性分支被发布到正式环境,并且合并到主干分支后,即可标记特性分支为可以被删除。与发布分支重建后,老发布分支可被立刻删除不同,我们对特性分支采取了延迟删除的策略,每周将历史上已经处于冻结状态的特性分支进行清理。

为什么不立刻删除已经上线的特性分支呢?这是因为虽然特性分支已经被合并到主干分支,但可能在上线后发现代码存在Bug,需要进行及时修复,这时最高效的方式是在引入Bug的特性分支上进行代码修改,并立刻进行回归测试,然后重新上线修复。但特性分支在上线后,状态会被标记为已冻结,无法重新上线,这时发布平台可以从已冻结的特性分支中快速复制并得到一个新的分支,进行上线修复。如果我们在特性分支上线后,立刻删除该特性分支,就无法做到这一点了。

更多Q&A集锦,请参阅《云原生落地:企业级DevOps实践》一书

推荐阅读:

ee758976694f0fa833d319faedba069f.png

《云原生落地:企业级DevOps实践》

自如官方深度复盘云原生落地全过程,云原生和DevOps实践标准读物,沈剑、孙玄、乔新亮、史海峰等17位专家力荐

往期推荐

如葑:阿里云原生网关Envoy Gateway实践

阿里在云原生架构下的微服务选型和演进

一线大厂的企业云原生成本优化实践指南

科技创新的大湾区,怎能不谈云原生

漫画:如何用 K8s 实现 CI/CD 发布流程?

Kubernetes时代的微服务

漫画 | 如何用 Kubernetes 实现 CI/CD 发布流程?

蚂蚁集团沈凋墨:Kubernetes-微内核的分布式操作系统

为荣誉而战,成都Kubernetes Meetup纪实|文末PPT+视频

这篇关于2年云原生落地实践在运维和开发侧踩过的6个坑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/284843

相关文章

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

90、k8s之secret+configMap

一、secret配置管理 配置管理: 加密配置:保存密码,token,其他敏感信息的k8s资源 应用配置:我们需要定制化的给应用进行配置,我们需要把定制好的配置文件同步到pod当中容器 1.1、加密配置: secret: [root@master01 ~]# kubectl get secrets ##查看加密配置[root@master01 ~]# kubectl get se

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。