阅读视图

发现新文章,点击刷新页面。
🔲 ☆

一天重写 JSONata,我用 400 美元干掉了公司 50 万美元的 K8s 集群

本文永久链接 – https://tonybai.com/2026/04/01/rewrote-jsonata-in-golang-with-ai

大家好,我是Tony Bai。

过去的几年,我们见证了 AI 编程工具从“玩具”到“神器”的进化。无数开发者都在分享自己效率翻倍的喜悦。

你有没有想过,用 AI 来完成一次“外科手术式”的精准重构,一天之内,就能帮你把公司每年烧掉的 50 万美元(约 360 万人民币)的服务器成本,直接砍到零?

这听起来像天方夜谭,但它真实地发生了。

就在前几天,以色列安全公司 Reco 的工程师 Nir Barak 发表了一篇极其硬核的博客。他详细复盘了自己是如何在一天之内,花费了仅仅 400 美元的 Token 费用,利用 AI 将一个用 JavaScript 编写的核心组件 JSONata,完美地重写为了纯 Go 版本,最终为公司节省了每年 50 万美元的开销,并带来了 1000 倍的性能提升。

这不仅仅是一个“AI 真牛逼”的简单故事。它背后揭示的,是一套足以改变我们未来架构选型和技术债偿还方式的“AI 驱动重构(AI-Driven Refactoring)”实用方法。

跨语言 RPC,微服务架构中最昂贵的“性能税”

要理解这次重构的意义有多么重大,首先得看看 Nir Barak 的团队曾经陷入了多深的泥潭。

他们的核心业务是一个用 Go 编写的高性能数据管道,每天处理数十亿的事件。但其中有一个环节,需要用到一个名为 JSONata 的查询语言(你可以把它想象成带 Lambda 函数的 jq)来执行动态策略。

尴尬的是,JSONata 的官方实现是 JavaScript 写的。

这就导致了一个极其痛苦的架构:他们的主业务 Go 服务,为了执行这些规则,不得不去远程调用(RPC)一个专门部署在 Kubernetes 上的庞大的 Node.js 服务集群。

这个“小小的”跨语言调用,给他们带来了三大噩梦:

  1. 恐怖的成本:为了扛住流量,这个 jsonata-js 集群常年需要维持 300 多个 Pod 副本,光是这部分,每年就要烧掉 30 万美元的计算资源。
  2. 惊人的延迟:一次最简单的字段查找,比如 email = “admin@co.com”,在 Node.js 内部执行可能只需要几纳秒。但算上序列化、跨进程网络往返的开销,一次 RPC 调用在啥也没干之前,150 微秒的延迟就先进来了。对于一个每天处理几十亿事件的系统来说,这简直是灾难。
  3. 意想不到的运维黑洞:随着业务增长,Pod 数量一度多到耗尽了 Kubernetes 集群的 IP 地址分配上限!

Nir Barak 的团队当然也尝试过各种小修小补:优化表达式、加缓存、甚至用 CGO 把 V8 引擎直接嵌进 Go 里……但这些都只是“头痛医头”,无法根治“跨语言”这颗毒瘤。

Cloudflare 的“抄作业”哲学

转机发生在前几周。Nir Barak 看到了 Cloudflare 那篇刷爆全网的文章《我们如何用 AI 在一周内重构 Next.js》。

Cloudflare 的做法极其“暴力”且有效:他们没有让 AI 去创造新东西,而是把 Next.js 现成的spec,以及包含几千个 case 的官方测试套件(Test Suite)直接扔给大模型,然后对 AI 下达了一个简单粗暴的指令:

“我不管你怎么实现,给我写一个能在 Vite 上跑通所有这些测试的 API 就行!”

Nir Barak 看到这里,瞬间被点醒了:“我们面临的问题一模一样!我们也有 jsonata-js 官方那套包含 1778 个测试用例的完整套件啊!”

与其让 AI 去搞创新,不如把它变成一个任劳任怨、24 小时待命的“代码翻译工”!

于是,他花了一个周末,用 AI 制定了一个极其清晰的“三步走”作战计划:

  1. 第一步(人类智慧):用 Go 语言把 jsonata-js 的测试套件先“翻译”过来。
  2. 第二步(AI 体力):把 JSONata 2.x 的官方文档和规范全部喂给 AI。
  3. 第三步(测试驱动):对 AI 下达指令:“开始写 Go 代码,目标是跑通第一步的所有测试用例。”

第二天,他按下了“开始键”。

7 小时,400 美元,13000 行 Go 代码

接下来的故事,充满了令人肾上腺素飙升的极客快感。

Nir Barak 坐在电脑前,看着 AI Agent 像一台失控的缝纫机一样,疯狂地生成 Go 代码、运行测试、读取报错、然后自我修正。

整个过程被划分成了几个“波次(Waves)”:先实现核心解析器,再实现内置函数,最后处理各种边缘 case。

在 AI 与测试用例的左右互搏之下,仅仅 7 个小时 后,奇迹发生了:

一个包含 13,000 多行纯 Go 代码的、名为 gnata 的全新 JSONata 实现诞生了。它完美通过了官方所有的 1778 个测试用例。

而这整个过程的成本呢?

400 美元的 Token 费用。

Nir Barak 在博客中晒出了一张截图,数据显示,在重构 gnata 的那一天,AI 生成的代码占比高达 91.7%

当他把这个 PR 提交到公司内部时,立刻有人质疑 ROI(投资回报率)。而他的回答简单粗暴:

“上个月,jsonata-js 集群的成本是 2.5 万美元。现在,是 0。”

百倍性能与意外之喜:“手术刀式”重构的深远影响

成本降为零已经足够震撼,但性能上的收益更是堪称“恐怖”。

这还只是开始。由于 gnata 是纯 Go 实现,Nir Barak 团队得以进行更深度的“魔改”:他们设计了一套两层评估架构。对于简单的字段查找,gnata 直接在原始的 JSON 字节流上操作,实现了 零堆内存分配(Zero Heap Allocations)!只有遇到复杂表达式时,才会启动完整的解析器。

在接下来的两周内,他们乘胜追击,用 gnata 的批量处理能力,替换掉了主数据管道中另一个极其臃肿、靠启动上万个 Goroutine 来并发处理规则的旧引擎。 结果:又省下了每年 20 万美元。

短短两周,两次“外科手术式”的重构,总共为公司节省了每年 50 万美元的开销。

最让人意想不到的是,这次重构还带来了组织层面的“意外之喜”:

gnata 是公司内部第一个完全由 AI Agent 大规模参与生成的 PR。在 Code Review 的过程中,团队成员被迫去学习如何分辨“AI 真正发现的并发 Bug”和“AI 瞎操心的代码格式问题”。这次经历,为他们后续制定全公司的 AI Code Review 规范积累了宝贵的实战经验。

小结:我们不再只是“氛围感编码”

在文章的结尾,Nir Barak 提到了 AI 大神 Andrej Karpathy 最近的观点,大意是:

“编程正在变得面目全非。在底层,深厚的技术专长正成为比以往任何时候都更强大的‘乘数效应放大器’。”

Nir Barak 感慨道,直到最近,他自己都对那种完全由 AI Agent 生成代码的“氛围编码(Vibe coding)”持怀疑态度。但 2026 年 2 月,成为了一个连他这样固执的开发者都无法忽视的“拐点”

gnata 的诞生,标志着我们不再只是用 AI 去写一些无关紧要的玩具项目。在拥有明确测试用例和边界规范的前提下,AI 已经具备了对生产环境核心组件进行“手术刀式重构”的惊人能力。

你准备好拿起这把名为“AI”的手术刀,去切掉你系统里那些最昂贵、最臃肿的“技术肿瘤”了吗?

资料链接:https://www.reco.ai/blog/we-rewrote-jsonata-with-ai


今日互动探讨:

在你的公司里,是否存在类似的“异构技术栈”调用导致的性能瓶颈或成本黑洞?你有没有想过,可以用 AI + 测试用例的方式,对某个核心组件进行“代码翻译”式的重构?

欢迎在评论区分享你的架构痛点与大胆构想!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

Kubernetes PV数据卷缩容方案及统计PV容量

在 Kubernetes 中,直接对 Persistent Volume (PV) 进行容量缩容(减小容量)通常是不被支持的。这主要是出于数据安全的考虑,因为贸然缩小底层存储设备可能会破坏数据。

理解 PV 缩容的限制

Kubernetes 及其存储生态系统在设计上就偏向于扩容,对缩容则有严格限制,主要原因如下:

  • 数据安全风险:缩容操作可能导致数据被截断或丢失。如果新的容量小于已存储的数据量,后果不堪设想。

  • 底层存储限制:许多底层存储系统(如 AWS EBS、Longhorn 等)并不支持在线缩小卷容量。例如,LVM 的 pvresize 命令也会拒绝缩小已分配空间的物理卷。

  • Kubernetes 机制限制:Kubernetes 的 allowVolumeExpansion 配置项仅控制扩容,并不会开启缩容功能。

替代方案与变通方法

虽然不能直接缩容,但是可以考虑以下替代方案来管理存储资源:

方案核心步骤优点注意事项
重建 PV 和 PVC1. 备份数据
2. 创建新的、容量更小的 PV 和 PVC。
3. 将数据从旧 PV 迁移到新 PV。
4. 更新 Pod 配置以使用新 PVC,并删除旧资源。
可靠且通用,适用于大多数不支持直接缩容的场景。过程繁琐,涉及业务中断数据迁移
调整存储配额通过 Kubernetes 的 ResourceQuota 来限制命名空间未来的存储请求总量,而不是缩小已有卷。无数据风险,有效预防未来存储资源的过度使用。无法释放已分配的存储空间
利用存储供应商特性某些云厂商的存储服务(如 NetApp Trident)可能通过精简配置(Thin Provisioning)等方式,在逻辑上限制容量使用,而无需物理缩容高效利用存储资源依赖特定存储后端,并非普适性解决方案。

总而言之,在 Kubernetes 中直接对 PV 进行缩容操作是不可行的。当需要减少 PV 容量时,数据迁移和PV/PVC重建是目前最主流和安全的做法。通过创建一个容量更小的新PV,然后迁移数据,可以实现存储空间的"缩容"。这个过程的核心步骤包括:准备新PV、迁移数据、更新应用指向新存储,以及清理旧资源。

为了更安全地进行操作,请务必先留意以下关键风险点和前提:

关键项目重要说明
数据备份务必先备份原始数据,以防迁移过程中发生意外丢失。
业务中断数据迁移通常需要停止相关Pod,请规划在业务低峰期进行。
存储类型确保Kubernetes集群和存储系统支持动态供应或静态PV创建。
应用兼容性确保应用能够适应存储卷的切换。

详细操作步骤

1、确认当前PV/PVC状态并备份数据
执行以下命令,记录当前PV(Persistent Volume)和PVC(Persistent Volume Claim)的详细信息,特别是当前的存储容量和访问模式。

kubectl get pv <old-pv-name> -o yaml
kubectl get pvc <old-pvc-name> -n <namespace> -o yaml

务必确保已经对重要数据进行了可靠备份

2、停止使用旧存储的Pod
为了避免数据不一致,需要先停止所有正在使用该PVC的Pod。具体的操作方法取决于工作负载类型:

    • 如果是Deployment,可以将其副本数缩容到0:kubectl scale deployment <deployment-name> --replicas=0 -n <namespace>

    • 如果是StatefulSet,同样缩容到0:kubectl scale statefulset <statefulset-name> --replicas=0 -n <namespace>

    • 如果是直接创建的Pod,则直接删除:kubectl delete pod <pod-name> -n <namespace>

    3、创建新的、容量更小的PV
    根据存储供应方式,选择静态或者动态方式创建新PV。

    • 静态供应:编写新的PV YAML文件,注意capacity.storage字段设置为目标缩容容量。

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: new-small-pv  # 给新PV起个名字
    spec:
      capacity:
        storage: 5Gi  # 这里设置你想要的、更小的容量
      accessModes:
        - ReadWriteOnce  # 根据你的需求设置访问模式
      persistentVolumeReclaimPolicy: Retain  # 建议先设置为Retain
      storageClassName: manual  # 指定存储类
      # ... 其他必要字段根据你的存储系统来定

    然后应用这个YAML文件:kubectl apply -f new-pv.yaml

    • 动态供应:创建一个新的PVC,其spec.resources.requests.storage字段指定为缩容后的容量,并确保其绑定的StorageClass能正确工作。Kubernetes会自动为其创建并绑定一个符合要求的PV。

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: new-small-pvc
      namespace: your-namespace
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 5Gi  # 指定缩容后的容量
      storageClassName: standard  # 指定能动态创建PV的StorageClass

    应用这个YAML文件:kubectl apply -f new-pvc.yaml

    4、将数据从旧PV迁移到新PV
    这是最关键的一步。常见的方法有:

      • 使用kubectl cp命令:如果PV支持文件系统模式,可以启动一个临时的Pod挂载旧PVC,将数据拷贝到本地,然后另一个临时Pod挂载新PVC,再将数据从本地拷贝过去。

      • 使用rsync工具:对于大量数据,rsync效率更高。可能需要启动一个包含rsync的临时Pod来完成。

      • 存储系统快照/克隆:部分高级存储系统(如Longhorn, Portworx)自身支持卷的快照和克隆功能,可以利用这些功能来加速数据迁移。

      5、修改Pod配置,指向新的PV/PVC
      数据迁移并验证无误后,更新Pod配置模板(例如Deployment或StatefulSet的YAML文件),将其volumes部分指向新的PVC名称

      volumes:
      - name: my-storage
        persistentVolumeClaim:
          claimName: new-small-pvc  # 这里改为新PVC的名字

      然后应用这个更新:kubectl apply -f your-app.yaml

      6、重启应用Pod
      根据第二步中停止Pod的方式,重新启动应用。

        • 如果是Deployment,将副本数扩展回目标数量:kubectl scale deployment <deployment-name> --replicas=<target-replicas> -n <namespace>

        • 如果是StatefulSet,同样操作:kubectl scale statefulset <statefulset-name> --replicas=<target-replicas> -n <namespace>

        • 如果是直接创建的Pod,则重新创建。

        7、验证应用和数据
        密切观察新Pod的启动日志和行为,确认应用能够正常访问新PV上的数据,并且功能符合预期。
        可以再次使用kubectl get pvkubectl get pvc命令,确认新的PVC new-small-pvc已经处于Bound状态,并且绑定到了正确的PV上。

        8、谨慎清理旧PV/PVC
        在确认新环境稳定运行一段时间(例如24小时)后,并且确定不再需要旧数据时,再考虑清理旧的PV和PVC。
        注意:清理操作是不可逆的,请再次确认数据安全。

        kubectl delete pvc <old-pvc-name> -n <namespace>
        kubectl delete pv <old-pv-name>

        补充建议

        • 预防胜于治疗:在Kubernetes命名空间中,可以通过创建ResourceQuotaLimitRange资源来预防单个PVC请求过大的存储空间,从源头控制存储使用。

        • 理解回收策略:在删除旧PV时,务必了解其persistentVolumeReclaimPolicy(回收策略)。设置为Retain时,删除PVC后PV及相关后端存储资源会被保留;设置为Delete时,则可能会被自动清理。


        K8s统计PV实际使用量

        在PV卷缩容替代方案实施前,我们通常会统计Kubernetes集群中PV的总使用量,需要明白一点:kubectl get pv 命令本身无法直接提供PV的实际使用量,它主要显示的是PV的配置容量和状态信息。要获取PV的实际使用量,还需要借助其他方法。下面梳理几种可行的方案。

        快速查看PV使用情况

        如果想快速查看单个PV或PVC的实时使用情况,可以尝试以下方法:

        方法操作说明
        进入Pod查看1. 找到挂载目标PV的Pod:kubectl get pods -o wide --all-namespaces
        2. 进入Pod并查看挂载点使用情况:kubectl exec -it <pod_name> -- df -h 或 `kubectl exec -it <pod_name> -- df -h
        grep <mount_point>`优点:简单直接,无需额外工具。
        局限:需要Pod处于运行状态,且只能查看单个Pod挂载的PV情况。
        使用第三方工具安装 pvcpie 工具:pip install pvcpie,然后执行 pvcpie 命令一个专门用于获取集群中PVC使用信息的Python工具。

        搭建监控系统获取全面数据

        对于生产环境或需要长期、全面监控的场景,建议搭建监控系统:

        1.部署Metrics Server与监控工具

          • 确保集群已安装 Metrics Server

          • 部署 Prometheus 来收集和存储指标数据

          • 部署 Grafana 进行数据可视化

          2.利用Prometheus指标查询
          Prometheus可以收集PV的实际使用量指标,例如 kubelet_volume_stats_used_bytes (卷中已使用的字节数) 和 kubelet_volume_stats_capacity_bytes (卷的总容量字节数)。可以使用PromQL查询语言进行统计。例如,查询所有PV的已用空间和容量:

          kubelet_volume_stats_used_bytes
          kubelet_volume_stats_capacity_bytes

          3.配置Grafana仪表板

          在Grafana中,可以基于Prometheus的数据源创建仪表板,通过图表直观展示PV的总使用量、使用率趋势等。

          云平台或特定发行版的集成监控

          如果使用的是特定云平台的Kubernetes服务(如Azure AKS)或特定发行版(如Red Hat OpenShift),它们通常提供了集成的监控方案:

          • Azure AKS:Azure Monitor的容器见解功能可以自动收集PV使用情况指标(如 pvUsedBytes),并提供了预配置的工作簿进行查看。

          • Red Hat OpenShift:集成了Prometheus,可以直接使用相关的PV监控指标

          最佳实践与建议

          • 明确需求:根据需求(临时检查 vs. 长期监控)选择合适的方法。

          • 数据准确性:请注意,某些监控工具(如 kubectl top)提供的指标可能专为Kubernetes自动扩缩容决策优化,与操作系统工具(如 top)的结果可能不完全一致。

          • 配置告警:在监控系统基础上,为PV使用率配置告警,以便在空间不足时及时收到通知。

          统计所有PV的配置容量总量,用kubectl get pv命令结合一些简单的文本处理工具就能做到。下面是一个汇总表格,列出了几种常用的方法:

          方法命令特点
          JSONPath提取kubectl get pv -o jsonpath='{.items[*].spec.capacity.storage}'直接提取容量值,但结果在同一行,需后续处理
          JSONPath循环kubectl get pv -o jsonpath='{range .items[*]}{.spec.capacity.storage}{"\n"}{end}'每个PV容量单独一行,方便后续处理
          自定义列kubectl get pv -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage输出表格,直观显示每个PV的容量
          自定义列(仅容量)kubectl get pv -o custom-columns=CAPACITY:.spec.capacity.storage --no-headers仅输出容量列,无表头,适合直接处理

          重要提醒

          • 配置容量 vs 实际使用量kubectl get pv命令显示的是PV的配置容量(申请或初始设置的大小),而不是当前的实际数据使用量。要查看实际使用量,通常需要进入Pod内部使用df -h等命令,或借助集群监控系统(如Prometheus)。

          • 单位一致性:确保所有PV的容量单位一致(例如,都是Gi或都是Mi),或者在处理时进行了适当的单位转换,否则求和结果可能不准确。

          • 动态供应PV:对于通过StorageClass动态供应的PV,其配置容量通常由对应的PersistentVolumeClaim (PVC) 指定。

          K8s统计PV配置容量

          当PV的容量单位不一致时(比如同时存在Gi、Ki、Mi等),需要先统一单位再累加。以下是几种处理这种情况的方法:

          方法一:使用awk进行单位转换和累加

          kubectl get pv -o custom-columns=CAPACITY:.spec.capacity.storage --no-headers | awk '
          {
              # 提取数字和单位
              if ($1 ~ /Gi$/) {
                  gsub(/Gi/, "", $1)
                  sum += $1 * 1024 * 1024  # Gi → Ki
              }
              else if ($1 ~ /Mi$/) {
                  gsub(/Mi/, "", $1)
                  sum += $1 * 1024  # Mi → Ki
              }
              else if ($1 ~ /Ki$/) {
                  gsub(/Ki/, "", $1)
                  sum += $1  # Ki保持不变
              }
              else if ($1 ~ /G$/) {
                  gsub(/G/, "", $1)
                  sum += $1 * 1000 * 1000  # G → K (十进制)
              }
              else if ($1 ~ /M$/) {
                  gsub(/M/, "", $1)
                  sum += $1 * 1000  # M → K (十进制)
              }
              else if ($1 ~ /K$/) {
                  gsub(/K/, "", $1)
                  sum += $1  # K保持不变
              }
              else {
                  # 没有单位,假设是字节
                  sum += $1 / 1024  # 字节 → Ki
              }
          }
          END {
              # 输出结果,可以选择合适的单位
              if (sum >= 1024*1024*1024) {
                  printf "总容量: %.2f Ti\n", sum / (1024*1024*1024)
              } else if (sum >= 1024*1024) {
                  printf "总容量: %.2f Gi\n", sum / (1024*1024)
              } else if (sum >= 1024) {
                  printf "总容量: %.2f Mi\n", sum / 1024
              } else {
                  printf "总容量: %.2f Ki\n", sum
              }
          }'

          方法二:使用jq进行更精确的处理

          kubectl get pv -o json | jq '
          [.items[].spec.capacity.storage | 
           capture("(?<value>[0-9.]+)(?<unit>[A-Za-z]+)") |
           {
             value: (.value | tonumber),
             factor: (if .unit == "Ti" then 1024*1024*1024*1024
                     elif .unit == "Gi" then 1024*1024*1024
                     elif .unit == "Mi" then 1024*1024
                     elif .unit == "Ki" then 1024
                     elif .unit == "T" then 1000*1000*1000*1000
                     elif .unit == "G" then 1000*1000*1000
                     elif .unit == "M" then 1000*1000
                     elif .unit == "K" then 1000
                     else 1 end)
           } | .value * .factor] | add' | awk '
          {
              total_ki = $1 / 1024  # 转换为KiB基数
              if (total_ki >= 1024*1024*1024) {
                  printf "总容量: %.2f Ti\n", total_ki / (1024*1024*1024)
              } else if (total_ki >= 1024*1024) {
                  printf "总容量: %.2f Gi\n", total_ki / (1024*1024)
              } else if (total_ki >= 1024) {
                  printf "总容量: %.2f Mi\n", total_ki / 1024
              } else {
                  printf "总容量: %.2f Ki\n", total_ki
              }
          }'

          方法三:简化的单位转换脚本

          如果经常需要这个功能,可以创建一个可重用的脚本:

          #!/bin/bash
          # pv-total-capacity.sh
          
          calculate_pv_capacity() {
              kubectl get pv -o custom-columns=CAPACITY:.spec.capacity.storage --no-headers | awk '
              function to_kib(value, unit) {
                  switch(unit) {
                      case "Ti": return value * 1024 * 1024 * 1024 * 1024
                      case "Gi": return value * 1024 * 1024 * 1024
                      case "Mi": return value * 1024 * 1024
                      case "Ki": return value * 1024
                      case "T": return value * 1000 * 1000 * 1000 * 1000
                      case "G": return value * 1000 * 1000 * 1000
                      case "M": return value * 1000 * 1000
                      case "K": return value * 1000
                      default: return value  # 假设已经是字节
                  }
              }
              
              {
                  if (match($0, /([0-9.]+)([A-Za-z]*)/, parts)) {
                      value = parts[1]
                      unit = parts[2]
                      sum_kib += to_kib(value, unit) / 1024
                  }
              }
              END {
                  # 输出各种单位的表示
                  printf "总容量统计:\n"
                  printf "  %d Bytes\n", sum_kib * 1024
                  printf "  %.2f KiB\n", sum_kib
                  printf "  %.2f MiB\n", sum_kib / 1024
                  printf "  %.2f GiB\n", sum_kib / (1024*1024)
                  printf "  %.2f TiB\n", sum_kib / (1024*1024*1024)
              }'
          }
          
          calculate_pv_capacity

          使用建议

          1. 推荐使用方法一的awk脚本,它简单且功能完备

          2. 如果环境中有jq工具,方法二可以提供更精确的解析

          3. 对于生产环境,建议将方法三保存为脚本文件,方便重复使用

          这些方法都能正确处理混合单位的情况,并输出易于理解的总容量统计结果。可以根据实际需求选择最适合的方法。

          参考:Kubernetes 环境下华为云盘与 PV 数据目录缩容方案分析与实施

          🔲 ☆

          K8s HPA原理及最佳实践

          在Kubernetes整个体系中,弹性伸缩是至关重要的功能,其分为两种:水平弹性伸缩(Horizontal Pod Autoscaling,简称HPA)垂直弹性伸缩(Vertical Pod Autoscaler,简称VPA)。HPA可以根据观察到的资源实际使用情况(如CPU/内存)或其它自定义指标负载自动调整Pod副本数,VPA可以根据资源实际使用情况为其Pod中的容器设置最新的资源配额requests。这两者合理使用既可以满足业务对实例数的要求保证系统稳定性,同时又能充分提升集群资源的利用率。

          1758179126855162.png

          一、HPA 原理

          HPA (Horizontal Pod Autoscaler) 的核心原理可以概括为:定期查询监控指标,然后根据其目标值(Target Value) 和当前值(Current Value),通过特定的算法计算出所需的 Pod 副本数量,自动控制 Deployment、StatefulSet 等资源的副本数量,以使应用的平均资源利用率(如 CPU、内存)维持在你设定的目标值

          1758176659278527.png

          下面是图中涉及的核心组件及其作用的文字说明:

          • HPA Controller:HPA 的“决策大脑”,是 kube-controller-manager 的一部分。它负责[定期循环](默认每隔 15秒)通过 API Server 查询其管理的所有 HPA 对象及其对应的监控指标,并根据指标数据计算是否需要扩缩容以及需要多少个 Pod 副本

          • API Server:Kubernetes 的“信息枢纽”。HPA Controller 通过它查询 HPA 配置、获取监控指标,也是通过它来更新工作负载的副本数量。

          • Metrics API指标数据的标准化接口。HPA Controller 只通过统一的 Metrics API 来获取指标数据,而不关心数据的具体来源。这层抽象使得 HPA 可以灵活地使用不同种类的指标。

            • resource.metrics.k8s.io:提供资源指标,如 Pod 的 CPU 和内存使用率,通常由 Metrics Server 实现

            • custom.metrics.k8s.io:提供自定义指标,这些指标通常与特定应用或 Kubernetes 对象(如 Pod 的 QPS)相关。常用 Prometheus Adapter 来从 Prometheus 中获取并暴露这些指标

            • external.metrics.k8s.io:提供外部指标,这些指标完全来自于 Kubernetes 集群之外的系统,如消息队列的长度、云服务的监控指标等。

          • Metrics Server:一个集群范围内的资源使用数据聚合器。它从每个节点上的 kubelet 收集核心资源指标(如 CPU 和内存),并通过 resource.metrics.k8s.io API 暴露它们,供 HPA 等组件使用。

          • Prometheus + Prometheus Adapter:一个常见的自定义指标方案Prometheus 负责采集和存储丰富的监控数据。Prometheus Adapter 则作为一个桥梁,查询 Prometheus 中的数据,并将其转换为 Kubernetes 能够理解的格式,通过 custom.metrics.k8s.io API 暴露给 HPA Controller

          HPA 的工作流程是一个持续的控制循环,上图的数字编号也大致对应了这些步骤:

          1. 查询 HPA 配置:HPA Controller 定期(默认 15s,可通过 --horizontal-pod-autoscaler-sync-period 参数调整)通过 API Server 查询所有 HPA 对象的配置定义,包括目标工作负载、目标指标类型及其目标值、最小/最大副本数等。

          2. 获取监控指标:对于每个 HPA,Controller 通过对应的 Metrics API(资源、自定义或外部)来获取当前的监控指标值。

          • 源指标(如 CPU、内存)由 Metrics Server 提供。Metrics Server 通过 Kubelet 从每个节点的 cAdvisor 中采集所有 Pod 的资源使用数据,并聚合到集群层面。

            1. metrics.k8s.io: 主要提供Pod和Node的CPU和Memory相关的监控指标。

            2. custom.metrics.k8s.io: 主要提供Kubernetes Object相关的自定义监控指标。

            3. external.metrics.k8s.io:指标来源外部,与任何的Kubernetes资源的指标无关。

          • 自定义指标(如 QPS、应用内部指标)由 Kubernetes Custom Metrics API 提供,这通常需要安装像 Prometheus Adapter 这样的组件来将 Prometheus 等监控系统的指标转换为 Kubernetes API 可以理解的格式。

        1. 计算所需副本数:Controller 使用获取到的当前指标值和 HPA 中定义的目标指标值,通过算法计算出期望的 Pod 副本数量。

          • 计算公式期望副本数 = ceil[当前副本数 * (当前指标值 / 目标指标值)]

            例如,当前有 2 个 Pod,CPU 使用率为 80%,HPA 目标 CPU 使用率为 40%,则期望副本数 = ceil[2 * (80 / 40)] = ceil[4] = 4。HPA 会考虑多种指标(选择计算结果中最大的副本数)并内置了容忍度(默认 0.1,即比率变化不超过 10% 则忽略)和冷却窗口等机制来防止副本数抖动

        2. 调整副本数:如果计算出的期望副本数与当前副本数不同,HPA Controller 会通过 API Server更新目标工作负载(如 Deployment)的 replicas 字段,为了避免副本数量因指标瞬时波动而剧烈变化(“抖动”),HPA 引入了冷却机制

          • 扩容:如果计算出的 期望副本数 > 当前副本数,HPA 就会增加副本数。

            扩容冷却 (默认 3 分钟):扩容操作没有全局等待期,但快速连续扩容时,每次扩容后等待时间会逐渐增加。

          • 缩容:如果计算出的 期望副本数 < 当前副本数,HPA 就会减少副本数。

            缩容冷却 (--horizontal-pod-autoscaler-downscale-stabilization,默认 5 分钟):在一次缩容操作后,会等待一段时间(默认5分钟),再执行下一次缩容操作。

        3. 工作负载控制器响应:相应的工作负载控制器(如 Deployment Controller)检测到 replicas 字段变化,会开始创建删除 Pod,以使实际运行的 Pod 数量等于期望值。

        4. 调度新 Pod:新创建的 Pod 会被 Scheduler 调度到合适的节点上运行。如果节点资源不足,这些 Pod 会处于 Pending 状态。

        5. 节点扩缩容(可选):如果集群中使用了 Cluster Autoscaler (CA),它会检测到因资源不足而无法调度的 Pod(Pending状态),然后自动扩容集群节点来提供更多资源。Pod 被成功调度和运行后,才能开始处理业务流量,此时指标数据也会随之变化,HPA 的下一个循环周期又会开始。

        6. 在实际应用中,基于原生HPA系统架构实现适配业务自定义特性的整体设计架构如下:

          171a62d0e6002878~tplv-t2oaga2asx-jj-mark_3024_0_0_0_q75.png

          二、基于 CPU 和内存的最佳实践示例

          前提条件

          1. 安装 Metrics Server

          • HPA 需要 Metrics Server 来获取 CPU/内存指标。

          • 安装命令(对于大多数集群):

          kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
          • 验证安装:kubectl top nodes

        7. 为 Pod 设置资源请求

          • 这是最关键的一步! HPA 计算使用率的公式是:(Pod 当前实际使用量 / Pod 的资源请求值) * 100%

          • 如果 Pod 没有设置 spec.containers[].resources.requests,HPA 将无法进行有意义的计算。

          示例:为 Nginx Deployment 配置同时基于 CPU 和内存的 HPA

          1. 创建 Deployment (nginx-deployment.yaml)

          这个 Deployment 明确设置了 CPU 和内存的 requests

          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: nginx-deployment
            labels:
              app: nginx
          spec:
            replicas: 3 # 初始副本数
            selector:
              matchLabels:
                app: nginx
            template:
              metadata:
                labels:
                  app: nginx
              spec:
                containers:
                - name: nginx
                  image: nginx:1.25
                  ports:
                  - containerPort: 80
                  resources:
                    requests:
                      cpu: "250m"   # 每个 Pod 请求 0.25 核 CPU
                      memory: "256Mi" # 每个 Pod 请求 256MiB 内存
                    limits:
                      cpu: "500m"
                      memory: "512Mi"

          应用它:kubectl apply -f nginx-deployment.yaml

          2. 创建 HPA (nginx-hpa.yaml)

          我们使用 autoscaling/v2 API,它支持多指标和更丰富的配置。

          apiVersion: autoscaling/v2
          kind: HorizontalPodAutoscaler
          metadata:
            name: nginx-hpa
          spec:
            scaleTargetRef:
              apiVersion: apps/v1
              kind: Deployment
              name: nginx-deployment
            minReplicas: 2   # 最小副本数
            maxReplicas: 10  # 最大副本数
            metrics:
            - type: Resource
              resource:
                name: cpu
                target:
                  type: Utilization # 目标类型为利用率
                  averageUtilization: 50 # CPU 平均使用率目标为 50%
            - type: Resource
              resource:
                name: memory
                target:
                  type: Utilization # 目标类型为利用率
                  averageUtilization: 70 # 内存平均使用率目标为 70%
            behavior: # (可选) 高级伸缩行为控制
              scaleDown:
                stabilizationWindowSeconds: 300 # 缩容冷却期 300 秒 (5分钟)
                policies:
                - type: Pods
                  value: 2
                  periodSeconds: 60
              scaleUp:
                stabilizationWindowSeconds: 0
                policies:
                - type: Pods
                  value: 2
                  periodSeconds: 60

          应用它:kubectl apply -f nginx-hpa.yaml

          HPA 行为解释

          • CPU 规则:HPA 会努力使所有 Pod 的 CPU 平均使用率 维持在 50%

            • 如果平均使用率是 75%,期望副本数 = ceil[3 * (75 / 50)] = ceil[3 * 1.5] = ceil[4.5] = 5。HPA 会将副本数扩容到 5。

          • 内存规则:HPA 会努力使所有 Pod 的 内存平均使用率 维持在 70%

            • 如果平均使用率是 85%,期望副本数 = ceil[3 * (85 / 70)] = ceil[3 * 1.21] = ceil[3.63] = 4

          • 多指标决策:当同时配置了多个指标时,HPA 会分别计算每个指标所需的副本数,然后选择其中最大的那个值

            • 在上面的例子中,CPU 算出来需要 5 个,内存算出来需要 4 个,那么 HPA 最终会将副本数扩容到 5。

          验证与监控

          1.查看 HPA 状态

          kubectl get hpa nginx-hpa -w

          输出会显示当前的指标值、目标值、最小/最大副本数和当前的副本数。

          NAME         REFERENCE                       TARGETS             MINPODS   MAXPODS   REPLICAS   AGE
          nginx-hpa    Deployment/nginx-deployment    50%/70%, 45%/65%    2         10        3          5m
          # TARGETS 列显示为 CPU目标%/内存目标%,当前CPU%/当前内存%

          2.查看 HPA 详细信息

          kubectl describe hpa nginx-hpa

          这个命令会输出非常详细的信息,包括事件日志,帮助你诊断 HPA 为什么不伸缩。

          三、最佳实践总结

          1. 必须设置 resources.requests:没有它,HPA 无法工作。

          2. 使用 autoscaling/v2 APIv2 比 v2beta2 更稳定,且功能强大。

          3. 谨慎设置目标值

          • 目标值设置过高(如 CPU 90%)可能导致 Pod 在触发扩容前就已过载。

          • 目标值设置过低(如 CPU 10%)可能导致资源浪费和过多的副本。

          • 建议:从保守值开始(如 CPU 50-70%),根据实际生产监控数据进行调整。

        8. 合理配置 minReplicas 和 maxReplicas

          • minReplicas 应保证应用的基本可用性。

          • maxReplicas 应避免因意外流量或程序 Bug 导致资源耗尽。

        9. 利用 behavior 字段控制伸缩速率和冷却:根据应用的启动和冷却特性,调整 scaleUp 和 scaleDown 策略,避免抖动。

        10. 结合就绪探针和存活探针:确保新扩容的 Pod 真正准备好接收流量后,才被纳入服务的负载均衡。

        11. 结合日志与事件:注意容忍度、冷却窗口对扩缩容的影响,关注 ScalingActive、ScalingLimited 等状态变化。

        12. 考虑使用自定义指标:对于 Web 服务,基于 HTTP QPS(每秒请求数)进行扩缩容通常比基于 CPU 更直接、更灵敏。这需要安装 Prometheus 和 Prometheus Adapter。

        13. 让应用支持平滑启动和终止:确保新 Pod 启动后能快速就绪(利用就绪探针),并在终止时能优雅处理完已有请求(处理 SIGTERM 信号),避免服务中断。

        14. 四、关于 HPA 的进阶思考

          1. HPA 的局限性及与其他自动伸缩方案的搭配

          HPA 主要负责 Pod 水平扩缩容,但它依赖于集群有足够的资源来调度新创建的 Pod。如果集群资源不足,即使 HPA 创建了新的 Pod,这些 Pod 也会因无法调度而处于 Pending 状态,无法提供服务。

          因此,在生产环境中,HPA 通常需要与 Cluster Autoscaler (CA) 或 Karpenter 搭配使用

          • HPA: 负责应用层伸缩,根据业务负载调整 Pod 数量。

          • CA/Karpenter: 负责基础设施层伸缩,根据 Pod 的资源请求调整集群节点数量,为 HPA 创建的 Pod 提供运行资源

          2. 克服“弹性滞后”:AHPA 与预测性伸缩

          传统 HPA 基于当前的监控指标进行反应式扩缩容,这存在一定的“弹性滞后”(Reactive Scaling)。即从流量增加到 HPA 完成扩容需要一定时间(指标采集、计算、Pod 启动、应用预热等),可能导致流量高峰时应用响应变慢

          为解决这个问题,出现了预测性弹性伸缩(Predictive Scaling)方案,如 Advanced Horizontal Pod Autoscaler (AHPA)。AHPA 能够分析历史指标数据(如过去几天的 CPU 负载、QPS 变化规律),预测未来的负载波动,并提前进行扩容。例如,对于每天早高峰流量明显增大的应用,AHPA 可以在高峰来临前就提前扩容,避免流量激增时的响应延迟。在业务低谷时,它也会定时回收资源,节约成本


          参考:

          🔲 ☆

          k8s学习

          <link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>Kubernetes核心概念与基本使用<br><span id="more"></span></p><h1 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h1><p><strong>Kubernetes 是一个生产级别的开源平台, 可编排在计算机集群内和跨计算机集群的应用容器的部署(调度)和执行。</strong></p><p><strong>Kubernetes 协调一个高可用计算机集群,每个计算机互相连接之后作为同一个工作单元运行。</strong> Kubernetes 中的抽象允许你将容器化的应用部署到集群,而无需将它们绑定到某个特定的独立计算机。 为了使用这种新的部署模型,需要以将应用与单个主机解耦的方式打包:它们需要被容器化。 与过去的那种应用直接以包的方式深度与主机集成的部署模型相比,容器化应用更灵活、更可用。 <strong>Kubernetes 以更高效的方式跨集群自动分布和调度应用容器。</strong> Kubernetes 是一个开源平台,并且可应用于生产环境。</p><p>一个 Kubernetes 集群包含两种类型的资源:</p><ul><li><strong>控制面(Control Plane)</strong> 调度整个集群</li><li><strong>节点(Nodes)</strong> 负责运行应用</li></ul><p>Kubernetes 集群由一个<strong>控制平面</strong>和<strong>一组用于运行容器化应用的工作机器</strong>组成, 这些工作机器称作节点(Node)。每个集群至少需要一个工作节点来运行 Pod。</p><p><img data-src="https://kubernetes.io/docs/tutorials/kubernetes-basics/public/images/module_01_cluster.svg" alt="img" style="zoom:67%;" /></p><p>工作节点托管着组成应用负载的 Pod。控制平面管理集群中的工作节点和 Pod。 在生产环境中,控制平面通常跨多台计算机运行,而一个集群通常运行多个节点,以提供容错和高可用。</p><p><img data-src="https://kubernetes.io/images/docs/kubernetes-cluster-architecture.svg" alt="控制平面(kube-apiserver、etcd、kube-controller-manager、kube-scheduler)和多个节点。每个节点运行 kubelet 和 kube-proxy。"></p><h2 id="控制平面"><a href="#控制平面" class="headerlink" title="控制平面"></a>控制平面</h2><p>有些地方又叫做Kubernetes Master,是K8s集群中的核心组件,负责集群的管理和控制。</p><p>控制平面组件会为集群做出全局决策,比如资源的调度。 以及检测和响应集群事件,例如当不满足 Deployment 的 <code>replicas</code> 字段时,要启动新的 <a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/">Pod</a>)。</p><p>控制平面组件可以在集群中的任何节点上运行。 然而,为了简单起见,安装脚本通常会<strong>在同一个计算机上启动所有控制平面组件</strong>, <strong>并且不会在此计算机上运行用户容器。</strong> </p><p>包括以下组件:</p><ul><li><p><strong>etcd</strong>:一个分布式键值存储系统,用于存储集群状态。</p></li><li><p><strong>API Server</strong>:集群的入口点,提供RESTful API接口。</p></li><li><p><strong>Controller Manager</strong>:负责集群中各种资源的监控和自动修复。</p><p>controller manager又包括<code>kube-scheduler</code> , 负责监视新创建的、未指定运行节点的 Pod, 并选择节点来让 Pod 在上面运行。从逻辑上讲, 每个controller都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在同一个进程中运行。</p><p>还包括cloud-controller-manager 嵌入了特定于云平台的控制逻辑。 云控制器管理器(Cloud Controller Manager)允许将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。</p></li></ul><p>控制器有许多不同类型。以下是一些例子:</p><p>Node 控制器:负责在节点出现故障时进行通知和响应</p><p>Job 控制器:监测代表一次性任务的 Job 对象,然后创建 Pod 来运行这些任务直至完成</p><p>EndpointSlice 控制器:填充 EndpointSlice 对象(以提供 Service 和 Pod 之间的链接)。</p><p>ServiceAccount 控制器:为新的命名空间创建默认的 ServiceAccount。</p><ul><li><strong>Scheduler</strong>:负责将Pod调度到合适的节点上。</li></ul><h2 id="节点组件"><a href="#节点组件" class="headerlink" title="节点组件"></a>节点组件</h2><p>Kubernetes Node是集群中的工作节点,负责运行Pod。每个Node节点包括以下组件:</p><ul><li><strong>Kubelet</strong>:负责Pod的生命周期管理,包括Pod的创建、启动、停止和删除。</li><li><strong>Container Runtime</strong>:用于运行容器的环境,如Docker。</li><li><strong>Kube-Proxy</strong>:负责网络代理,实现Pod之间的通信。</li></ul><p>Pod是Kubernetes中的基本部署单元,包含一个或多个容器。Pod在Kubernetes集群中负责运行应用程序。</p><p>控制平面组件会为集群做出全局决策,比如资源的调度。 以及检测和响应集群事件,例如当不满足 Deployment 的 <code>replicas</code> 字段时,要启动新的 <a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/">Pod</a>)。</p><p>控制平面组件可以在集群中的任何节点上运行。 然而,为了简单起见,安装脚本通常会在同一个计算机上启动所有控制平面组件, 并且不会在此计算机上运行用户容器。 请参阅<a href="https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/high-availability/">使用 kubeadm 构建高可用性集群</a>中关于跨多机器安装控制平面的示例。</p><h3 id="节点"><a href="#节点" class="headerlink" title="节点"></a>节点</h3><p>Kubernetes 通过将容器放入在节点(Node)上运行的 Pod 中来执行<a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/">工作负载</a>。 节点可以是一个虚拟机或者物理机器,取决于所在的集群配置。 每个节点包含运行 <a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/">Pod</a> 所需的服务; 这些节点由<a href="https://kubernetes.io/zh-cn/docs/reference/glossary/?all=true#term-control-plane">控制面</a>负责管理。</p><p>通常集群中会有若干个节点;而在一个学习所用或者资源受限的环境中,你的集群中也可能只有一个节点。一个 Pod 总是运行在某个 <strong>Node(节点)</strong> 上。节点是 Kubernetes 中工作机器, 可以是虚拟机或物理计算机,具体取决于集群。每个 Node 都由控制面管理。 节点可以有多个 Pod,Kubernetes 控制面会自动处理在集群中的节点上调度 Pod。 控制面的自动调度考量了每个节点上的可用资源。</p><p><strong>节点上的组件包括kubelet、容器运行时以及kube-proxy</strong>。</p><h3 id="Pod"><a href="#Pod" class="headerlink" title="Pod"></a>Pod</h3><p>Pod 是一个或多个应用容器(例如 Docker)的组合,并且包含共享的存储(卷)、IP 地址和有关如何运行它们的信息。</p><p> Pod 是 Kubernetes 抽象出来的,表示一组一个或多个应用容器(如 Docker), 以及这些容器的一些共享资源。这些资源包括:</p><ul><li>卷形式的共享存储</li><li>集群内唯一的 IP 地址,用于联网</li><li>有关每个容器如何运行的信息,例如容器镜像版本或要使用的特定端口</li></ul><p><strong>Pod</strong> 是可以<strong>在 Kubernetes 中创建和管理的、最小的可部署的计算单元</strong>。<strong>只有容器紧耦合并且需要共享磁盘等资源时,才应将其编排在一个 Pod 中。</strong></p><p><strong>Pod</strong>(就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个)容器; 这些容器共享存储、网络、以及怎样运行这些容器的规约。 Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行。 Pod 所建模的是特定于应用的“逻辑主机”,其中包含一个或多个应用容器, 这些容器相对紧密地耦合在一起。 在非云环境中,在相同的物理机或虚拟机上运行的应用类似于在同一逻辑主机上运行的云应用。</p><p>除了应用容器,Pod 还可以包含在 Pod 启动期间运行的Init容器。 也可以注入<a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/ephemeral-containers/">临时性容器</a>来调试正在运行的 Pod。</p><p>Pod 的<strong>共享上下文包括一组 Linux 名字空间、控制组(CGroup)</strong>和可能一些其他的隔离方面, 即用来隔离容器的技术。 在 Pod 的上下文中,每个独立的应用可能会进一步实施隔离。</p><p>Kubernetes 集群中的 Pod 主要有两种用法:</p><ul><li><p><strong>运行单个容器的 Pod</strong>。”每个 Pod 一个容器”模型是最常见的 Kubernetes 用例; 在这种情况下,可以将 Pod 看作单个容器的包装器,并且 Kubernetes 直接管理 Pod,而不是容器。</p></li><li><p><strong>运行多个协同工作的容器的 Pod</strong>。 Pod 可以封装由紧密耦合且需要共享资源的多个并置容器组成的应用。 这些位于同一位置的容器构成一个内聚单元。</p><p>将多个并置、同管的容器组织到一个 Pod 中是一种相对高级的使用场景。 只有在一些场景中,容器之间紧密关联时你才应该使用这种模式。</p></li></ul><h3 id="容器"><a href="#容器" class="headerlink" title="容器"></a>容器</h3><p>每个运行的容器都是可重复的; 包含依赖环境在内的标准,意味着无论你在哪里运行它都会得到相同的行为。</p><p>容器将应用程序从底层的主机设施中解耦。 这使得在不同的云或 OS 环境中部署更加容易。</p><p>Kubernetes 集群中的每个节点都会运行容器, 这些容器构成分配给该节点的Pod。 单个 Pod 中的容器会在共同调度下,于同一位置运行在相同的节点上</p><h2 id="容器技术和虚拟机差别"><a href="#容器技术和虚拟机差别" class="headerlink" title="容器技术和虚拟机差别"></a>容器技术和虚拟机差别</h2><p><strong>Docker(容器技术)</strong> 和 <strong>虚拟机(VM)</strong> 的区别是理解现代云计算架构的基础。</p><p>虽然它们的目的都是提<strong>供隔离的环境来运行应用程序</strong>,但它们的底层架构和资源使用方式有着根本性的不同。</p><p>最大的区别在于它们如何与底层硬件和操作系统(Host OS)交互。</p><div class="table-container"><table><thead><tr><th><strong>特性</strong></th><th><strong>Docker 容器 (Container)</strong></th><th><strong>虚拟机 (VM)</strong></th></tr></thead><tbody><tr><td><strong>隔离级别</strong></td><td><strong>进程级隔离</strong> (共享内核)</td><td><strong>硬件级隔离</strong> (完全虚拟化)</td></tr><tr><td><strong>操作系统</strong></td><td><strong>共享宿主机操作系统内核</strong></td><td>包含<strong>完整的独立操作系统</strong> (Guest OS)</td></tr><tr><td><strong>启动速度</strong></td><td>极快(秒级甚至毫秒级)</td><td>慢(需要启动整个操作系统)</td></tr><tr><td><strong>资源占用</strong></td><td>极低(只包含应用和必需的库)</td><td>很高(每个 VM 都要占用大量 CPU、内存和磁盘)</td></tr><tr><td><strong>核心组件</strong></td><td><strong>Docker Engine</strong> (容器运行时)</td><td><strong>Hypervisor</strong> (VMM, 如 VMware, VirtualBox)</td></tr><tr><td><strong>大小</strong></td><td>几十到几百 MB</td><td>几 GB 到几十 GB</td></tr></tbody></table></div><p>VM 的原理是完全虚拟化硬件。</p><p><strong>Hypervisor (管理程序)</strong>:这是一个软件层,直接运行在物理硬件或宿主操作系统之上。</p><p><strong>完整 OS</strong>:每个虚拟机都包含一个<strong>完整的、独立的操作系统</strong>(Guest OS),包括自己的内核、驱动、系统库和应用。</p><p><strong>硬件模拟</strong>:Hypervisor 负责模拟所有硬件(CPU、内存、网卡、磁盘),让 Guest OS 以为自己独占了硬件。</p><p><strong>总结</strong>:VM 提供了强大的隔离性,但代价是<strong>巨大的资源开销</strong>,因为你需要同时运行两个或更多的完整操作系统</p><p><strong>Docker 的原理是操作系统级虚拟化(或容器化)。</strong></p><ul><li><strong>共享内核</strong>:所有容器都<strong>共享宿主机的操作系统内核</strong>。容器只是宿主机上的一个隔离进程。</li><li><strong>精简文件系统</strong>:容器只打包应用程序运行时所需的文件、库和依赖项,不包含整个操作系统。</li><li><strong>Docker Engine</strong>:Docker 引擎负责管理容器的生命周期,并使用 Linux 内核的两个核心技术来实现隔离和资源限制:<ul><li><strong>Namespaces (命名空间)</strong>:实现进程、网络、用户等资源的隔离。</li><li><strong>Cgroups (控制组)</strong>:实现对 CPU、内存、I/O 等资源的限制和配额。</li></ul></li><li><strong>总结</strong>:容器非常轻量,启动速度快,资源利用率高,但隔离性略低于 VM(如果内核存在安全漏洞,所有容器都可能受到影响)。</li></ul><p><strong>容器镜像</strong></p><p>容器镜像是一个随时可以运行的软件包, 包含运行应用程序所需的一切:代码和它需要的所有运行时、应用程序和系统库,以及一些基本设置的默认值。</p><p>容器旨在设计成无状态且<a href="https://glossary.cncf.io/immutable-infrastructure/">不可变的</a>: 你不应更改已经运行的容器的代码。如果有一个容器化的应用程序需要修改, 正确的流程是:先构建包含更改的新镜像,再基于新构建的镜像重新运行容器。</p><p><strong>容器运行时</strong></p><p>这个基础组件使 Kubernetes 能够有效运行容器。 它负责管理 Kubernetes 环境中容器的执行和生命周期。</p><p> 可以允许集群为一个 Pod 选择其默认的容器运行时。如果你需要在集群中使用多个容器运行时, 你可以为一个 Pod 指定 <a href="https://kubernetes.io/zh-cn/docs/concepts/containers/runtime-class/">RuntimeClass</a>, 以确保 Kubernetes 会使用特定的容器运行时来运行这些容器。</p><p><strong>每个 Pod 内部部署什么容器,完全取决于你的应用需求,但它通常包含:</strong></p><ol><li><strong>一个主要的业务容器(Main Container)。</strong></li><li><strong>零个或多个辅助容器(Sidecar, Init Containers)</strong></li></ol><p>主容器 (Main Container)</p><p>这是 Pod 的<strong>核心</strong>,它运行你的应用程序的实际业务逻辑。</p><ul><li><strong>数量:</strong> 通常是 <strong>1 个</strong>。</li><li><strong>示例:</strong><ul><li>一个 Java Spring Boot 应用服务器。</li><li>一个 NGINX Web 服务器。</li><li>一个 Python Flask API 服务。</li><li>一个 Node.js 后端应用</li></ul></li></ul><p>辅助容器(尤其是 Sidecar 模式)是 Pod 设计的强大之处。它们<strong>与主容器共享网络和存储空间,但执行不同的辅助任务。</strong></p><p>A. Sidecar 容器 (最常见)</p><p>Sidecar 容器是与主容器同时运行的辅助容器,用于增强或扩展主容器的功能,而不必修改主容器的代码。</p><ul><li><strong>数量:</strong> 0 个或多个。</li><li><strong>运行时间:</strong> 与主容器的生命周期相同(同时启动,同时终止)。</li><li><strong>常见作用:</strong><ul><li><strong>日志收集:</strong> Sidecar 容器运行一个日志代理(如 Fluentd、Logstash),监控主容器的日志文件并将其转发到集中的日志系统。</li><li><strong>服务网格代理 (如 Istio/Envoy):</strong> Sidecar 容器作为网络代理,处理主容器所有的入站和出站流量,实现安全、路由、监控等功能。</li><li><strong>监控采集:</strong> Sidecar 容器运行一个 Prometheus Exporter,专门暴露主容器的性能指标。</li><li><strong>数据同步:</strong> Sidecar 容器负责定期从外部系统同步配置或证书文件。</li></ul></li></ul><p>B. Init 容器 (初始化容器)</p><p>Init 容器在任何主容器启动<strong>之前</strong>运行,并且必须成功完成才能让主容器启动。它们用于执行设置和准备工作。</p><ul><li><strong>数量:</strong> 0 个或多个。</li><li><strong>运行时间:</strong> 串行运行(一个接一个),并在主容器启动前完成。</li><li><strong>常见作用:</strong><ul><li><strong>等待依赖:</strong> 等待外部数据库或服务启动并可用。</li><li><strong>环境准备:</strong> 从配置服务器下载配置文件、脚本或证书。</li><li><strong>权限设置:</strong> 在共享卷上设置正确的文件权限。</li></ul></li></ul><p>每个节点上的多个 Pod 可以有<strong>完全不同</strong>的容器配置。例如:</p><ul><li><p><strong>Node 1</strong> 可能运行一个 <strong>Web Pod</strong>(主容器 + Envoy Sidecar)。</p></li><li><p><strong>Node 2</strong> 可能运行一个 <strong>DB Pod</strong>(主容器 + Init 容器用于数据初始化)</p></li></ul><p>数据库(如 MySQL, PostgreSQL)和 Redis 这种重量级、有状态的服务, <strong>一般不会</strong> <strong>作为辅助容器 (Sidecar) 部署在业务 Pod 内部。它们通常会被部署为</strong>独立的、单容器 Pod 或 StatefulSet<strong>,位于集群的</strong>独立节点上。</p><p>集群包含多个运行着 Kubernetes 代理程序、 由控制平台管理的一组节点(物理机或虚拟机)。 Kubernetes v1.34 单个集群支持的最大节点数为 5,000。 更具体地说,Kubernetes 设计为满足以下<strong>所有</strong>标准的配置:</p><ul><li>每个节点的 Pod 数量不超过 110</li><li>节点数不超过 5,000</li><li>Pod 总数不超过 150,000</li><li>容器总数不超过 300,000</li></ul><h2 id="Minikube"><a href="#Minikube" class="headerlink" title="Minikube"></a>Minikube</h2><p>Minikube,k3s 和 Kind (Kubernetes IN Docker) 等都是用于在本地机器上运行 Kubernetes 集群的工具</p><p>常用的 NGINX Web 服务器 Deployment 示例</p><p>Deployment 是用于管理<strong>无状态应用</strong>(Stateless Application)的标准控制器。</p><p>在无状态应用中,所有的 Pod 副本都是<strong>完全相同的</strong>,并且<strong>可以互相替换</strong>。Pod 本身不保存任何需要持久化的数据,它们通常从外部数据库或存储中获取数据。</p><p>Deployment 和 StatefulSet 是“管理者”和“指导者”,而 Pod 是它们管理的“工作单位”</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># my-app-deployment.yaml</span></span><br><span class="line"></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">my-nginx-deployment</span> <span class="comment"># Deployment 的名称</span></span><br><span class="line"> <span class="attr">labels:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">my-nginx</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line"> <span class="comment"># 1. 副本设置</span></span><br><span class="line"> <span class="attr">replicas:</span> <span class="number">3</span> <span class="comment"># 期望运行的 Pod 副本数量</span></span><br><span class="line"> <span class="comment"># 2. 选择器:匹配哪些 Pod 属于这个 Deployment</span></span><br><span class="line"> <span class="attr">selector:</span></span><br><span class="line"> <span class="attr">matchLabels:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">my-nginx</span></span><br><span class="line"> <span class="comment"># 3. Pod 模板</span></span><br><span class="line"> <span class="attr">template:</span></span><br><span class="line"> <span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">labels:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">my-nginx</span> <span class="comment"># 这个标签非常关键,用于 Service 选择 Pod</span></span><br><span class="line"> <span class="attr">spec:</span></span><br><span class="line"> <span class="attr">containers:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-container</span> <span class="comment"># 容器的名称</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">nginx:latest</span> <span class="comment"># 容器使用的镜像</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span> <span class="comment"># 容器暴露的端口</span></span><br><span class="line"> <span class="comment"># 资源请求(可选但推荐)</span></span><br><span class="line"> <span class="attr">resources:</span></span><br><span class="line"> <span class="attr">limits:</span></span><br><span class="line"> <span class="attr">memory:</span> <span class="string">&quot;128Mi&quot;</span></span><br><span class="line"> <span class="attr">cpu:</span> <span class="string">&quot;500m&quot;</span></span><br><span class="line"> <span class="attr">requests:</span></span><br><span class="line"> <span class="attr">memory:</span> <span class="string">&quot;64Mi&quot;</span></span><br><span class="line"> <span class="attr">cpu:</span> <span class="string">&quot;250m&quot;</span></span><br></pre></td></tr></table></figure><p>Kubernetes <a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/"><strong>Deployment</strong></a> 检查 Pod 的健康状况,并在 Pod 中的容器终止的情况下重新启动新的容器。 Deployment 是管理 Pod 创建和扩展的推荐方法。</p><h2 id="部署应用"><a href="#部署应用" class="headerlink" title="部署应用"></a>部署应用</h2><h3 id="利用minikube创建Deployment"><a href="#利用minikube创建Deployment" class="headerlink" title="利用minikube创建Deployment"></a>利用minikube创建Deployment</h3><p>Deployment 指挥 Kubernetes 如何创建和更新应用的实例。 <strong>创建 Deployment 后,Kubernetes 控制平面将 Deployment 中包含的应用实例调度到集群中的各个节点上</strong>。</p><p>创建应用实例后,Kubernetes Deployment 控制器会持续监视这些实例。 如果托管实例的节点关闭或被删除,则 Deployment 控制器会将该实例替换为集群中另一个节点上的实例。 <strong>这提供了一种自我修复机制来解决机器故障维护问题。</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">运行包含 Web 服务器的测试容器镜像</span></span><br><span class="line">kubectl create deployment hello-node --image=registry.k8s.io/e2e-test-images/agnhost:2.53 -- /agnhost netexec --http-port=8080</span><br></pre></td></tr></table></figure><p><code>kubectl create deployment</code> 是一个<strong>快捷命令</strong>,而 <code>kubectl apply -f xx.yaml</code> 是管理集群资源(包括 Deployment)的<strong>标准、推荐方式</strong></p><p>查看Deployment:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods //查看pods</span><br><span class="line">kubectl get deployments</span><br><span class="line">kubectl get events // 查看集群事件</span><br><span class="line">kubectl get services</span><br><span class="line">kubectl config view //查看 kubectl 配置</span><br></pre></td></tr></table></figure><p>创建 Deployment 时,Kubernetes 创建了一个 <strong>Pod</strong> 来托管你的应用实例。 Pod 是 Kubernetes 抽象出来的,表示一组一个或多个应用容器(如 Docker), 以及这些容器的一些共享资源。这些资源包括:</p><ul><li><strong>卷形式的共享存储</strong></li><li><strong>集群内唯一的 IP 地址</strong>,用于联网</li><li>有关<strong>每个容器如何运行的信息</strong>,例如容器镜像版本或要使用的特定端口</li></ul><p>Pod 为特定于应用的“逻辑主机”建模,并且可以包含相对紧耦合的不同应用容器。 例如,Pod 可能既包含带有 Node.js 应用的容器,也包含另一个不同的容器, 用于提供 Node.js 网络服务器要发布的数据。Pod 中的容器共享 IP 地址和端口, 始终位于同一位置并且共同调度,并在同一节点上的共享上下文中运行。</p><h2 id="利用minikbe创建Service"><a href="#利用minikbe创建Service" class="headerlink" title="利用minikbe创建Service"></a>利用minikbe创建Service</h2><p>Kubernetes 中的Service是一种抽象概念,它定义的是 Pod 的一个逻辑集合和一种用来访问 Pod 的协议。 Service 使从属 Pod 之间的松耦合成为可能。 </p><p>和所有 Kubernetes 对象清单一样,Service 用 YAML 或者 JSON 来定义。 Service 下的一组 Pod 通常由一个<strong>标签选择算符</strong>来标记 (请参阅下面的说明来了解为什么你可能想要一个 spec 中不包含 <code>selector</code> 的 Service)。</p><p>默认情况下,Pod 只能通过 Kubernetes 集群中的内部 IP 地址访问。 要使得容器可以从 Kubernetes 虚拟网络的外部访问,必须将 Pod 通过 Kubernetes <a href="https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/"><strong>Service</strong></a> 公开出来。</p><p>Service 的作用是为一组 Pod 提供一个<strong>稳定、不变</strong>的网络入口。这非常关键,因为 <strong>Pod 的 IP 地址是临时的、不稳定的。Service 负责跟踪 Pod 的变化,并将请求自动转发到健康的 Pod 副本上</strong>。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">my-web-service</span> <span class="comment"># Service 的名称</span></span><br><span class="line"> <span class="attr">labels:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">my-web</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line"> <span class="comment"># 关键:Service如何找到它要暴露的Pod</span></span><br><span class="line"> <span class="attr">selector:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">my-nginx</span> <span class="comment"># 必须与目标 Deployment/Pod 的标签匹配</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 关键:Service 的类型,决定了如何被访问</span></span><br><span class="line"> <span class="attr">type:</span> <span class="string">LoadBalancer</span> <span class="comment"># 常见的类型:ClusterIP, NodePort, LoadBalancer</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 关键:端口配置</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">80</span> <span class="comment"># Service 暴露给外部或集群内部的端口</span></span><br><span class="line"> <span class="attr">targetPort:</span> <span class="number">80</span> <span class="comment"># Service 转发到的 Pod 容器的端口</span></span><br><span class="line"> <span class="comment"># nodePort: 30000 (如果 type 为 NodePort,可以指定或让K8s自动分配)</span></span><br></pre></td></tr></table></figure><p><code>type</code> 字段是创建 Service 时最重要的决定,它定义了 Service 的访问方式:</p><div class="table-container"><table><thead><tr><th><strong>Service Type</strong></th><th><strong>访问方式</strong></th><th><strong>访问范围</strong></th><th><strong>典型用途</strong></th></tr></thead><tbody><tr><td><strong>ClusterIP</strong> (默认)</td><td>Service 分配一个集群内部 IP (Cluster IP)。</td><td><strong>集群内部</strong>。只能从集群内部的 Pod 或 Node 访问。</td><td>集群内部组件通信(如 Web-API 连 Database)。</td></tr><tr><td><strong>NodePort</strong></td><td>在每个节点上打开一个固定的端口。</td><td><strong>集群外部</strong>。可以通过 <code>任何节点 IP:NodePort</code> 访问。</td><td>测试环境或自建集群中简单暴露服务。</td></tr><tr><td><strong>LoadBalancer</strong></td><td>在云环境中(如 AWS, GKE, Azure)自动创建<strong>云平台负载均衡器</strong>。</td><td><strong>集群外部</strong>。提供一个外部 IP 地址和负载均衡能力。</td><td>生产环境中暴露 Web 服务。</td></tr><tr><td><strong>ExternalName</strong></td><td>将服务映射到 DNS 名称。</td><td><strong>集群内部</strong>。用于代理外部服务。</td><td>将集群内服务重定向到集群外的数据库或服务</td></tr></tbody></table></div><h2 id="kubectl基础"><a href="#kubectl基础" class="headerlink" title="kubectl基础"></a>kubectl基础</h2><p>kubectl 命令的常见格式是:<code>kubectl action resource</code>。</p><p>这会对指定的<strong>资源</strong>(类似 <code>node</code> 或 <code>deployment</code>)执行指定的<strong>操作</strong>(类似 <code>create</code>、<code>describe</code> 或 <code>delete</code>)。 你可以在子命令之后使用 <code>--help</code> 获取可能参数相关的更多信息 (例如:<code>kubectl get nodes --help</code>)。</p><p>通过运行 <code>kubectl version</code> 命令,查看 kubectl 是否被配置为与你的集群通信。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1</span><br></pre></td></tr></table></figure><p>创建deployment</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl proxy</span><br></pre></td></tr></table></figure><p>在 Kubernetes 内运行的Pod]运行在一个私有的、隔离的网络上。 默认这些 Pod 可以从同一 Kubernetes 集群内的其他 Pod 和服务看到,但超出这个网络后则看不到。 当我们使用 <code>kubectl</code> 时,我们通过 API 端点交互与应用进行通信。</p><ul><li><code>kubectl get</code> - 列出资源</li><li><code>kubectl describe</code> - 显示有关资源的详细信息</li><li><code>kubectl logs</code> - 打印 Pod 中容器的日志</li><li><code>kubectl exec</code> - 在 Pod 中的容器上执行命令</li></ul><p>扩缩应用</p><p> Deployment 仅创建了一个 Pod 用于运行这个应用。当流量增加时,需要扩容应用满足用户需求。</p><p>扩缩是通过改变 Deployment 中的副本数量来实现的</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get deployments</span><br></pre></td></tr></table></figure><p>输出应该类似这样:</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">NAME</span> READY UP-TO-DATE AVAILABLE AGE</span><br><span class="line"><span class="attribute">kubernetes</span>-bootcamp <span class="number">1</span>/<span class="number">1</span> <span class="number">1</span> <span class="number">1</span> <span class="number">11</span>m</span><br></pre></td></tr></table></figure><p>我们应该有 1 个 Pod。如果没有,请重新运行命令。结果显示:</p><ul><li><strong>NAME</strong> 列出了集群中的 Deployment 的名称。</li><li><strong>READY</strong> 显示当前副本数与期望副本数的比例。</li><li><strong>UP-TO-DATE</strong> 显示已更新至期望状态的副本数。</li><li><strong>AVAILABLE</strong> 显示可用的 Pod 的数量。</li><li><strong>AGE</strong> 显示应用已运行的时间。</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl scale deployments/kubernetes-bootcamp --replicas=4</span><br></pre></td></tr></table></figure><p>进行扩缩容</p><p><img data-src="https://kubernetes.io/docs/tutorials/kubernetes-basics/public/images/module_05_scaling2.svg" alt="img" style="zoom:50%;" /></p><h2 id="负载均衡"><a href="#负载均衡" class="headerlink" title="负载均衡"></a>负载均衡</h2><p>使用副本后,使用 <code>curl</code> 访问对外公开的 IP 和端口。多次执行访问命令,每个请求都命中了不同的 Pod,这证明负载均衡正在工作</p><h2 id="滚动更新"><a href="#滚动更新" class="headerlink" title="滚动更新"></a>滚动更新</h2><p>滚动更新通过增量式更新 Pod 实例并替换为新的实例,允许在 Deployment 更新过程中实现零停机</p><p>用户希望应用程序始终可用,而开发人员则需要每天多次部署它们的新版本。 在 Kubernetes 中,这些是通过滚动更新(Rolling Update)完成的。</p><p> <strong>滚动更新</strong>允许通过<strong>使用新的实例逐步更新 Pod 实例,实现零停机的 Deployment 更新</strong>。 新的 Pod 将被调度到具有可用资源的节点上。</p><p><img data-src="https://kubernetes.io/docs/tutorials/kubernetes-basics/public/images/module_06_rollingupdates1.svg" alt="img" style="zoom:67%;" /></p><p>如果 Deployment 的访问是公开的,Service 在更新期间仅将流量负载均衡到可用的 Pod。</p><p>与应用程序规模扩缩类似,如果 Deployment 的访问是公开的,Service 在更新期间仅将流量负载均衡到可用的 Pod。可用的 Pod 是指对应用的用户可用的实例。</p><p>滚动更新允许以下操作:</p><ul><li>将应用程序从一个环境升级到另一个环境(通过容器镜像更新)</li><li>回滚到以前的版本</li><li>持续集成和持续交付应用程序,无需停机</li></ul><p>要列出你的 Deployment,可以运行 <code>get deployments</code> 子命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get deployments</span><br></pre></td></tr></table></figure><p>要列出正在运行的 Pod,可以运行 <code>get pods</code> 子命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods</span><br></pre></td></tr></table></figure><p>要查看应用程序当前的镜像版本,可以运行 <code>describe pods</code> 子命令, 然后查找 <code>Image</code> 字段:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl describe pods</span><br></pre></td></tr></table></figure><p>要将应用程序的镜像版本更新为 v2,可以使用 <code>set image</code> 子命令, 后面跟着 Deployment 名称和新版本的镜像:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=docker.io/jocatalin/kubernetes-bootcamp:v2</span><br></pre></td></tr></table></figure><p>此命令通知 Deployment 为应用程序使用不同的镜像,并启动滚动更新。 要检查新 Pod 的状态,并查看旧 Pod 的终止状况,可以使用 <code>get pods</code> 子命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods</span><br></pre></td></tr></table></figure><p>要回滚 Deployment 到你上一次工作版本的更新,可以运行 <code>rollout undo</code> 子命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl rollout undo deployments/kubernetes-bootcamp</span><br></pre></td></tr></table></figure><p><code>rollout undo</code> 命令会恢复 Deployment 到先前的已知状态(<code>v2</code> 的镜像)。 更新是有版本控制的,你可以恢复 Deployment 到任何先前已知状态。</p><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>很多应用<strong>在其初始化或运行期间要依赖一些配置信息</strong>。 大多数时候,存在要调整配置参数所设置的数值的需求。 ConfigMap 是 Kubernetes 的一种机制,可让你将配置数据注入到应用的Pod内部。</p><p>ConfigMap 概念允许你将配置清单与镜像内容分离,以保持容器化的应用程序的可移植性</p><p>从字面量创建ConfigMap</p><p>使用 <code>kubectl create configmap</code> 命令基于字面量创建一个 ConfigMap:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl create configmap fruits --from-literal=fruits=apples</span><br></pre></td></tr></table></figure><p>可以<strong>从目录</strong>和<strong>文件</strong>以及<strong>字面值</strong>都能创建ConfiMap</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将示例文件下载到 `configure-pod-container/configmap/` 目录</span></span><br><span class="line">wget https://kubernetes.io/examples/configmap/game.properties -O configure-pod-container/configmap/game.properties</span><br><span class="line">wget https://kubernetes.io/examples/configmap/ui.properties -O configure-pod-container/configmap/ui.properties</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">创建 ConfigMap</span></span><br><span class="line">kubectl create configmap game-config --from-file=configure-pod-container/configmap/</span><br><span class="line">kubectl create configmap game-config-2 --from-file=configure-pod-container/configmap/game.properties</span><br><span class="line">kubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm</span><br></pre></td></tr></table></figure><p>编辑配置文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl edit configmaps special-config</span><br></pre></td></tr></table></figure><p>查看配置文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">kubectl get configmaps special-config -o yaml</span><br><span class="line">apiVersion: v1</span><br><span class="line">data:</span><br><span class="line"> special.how: very</span><br><span class="line"> special.type: charm</span><br><span class="line">kind: ConfigMap</span><br><span class="line">metadata:</span><br><span class="line"> creationTimestamp: &quot;2025-12-10T13:35:12Z&quot;</span><br><span class="line"> name: special-config</span><br><span class="line"> namespace: default</span><br><span class="line"> resourceVersion: &quot;10165&quot;</span><br><span class="line"> uid: ecdc4d73-0b2a-406b-ae65-b19a64977c56</span><br></pre></td></tr></table></figure><p>删除配置文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl delete configmap special-config</span><br></pre></td></tr></table></figure><h3 id="卷挂载的ConfigMap"><a href="#卷挂载的ConfigMap" class="headerlink" title="卷挂载的ConfigMap"></a>卷挂载的ConfigMap</h3><p>实现这一目标需要三个步骤:<strong>创建 ConfigMap</strong>、<strong>定义 Deployment 中的 Volume</strong>、<strong>定义容器中的 Volume Mount</strong>。</p><p>首先创建一个包含多个配置文件的 ConfigMap</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ConfigMap</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">game-config</span> <span class="comment"># ConfigMap 名称</span></span><br><span class="line"><span class="attr">data:</span></span><br><span class="line"> <span class="comment"># 配置项 1: 作为一个文件挂载</span></span><br><span class="line"> <span class="attr">game.properties:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> server.port=8080</span></span><br><span class="line"><span class="string"> debug.mode=false</span></span><br><span class="line"><span class="string"> version=1.2.0</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"> <span class="comment"># 配置项 2: 作为一个文件挂载</span></span><br><span class="line"> <span class="attr">ui.properties:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> theme=dark</span></span><br><span class="line"><span class="string"> language=zh-cn</span></span><br></pre></td></tr></table></figure><p>创建Deployement,在 Deployment 的 Pod 模板中进行配置:</p><ol><li>在 <code>spec.volumes</code> 中定义一个卷(Volume),类型为 <code>configMap</code>,并引用我们创建的 <code>game-config</code>。</li><li>在 <code>spec.containers</code> 的 <code>volumeMounts</code> 中,指定这个卷应该挂载到容器内的哪个路径</li></ol><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">game-server-deployment</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line"> <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line"> <span class="attr">selector:</span></span><br><span class="line"> <span class="attr">matchLabels:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">game-server</span></span><br><span class="line"> <span class="attr">template:</span> <span class="comment"># 创建pod</span></span><br><span class="line"> <span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">labels:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">game-server</span></span><br><span class="line"> <span class="attr">spec:</span></span><br><span class="line"> <span class="attr">containers:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">game-container</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">your-game-image:latest</span> <span class="comment"># 替换为您的应用镜像</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8080</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 2. 定义容器中的卷挂载点 (Volume Mount)</span></span><br><span class="line"> <span class="attr">volumeMounts:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">config-volume</span> <span class="comment"># 必须匹配下面 volumes 中定义的卷名称</span></span><br><span class="line"> <span class="attr">mountPath:</span> <span class="string">/etc/config</span> <span class="comment"># 挂载到容器内的路径</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 1. 定义 Pod 中的卷 (Volume)</span></span><br><span class="line"> <span class="attr">volumes:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">config-volume</span> <span class="comment"># 卷的名称</span></span><br><span class="line"> <span class="attr">configMap:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">game-config</span> <span class="comment"># 引用上面创建的 ConfigMap 名称</span></span><br><span class="line"> <span class="comment"># 3. (可选) 指定权限</span></span><br><span class="line"> <span class="attr">defaultMode:</span> <span class="number">0644</span></span><br></pre></td></tr></table></figure><p>在 Pod 上<strong>作为卷挂载的 ConfigMap 所发生的变更将在后续的 kubelet 同步后</strong>无缝生效。</p><p><strong>配置为 Pod 环境变量的 ConfigMap 所发生变更将在后续的 Pod 上线</strong>操作后生效。</p><p>尽管 ConfigMap 中的键的取值已经变更,Pod 中的环境变量仍然显示先前的值。 这是因为当源数据变更时,在 Pod 内运行的进程的环境变量<strong>不会</strong>被更新; 如果你想强制更新,需要让 Kubernetes 替换现有的 Pod。新 Pod 将使用更新的信息来运行。</p><p>你可以触发该替换。使用 <a href="https://kubernetes.io/zh-cn/docs/reference/kubectl/generated/kubectl_rollout/"><code>kubectl rollout</code></a> 为 Deployment 执行上线操作:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">触发上线操作</span></span><br><span class="line">kubectl rollout restart deployment configmap-env-var</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">等待上线操作完成</span></span><br><span class="line">kubectl rollout status deployment configmap-env-var --watch=true</span><br></pre></td></tr></table></figure><h3 id="环境变量注入"><a href="#环境变量注入" class="headerlink" title="环境变量注入"></a>环境变量注入</h3><p>通过 <strong>ConfigMap</strong> 将配置数据注入到容器的<strong>环境变量</strong>中。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">apiVersion: v1</span><br><span class="line">kind: ConfigMap</span><br><span class="line">metadata:</span><br><span class="line"> name: fruits # 必须与 Deployment 中引用的 name: fruits 匹配</span><br><span class="line">data:</span><br><span class="line"><span class="meta prompt_"> # </span><span class="language-bash">键:fruits (必须与 Deployment 中引用的 key: fruits 匹配)</span></span><br><span class="line"> fruits: &quot;Apple, Banana, Orange, Grapes&quot; </span><br><span class="line"> </span><br><span class="line"><span class="meta prompt_"> # </span><span class="language-bash">您也可以添加其他键值对,但它们不会被这个 Deployment 引用</span></span><br><span class="line"><span class="meta prompt_"> # </span><span class="language-bash">color: <span class="string">&quot;Red&quot;</span></span></span><br></pre></td></tr></table></figure><p>创建ConfigMap文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply -f fruits-configmap.yaml</span><br></pre></td></tr></table></figure><p>Deployment文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">apiVersion: apps/v1</span><br><span class="line">kind: Deployment</span><br><span class="line">metadata:</span><br><span class="line"> name: configmap-env-var</span><br><span class="line"> labels:</span><br><span class="line"> app.kubernetes.io/name: configmap-env-var</span><br><span class="line">spec:</span><br><span class="line"> replicas: 3</span><br><span class="line"> selector:</span><br><span class="line"> matchLabels:</span><br><span class="line"> app.kubernetes.io/name: configmap-env-var</span><br><span class="line"> template:</span><br><span class="line"> metadata:</span><br><span class="line"> labels:</span><br><span class="line"> app.kubernetes.io/name: configmap-env-var</span><br><span class="line"> spec:</span><br><span class="line"> containers:</span><br><span class="line"> - name: alpine</span><br><span class="line"> image: alpine:3</span><br><span class="line"> env:</span><br><span class="line"> - name: FRUITS</span><br><span class="line"> valueFrom:</span><br><span class="line"> configMapKeyRef:</span><br><span class="line"> key: fruits</span><br><span class="line"> name: fruits</span><br><span class="line"> command:</span><br><span class="line"> - /bin/sh</span><br><span class="line"> - -c</span><br><span class="line"> - while true; do echo &quot;$(date) The basket is full of $FRUITS&quot;;</span><br><span class="line"> sleep 10; done;</span><br><span class="line"> ports:</span><br><span class="line"> - containerPort: 80</span><br></pre></td></tr></table></figure><p><code>env</code> 属性用于设置容器的环境变量。当定义一个环境变量时,通常会直接使用 <code>value</code> 属性来指定一个静态值。</p><p>然而,<code>valueFrom</code> 属性提供了一种更动态的方式来设置环境变量的值,它可以从集群内的其他资源(如 ConfigMap、Secret、或 Pod/Node 的字段)中获取数据。这在您不希望将敏感信息(如密码)或配置值直接硬编码到 Pod 定义中时非常有用。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spec:</span></span><br><span class="line"> <span class="attr">containers:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">my-app</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">my-registry/my-app:latest</span></span><br><span class="line"> <span class="attr">env:</span></span><br><span class="line"> <span class="comment"># 使用 value 属性设置一个静态的环境变量</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">APP_ENVIRONMENT</span> </span><br><span class="line"> <span class="attr">value:</span> <span class="string">&quot;production&quot;</span> <span class="comment"># 环境变量 APP_ENVIRONMENT 的值被硬编码为 &quot;production&quot;</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 使用 value 属性设置另一个静态变量</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">TIMEOUT_SECONDS</span></span><br><span class="line"> <span class="attr">value:</span> <span class="string">&quot;30&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">env:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">LOG_LEVEL</span></span><br><span class="line"> <span class="attr">valueFrom:</span></span><br><span class="line"> <span class="attr">configMapKeyRef:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">app-config</span> <span class="comment"># 引用 ConfigMap 的名称</span></span><br><span class="line"> <span class="attr">key:</span> <span class="string">log_level</span> <span class="comment"># 引用 ConfigMap 中要使用的键</span></span><br><span class="line"> <span class="attr">optional:</span> <span class="literal">false</span> <span class="comment"># (可选) 如果 ConfigMap 或 key 不存在,是否允许启动</span></span><br></pre></td></tr></table></figure><h3 id="边车容器"><a href="#边车容器" class="headerlink" title="边车容器"></a>边车容器</h3><p>边车容器是<strong>与主应用程序容器在同一Pod内一起运行的辅助容器</strong>。这些容器通过提供额外的服务或功能(如日志记录、监控、安全或数据同步)来增强或扩展主<strong>应用容器</strong>的功能, 而<strong>无需直接修改主应用程序代码。</strong></p><p>Kubernetes将边车容器作为Init容器的一个特例来实现, Pod 启动后,边车容器仍保持运行状态。 本文档使用术语”常规 Init 容器”来明确指代仅在 Pod 启动期间运行的容器。</p><p>如果你的集群启用了 <code>SidecarContainers</code> 特性门控,你可以为 Pod 的 <code>initContainers</code> 字段中列出的容器指定 <code>restartPolicy</code>。 这些可重新启动的<strong>边车(Sidecar)</strong> 容器独立于其他 Init 容器以及同一 Pod 内的主应用容器, 这些容器可以启动、停止和重新启动,而不会影响主应用容器和其他 Init 容器。</p><h2 id="无状态应用"><a href="#无状态应用" class="headerlink" title="无状态应用"></a>无状态应用</h2><h3 id="公开外部IP地址"><a href="#公开外部IP地址" class="headerlink" title="公开外部IP地址"></a>公开外部IP地址</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">labels:</span></span><br><span class="line"> <span class="attr">app.kubernetes.io/name:</span> <span class="string">load-balancer-example</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">hello-world</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line"> <span class="attr">replicas:</span> <span class="number">5</span></span><br><span class="line"> <span class="attr">selector:</span></span><br><span class="line"> <span class="attr">matchLabels:</span></span><br><span class="line"> <span class="attr">app.kubernetes.io/name:</span> <span class="string">load-balancer-example</span></span><br><span class="line"> <span class="attr">template:</span></span><br><span class="line"> <span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">labels:</span></span><br><span class="line"> <span class="attr">app.kubernetes.io/name:</span> <span class="string">load-balancer-example</span></span><br><span class="line"> <span class="attr">spec:</span></span><br><span class="line"> <span class="attr">containers:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">image:</span> <span class="string">gcr.io/google-samples/hello-app:2.0</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">hello-world</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8080</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>创建一个Deployment对象和一个关联的ReplicaSet对象。 ReplicaSet 有五个Pod, 每个都运行 Hello World 应用。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply -f https://k8s.io/examples/service/load-balancer-example.yaml</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">显示有关 Deployment 的信息</span></span><br><span class="line">kubectl get deployments hello-world</span><br><span class="line">kubectl describe deployments hello-world</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">显示有关 ReplicaSet 对象的信息</span></span><br><span class="line">kubectl get replicasets</span><br><span class="line">kubectl describe replicasets</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">创建公开 Deployment 的 Service 对象</span></span><br><span class="line">kubectl expose deployment hello-world --type=LoadBalancer --name=my-service</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">显示有关 Service 的信息</span></span><br><span class="line">kubectl get services my-service</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">显示有关 Service 的详细信息</span></span><br><span class="line">kubectl describe services my-service</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">显示节点信息</span></span><br><span class="line">kubectl get pods --output=wide</span><br></pre></td></tr></table></figure><h2 id="有状态应用"><a href="#有状态应用" class="headerlink" title="有状态应用"></a>有状态应用</h2><blockquote><p>StatefulSet 运行一组 Pod,并为每个 Pod 保留一个稳定的标识。 这可用于<strong>管理需要持久化存储或稳定、唯一网络标识的应用</strong></p></blockquote><p>StatefulSet 是用来管理有状态应用的工作负载 API 对象。</p><p>StatefulSet 用来管理某Pod集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符。</p><p>和Deplopyment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是, StatefulSet 为<strong>它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID</strong>。</p><p>StatefulSet 对于需要满足以下一个或多个需求的应用程序很有价值:</p><ul><li>稳定的、唯一的网络标识符。</li><li>稳定的、持久的存储。</li><li>有序的、优雅的部署和扩缩。</li><li>有序的、自动的滚动更新。</li></ul><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line"> <span class="attr">labels:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line"> <span class="attr">clusterIP:</span> <span class="string">None</span></span><br><span class="line"> <span class="attr">selector:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">nginx</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">StatefulSet</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line"> <span class="attr">serviceName:</span> <span class="string">&quot;nginx&quot;</span></span><br><span class="line"> <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line"> <span class="attr">selector:</span></span><br><span class="line"> <span class="attr">matchLabels:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line"> <span class="attr">template:</span></span><br><span class="line"> <span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">labels:</span></span><br><span class="line"> <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line"> <span class="attr">spec:</span></span><br><span class="line"> <span class="attr">containers:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">registry.k8s.io/nginx-slim:0.21</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">web</span></span><br><span class="line"> <span class="attr">volumeMounts:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">www</span></span><br><span class="line"> <span class="attr">mountPath:</span> <span class="string">/usr/share/nginx/html</span></span><br><span class="line"> <span class="attr">volumeClaimTemplates:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">metadata:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">www</span></span><br><span class="line"> <span class="attr">spec:</span></span><br><span class="line"> <span class="attr">accessModes:</span> [ <span class="string">&quot;ReadWriteOnce&quot;</span> ]</span><br><span class="line"> <span class="attr">resources:</span></span><br><span class="line"> <span class="attr">requests:</span></span><br><span class="line"> <span class="attr">storage:</span> <span class="string">1Gi</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>StatefulSet 默认以严格的顺序创建其 Pod。</p><p>对于一个拥有 <strong>n</strong> 个副本的 StatefulSet,Pod 被部署时是按照 <strong>{0..n-1}</strong> 的序号顺序创建的。 在第一个终端中使用 <code>kubectl get</code> 检查输出</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/">Kubernetes 架构 | Kubernetes</a></li><li><a href="https://www.oryoy.com/news/k8s-ru-men-zhi-nan-ruan-yi-feng-qin-shou-qing-song-zhang-wo-rong-qi-bian-pai-he-xin-ji-qiao.html">K8s入门指南:阮一峰亲授,轻松掌握容器编排核心技巧 - 云原生实践</a></li><li><a href="https://www.ruanyifeng.com/blog/2024/09/tke-appfabric.html">白话多集群:工具和应用助手 - 阮一峰的网络日志</a></li><li><a href="https://www.bilibili.com/video/BV1Se411r7vY/?spm_id_from=333.337.search-card.all.click&amp;vd_source=177ef88aa6608bc3652c72d71b0aa098">Kubernetes一小时轻松入门_哔哩哔哩_bilibili</a></li></ol><p>在线环境:</p><ul><li><a href="https://killercoda.com/learn">Killercoda Interactive Environments</a></li><li><a href="https://labs.play-with-k8s.com/">Play with Kubernetes</a></li></ul><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>
          🔲 ☆

          K8s集群etcd磁盘更换

          在 Kubernetes 生产环境中更换 etcd 节点的磁盘是一个高风险操作,需谨慎执行。什么情况需要更换磁盘?

          • 磁盘性能不足,如机械盘升级到 SSD

          • 磁盘硬件存在问题,影响稳定性

          ETCD 对磁盘性能要求非常敏感,强烈建议使用 SSD 且独立挂盘。如果有更高性能要求,可以选择分离 snapshot(快照文件)/wal(预写日志)的目录,使用两块盘分离 IO 读写。

          以下是详细步骤和注意事项,以三节点 etcd 集群 (ETCD 使用 static pod 方式部署)为例:

          核心原则

          1. 一次只操作一个节点:确保集群始终满足 N/2 + 1 的存活节点(3 节点集群需至少 2 节点在线)。

          2. 完整备份:操作前必须备份 etcd 数据,并制定好灾备恢复计划。

          3. 业务低峰期操作:减少对集群的影响,并制定好验证方案。

          4. 演练及回滚计划:生产环境操作前务必在测试环境演练!确保团队熟悉流程并备有回滚计划。

          操作步骤

          1. 前置准备

          • 备份 etcd 数据(在任一 etcd 节点执行):

            ETCDCTL_API=3 etcdctl \
              --endpoints=https://127.0.0.1:2379 \
              --cacert=/etc/kubernetes/pki/etcd/ca.crt \
              --cert=/etc/kubernetes/pki/etcd/server.crt \
              --key=/etc/kubernetes/pki/etcd/server.key \
              snapshot save /tmp/etcd-snapshot-$(date +%Y%m%d).db

            验证备份完整性:

            etcdutl snapshot status /tmp/etcd-snapshot-*.db
          • 检查集群健康状态

            ETCDCTL_API=3 etcdctl \
              --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 \
              --cacert=/etc/kubernetes/pki/etcd/ca.crt \
              --cert=/etc/kubernetes/pki/etcd/server.crt \
              --key=/etc/kubernetes/pki/etcd/server.key \
              endpoint status -w table
            
            ETCDCTL_API=3 etcdctl \
              --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 \
              --cacert=/etc/kubernetes/pki/etcd/ca.crt \
              --cert=/etc/kubernetes/pki/etcd/server.crt \
              --key=/etc/kubernetes/pki/etcd/server.key \
              endpoint health -w table

          找到 leader 节点,换盘先从 follower 节点开始

          2. 更换第一个 etcd 节点的磁盘(以 etcd-node1 为例)

          • 步骤 2.1:停止 etcd 服务,备份 etcd 目录数据

            mv /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/
            # 检查 etcd 进程是否已经退出
            ps -ef | grep etcd 
            kubectl get po -n kube-system | grep etcd
            # 备份 etcd 目录数据
            cp -rp {etcd 数据目录} {备份目录}
          • 步骤 2.2:更换物理磁盘

          1. 卸载旧磁盘(如 umount -l dev/sdb)。

          2. 安装新磁盘并格式化(例如 mkfs.xfs /dev/sdc)。

          3. 挂载到原目录(如 /var/lib/etcd):

            # 可将新盘做成lvm挂载,方便后续的运维和扩容等
            pvcreate /dev/sdc
            vgcreate vg_etcd /dev/sdc
            lvcreate -l +100%FREE -n lv_etcd vg_etcd
            mkfs.xfs /dev/vg_etcd/lv_etcd
            # 配置挂载信息,防止机器重启后丢失
            echo "/dev/vg_etcd/lv_etcd /var/lib/etcd xfs defaults 0 0" >> /etc/fstab 
            mount -a
        15. 步骤 2.3:恢复数据(可选)

          通常新节点会从集群同步数据,但若需快速恢复,可从备份还原:

          cp -r {备份目录}  {etcd 新盘挂载的数据目录}
        16. 步骤 2.4:重启 etcd 服务

          mv /etc/kubernetes/etcd.yaml /etc/kubernetes/manifests/
          # 确认etcd启动
          kubectl get po -n kube-system | grep etcd
        17. 步骤 2.5:验证节点恢复

          ETCDCTL_API=3 etcdctl \
            --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 \
            --cacert=/etc/kubernetes/pki/etcd/ca.crt \
            --cert=/etc/kubernetes/pki/etcd/server.crt \
            --key=/etc/kubernetes/pki/etcd/server.key \
            endpoint status -w table
          
          ETCDCTL_API=3 etcdctl \
            --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 \
            --cacert=/etc/kubernetes/pki/etcd/ca.crt \
            --cert=/etc/kubernetes/pki/etcd/server.crt \
            --key=/etc/kubernetes/pki/etcd/server.key \
            endpoint health -w table
          
          watch "etcdctl member list"  # 观察节点状态变为 started
        18. 3. 重复操作其他节点

          • 按相同流程操作 etcd-node2 → etcd-node3

          • 每次间隔至少 10 分钟,确保集群完全稳定,中间间隔可做集群验证。

          4. 最终验证

          • 集群状态

            ETCDCTL_API=3 etcdctl \
              --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 \
              --cacert=/etc/kubernetes/pki/etcd/ca.crt \
              --cert=/etc/kubernetes/pki/etcd/server.crt \
              --key=/etc/kubernetes/pki/etcd/server.key \
              endpoint status --cluster -w table
          • Kubernetes 功能测试

            kubectl get nodes --v=7
            kubectl create deployment test --image=nginx && kubectl delete deployment test

          关键注意事项

          1. 磁盘挂载配置

          • 更新 /etc/fstab 确保重启后自动挂载:

            echo "/dev/sdc /var/lib/etcd xfs defaults 0 0" >> /etc/fstab
          • 使用 lsblk 确认磁盘路径。

        19. etcd 参数检查

          • 确保 etcd.yaml 中的 data-dir 指向新磁盘路径(如 /var/lib/etcd)。

          • 如果选择分离 snapshot/wal 以达到提升性能的目的, 需在启动前,需修改 etcd.yaml文件,增加 wal-dir 配置:

          --data-dir=/var/lib/etcd/data
          --wal-dir=/var/lib/etcd/wal
          • 验证证书路径和参数正确。

        20. 灾难恢复预案

          • 若操作中集群崩溃(如意外停机两个节点):

            • 从备份恢复整个集群:etcdutl snapshot restore ...

            • 重置 Kubernetes 控制平面组件。

        21. 性能优化建议

          • 使用 SSD 磁盘(etcd 对 I/O 延迟敏感)。

          • 分离 etcd 数据目录与操作系统磁盘

          • 调整 etcd 的 --quota-backend-bytes 限制磁盘用量(默认 2GB)。

          • 如果选择分离 snapshot/wal 以达到提升性能的目的,可以使用使用两块新盘挂载到不同的目录,如 /var/lib/etcd/data 与 /var/lib/etcd/wal 

          常见问题处理

          • 节点无法加入集群

            • 检查网络防火墙(端口 2379/2380)。

            • 确保证书未过期且 CN/SAN 匹配。

            • 查看 etcd 日志:journalctl -u etcd 或 kubectl logs -n kube-system etcd-node1

          • 数据不一致

            • 使用 etcdctl check perf 测试性能。

            • 若数据损坏,从备份恢复受影响节点。


          参考:


          🔲 ☆

          Kubernetes 应用之 JupyterHub 搭建和运维

          前言

            之前在《JupyterLab 的搭建与运维》一文中,尝试了在单机上搭建部署 JupyterHub。不得不说,的确方便了团队内部共同使用同一台 GPU 服务器。但也有比较大的限制:

          • 运行中的实例对于 CPU、GPU、内存、硬盘等资源完全共享。当所有用户都申请的资源总和超出服务器所拥有的资源时,任务的运行效率将会大打折扣。甚至可能会容易出现內存溢出的问题,造成宿主机出现 BUG。
          • 难以同时管理多台服务器。在有多台不同 CPU/GPU 服务器时,单机部署的方案会造成多个入口,且很难实现用户数据在多机间的实时同步。
          • 资源回收和重置存在一定的难度。在单机部署方案中虽然也可以通过 JupyterHub 来限制闲置时间不超过多久,但是实例只会被关闭,而非销毁。如果用户实例出现了某些未知的配置问题,只能依靠管理员手动销毁实例来解决。

            其实,JupyterHub 官方很早就意识到了这些,并通过拥抱 Kubernetes (以下简称“K8S”)来解决以上限制。可以说 K8S 天然是为 JupyterHub 多机资源管理调度而生,可以:

          • 对运行实例的资源进行严格地限制,防止运行实例申请资源总和超出节点资源。
          • 根据集群实际运行情况来自动分布部署运行实例,在具有很大的节点池的情况下非常有效。
          • 共享持久化存储,平稳迁移运行实例到任一节点,自由切换 CPU/GPU 节点。
          • 自动销毁超过一定闲置时间的实例,并且在每次启动运行实例时都会拉取最新镜像

          JupyterHub for K8S 架构图(来自 https://z2jh.jupyter.org/)

          搭建

            这里我们以一个简单的 CPU/GPU 科学计算集群为例:

          • 登录节点 l0:提供服务入口(Web)
          • CPU/GPU 共用节点 l1、l2:运行实例部署池(可以根据实际情况和需求扩充或缩小)
          • 存储节点 nas:提供持久化存储(独立存储方案优于登录节点 NFS 服务)

          网络规划

            以下为集群节点对应的 IP 地址信息:

          节点主机名 IP 地址 备注
          l0 192.168.120.100 登录节点,K8S 控制节点
          l1 192.168.120.101 CPU/GPU 节点,K8S 工作节点
          l2 192.168.120.102 CPU/GPU 节点,K8S 工作节点
          nas 192.168.120.99 存储节点,NFS 服务

          K8S 集群节点子网为 192.168.120.0/24。另外Pod 子网设置为 192.168.144.0/20Service 子网设置为 192.168.244.0/20

          K8S 集群搭建

            集群搭建过程请见《Kubernetes 不完全入门》一文,需配置好节点识别 NVIDIA 显卡和 NFS CSI 存储。

          Helm 部署 JupyterHub

          安装 Helm

          Helm 是什么?

            类似于操作系统的 APT 等包管理器,Helm 是 Kubernetes 的包管理器,一般定义了部署在 K8S 集群中的应用所需的所有配置文件。

            Helm 可以通过系统包管理工具安装或者直接下载二进制文件使用。Ubuntu 系统如下操作:

          curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
          sudo apt-get install apt-transport-https --yes
          echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
          sudo apt-get update
          sudo apt-get install helm -y
          

          二进制文件请自行前往 https://github.com/helm/helm/releases 下载。

          添加 Chart

          Chart 是什么?

            Chart 是 Helm 使用的包格式,可以被认为是“软件源中的软件名”(实际是多种软件的集合)。这主要是因为如果要编写部署一整套应用所需的配置文件实在太复杂、耗时了,使用 Chart 只需要写一个自定义配置文件来覆盖想要修改的默认配置即可。

          helm repo add jupyterhub https://hub.jupyter.org/helm-chart/
          helm repo update
          

          准备自定义配置文件

            自定义配置文件可以是任意文件名,但必须是 yaml 格式,比如 config.yaml。对于以下配置我们可能需要进行自定义:

          • 对外代理服务:一般来说,JupyterHub 只有 Web 访问端口需要由 K8S 集群在控制节点暴露给反向代理服务(比如 Nginx)。这里的 proxy.service.nodePorts.http 配置为 34567 端口。另外,我们可以将 proxy.chp.networkPolicy.enabled 置为 false 来取消 K8S 网络限制。为了安全,在 1.0.0 版本之前也许手动设置 proxy.secretToken 字段(使用 openssl rand -hex 32 命令生成)。
          • hub 配置:(1)设置 hub.networkPolicy.enabledfalse 取消网络限制;(2)(可选)使用 hub.extraVolumes 字段来添加指定的持久化卷名;(3)(可选,推荐)配置 hub.config 来启用 Oauth2 认证登录,目前官方支持 Github、Gitlab 在内的多款认证方式,详细请见 The OAuthenticator。这里我们使用自建 Gitlab 来测试。
          • 全局配置:(1)(可选)可以修改 prePuller.hook.enabledfalse 来禁用节点预拉取运行实例镜像。启用的情况下,当有新节点加入可用集群时可以自动拉取,以避免第一次在新节点部署实例时用户需要等待一段时间。(2)(可选)限制实例最长可运行时间 cull.maxAge和最长闲置时间cull.timeout,通过自动销毁来提升集群的可用率。cull.enabled字段也需要置为 true 从而生效。cull.every 字段可以设置每分钟检查是否超出限制。
          • 用户实例配置:(1)NFS 持久化,通过设置 singleuser.extraPodConfig.securityContext 中的 fsGroup (值为 100) 和 fsGroupChangePolicy (值为 OnRootMismatch) 来实现启动实例跳过每次修改文件夹权限,仅当文件夹父目录不为 root 用户 (id 为 100) 拥有时才会修改文件夹权限。(2)基本配置,包括网络策略、环境变量、启动超时最长限制(即最长等待启动时间)。(3)动态存储卷配置,设置 singleuser.storage.dynamic.storageClassnfs-csi 来启用自动动态存储卷,可以用 singleuser.storage.capacity 来设置默认卷大小限制。由于实例中默认的缓冲区较小,在內存有限的情况下某些任务可能用缓冲区,因此可以挂载较大的本地临时卷来充当 /dev/shm/dev/fuse。(4)可用资源配置方案,相比单机部署的单一选择,K8S 部署方案可以提供多样化的资源配置方案,不仅包括 CPU、内存资源的集合,还有 GPU 资源。甚至于还可以通过 K8S 的节点标签来由用户手动选择哪个节点(当然仅在资源满足的情况下会成功创建)。

          以下为一个样例:

          proxy:
            chp:
              networkPolicy:
                enabled: false
            service:
              nodePorts:
                http: 34567
            secretToken: "<GENERATE SECRET TOKEN BY YOURSELF>"
          
          hub:
            networkPolicy:
              enabled: false
            extraVolumes:
              - name: hub-db-dir
                persistentVolumeClaim:
                  claimName: hub-db-dir
            config:
              JupyterHub:
                authenticator_class: oauthenticator.gitlab.GitLabOAuthenticator
              GitLabOAuthenticator:
                client_id: "<COPY IT FROM YOUR OAUTH2 SERVER>"
                client_secret: "<COPY IT FROM YOUR OAUTH2 SERVER>"
                oauth_callback_url: "https://jupyter.lisz.me/hub/oauth_callback"
                gitlab_url: "https://git.lisz.me"
                login_service: "Gitlab"
                scope:
                  - read_user
                  - read_api
                  - api
                  - openid
                  - profile
                  - email
                admin_users:
                  - <adminer_username>
                allowed_gitlab_groups:
                  - <group_name>
          
          prePuller:
            hook:
              enabled: false
          
          cull:
            enabled: true
            maxAge: 172800
            timeout: 600
            every: 60
          
          singleuser:
             extraPodConfig:
              securityContext:
                fsGroup: 100
                fsGroupChangePolicy: "OnRootMismatch"
            networkPolicy:
              enabled: false
            extraEnv:
              EDITOR: "vim"
              SHELL: "/bin/zsh"
              PYTHONUNBUFFERED: "1"
            startTimeout: 300
            storage:
              capacity: 100Gi
              dynamic:
                storageClass: nfs-csi
              extraVolumes:
                - name: shm-volume
                  emptyDir:
                    medium: Memory
                    sizeLimit: "20Gi"
                - name: fuse-device
                  hostPath:
                    path: /dev/fuse
                    type: CharDevice
              extraVolumeMounts:
                - name: shm-volume
                  mountPath: /dev/shm
                - name: fuse-device
                  mountPath: /dev/fuse
            image:
              name: quay.io/zhonger/base-notebook
              tag: v3
              pullPolicy: Always
            profileList:
              - display_name: "CPU 分区"
                description: '包含 Conda、Python 环境(8核16G)'
                default: true
                kubespawner_override:
                  cpu_gurantee: 1
                  memo_gurantee: "1G"
                  cpu_limit: 8
                  mem_limit: "16G"
                profile_options:
                  image:
                    display_name: "主机"
                    choices:
                      lab6:
                        display_name: "l1"
                        kubespawner_override:
                          node_selector: {'kubernetes.io/hostname': 'l1'}
                      lab9:
                        display_name: "l2"
                        kubespawner_override:
                          node_selector: {'kubernetes.io/hostname': 'l2'}
              - display_name: "GPU 分区"
                description: "包含 Conda、Python、CUDA 环境(8核16G)"
                kubespawner_override:
                  image: quay.io/zhonger/gpu-notebook:v3
                  image_pull_policy: Always
                  cpu_gurantee: 1
                  mem_gurantee: "1G"
                  cpu_limit: 8
                  mem_limit: "16G"
                profile_options:
                  image:
                    display_name: "资源配置"
                    choices:
                      A100x1:
                        display_name: "A100 (Python 3.11, CUDA 12) GPU x1"
                        kubespawner_override:
                          node_selector: {'gputype': 'A100'}
                          extra_resource_limits:
                            nvidia.com/gpu: "1"
                      P100x1:
                        display_name: "P100 (Python 3.11, CUDA 12) GPU x1"
                        kubespawner_override:
                          node_selector: {'gputype': 'P100'}
                          extra_resource_limits:
                            nvidia.com/gpu: "1"
          
          小提示

            如果用标签来选择节点的话,需要通过类似 kubectl label node l1 gputype=A100 命令预先配置好标签。

          启动 JupyterHub

            准备好以上配置文件后,可以使用以下命令启动。

          helm upgrade --cleanup-on-fail \
            --install <helm-release-name> jupyterhub/jupyterhub \
            --namespace <k8s-namespace> \
            --create-namespace \
            --version=<chart-version> \
            --values config.yaml
          
          小提示

            建议先下载好 JupyterHub 所需的镜像,可以通过 helm show values jupyterhub 来查看所有的镜像列表。或者可以用 helm pull jupyterhub/jupyterhub --version 4.2.0 来下载原始 Chart 文件,解压后查看 values.yaml 文件即可。如果想要使用国内镜像的话,就修改 values.yaml 文件里的镜像名再启动 JupyterHub。这里可以用本地的文件夹名称或压缩包名称来替代 jupyterhub/jupyterhub

          配置 Nginx

            当 JupyterHub 启动后,默认用户还是无法从本地访问服务器上部署的 JupyterHub 的,还需要使用 Nginx 代理一下。以下是 Nginx 虚拟主机配置样例。这样一来,就可以在用户端通过域名来直接访问部署好的 JupyterHub 了。

          server {
              listen 443 ssl;
              server_name jupyter.lisz.me;
          
              ssl_certificate /home/ubuntu/ssl/jupyter.lisz.me.cert.pem;
              ssl_certificate_key /home/ubuntu/ssl/jupyter.lisz.me.key.pem;
          
              # SSL settings (optional but recommended)
              ssl_protocols TLSv1.2 TLSv1.3;
              ssl_ciphers HIGH:!aNULL:!MD5;
          
              client_max_body_size 10G;
          
              # Logging
              access_log /var/log/nginx/jupyter_access.log;
              error_log /var/log/nginx/jupyter_error.log;
          
              location / {
                  proxy_pass http://localhost:30000;
                  proxy_set_header Host $host;
                  proxy_set_header X-Real-IP $remote_addr;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_set_header X-Forwarded-Proto $scheme;
          
                  # WebSocket support
                  proxy_http_version 1.1;
                  proxy_set_header Upgrade $http_upgrade;
                  proxy_set_header Connection "upgrade";
              }
          }
          
          # Redirect HTTP to HTTPS
          server {
              listen 80;
              server_name jupyter.lisz.me;
          
              return 301 https://$host$request_uri;
          }
          
          小提示

            JupyterHub 的 proxy 本身也可以提供对外访问的 HTTPS,详见 JupyterHub for Kubernetes – Administrator Guide/Security/HTTPS。其他反向代理软件也都适用。

          如果 Nginx 不在控制节点能反向代理 JupyterHub 吗?

            由于 proxy 配置使用了 nodePorts 来创建端口映射,默认是可以在其他节点访问到指定的端口的。如果想要仅允许 Nginx 代理所在主机访问,可以通过 ingress 来支持更精细的访问控制,详见 JupyterHub for Kubernetes – Resources/ingress

          运维

          基本管理

            部署完成后,我们需要通过 K8S 的 kubectl 命令来查看、管理 JupyterHub 应用。以下为一些常见的命令:

          ## 假设为 JupyterHub 创建的 namespace 为 jhub
          
          # 查看 JupyterHub 所有 Pod 状态
          ╰─$ kubectl get pod -n jhub
          NAME                             READY   STATUS    RESTARTS        AGE
          continuous-image-puller-76bkq    1/1     Running   0               5d1h
          continuous-image-puller-hntww    1/1     Running   0               5d1h
          hub-6867b9b6c7-slg9c             1/1     Running   0               5d1h
          proxy-cc45cd6f6-g2t24            1/1     Running   0               5d1h
          user-scheduler-7b465896b-bq4l6   1/1     Running   0               5d1h
          user-scheduler-7b465896b-rvqgx   1/1     Running   0               5d1h
          
          # 查看节点资源使用情况
          ╰─$ kubectl describe node l1
          
          # 查看用户实例状态或启动问题
          ╰─$ kubectl descirbe -n jhub pod jupyter-zhonger
          
          # 查看用户动态存储卷情况
          ╰─$ kubectl get -n jhub pvc
          

          备份和恢复存储卷

            由于使用动态存储卷,卷配置显得尤为重要。(毕竟 NFS 存储在远端,独立于 K8S 集群。)可以通过以下命令备份和恢复存储卷。

          # 备份所有 PV 和 PVC
          kubectl get pv -o yaml > all_pvs.yaml
          kubectl get pvc --all-namespaces -o yaml > all_pvc_by_namespace.yaml
          
          # 从备份文件中恢复所有 PV 和 PVC
          kubectl apply -f all_pvs.yaml
          kubectl apply -f all_pvc_by_namespace.yaml
          

          更改存储卷大小

            从查阅的资料来看,NFS 存储是无法动态更新存储卷大小的。换句话说,重新定义存储卷就可以手动更改存储大小。举个例子,现在想要为用户 zhonger 从默认的存储卷大小 100G 更改到 1T。那么我们先要获得用户 zhonger 的存储卷配置文件 pvc 和 pv。

          # 保存 PVC 配置到 YAML 文件
          kubectl get pvc claim-zhonger -n jhub -o yaml > claim-zhonger-pvc.yaml
          
          # 从 claim-zhonger-pvc.yaml 获知 PV_NAME
          kubectl get pv <PV_NAM> -o yaml > claim-zhonger-pv.yaml
          
          # 确保实例已经被销毁后,删除 PVC 和 PV
          kubectl delete -f claim-zhonger-pvc.yaml
          kubectl delete -f claim-zhonger-pv.yaml
          
          # 修改存储卷大小
          sed -i "s/100Gi/1Ti/" claim-zhonger-*.yaml
          
          # 重新定义存储卷
          kubectl apply -f claim-zhonger-pv.yaml
          kubectl apply -f claim-zhonger-pvc.yaml
          
          注意

            这里需要注意的是,PV 和 PVC 之间的依赖关系。PV 是先定义的,不属于任何命名空间。PVC 是依托于 PV 定义的,必须属于某个命名空间。所以删除的时候要先 PVC 再 PV,定义的时候要先 PV 再 PVC。

          资源配置方案

            对于资源配置方案,我们可以根据镜像CPU 核数内存大小GPU 块数的不同来创建出多样化方案。可以参考 Amazon 提供的丰富示例 jupyterhub-values-dummy.yaml 了解更多。

          利用情况监控与统计

            目前可以使用 Grafana + Prometheus 的方式来对 K8S 集群中所有的资源利用情况进行监控,也可以自行设计一个 Grafana 面板来展示当前 JupyterHub 应用中启动的用户实例情况。但对于更加进一步详细、细致的监控与统计还有待设计(类似于“单个用户的利用报告”、“全平台的利用报告”等)。

          总结

            JupyterHub 在 K8S 平台上散发出越来越强大的魅力,使得研究团队搭建自己的科学计算平台越来越容易。当然目前依然还是有一些挑战,比如“多节点 GPU 的调用”。类似于“机器学习模型训练任务”通常需要调试后再放在大规模的 GPU 集群上训练,而 JupyterHub 长于调试代码,是否可以调试完成后直接提交给更大规模的 GPU 集群后台计算呢?

          参考资料

          🔲 ⭐

          简化kubectl常用命令

          在k8s集群使用和运维中,每天都需要输入大量 kubectl 常用命令,其实我们可以通过定义 alias 别名和函数简化 kubectl 命令,比如使用k代替kubectl,po代替pods,svc代替services,kgpon代替kubectl get po -n [命名空间+资源名](以参数传入),并且添加kubectl命令自动补全,将这些别名和函数添加到 Shell 配置文件(如 ~/.bashrc、~/.zshrc)中,方便日常使用。以下脚本可以登陆到linux shell环境执行,结合这些别名和函数,可以大幅提高操作 Kubernetes 的效率,以后就可以轻松愉快地使用 kubectl 命令啦

          # 在 bash 中设置当前 shell 的自动补全
          source <(kubectl completion bash)
          echo "source <(kubectl completion bash)" >> ~/.bashrc
          
          # 在 zsh 中设置当前 shell 的自动补全
          # source <(kubectl completion zsh)  
          # echo '[[ $commands[kubectl] ]] && source <(kubectl completion zsh)' >> ~/.zshrc
          # zshrc添加将 .bashrc 改为 .zshrc
          
          # 添加简化命令,单引号包裹 EOF,禁止所有变量解析
          cat >> ~/.bashrc << 'EOF' 
          
          # 基础别名
          alias k='kubectl'                     # 用 k 代替 kubectl
          alias kg='kubectl get'                # 快速 get 资源
          alias kd='kubectl describe'           # 快速 describe 资源
          alias kdel='kubectl delete'           # 快速 delete 资源
          alias ka='kubectl apply -f'           # 快速 apply 文件
          alias ke='kubectl edit'               # 快速 edit 资源
          alias kex='kubectl explain'           # 快速 explain 资源
          alias kl='kubectl logs'               # 快速查看日志
          alias klf='kubectl logs -f'           # 实时查看 Pod 日志
          alias kx='kubectl exec -it'           # 快速进入 Pod 的 Shell
          alias kw='kubectl get --watch'        # 实时监控资源变化
          alias kt='kubectl top'                # 查看资源使用情况
          alias kr='kubectl rollout'            # 更新资源
          alias ks='kubectl scale'              # 资源扩缩容
          alias kcp='kubectl cp'                # 快速 cp 文件
          alias kco='kubectl config'            # 快速 config 资源
          alias kcd='kubectl cordon'            # 节点不可调度
          alias kucd='kubectl uncordon'         # 节点可调度
          alias kapi='kubectl api-resources'    # 列出支持的资源类型
          alias kcl='kubectl cluster-info'      # 显示主控节点和服务的地址
          alias kh='kubectl get --v=7'          # 显示资源请求的HTTP信息
          
          
          # 资源类型别名
          alias kgpo='kubectl get pods'         # pods
          alias kgno='kubectl get nodes'        # nodes
          alias kgns='kubectl get namespaces'   # namespaces
          alias kgcs='kubectl get cs'           # ComponentStatus
          alias kgep='kubectl get ep'           # endpoints
          alias kgsvc='kubectl get services'    # services
          alias kgdep='kubectl get deployments' # deployments
          alias kgrs='kubectl get replicasets'  # replicasets
          alias kging='kubectl get ingress'     # ingress
          alias kgcm='kubectl get configmaps'   # configmaps
          alias kgsec='kubectl get secrets'     # secrets
          alias kgsa='kubectl get sa'           # serviceaccounts
          alias kgpv='kubectl get pv'           # persistentvolumes
          alias kgpvc='kubectl get pvc'         # persistentvolumeclaims
          alias kgsc='kubectl get sc'           # storageclasses
          alias kgsts='kubectl get sts'         # StatefulSet
          alias kgds='kubectl get ds'           # DaemonSet
          alias kgev='kubectl get events'       # events
          alias kgjo='kubectl get jobs'         # jobs
          
          
          # 查看资源描述(需替换 <resource-name>)
          alias kdpo='kubectl describe pod'
          alias kdno='kubectl describe nodes'
          alias kdde='kubectl describe deployment'
          alias kdst='kubectl describe sts'
          alias kdds='kubectl describe ds'
          alias kdsv='kubectl describe svc'
          alias kdcm='kubectl describe cm'
          alias kdse='kubectl describe secrets'
          alias kdsa='kubectl describe sa'
          alias kdpv='kubectl describe pv'
          alias kdpvc='kubectl describe pvc'
          alias kdsc='kubectl describe sc'
          alias kding='kubectl describe ingress'
          alias kdjo='kubectl describe jobs'
          
          
          # 常用组合别名
          
          # 查看所有命名空间的 Pods
          alias kgpoa='kubectl get pods -A'
          
          # 查看 Pods 并显示附加信息(如 IP、节点)
          alias kgpow='kubectl get pods -owide'
          
          # 查看 Nodes 并显示附加信息(如 IP、节点)
          alias kgnow='kubectl get nodes -owide'
          
          # 资源操作:$1对应命名空间、$2对应资源名称;y → yaml,n → namespace,默认输出格式为yaml
          OUTPUT="-oyaml"
          
          # kubectl get resources
          kgpon() { kubectl get po -n "$@"; }
          kgpoy() { kgpon "$@" $OUTPUT; }
          
          kgden() { kubectl get deploy -n "$@"; }
          kgdey() { kgden "$@" $OUTPUT; }
          
          kgstn() { kubectl get sts -n "$@"; }
          kgsty() { kgstn "$@" $OUTPUT; }
          
          kgsvn() { kubectl get svc -n "$@"; }
          kgsvy() { kgsvn "$@" $OUTPUT; }
          
          kgdsn() { kubectl get ds -n "$@"; }
          kgdsy() { kgdsn "$@" $OUTPUT; }
          
          kgcmn() { kubectl get ds -n "$@"; }
          kgcmy() { kgcmn "$@" $OUTPUT; }
          
          kgsen() { kubectl get secrets -n "$@"; }
          kgsey() { kgsen "$@" $OUTPUT; }
          
          kgsan() { kubectl get sa -n "$@"; }
          kgsay() { kgsan "$@" $OUTPUT; }
          
          kgpvn() { kubectl get pv -n "$@"; }
          kgpvy() { kgpvn "$@" $OUTPUT; }
          
          kgpvcn() { kubectl get pvc -n "$@"; }
          kgpvcy() { kgpvcn "$@" $OUTPUT; }
          
          kgscn() { kubectl get sc -n "$@"; }
          kgscy() { kgscn "$@" $OUTPUT; }
          
          kgingn() { kubectl get ingress -n "$@"; }
          kgingy() { kgingn "$@" $OUTPUT; }
          
          kgevn() { kubectl get events -n "$@"; }
          kgevy() { kgevn "$@" $OUTPUT; }
          
          kgjon() { kubectl get jobs -n "$@"; }
          kgjoy() { kgjon "$@" $OUTPUT; }
          
          # kubectl describe resources
          kdpon() { kubectl describe po -n "$@"; }
          kdden() { kubectl describe deploy -n "$@"; }
          kdstn() { kubectl describe sts -n "$@"; }
          kdsvn() { kubectl describe svc -n "$@"; }
          kddsn() { kubectl describe ds -n "$@"; }
          kdcmn() { kubectl describe cm -n "$@"; }
          kdsen() { kubectl describe secrets -n "$@"; }
          kdsan() { kubectl describe sa -n "$@"; }
          kdpvn() { kubectl describe pv -n "$@"; }
          kdpvcn() { kubectl describe pvc -n "$@"; }
          kdscn() { kubectl describe sc -n "$@"; }
          kdingn() { kubectl describe ingress -n "$@"; }
          kdjon() { kubectl describe jobs -n "$@"; }
          
          # kubectl edit resources
          kepon() { kubectl edit po -n "$@"; }
          keden() { kubectl edit deploy -n "$@"; }
          kestn() { kubectl edit sts -n "$@"; }
          kesvn() { kubectl edit svc -n "$@"; }
          kedsn() { kubectl edit ds -n "$@"; }
          kecmn() { kubectl edit cm -n "$@"; }
          kesen() { kubectl edit secrets -n "$@"; }
          kesan() { kubectl edit sa -n "$@"; }
          kepvn() { kubectl edit pv -n "$@"; }
          kepvcn() { kubectl edit pvc -n "$@"; }
          kescn() { kubectl edit sc -n "$@"; }
          keingn() { kubectl edit ingress -n "$@"; }
          kejon() { kubectl edit jobs -n "$@"; }
          
          # 进入 Pod 的 Shell
          kxsh() { kubectl exec -it -n "$@" -- /bin/sh; }
          kxbs() { kubectl exec -it -n "$@" -- /bin/bash; }
          
          # 查看 Pod 的日志
          klpn() { kubectl logs -n "$@"; }
          klfpn() { klpn "$@" -f; }
          klppn() { klpn "$@" -p; }
          
          # 删除 Pod
          kdelp() { kubectl delete po -n "$@"; }
          
          # 强制删除 Pod
          krmfp() { kubectl delete po -n "$@" --force --grace-period=0; }
          
          # 上下文和命名空间切换
          alias kuse='kubectl config use-context'      # 切换集群
          alias kns='kubectl config set-context --current --namespace' # 切换命名空间
          
          # 生成 Pod 的临时调试副本(类似 docker exec)
          alias kdebug='kubectl debug -it --image=busybox'
          
          # 转发本地端口到 Pod(需替换参数)
          alias kpf='kubectl port-forward pod/<pod-name>'
          
          
          complete -o default -F __start_kubectl k
          EOF
          
          source ~/.bashrc

          使用示例:

          # 输入参数:$@对应整个输入参数,$1对应命名空间、$2对应资源名称,其余缩写命令使用方法与示例(Pods)类似
          
          # 查看所有命名空间的 Pods
          kgpoa                   → kubectl get pods -A
          kgpow -A                → kubectl get pods -owide -A
          
          # 查看指定命名空间的 Pods
          k get po -n namespace   → kubectl get pods -n namespace
          kg po -n namespace
          kgpo -n namespace
          kgpon namespace
          kgpon namespace -owide  → kubectl get pods -n namespace -owide
          
          # 查看指定命名空间的 Pod
          kgpon namespace podname → kubectl get pods -n namespace podname
          kgpoy namespace podname → kubectl get pods -n namespace podname -oyaml
          kdpon namespace podname → kubectl describe pods -n namespace podname
          kepon namespace podname → kubectl edit pods -n namespace podname
          
          # 进入 Pod 的 Shell
          kxsh namespace podname  → kubectl exec -it -n namespace podname -- /bin/sh
          kxbs namespace podname  → kubectl exec -it -n namespace podname -- /bin/bash
          
          # 查看 Pod 的日志
          klpn namespace podname  → kubectl logs -n namespace podname
          klfpn namespace podname → kubectl logs -n namespace podname -f
          klppn namespace podname → kubectl logs -n namespace podname --previous
          
          # 删除 Pod
          kdelp namespace podname → kubectl delete pods -n namespace podname
          krmfp namespace podname → kubectl delete pods -n namespace podname --force --grace-period=0

          注意事项

          • 如果别名冲突(如 k 被其他工具占用),可替换为其他名称(如 kb)。

          • 删除资源时务必确认资源名称,避免误操作。

          • 编辑系统级配置文件前建议先备份:cp ~/.bashrc{,.bak}

          • 检察语法错误:bash -n ~/.bashrc

          • 检查别名或函数是否成功定义:type kg、type kgpon


          参考:

          🔲 ☆

          深入解析 Kubernetes VPA 调优:动态资源分配与压测实战

          作者:SRE运维博客
          博客地址:https://www.cnsre.cn/
          文章地址:https://www.cnsre.cn/posts/241205123704/
          相关话题:https://www.cnsre.cn/tags/k8s/

          在现代云原生应用中,合理地分配和调整容器的资源是保障应用性能和成本效益的关键。Kubernetes 的 Vertical Pod Autoscaler(VPA)提供了根据实际资源使用情况自动调整 Pod 资源请求和限制的能力。然而,默认的 VPA 配置可能无法满足所有场景的需求,因此我们需要根据实际情况调整 VPA 的参数。

          本文将通过实验,详细介绍如何安装配置以及调整 VPA 的参数,以及通过实验展示带来的效果。

          基础配置及测试

          部署VPA

          先决条件

          • 拥有现有 k8s 集群。
          • 已安装 Kubernetes Metrics Server。有关更多信息,请参阅 使用 KubernetesMetrics Server 查看资源使用情况
          • 安装配置好 kubectl 客户端。
          • 设备已安装 OpenSSL 1.1.1 或更高版本(如本地设备可以通过kubectl控制k8s,即在本地安装即可,如在 master 控制集群则在master安装)。

          部署 Vertical Pod Autoscaler

          在此部分中,我们将部署 Vertical Pod Autoscaler 到集群。

          部署 Vertical Pod Autoscaler

          1. 打开终端窗口,导航到我们要下载 Vertical Pod Autoscaler 源代码的目录。

          2. 克隆 kubernetes/autoscaler GitHub 存储库。

            git clone https://github.com/kubernetes/autoscaler.git
            
          3. 切换到 vertical-pod-autoscaler 目录。

            cd autoscaler/vertical-pod-autoscaler/
            
          4. (可选)如果我们已经部署另一个版本的 Vertical Pod Autoscaler,请使用以下命令将其删除。

            ./hack/vpa-down.sh
            
          5. 使用以下命令将 Vertical Pod Autoscaler 部署到我们的集群。

            ./hack/vpa-up.sh
            
          6. 验证已成功创建 Vertical Pod Autoscaler Pods。

            kubectl get pods -n kube-system
            

            示例输出如下。

            NAME                                        READY   STATUS    RESTARTS   AGE
            [...]
            metrics-server-788b46889b-s6jst             1/1     Running   0               55m
            metrics-server-788b46889b-tz8m9             1/1     Running   0               55m
            vpa-admission-controller-6b6b54cbd6-wkgqw   1/1     Running   0               43m
            vpa-recommender-7cf697b548-xq9td            1/1     Running   0               43m
            vpa-updater-856fc8c478-kx747                1/1     Running   0               45m
            

          测试 Vertical Pod Autoscaler 安装

          在此部分中,我们部署示例应用程序以验证 Vertical Pod Autoscaler 在正常运行。

          测试 Vertical Pod Autoscaler 安装

          1. 使用以下命令部署 hamster.yaml Vertical Pod Autoscaler 示例。

            kubectl apply -f examples/hamster.yaml
            
          2. hamster 示例应用程序获取 Pods。

            kubectl get pods -l app=hamster
            

            示例输出如下。

            hamster-789f8477df-d8bd8   1/1     Running   0          48s
            hamster-789f8477df-7swj6   1/1     Running   0          48s
            
          3. 描述其中一个 Pods 以查看其 cpumemory 预留。请将 c7d89d6db-rglf5 替换为上一步输出中返回的 ID 之一。

            kubectl describe pod hamster-789f8477df-7swj6
            

            示例输出如下。

            [...]
            Containers:
              hamster:
                Container ID:  docker://e76c2413fc720ac395c33b64588c82094fc8e5d590e373d5f818f3978f577e24
                Image:         registry.k8s.io/ubuntu-slim:0.1
                Image ID:      docker-pullable://registry.k8s.io/ubuntu-slim@sha256:b6f8c3885f5880a4f1a7cf717c07242eb4858fdd5a84b5ffe35b1cf680ea17b1
                Port:          <none>
                Host Port:     <none>
                Command:
                  /bin/sh
                Args:
                  -c
                  while true; do timeout 0.5s yes >/dev/null; sleep 0.5s; done
                State:          Running
                  Started:      Thu, 05 Dec 2024 02:21:42 +0800
                Ready:          True
                Restart Count:  0
                Requests:
                  cpu:        100m
                  memory:     50Mi
            [...]
            

            我们可以看到原始 Pod 预留了 100 millicpu 和 50 MiB 内存。对于本示例应用程序,100 millicpu 小于 Pod 运行所需的数量,因此 CPU 受限。它预留的内存也远小于所需的数量。Vertical Pod Autoscaler vpa-recommender 部署分析 hamster Pods,以查看 CPU 和内存需求是否合适。如果需要调整,vpa-updater 使用更新后的值重新启动 Pods。

          4. 等待 vpa-updater 启动新 hamster Pod。这大概需要一两分钟。我们可以使用以下命令监控 Pods。

          注意

            如果我们不确定已经启动了新 Pod,请将 Pod 名称与我们之前的列表比较。新 Pod 启动时,我们会看到新 Pod 名称。
          
            ```
            kubectl get --watch Pods -l app=hamster
            ```
          
          1. 当新 hamster Pod 启动时,描述它并查看更新后的 CPU 和内存预留。

            kubectl describe pod hamster-789f8477df-ldpxc
            

            示例输出如下。

            [...]
            Containers:
              hamster:
                Container ID:  docker://2c3e7b6fb7ce0d8c86444334df654af6fb3fc88aad4c5d710eac3b1e7c58f7db
                Image:         registry.k8s.io/ubuntu-slim:0.1
                Image ID:      docker-pullable://registry.k8s.io/ubuntu-slim@sha256:b6f8c3885f5880a4f1a7cf717c07242eb4858fdd5a84b5ffe35b1cf680ea17b1
                Port:          <none>
                Host Port:     <none>
                Command:
                  /bin/sh
                Args:
                  -c
                  while true; do timeout 0.5s yes >/dev/null; sleep 0.5s; done
                State:          Running
                  Started:      Thu, 05 Dec 2024 02:21:42 +0800
                Ready:          True
                Restart Count:  0
                Requests:
                  cpu:        587m
                  memory:     262144k
            [...]
            

            在之前的输出中,我们可以看到 cpu 预留提升到了 587 个 millicpu,这是原始值的五倍多。memory 提高到了 262144 KB,即大约 250 MB,也就是原始值的五倍。此 Pod 资源不足,Vertical Pod Autoscaler 使用更为合适的值纠正了估计值。

          2. 描述 hamster-vpa 资源以查看新的建议。

            kubectl describe vpa/hamster-vpa
            

            示例输出如下。

            Name:         hamster-vpa
            Namespace:    default
            Labels:       <none>
            Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                            {"apiVersion":"autoscaling.k8s.io/v1beta2","kind":"VerticalPodAutoscaler","metadata":{"annotations":{},"name":"hamster-vpa","namespace":"d...
            API Version:  autoscaling.k8s.io/v1beta2
            Kind:         VerticalPodAutoscaler
            Metadata:
              Creation Timestamp:  2024-12-04T02:22:51Z
              Generation:          23
              Resource Version:    14411
              Self Link:           /apis/autoscaling.k8s.io/v1beta2/namespaces/default/verticalpodautoscalers/hamster-vpa
              UID:                 d0d85fb9-e153-11e9-ae53-0205785d75b0
            Spec:
              Target Ref:
                API Version:  apps/v1
                Kind:         Deployment
                Name:         hamster
            Status:
              Conditions:
                Last Transition Time:  2024-12-04T02:23:28Z
                Status:                True
                Type:                  RecommendationProvided
              Recommendation:
                Container Recommendations:
                  Container Name:  hamster
                  Lower Bound:
                    Cpu:     550m
                    Memory:  262144k
                  Target:
                    Cpu:     587m
                    Memory:  262144k
                  Uncapped Target:
                    Cpu:     587m
                    Memory:  262144k
                  Upper Bound:
                    Cpu:     21147m
                    Memory:  387863636
            Events:          <none>
            
          3. 在完成对示例应用程序的试验后,使用以下命令可将其删除。

            kubectl delete -f examples/hamster.yaml
            

          优化vpa配置

          调整 VPA Updater 参数

          背景问题分析

          由于业务需求,某些 Pod 的副本数必须固定为单副本,这与 VPA 默认策略的假设(需要至少两个副本以避免更新时的服务中断)相冲突。使用 VPA 时,Updater 遇到了以下报错

          pods_eviction_restriction.go:226] too few replicas for ReplicaSet cnsre/cnsrevpapod-555464bbc9. Found 1 live pods, needs 2 (global 2)
          

          为解决此问题,需要对 Updater 和 Recommender 进行参数调整,以适应单副本 Pod 的场景,提升配置调整的灵活性和稳定性。

          配置文件

           1
           2
           3
           4
           5
           6
           7
           8
           9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: vpa-updater
            namespace: kube-system
          spec:
            replicas: 1
            selector:
              matchLabels:
                app: vpa-updater
            template:
              metadata:
                labels:
                  app: vpa-updater
              spec:
                serviceAccountName: vpa-updater
                securityContext:
                  runAsNonRoot: true
                  runAsUser: 65534 # nobody 用户
                containers:
                - name: updater
                  args:
                  - "--min-replicas=1"
                  - "--v=4"
                  - "--stderrthreshold=info"
                  - "--kube-api-qps=20"
                  - "--kube-api-burst=40"
                  - "--in-recommendation-bounds-eviction-lifetime-threshold=0s" 
                  image: registry.k8s.io/autoscaling/vpa-updater:1.2.1
                  imagePullPolicy: Always
                  resources:
                    limits:
                      cpu: 200m
                      memory: 1000Mi
                    requests:
                      cpu: 50m
                      memory: 500Mi
                  ports:
                  - name: prometheus
                    containerPort: 8943
          

          参数解析

          1. --min-replicas=1:设置最小副本数为 1,确保当部署的副本数大于等于 1 时,VPA Updater 才会执行更新操作,防止在只有一个副本时频繁重启影响服务可用性。

          2. --kube-api-qps=20--kube-api-burst=40:提高与 Kubernetes API 交互的 QPS(每秒请求数)和突发请求数限制,确保在大规模集群或频繁更新情况下,VPA Updater 能够及时获取和更新资源信息。

          3. --in-recommendation-bounds-eviction-lifetime-threshold=0s:设置当 Pod 的资源请求在推荐范围内时,允许立即驱逐 Pod。默认情况下,VPA 会等待一定时间才会驱逐这些 Pod,以减少不必要的重启。将此参数设置为 0s,可以加速资源更新,但需要谨慎使用,避免因频繁重启影响服务稳定性。

          4. -v=4--stderrthreshold=info:设置日志级别为详细模式,方便调试和监控 VPA Updater 的行为。

          效果与注意事项

          通过上述参数调整,VPA Updater 将更积极地更新 Pod 的资源配置,尤其是在资源请求已经在推荐范围内但需要更新的情况下。需要注意的是,过于频繁的 Pod 重启可能会导致服务不稳定,因此应根据实际需求和应用特点进行权衡。

          调整 VPA Recommender 参数

          背景问题分析*

          在创建 Vertical Pod Autoscaler (VPA) 的配置后,经常会遇到推荐参数更新不及时或不符合预期的问题。这些问题主要表现为以下情况:

          ​ 1. 推荐参数延迟:VPA Recommender 无法及时计算和更新资源需求,导致 Pod 重建和调优速度滞后。

          ​ 2. 推荐效果不理想:推荐的 CPU 或内存参数无法满足实际工作负载需求,影响应用性能或资源利用率。

          为了解决这些问题,我们需要对 VPA Recommender 的默认参数进行调整,使其更加贴合业务需求,提高资源管理的及时性和准确性。

          配置文件

           1
           2
           3
           4
           5
           6
           7
           8
           9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: vpa-recommender
            namespace: kube-system
          spec:
            replicas: 1
            selector:
              matchLabels:
                app: vpa-recommender
            template:
              metadata:
                labels:
                  app: vpa-recommender
              spec:
                serviceAccountName: vpa-recommender
                securityContext:
                  runAsNonRoot: true
                  runAsUser: 65534 # nobody 用户
                containers:
                - name: recommender
                  image: registry.k8s.io/autoscaling/vpa-recommender:1.2.1
                  imagePullPolicy: Always
                  args:
                  - "--recommender-interval=1m"
                  - "--history-length=30m"
                  - "--oom-bump-up-ratio=1.5"
                  - "--oom-min-bump-up-bytes=209715200"
                  - "--recommendation-margin-fraction=0.25"
                  - "--recommendation-lower-bound-cpu-percentile=0.1"
                  - "--recommendation-lower-bound-memory-percentile=0.1"
                  - "--recommendation-upper-bound-cpu-percentile=0.95"
                  - "--recommendation-upper-bound-memory-percentile=0.95"
                  - "--kube-api-qps=20"
                  - "--kube-api-burst=40"
                  - "-v=4"
                  resources:
                    limits:
                      cpu: 200m
                      memory: 1000Mi
                    requests:
                      cpu: 50m
                      memory: 500Mi
                  ports:
                  - name: prometheus
                    containerPort: 8942
          

          参数解析

          1. --recommender-interval=1m:将推荐器的运行间隔设置为 1 分钟,意味着 VPA 每分钟计算一次新的资源推荐值。

          2. --history-length=30m:仅使用过去 30 分钟的历史数据进行推荐,使得推荐结果更加敏感,能及时反映近期的资源使用情况。

          3. --oom-bump-up-ratio=1.5:当发生 OOM(内存不足)时,将内存推荐值提高 1.5 倍,增加内存缓冲,防止再次发生 OOM。

          4. --oom-min-bump-up-bytes=209715200:当发生 OOM 时,最少增加 200MB 的内存,确保有足够的内存缓冲。

          5. --recommendation-margin-fraction=0.25:将资源推荐的安全边际提高到 25%,在推荐值上增加额外的缓冲,减少资源不足的风险。

          6. --recommendation-lower-bound-cpu-percentile=0.1--recommendation-lower-bound-memory-percentile=0.1:将 CPU 和内存的推荐下限百分位设置为 10%,过滤掉短期的低资源使用情况,防止推荐值过低。

          7. --recommendation-upper-bound-cpu-percentile=0.95--recommendation-upper-bound-memory-percentile=0.95:将 CPU 和内存的推荐上限百分位设置为 95%,限制推荐值的最大值,防止过度分配资源。

          8. --kube-api-qps=20--kube-api-burst=40:同样提高与 Kubernetes API 交互的请求限制,确保 Recommender 能够及时获取数据。

          9. -v=4:设置详细的日志级别,方便调试。

          效果与注意事项

          通过调整 Recommender 的参数,可以使资源推荐更加灵活和贴近实际使用情况。例如,缩短历史数据的参考范围,可以使推荐值更快地响应负载变化。然而,过短的历史数据可能导致推荐值波动过大,需要通过增加安全边际和调整百分位来平衡。

          压测过程与结果分析

          在调整 VPA 配置后,为验证优化效果,我们进行了详细的压测。以下是完整的测试过程,包括观察 VPA 推荐值的变化、Pod 状态的更新,以及通过负载工具模拟高负载场景。


          压测准备

          1. 检查 VPA 推荐值

          通过以下命令实时观察 VPA 推荐值的更新情况:

          1
          
          kubectl get vpa cnsrevpapod-vpa -n cnsre -w
          

          输出结果:

          NAME                MODE       CPU     MEM           PROVIDED   AGE
          cnsrevpapod-vpa   Recreate   2315m   30751634503   True       45h
          cnsrevpapod-vpa   Recreate   2315m   30751634503   True       45h
          

          观察到 VPA 内存推荐值从 30Gi 提升至 50Gi,说明推荐器对负载进行了及时响应。

          2. 监控 Deployment 状态

          在 VPA 更新生效期间,通过以下命令实时跟踪 Deployment 的状态变化:

          1
          
          kubectl get deploy cnsrevpapod -n cnsre -w
          

          输出结果:

          NAME            READY   UP-TO-DATE   AVAILABLE   AGE
          cnsrevpapod   1/1     1            1           23d
          

          分析:

          • Pod 在 VPA 更新生效时被重建(Recreate 模式),出现短暂的不可用状态。
          • 更新完成后,Pod 恢复为 READY 1/1 状态,资源调整成功。

          3. 安装压测工具

          进入 Pod 内部并安装 stress 工具,用于生成高负载。

          1
          
          kubectl exec -it cnsrevpapod-555464bbc9-6vjst -n cnsre -- /bin/bash
          

          安装步骤:

          1. 下载 RPM 包:

            1
            
            curl -O https://dl.fedoraproject.org/pub/epel/8/Everything/x86_64/Packages/s/stress-1.0.4-24.el8.x86_64.rpm
            
          2. 安装 RPM 包:

            1
            
            rpm -ivh stress-1.0.4-24.el8.x86_64.rpm
            
          3. 验证安装:

            1
            
            stress --version
            

          输出:

          stress 1.0.4
          

          压测执行

          运行以下命令,在 Pod 内部生成高负载:

          1
          
          stress --cpu 10 --vm 5 --vm-bytes 10G --vm-keep
          

          压测输出

          kube exec -it cnsrevpapod-555464bbc9-6vjst      -n cnsre /bin/bash
          ─╯
          kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
          bash-4.4# curl -O https://dl.fedoraproject.org/pub/epel/8/Everything/x86_64/Packages/s/stress-1.0.4-24.el8.x86_64.rpm
            % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                           Dload  Upload   Total   Spent    Left  Speed
          100 39864  100 39864    0     0  1441k      0 --:--:-- --:--:-- --:--:-- 1441k
          bash-4.4# rpm -ivh stress-1.0.4-24.el8.x86_64.rpm
          warning: stress-1.0.4-24.el8.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID 2f86d6a1: NOKEY
          Verifying...                          ################################# [100%]
          Preparing...                          ################################# [100%]
          Updating / installing...
             1:stress-1.0.4-24.el8              ################################# [100%]
          bash-4.4# stress --version
          stress 1.0.4
          bash-4.4# stress --cpu 10 --vm 5 --vm-bytes 10G --vm-keep 
          stress: info: [11485] dispatching hogs: 10 cpu, 0 io, 5 vm, 0 hdd
          command terminated with exit code 137
          

          断开是因为pod 进行了重启
          参数说明:

          • --cpu 10:使用 10 个 CPU 核心生成计算负载。
          • --vm 5:启动 5 个虚拟内存工作线程。
          • --vm-bytes 10G:每个线程分配 10GB 内存。
          • --vm-keep:持续分配内存,防止释放后被回收。

          压测观察与结果

          1. Pod 内部状态

          在压测过程中,Pod 的 CPU 和内存资源被占满,符合预期。可以通过以下命令验证资源使用情况:
          输出示例:

          1
          2
          3
          4
          5
          6
          7
          8
          
          kube top   pods cnsrevpapod-555464bbc9-6vjst -n cnsre
          NAME                             CPU(cores)   MEMORY(bytes)   
          cnsrevpapod-555464bbc9-6vjst   6882m        17166Mi         
          kube top   pods cnsrevpapod-555464bbc9-6vjst -n cnsre
          NAME                             CPU(cores)   MEMORY(bytes)   
          cnsrevpapod-555464bbc9-6vjst   6882m        17166Mi         
          kube top   pods cnsrevpapod-555464bbc9-6vjst -n cnsre
          Error from server (NotFound): pods "cnsrevpapod-555464bbc9-6vjst" not found
          

          pod 不存在是因为pod进行了重启

          2. VPA 推荐值更新

          压测运行一段时间后,VPA 推荐值会自动调整,提升资源分配以应对负载:

          1
          
          kubectl get vpa cnsrevpapod-vpa -n cnsre -w
          

          更新后的推荐值:

          NAME                MODE       CPU     MEM           PROVIDED   AGE
          cnsrevpapod-vpa   Recreate   2315m   30751634503   True       45h
          cnsrevpapod-vpa   Recreate   2315m   30751634503   True       45h
          cnsrevpapod-vpa   Recreate   9500m   50Gi          True       45h
          

          3. Deployment 状态变化

          VPA 调整完成后,Deployment 更新状态稳定,Pod 正常运行:

          1
          2
          3
          4
          5
          6
          
          kube get  deploy cnsrevpapod  -n cnsre -w
          NAME            READY   UP-TO-DATE   AVAILABLE   AGE
          cnsrevpapod   1/1     1            1           23d
          cnsrevpapod   0/1     0            0           23d
          cnsrevpapod   0/1     1            0           23d
          cnsrevpapod   1/1     1            1           23d
          

          观察新pod的配置参数

          1
          
          kubectl describe pod cnsrevpapod-555464bbc9-tlgx8 -n cnsre
          

          输出示例:

              State:          Running
                Started:      Thu, 05 Dec 2024 12:11:42 +0800
              Ready:          True
              Restart Count:  0
              Requests:
                cpu:     2315m
                memory:  50Gi
              Environment:
          

          压测结论

          1. VPA 能够动态调整单副本 Pod 的资源分配,及时响应高负载需求。
          2. 在压测过程中,VPA 推荐值更新迅速,资源分配上限(50Gi 内存和 9500m CPU)有效保障了服务的稳定性。
          3. Deployment 的短暂重建(Recreate 模式)可能会导致服务瞬时不可用。建议在关键场景中考虑切换至 Auto 模式,结合 PodDisruptionBudget 进行平滑更新。
          4. stress 工具成功验证了优化配置的效果,为业务提供了可靠的负载保障机制。

          通过此次测试,我们验证了优化配置在生产环境中的实际效果,为后续性能调优和稳定性保障提供了重要依据。

          总结

          通过以上调整,我们对 VPA 的 Updater、Recommender 和 VPA 对象本身进行了自定义配置,以满足特定的资源管理需求。具体效果包括:

          • 更快速的资源更新:缩短了 Recommender 的运行间隔,并允许 Updater 立即驱逐符合条件的 Pod。

          • 更灵活的资源推荐:调整了历史数据参考范围和推荐值的百分位,使推荐结果更贴近实际使用情况。

          • 资源分配的安全性:增加了资源推荐的安全边际,设置了资源请求和限制的上下限,防止过度分配或资源不足。

          在实际应用中,建议根据应用的特性和负载模式,逐步调整 VPA 的参数,并在测试环境中验证效果,确保在满足性能要求的同时,优化资源的利用率和成本效益。

          参考资料

          希望本文能帮助您更好地理解和使用 Kubernetes VPA,以实现高效的资源管理和自动伸缩。


          作者:SRE运维博客
          博客地址:https://www.cnsre.cn/
          文章地址:https://www.cnsre.cn/posts/241205123704/
          相关话题:https://www.cnsre.cn/tags/k8s/

          🔲 ⭐

          Kubernetes的安装和使用(二)

          k8s的使用

          构建和运行镜像

          编写一个go程序

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          package main

          import (
          "io"
          "log"
          "net/http"
          )

          func main() {
          http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
          io.WriteString(w, "[v1] Hello, Kubernetes!")
          })

          log.Printf("v1 access http://localhost:3000\n")
          panic(http.ListenAndServe(":3000", nil))
          }

          编写Dockerfile

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          # 引入golang的环境,并设置别名
          FROM golang:1.20-alpine AS builder
          # 工作目录
          WORKDIR /project
          # 把当前文件夹的内容都添加到镜像的/project文件夹下
          ADD . .
          # 编译golang源代码
          # 设置代理、操作系统、CPU架构、禁用cgo编译
          # 设置mod为自动模式,防止因为没有设置go.mod而编译报错
          RUN GOPROXY=https://goproxy.cn,direct GOOS=linux GOARCH=amd64 CGO_ENABLED=0 GO111MODULE=auto go build -o main -ldflags "-w -extldflags -static"

          # 引入alpine系统环境
          FROM alpine as prod
          # 从上面的build镜像复制编译好的文件/project/main到当前目录
          COPY --from=builder /project/main .
          # 暴露3000端口
          EXPOSE 3000
          # 启动main程序
          ENTRYPOINT ["/main"]

          打包镜像

          docker build . -t derobukal/hellok8s:v1

          执行镜像,并把3000端口暴露出来

          docker run --rm -p 3000:3000 derobukal/hellok8s:v1

          之后可以用curl访问3000端口,结果正常显示了

          ~ curl 127.0.0.1:3000[v1] Hello, Kubernetes!%   

          之后可以把这个镜像推送到镜像仓库

          docker logindocker push derobukal/hellok8s:v1

          使用Pod

          编写pod.yaml

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          apiVersion: v1
          kind: Pod # 资源类型为pod
          metadata:
          name: go-http # 名称,需要在当前命名空间中唯一
          labels:
          app: go
          version: v1
          spec:
          containers: # pod内的容器组
          - name: go-http
          image: derobukal/hellok8s:v1 # 镜像默认来源 DockerHub

          之后创建pod

          ~ kc apply -f pod.yamlpod/go-http created~ kc get podsNAME      READY   STATUS    RESTARTS   AGEgo-http   1/1     Running   0          65s

          然后临时开启端口转发,就可以访问相应的服务了

          ~ kc port-forward go-http 3000:3000Forwarding from 127.0.0.1:3000 -> 3000Forwarding from [::1]:3000 -> 3000~ curl http://127.0.0.1:3000[v1] Hello, Kubernetes!%

          在进行以上测试的时候,如果出现pod启动不了的情况,可能是因为防火墙的原因,可以开启网络代理之后再试。

          使用Deployment

          一般来说,pod不会被直接的使用,而是用Deployment来进行相关的操作。

          编写deployment.yaml

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          apiVersion: apps/v1
          kind: Deployment
          metadata:
          # deployment 唯一名称
          name: hellok8s-go-http
          spec:
          replicas: 2 # 副本数量
          selector:
          matchLabels:
          app: hellok8s # 管理template下所有 app=hellok8s的pod,(要求和template.metadata.labels完全一致!!!否则无法部署deployment)
          template: # template 定义一组容器
          metadata:
          labels:
          app: hellok8s
          spec:
          containers:
          - image: derobukal/hellok8s:v1
          name: hellok8s

          部署deployment

          ~ kc apply -f deployment.yaml deployment.apps/hellok8s-go-http created~ kc get deploymentsNAME               READY   UP-TO-DATE   AVAILABLE   AGEhellok8s-go-http   2/2     2            2           79s

          kc get pod -o wide可以查看更详细的pod信息。我们可以把配置中的副本数改为3,之后重新执行部署命令,然后查看详细的pod信息如下,可以看到又新增了一个pod

          NAME                                READY   STATUS    RESTARTS   AGE     IP           NODE       NOMINATED NODE   READINESS GATESgo-http                             1/1     Running   0          14m     10.244.0.6   minikube   <none>           <none>hellok8s-go-http-6db476b8cb-4n2nx   1/1     Running   0          4m35s   10.244.0.7   minikube   <none>           <none>hellok8s-go-http-6db476b8cb-s76jr   1/1     Running   0          4m35s   10.244.0.8   minikube   <none>           <none>hellok8s-go-http-6db476b8cb-tjqs9   1/1     Running   0          8s      10.244.0.9   minikube   <none>           <none>

          我们选取任意一个pod进行端口转发,随后请求可以看到结果正常

          ~ kc port-forward hellok8s-go-http-6db476b8cb-4n2nx 3000:3000 Forwarding from 127.0.0.1:3000 -> 3000Forwarding from [::1]:3000 -> 3000Handling connection for 3000

          更新deployment

          我们将golang程序中的v1修改为v2,之后打包新版本镜像并上传

          docker build . -t derobukal/hellok8s:v2docker push derobukal/hellok8s:v2

          之后我们修改deployment.yaml中的镜像为derobukal/hellok8s:v2,然后更新部署

          ~ kc apply -f deployment.yaml deployment.apps/hellok8s-go-http configured

          可以看到pod都重新部署了

          ~ kc get pod -o wide                                         NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATESgo-http                            1/1     Running   0          25m   10.244.0.6    minikube   <none>           <none>hellok8s-go-http-8b44d58c5-5bmx7   1/1     Running   0          34s   10.244.0.12   minikube   <none>           <none>hellok8s-go-http-8b44d58c5-djfcd   1/1     Running   0          35s   10.244.0.11   minikube   <none>           <none>hellok8s-go-http-8b44d58c5-rt29z   1/1     Running   0          58s   10.244.0.10   minikube   <none>           <none>

          之后选取一个Pod进行端口转发,并请求发现返回结果发生了变化

          ~ kc port-forward hellok8s-go-http-8b44d58c5-5bmx7 3000:3000 Forwarding from 127.0.0.1:3000 -> 3000Forwarding from [::1]:3000 -> 3000Handling connection for 3000~ curl http://127.0.0.1:3000[v2] Hello, Kubernetes!%

          回滚deployment

          查看版本信息

          ~ kc rollout history deployment/hellok8s-go-httpdeployment.apps/hellok8s-go-http REVISION  CHANGE-CAUSE1         <none>2         <none>

          查看具体版本2

          ~ kc rollout history deployment/hellok8s-go-http --revision=2deployment.apps/hellok8s-go-http with revision #2Pod Template:Labels:app=hellok8s    pod-template-hash=8b44d58c5Containers:hellok8s:    Image:derobukal/hellok8s:v2    Port:<none>    Host Port:<none>    Environment:<none>    Mounts:<none>Volumes:<none>

          查看具体版本1

          ~ kc rollout history deployment/hellok8s-go-http --revision=1deployment.apps/hellok8s-go-http with revision #1Pod Template:Labels:app=hellok8s    pod-template-hash=6db476b8cbContainers:hellok8s:    Image:derobukal/hellok8s:v1    Port:<none>    Host Port:<none>    Environment:<none>    Mounts:<none>Volumes:<none>

          回退到版本1

          ~ kc rollout undo deployment/hellok8s-go-http --to-revision=1deployment.apps/hellok8s-go-http rolled back

          此时查看pod可以发现pod已经发生了改变,访问服务返回的也是v1版本信息了。

          部署失败

          我们构建一个如下的程序

          1
          2
          3
          4
          5
          package main

          func main() {
          panic("something went wrong")
          }

          之后打包一个新版本镜像:docker build . -t derobukal/hellok8s:v_error
          并对这个镜像进行push:docker push derobukal/hellok8s:v_error

          之后可以简单地使用命令而不修改deployment.yaml文件来重新部署deployment,通过命令修改镜像:

          ~ kc set image deployment/hellok8s-go-http hellok8s=derobukal/hellok8s:v2_error  deployment.apps/hellok8s-go-http image updated~ kc get podsNAME                                READY   STATUS              RESTARTS        AGEgo-http                             1/1     Running             1 (7m34s ago)   54mhellok8s-go-http-55669566cb-l69hx   0/1     ContainerCreating   0               41shellok8s-go-http-6db476b8cb-dlr2z   1/1     Running             1 (7m34s ago)   22mhellok8s-go-http-6db476b8cb-glx7h   1/1     Running             1 (7m34s ago)   22mhellok8s-go-http-6db476b8cb-sfxnr   1/1     Running             1 (7m34s ago)   22m

          重新部署之后我们查看pod可以发现,之前的pod仍然在正常运行,而新启动的pod则处于ContainerCreating状态。我们可以直接回退到上一个版本

          ~ kc rollout undo deployment/hellok8s-go-http --to-revision=2deployment.apps/hellok8s-go-http rolled back~ kc get podsNAME                                READY   STATUS        RESTARTS      AGEgo-http                             1/1     Running       1 (12m ago)   58mhellok8s-go-http-55669566cb-l69hx   0/1     Terminating   0             5m17shellok8s-go-http-8b44d58c5-74mhw    1/1     Running       0             22shellok8s-go-http-8b44d58c5-hfpjj    1/1     Running       0             20shellok8s-go-http-8b44d58c5-lzwfm    1/1     Running       0             19s
          🔲 ☆

          Kubernetes的安装和使用(一)

          k8s是一种可以实现容器集群的自动化部署、自动扩缩容、维护等功能的服务。Docker解决了应用运行时环境的问题,而k8s则可以用来构建大量应用服务,它能方便的管理海量应用容器。它拥有自动包装、自我修复、横向缩放、服务发现、负载均衡、自动部署、升级回滚、存储编排等特性。

          k8s的节点分为master和node,它的架构如下

          Master:官方叫做控制平面(Control Plane),它用于负责整个集群的管控。master由4个部分组成

          1. API Server进程,负责任何资源的管理和操作
          2. etcd,用于保存集群状态,只有apiServer可以读写
          3. 调度器(Scheduler),用于调度Pod资源
          4. 控制器管理器(kube-controller-manager)

          Node:数据平面,是实际的工作节点,直接负责对容器的资源控制。node由3个部分组成

          1. kubelet,运行在每个节点上面的代理进程
          2. kube-proxy,负责每个节点的网络服务
          3. 容器运行时,例如docker

          k8s还定义了一些内核抽象

          1. Pod

          Pod是k8s调度的基本单元,它封装了一个或多个容器。Pod中的容器会作为一个整体被k8s调度到一个Node上运行。同一个Pod内的容器可以互相操作对方的文件,这些容器就好像运行在同一个操作系统上的不同进程一样。

          2. 控制器

          一般来说,用户不会直接创建Pod,而是创建控制器来管理Pod,因为控制器能够更细粒度的控制Pod的运行方式,比如副本数量、部署位置等。 控制器包含下面几种:

          • Replication控制器(以及ReplicaSet控制器):负责保证Pod副本数量符合预期(涉及对Pod的启动、停止等操作)
          • Deployment控制器:是高于Replication控制器的对象,也是最常用的控制器,用于管理Pod的发布、更新、回滚等
          • StatefulSet控制器:与Deployment同级,提供排序和唯一性保证的特殊Pod控制器。用于管理有状态服务,比如数据库等
          • DaemonSet控制器:与Deployment同级,用于在集群中的每个Node上运行单个Pod,多用于日志收集和转发、监控等功能的服务。并且它可以绕过常规Pod无法调度到Master运行的限制
          • Job控制器:与Deployment同级,用于管理一次性任务,比如批处理任务
          • CronJob控制器:与Deployment同级,在Job控制器基础上增加了时间调度,用于执行定时任务

          3. Service、Ingress和Storage

          Service是对一组Pod的抽象,它定义了Pod的逻辑集合以及访问该集合的策略。前面的Deployment等控制器只定义了Pod运行数量和生命周期, 并没有定义如何访问这些Pod,由于Pod重启后IP会发生变化,没有固定IP和端口提供服务。
          Service对象就是为了解决这个问题。Service可以自动跟踪并绑定后端控制器管理的多个Pod,即使发生重启、扩容等事件也能自动处理, 同时提供统一IP供前端访问,所以通过Service就可以获得服务发现的能力,部署微服务时就无需单独部署注册中心组件。
          Ingress不是一种服务类型,而是一个路由规则集合,通过Ingress规则定义的规则,可以将多个Service组合成一个虚拟服务(如前端页面+后端API)。 它可实现业务网关的作用,类似Nginx的用法,可以实现负载均衡、SSL卸载、流量转发、流量控制等功能。
          Storage是Pod中用于存储的抽象,它定义了Pod的存储卷,包括本地存储和网络存储;它的生命周期独立于Pod之外,可进行单独控制。

          4. 资源划分

          命名空间(Namespace):k8s通过namespace对同一台物理机上的k8s资源进行逻辑隔离。
          标签(Labels):是一种语义化标记,可以附加到Pod、Node等对象之上,然后更高级的对象可以基于标签对它们进行筛选和调用, 例如Service可以将请求只路由到指定标签的Pod,或者Deployment可以将Pod只调度到指定标签的Node。
          注解(Annotations):也是键值对数据,但更灵活,它的value允许包含结构化数据。一般用于元数据配置,不用于筛选。 例如Ingress中通过注解为nginx控制器配置禁用ssl重定向。

          k8s的安装

          k8s的安装比较复杂,需要涉及到很多的Linux、网络、存储等设置。为了简单起见,我们先学习使用minikube安装单机的k8s环境,等学习并熟悉了k8s的使用之后,再去搭建k8s的集群环境。

          安装kubectl

          kubectl是k8s的客户端,我们可以通过它和k8s的服务进行交互,我们直接从k8s的官网上下载它并将其安装到/usr/local/bin目录下

          # 下载kubectl客户端,这里使用了代理curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" -x http://192.168.65.100:7890# 将kubectl客户端安装到指定的bin目录下sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

          为了方便后面的使用,可以将kc设置为kubectl的别名,将如下配置添加到~/.zshrc文件中

          alias kc="kubectl"

          之后我们就可以查看kubectl的版本了

          ~ kc version --client --output=json{    "clientVersion": {        "major": "1",        "minor": "29",        "gitVersion": "v1.29.0",        "gitCommit": "3f7a50f38688eb332e2a1b013678c6435d539ae6",        "gitTreeState": "clean",        "buildDate": "2023-12-13T08:51:44Z",        "goVersion": "go1.21.5",        "compiler": "gc",        "platform": "linux/amd64"    },    "kustomizeVersion": "v5.0.4-0.20230601165947-6ce0bf390ce3"}

          安装Docker

          Docker的安装参考了官方文档,具体步骤如下

          添加Docker的官方GPG秘钥

          sudo apt-get updatesudo apt-get install ca-certificates curl gnupgsudo install -m 0755 -d /etc/apt/keyringscurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpgsudo chmod a+r /etc/apt/keyrings/docker.gpg

          把仓库添加到apt的资源列表中

          echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullsudo apt-get update

          安装相关的程序并进行权限设置

          # 安装程序sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin# 设置文件权限,并把当前用户添加到docker组中sudo chmod 666 /var/run/docker.socksudo usermod -aG docker $USER

          安装好了Docker并设置完权限之后,可以执行Docker的hello-world查看是否安装成功了

          docker run hello-world

          安装成功的输出如下

          Hello from Docker!This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:    1. The Docker client contacted the Docker daemon.    2. The Docker daemon pulled the "hello-world" image from the Docker Hub.        (amd64)    3. The Docker daemon created a new container from that image which runs the        executable that produces the output you are currently reading.    4. The Docker daemon streamed that output to the Docker client, which sent it        to your terminal.To try something more ambitious, you can run an Ubuntu container with:    $ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:    https://hub.docker.com/For more examples and ideas, visit:    https://docs.docker.com/get-started/

          安装minikube

          与kubectl的安装类似,我们还是使用下载并安装的方式安装minikube

          # 下载curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64# 安装到指定目录下面sudo install minikube-linux-amd64 /usr/local/bin/minikube

          安装之后,我们就可以启动minikube了。因为网络原因,直接使用minikube start命令有的时候无法正常启动,因此我们可以使用代理

          ~ minikube start http_proxy=http://192.168.65.100:7890 https_proxy=http://192.168.65.100:7890😄  minikube v1.32.0 on Ubuntu 22.04✨  Using the docker driver based on existing profile👍  Starting control plane node minikube in cluster minikube🚜  Pulling base image ...🔄  Restarting existing docker container for "minikube" ...🐳  Preparing Kubernetes v1.28.3 on Docker 24.0.7 ...🔗  Configuring bridge CNI (Container Networking Interface) ...🔎  Verifying Kubernetes components...    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5🌟  Enabled addons: default-storageclass, storage-provisioner🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

          看到以上内容,则说明k8s已经启动好了。接下来我们就可以使用kubectl来管理k8s了

          查看版本信息

          ~ kc versionClient Version: v1.29.0Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3Server Version: v1.28.3

          查看k8s集群信息

          ~ kc cluster-infoKubernetes control plane is running at https://192.168.49.2:8443CoreDNS is running at https://192.168.49.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxyTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

          查看节点信息

          ~ kc get nodesNAME       STATUS   ROLES           AGE     VERSIONminikube   Ready    control-plane   4h48m   v1.28.3

          参考

          Kubernetes 安装小记
          Kubernetes 使用小记
          Kubernetes 基础教程
          Docker 入门教程

          🔲 ☆

          [Workshops]使用k3s部署镜像缓存以及管理系统

          第N次,和 K3S打上了交道。因为K8S的技术的学习曲线较为陡峭。也因为自己基础不牢。 基础部署都没熟练直攻Helm。

          然后导致集群就处于失控状态,从而导致了最终的学习/研究失败。

          现在重新捡起来,使用基础的例子一步步的构建K3S homelab体系。

          最终的目的是可以把目前所有的 docker-compose 的项目都迁移到K3S下面来。

          Workshop 说明

          需求背景

          因为国内的网络环境问题,拉取Docker的gcr/dockerio 之类的镜像时有失败。而且因为集群的多节点。导致 ImagePullErr 问题时有发生。

          所以这里需要建立起一个 在本地提供镜像缓存以及管理功能的 Registry。

          选型

          官方的Registry镜像中提供了一个 remote_porxy 的配置,可以直接实现对某一个远程仓库的 代理和 缓存功能。

          不过这里有一个进行二次打包的版本 yangchuansheng/registry-proxy 把一些配置直接写到了环境变量中去。简化了我们的使用流程。

          在有提供 代理服务的 仓库镜像之后,还需要一个管理工具,用来对我们的镜像进行可视化的webui管理。这里找到了下面的那这个项目

          可以直接进行部署用来Registry的资源管理。

          实践过程

          持久化存储

          因为需要进行镜像的缓存,所以对于拉去的镜像需要进行持久化存储。这里原计划是使用NFS来提供存储,不过这种基础的服务不希望降低系统的整体性。所以就在Master 的节点拓展了磁盘。直接进行磁盘的localpath存储。

          多个的镜像代理都是公用的一个PV存储,这样便于管理且不会冲突。下面的yaml就是对这个 PV 的定义

          ---
          apiVersion: v1
          kind: PersistentVolume
          metadata:
            name: master-local-storage
          spec:
            accessModes:
              - ReadWriteOnce
              - ReadWriteMany
            capacity:
              storage: 15Gi
            local:
              path: /data
            nodeAffinity:
              required:
                nodeSelectorTerms:
                  - matchExpressions:
                      - key: node-role.kubernetes.io/storage
                        operator: In
                        values:
                          - storage
            persistentVolumeReclaimPolicy: Retain
            storageClassName: local-path
            volumeMode: Filesystem
          

          这里需要注意的是两点,其中一个是 persistentVolumeReclaimPolicy

          Reclaim Policy

          Current reclaim policies are:

          • Retain — manual reclamation
          • Recycle — basic scrub (rm -rf /thevolume/*)
          • Delete — associated storage asset such as AWS EBS or GCE PD volume is deleted

          Currently, only NFS and HostPath support recycling. AWS EBS and GCE PD volumes support deletion.

          img

          另一个是 nodeAffinity,通过下面的scheme 来进行。

          小技巧使用 kubectl explain svc –recursive 来进行API的递归列出

          或者使用 api reference 来进行查询 Kubernetes API/Config and Storage Resources/PersistentVolume

          rms@k3s-master:~$ kubectl explain PersistentVolume.spec.nodeAffinity --recursive
          KIND:       PersistentVolume
          VERSION:    v1
          
          FIELD: nodeAffinity <VolumeNodeAffinity>
          
          DESCRIPTION:
              nodeAffinity defines constraints that limit what nodes this volume can be
              accessed from. This field influences the scheduling of pods that use this
              volume.
              VolumeNodeAffinity defines constraints that limit what nodes this volume can
              be accessed from.
          
          FIELDS:
            required  <NodeSelector>
              nodeSelectorTerms   <[]NodeSelectorTerm> -required-
                matchExpressions  <[]NodeSelectorRequirement>
                  key <string> -required-
                  operator    <string> -required-
                  values  <[]string>
                matchFields   <[]NodeSelectorRequirement>
                  key <string> -required-
                  operator    <string> -required-
                  values  <[]string>
          

          PVC 对PV 来进行 Claim,以提供给POD 来进行使用。

          apiVersion: v1
          kind: Namespace
          metadata:
            name: container-basis
          
          ---
          apiVersion: v1
          kind: PersistentVolumeClaim
          metadata:
            name: registry-pvc
            namespace: container-basis
          spec:
            accessModes:
              - ReadWriteOnce
            storageClassName: local-path
            volumeMode: Filesystem
            volumeName: master-local-storage
            resources:
              requests:
                storage: 10Gi
          

          多个站点的Proxy Deploy部署&UI 部署

          这里简单的就是一个 Deployment 的部署方式,这里的yaml 定义了Proxy 的Deploy ,配置都是比较浅显易懂的。 这个是根据官方进行进行二次打包的镜像。其中的一些配置值可以直接通过环境变量来直接传入。

          其中需要注意的是,Resources.limits 是推荐进行设置的否则存在容器对资源的过度使用的问题。

          ---
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: k8s-gcr-registry
            namespace: container-basis
            labels:
              app: k8s-gcr-registry
          spec:
            replicas: 1
            selector:
              matchLabels:
                app: k8s-gcr-registry
            template:
              metadata:
                labels:
                  app: k8s-gcr-registry
              spec:
                containers:
                - name: k8s-gcr-registry
                  resources:
                    requests:
                      cpu: 100m
                      memory: 128Mi
                    limits:
                      memory: "512Mi"
                      cpu: "500m"
                  image: yangchuansheng/registry-proxy:latest
                  ports:
                  - containerPort: 5000
                    protocol: TCP
                  volumeMounts:
                  - name: local
                    mountPath: /hub
                  env:
                  - name: REGISTRY_HTTP_ADDR
                    value: :5000
                  - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY
                    value: /hub
                  - name: PROXY_REMOTE_URL
                    value: https://k8s.gcr.io
                volumes:
                - name: local
                  persistentVolumeClaim:
                    claimName: registry-pvc
          
          

          关于多个缓存站点的定义,其yaml文件的大多数都是相同的。其具体的差异在Env 的部分。比如下面这个就是代理Gcr 的配置 。

            env:
            - name: REGISTRY_HTTP_ADDR
              value: :5000
            - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY
              value: /hub
            - name: PROXY_REMOTE_URL
              value: https://gcr.io
          

          Service 以及 Ingress

          在Registry运行之后,就需要考虑到前面的接入部份,定义servic和对应的ingress 来作为七层的路由转发。从而实现对集群外提供访问。具体的配置文件如下。

          service的几个类型,不指定默认是 ClusterIP 的模式。

          ---
          apiVersion: v1
          kind: Service
          metadata:
            name: k8s-gcr-registry-svc
            namespace: container-basis
            labels:
              run: k8s-gcr-registry
          spec:
            selector:
              app: k8s-gcr-registry
            ports:
              - protocol: TCP
                port: 5000
          
          ---
          apiVersion: networking.k8s.io/v1
          kind: Ingress
          metadata:
            name: registry-ingress-k8s-gcr
            namespace: container-basis
            annotations:
              kubernetes.io/ingress.class: "traefik"
          spec:
            rules:
            - host: k8s-gcr-k3s.io
              http:
                paths:
                - path: /
                  pathType: ImplementationSpecific
                  backend:
                    service: 
                      name: k8s-gcr-registry-svc
                      port:
                        number: 5000
          
          

          代理接入配置

          在前面的部分已经完成了Registry 的基础部署,后面就是使用上的配置。这里直接使用 ansible来进行配置文件的部署。

          先写Host到所有的节点中,用来劫持几个镜像域名的指向到我们自建的registry的IP。

          然后重启节点服务来进行应用生效

          For K3s

          - hosts: k3s
            remote_user: ep
            become: yes
            gather_facts: False
            vars_prompt:
            - name: "registryIP"
              prompt: "Please input Registry Server IP"
              private: no
            tasks:
              - name: add hosts
                when: registryIP is defined
                shell: |
                  sudo sed -i '/^.*hub-k3s\.io.*/d' /etc/hosts
                  sudo echo "{{ registryIP }}  hub-k3s.io      gcr-k3s.io        k8s-gcr-k3s.io" >> /etc/hosts
              - name: add remote private registry
                when: registryIP is defined
                shell: |
                  ls -als /etc/rancher/k3s/registries.yaml > /dev/null 2>&1 || sudo mkdir /etc/rancher/k3s/
                  sudo echo > /etc/rancher/k3s/registries.yaml
                  sudo cat <<EOT >> /etc/rancher/k3s/registries.yaml
                  mirrors:
                    docker.io:
                      endpoint:
                        - "http://hub-k3s.io"
                    gcr.io:
                      endpoint:
                        - "http://gcr-k3s.io"
                    k8s.gcr.io:
                      endpoint:
                        - "http://k8s-gcr-k3s.io"
                  EOT
          
          - hosts: k3s-master
            remote_user: ep
            become: yes
            tasks:
              - name: restart server
                shell: sudo systemctl restart k3s
          
          - hosts: k3s-workers
            remote_user: ep
            become: yes
            tasks:
              - name: restart workers
                shell: sudo systemctl restart k3s-agent
          
          

          For Docker

          普通的Docker服务直接修改 /etc/docker/daemon.json 加上下面的配置。因为Docker 默认的修改的只有 Dockerio 的地址。

          "registry-mirrors": ["http://hub-k3s.io"],
          "insecure-registries" : ["http://hub-k3s.io"]
          

          对于其他的镜像仓库的域名。需要使用自定义的域名来代替代理前的域名。

          image-20231112184322318

          一步步的来,在体系树上一片片叶子的补全

          🔲 ☆

          从优雅地查看K8s应用日志聊到日志管理

          曾不知在哪听过一经典名句:程序员的工作只有两件事,一是写 Bug,二是找 Bug。

          说归说笑归笑,奈何话糙理不糙。而在真正排查 Bug 时,才深刻体会到另一名句:不写日志一时爽,排查 Bug 火Z场。

          日志管理,一直是开发人员的老大难题。这个老大难题,大致分为几块内容:

          1. 打印日志

          狭义上的日志管理,也即打印日志。套用 3W1H 分析方法可以分为几个子问题:

          1. Why 为什么要打日志
            显而易见,日志是记录关键信息和数据的地方,以备未来排查问题和数据统计分析之用。
          2. What 要打什么样的日志

          3. Where/When 在哪里/什么时候打日志

          4. How 怎么打日志

          2. 记录日志

          3. 查看日志

          🔲 ☆

          8. kubebuilder 进阶: webhook

          创建或者删除资源的时候需要对资源进行一些检查的操作,如果校验不成功就不通过。或者是需要在完成实际的创建之前做一些其他操作,这些需求如何实现?
          🔲 ☆

          4. kustomize 简明教程

          makefile 当中大量存在了 kustomize 这样的命令,kustomizeb是什么,有什么用,怎么用?今天我们就一起来学习一下,在后续的文章当中,我们还会用到一些 kustomize 特性来部署不同的环境。
          🔲 ☆

          CKAD认证备考经验分享

          前言

          最近通过了CKAD认证考试,也算是填了一个去年底挖的坑。这一切要源于去年底圣诞的时候,Linux Fundation的认证考试打折,原价300刀的CKAD考试,打折下来只用花255刀,忍不住剁手了。

          https://xiaozhou.net/pics/ckad/1.png

          此认证费用,包括一年有效期内任意时间预约考试的机会,以及一次免费重考的机会。

          本来想着有一年的时间备考和准备,买了之后拖延症又犯了,就一直没管它。直到最近,突然收到了Linux Fundation的邮件,提醒认证考试年底就要过期了,才想起来之前竟然还买了个这个认证,突然开始慌起来……

          二话不说,立马着手开始备考,由于之前工作中也算用过Kubernetes,对其核心概念也有一个大致了解,从8月到现在,大概花了一个多月的时间来准备。最后,准备总算没白费,通过了这个认证考试。

          https://xiaozhou.net/pics/ckad/3.png

          本篇blog就介绍和分享一下CKAD认证备考的一些经验。

          什么是CKAD考试

          总的来说,CKA和CKAD是CNCF和Linux基金会联合推出的两个Kubernetes考试认证:

          • CKA: Kubernetes管理员认证(CKA)旨在确保认证持有者具备履行Kubernetes管理员职责的技能,知识和能力。 CKA认证可帮助经过认证的管理员在就业市场中快速建立自己的信誉和价值,并能帮助公司更快地雇用高质量的团队来支持他们的发展。

          • CKAD: Kubernetes应用程序开发人员认证(CKAD)旨在确保CKAD具备履行Kubernetes应用程序开发人员职责的技能,知识和能力。 经过认证的Kubernetes Application Developer可以定义应用程序资源并使用核心原语来构建,监视和排除Kubernetes中可伸缩应用程序和工具的故障。

          就两种考试的定位而言,CKA更偏运维一些,CKAD更面向开发人员一些,所以我选择了CKAD认证。

          考试内容与范围

          CKA和CKAD的考试范围和比重,是直接公布在认证官网的。CKAD的考试范围和比重如下:

          https://xiaozhou.net/pics/ckad/2.png

          跟CKA的考试时间不一样,相比CKA的3小时时间,CKAD只有2小时。CKAD考试题目总共19道题,总分100分,66以上就算是通过了认证。

          备考阶段

          虽然之前工作中也有用到Kubernetes,为了让知识点覆盖更全面,我还订阅了KodeKloud的两个课程:

          他们家的课程是订阅制的,最近疫情期间也打折,所以学完之后可以取消订阅。两个课程都带了在线的动手实验室,学过一个知识点过后,就可以立马去动手实验室操作,用以对知识的巩固加深,还不错哦!

          最近还发现他们也把这个课程放到了Udemy,可以一次性购买: https://www.udemy.com/course/certified-kubernetes-application-developer/ 相对于订阅更划算一些。

          和其他的一些认证考试不一样的地方在于:CKA和CKAD非常注重动手操作。考试题目并不是常规考试的判断题,选择题,问答题。所谓考试实际上就是在他们官方提供的Kubernetes环境中进行实际操作。所以备考的时候,需要对 kubectl 的一系列命令了如指掌。总的来说,多动手操作实验才是通过这门认证的最佳途径。

          在GitHub上,有人为这个考试专门准备了一个动手实验题库: https://github.com/dgkanatsios/CKAD-exercises

          在考试之前,我大概把里面每一个题目动手操作了5-6遍。最后要达到的目标,就是看到题目之后,能想到应该如何在Kubernetes上操作并实现题目要求,能做到了然于心才是最佳状态。

          关于实验环境,推荐在本机安装运行 minikube 或者 k3s ,他们都是轻量级的Kubernetes实现,用来动手操作实验题目还是不错的。如果你实在懒得搭建本机环境,用网上现成的也有:

          在操作一些比较复杂题目的时候,你甚至可以用上面那个”Play with Kubernetes”提供的免费资源,自己搭建一个Kubernetes集群进行演练。

          考试预约与考前准备

          在完成备考过后,可以真正的预约考试了。听说最近Linux Fundation还专门为中国准备了国内现场考点,国内考试可以去专门的考点上机操作完成。另外的方式就是在家或者在一个安静的地方在线参加考试。

          对于在线考试,有考前检查和一系列的规定,可以去官网逐一阅读一下。大致说来,规定如下:

          • 考试形式: 在线监控,需要共享桌面和摄像头。如果你的电脑外接了显示器,两个屏幕都得共享。另外,考试中只允许你的浏览器开两个窗口,一个是考试的界面,另外一个就是Kubernetes的官方文档界面。在考试中,遇到不会的配置项,是允许你去官方文档中查询的。

          • 考试环境: 在一个密闭空间,例如书房、卧室、会议室等,电脑屏幕不能对着窗户,房间里除了考生不能存在第二个人,考试的桌面不能放其它东西,水杯也不行(透明的玻璃杯是可以的)。

          • 考试时间及题目: CKA-3小时-24道题、CKAD-2小时-19道题,均为动手操作题。

          • 选择考试时间: 由于监考官在美国,所以考试的时候别忘了选择一个跟你所在的时区最匹配的时间。我选择在了我所在时区的周五晚上9:30,考完大概晚上11:30。

          • 电脑要求: Windows的电脑和Mac OS的电脑都可以。在考试前可以在这里 WebDelivery Compatibility Check 对你的电脑进行兼容性检测。

          考试过程

          • 关于时间管理
            前面有介绍,CKAD的题目是19道,时间2小时,平均一道题能花的时间是6分钟左右,所以时间管理是非常重要的。19道题目的难度不一,有的简单有的复杂。对于一道题目,先看题,如果觉得没头绪,可以先标记这道题,直接跳过去做后面的题目。等19道题大致做过一遍之后,再回来看标记过的不会的题目。

          • 题目权重与优先级
            每一道考试题目上会标注这道题目所占的分数比重,总分100分的题目,达到66分可以通过考试。所以,结合与时间管理的策略,整个考试可以并不按照题目的顺序来做题。我就是在考试开始的时候,直接先快速把19道题过一遍,把分值权重较高的题目先标记出来,优先做这些分值较高的题目,然后再做剩余的题目。

          • 注意考试场景的切换
            所有的19道题目并不是在一个Kubernetes环境中设立的,这就涉及到需要在做题之前,先切换到对应Kubernetes的Context。每道题目前都有对Context进行说明和切换的要求,在做题前特别留意一下,确保是在正确的Kubernetes环境中操作即可

          • 考试界面语言的选择
            由于是一个针对全世界开发者的考试,官方的考试界面也提供了多语言支持。为了避免翻译的偏差对题目的影响,我还是没选中文,选了英文。

          • 监考官的互动
            考试的整个过程,除了共享你所有的桌面,还得开摄像头,也就是你只能被监考官通过视频进行监督,而你是看不到监考官长啥样的。整个过程与监考官的互动,是在一个Web弹出的聊天窗口中进行的。包括考前注意事项说明,以及考试过程中遇到问题,都可以通过这个聊天窗口跟考官互动。考官除了回答你跟考试过程相关的问题,还会在考试过程中进行随机抽查,比如要求你在考试过程中,把双手或者桌面通过摄像头给他看看,确保你没有作弊……

          一些考试中争分夺秒的技巧

          • kubectl 设置 alias
            考试的整个过程都是在Kubernetes中进行操作,所以 kubectl 这个命令输入的频率那是相当高的。反复输入这么长的命令,实在是有点浪费时间,要知道在这2小时的考试过程中,时间就是一切。所以在开始考试之初,我就在考试环境中为 kubectl 设置了alias。比如 alias kc=kubectl 或者 alias k=kubectl 。后面所有输入 kubectl 的地方,都可以用 kc 或者 k 替代,能为你节省不少时间。

          • 熟练操作kubectl命令
            kubectl 能操作和创建的资源有很多,在考试过程中,能不用YAML来创建资源就尽量不用。因为编辑YAML是比较花时间的,还容易出错。一般做法是用kubectl命令创建资源,通过dry-run的方式,先生成YAML文件模版,再根据题目对这个YAML进行改动。这里有一份 kubectl Cheat Sheet 你当然不能错过。

          • 熟悉各种资源的简化名称
            Kubernetes本身也有提供一些简化的资源名称,比如namespace 可以简化为 nsdeployment 可以简化为 deploypod 可以简化为 po。了解这些简化的资源名称,也能为你省掉不少的时间,在备考的过程中,可以记忆下来这些简化的命令。Kubernetes的官方文档,列出了所有资源的简化名称,可以参考: https://kubernetes.io/docs/reference/kubectl/overview/#resource-types

          考试结果

          由于我的网络问题,在考试中竟然断网了两三次 (具体表现就是终端卡住不动了,无法输入任何命令),不得不重新连接进入考试界面,浪费掉了一些时间。当时的心情,真有种万念俱灰的感觉。所以,考前最好再检查一下你的网络环境。最后一道题本来打算做,临交卷还有几分钟的时候,又卡住了。我索性放弃了,直接交卷……

          考试结束之后,官方保证会在36小时之后出考试结果,结果会Email到你考试时候注册的邮箱。虽然考试过程中掉线了两三次,不过还是比较幸运,周五晚上的考试,周日就有了结果,通过了。我在想,要是不断网,再给我多来几分钟,我应该能上90分吧……

          https://xiaozhou.net/pics/ckad/4.png

          希望此篇blog对备考CKAD的同学有所帮助。

          🔲 ⭐

          了不起的 Istio

          很多企业都会面临从单体应用向微服务架构的转型,也会衍生出更多的分布式场景需求。随着规模和复杂度的不断增长,如何才能更好的理解、高效的管理服务网格呢?

          本节篇幅较长,我们主要围绕以下几点来展开:
          1.什么是服务网格?
          2.初识 Istio
          3.核心特性
          4.流程架构
          5.核心模块
          6.Envoy 进阶
          7.方案畅想

          对许多公司来说,DockerKubernetes 这样的工具已经解决了部署问题,或者说几乎解决了。但他们还没有解决运行时的问题,这就是服务网格(Service Mesh)的由来。

          一、什么是服务网格?

          服务网格(Service Mesh)用来描述组成这些应用程序的微服务网络以及它们之间的交互。它是一个用于保证服务间安全、快速、可靠通信的网络代理组件,是随着微服务和云原生应用兴起而诞生的基础设施层。

          它通常以轻量级网络代理的方式同应用部署在一起。比如 Sidecar 方式,如下图所示:

          Service Mesh

          我们对上图做个解释:
          Service Mesh 设计一般划分为两个模块,控制面数据面。对于应用来说,所有流量都会经过数据面进行转发。顺利转发的前提:数据面需要知道转发的目标地址,目标地址本身是由一些业务逻辑来决定的(例如服务发现)。

          所以自然而然地,我们可以推断控制面需要负责管理数据面能正常运行所需要的一些配置:

          • 需要知道某次请求转发去哪里:服务发现配置;
          • 外部流量进入需要判断是否已经达到服务流量上限:限流配置;
          • 依赖服务返回错误时,需要能够执行相应的熔断逻辑:熔断配置;

          Serivce Mesh 可以看作是一个位于 TCP/IP 之上的网络模型,抽象了服务间可靠通信的机制。但与 TCP 不同,它是面向应用的,为应用提供了统一的可视化和控制。

          1.Service Mesh 具有如下优点:

          • 屏蔽分布式系统通信的复杂性(负载均衡、服务发现、认证授权、监控追踪、流量控制等等),服务只用关注业务逻辑;
          • 真正的语言无关,服务可以用任何语言编写,只需和 Service Mesh 通信即可;
          • 对应用透明,Service Mesh 组件可以单独升级;

          2.Service Mesh 目前也面临一些挑战:

          • Service Mesh 组件以代理模式计算并转发请求,一定程度上会降低通信系统性能,并增加系统资源开销;
          • Service Mesh 组件接管了网络流量,因此服务的整体稳定性依赖于 Service Mesh,同时额外引入的大量 Service Mesh 服务实例的运维和管理也是一个挑战;

          随着服务网格的规模和复杂性不断的增长,它将会变得越来越难以理解和管理。

          Service Mesh 的需求包括服务发现、负载均衡、故障恢复、度量和监控等。Service Mesh 通常还有更复杂的运维需求,比如 A/B 测试、金丝雀发布、速率限制、访问控制和端到端认证。

          Service Mesh的出现,弥补了 Kubernetes 在微服务的连接、管理和监控方面的短板,为 Kubernetes 提供更好的应用和服务管理。因此,Service Mesh 的代表 Istio 一经推出,就被认为是可以和 Kubernetes 形成双剑合璧效果的微服务管理的利器,受到了业界的推崇。

          Istio 提供了对整个服务网格的行为洞察和操作控制的能力,以及一个完整的满足微服务应用各种需求的解决方案。Istio 主要采用一种一致的方式来保护、连接和监控微服务,降低了管理微服务部署的复杂性。

          二、初识 Istio

          Istio 发音「意丝帝欧」,重音在上。官方给出的 Istio 的总结,简单明了:

          1
          Istio lets you connect, secure, control, and observe services.

          连接、安全、控制和观测服务。

          初识 Istio

          简单来说,Istio 针对现有的服务网格,提供一种简单的方式将连接、安全、控制和观测的模块,与应用程序或服务隔离开来,从而开发人员可以将更多的精力放在核心的业务逻辑上,以下是 Istio 的核心功能:

          1.HTTPgRPCWebSocketTCP 流量的自动负载均衡;
          2.通过丰富的路由规则、重试、故障转移和故障注入,可以对流量行为进行细粒度控制;
          3.可插入的策略层和配置 API,支持访问控制、速率限制和配额;
          4.对出入集群入口和出口中所有流量的自动度量指标、日志记录和追踪;
          5.通过强大的基于身份的验证和授权,在集群中实现安全的服务间通信;

          从较高的层面来说,Istio 有助于降低这些部署的复杂性,并减轻开发团队的压力。它是一个完全开源的服务网格,作为透明的一层接入到现有的分布式应用程序里。它也是一个平台,拥有可以集成任何日志、遥测和策略系统的 API 接口。

          Istio 多样化的特性使我们能够成功且高效地运行分布式微服务架构,并提供保护、连接和监控微服务的统一方法。

          三、核心特性

          Istio 以统一的方式提供了许多跨服务网格的关键功能:
          官方介绍

          1.流量管理
          Istio 简单的规则配置和流量路由允许我们控制服务之间的流量和 API 调用过程。Istio 简化了服务级属性(如熔断器、超时和重试)的配置,并且让它轻而易举的执行重要的任务(如 A/B 测试、金丝雀发布和按流量百分比划分的分阶段发布)。

          有了更好的对流量的可视性和开箱即用的故障恢复特性,我们就可以在问题产生之前捕获它们,无论面对什么情况都可以使调用更可靠,网络更健壮。

          2.安全

          Istio 的安全特性解放了开发人员,使其只需要专注于应用程序级别的安全。

          Istio 提供了底层的安全通信通道,并为大规模的服务通信管理认证、授权和加密。有了 Istio,服务通信在默认情况下就是受保护的,可以在跨不同协议和运行时的情况下实施一致的策略,而所有这些都只需要很少甚至不需要修改应用程序。

          Istio 是独立于平台的,可以与 Kubernetes(或基础设施)的网络策略一起使用。但它更强大,能够在网络和应用层面保护 PodPod 或者服务到服务之间的通信。

          3.可观察性
          Istio 健壮的追踪、监控和日志特性让我们能够深入的了解服务网格部署。通过 Istio 的监控能力,可以真正的了解到服务的性能是如何影响上游和下游的。而它的定制 Dashboard 提供了对所有服务性能的可视化能力,并让我们看到它如何影响其他进程。

          IstioMixer 组件负责策略控制遥测数据收集。它提供了后端抽象和中介,将一部分 Istio与后端的基础设施实现细节隔离开来,并为运维人员提供了对网格与后端基础实施之间交互的细粒度控制。

          所有这些特性都使我们能够更有效地设置、监控和加强服务的 SLO。当然,底线是我们可以快速有效地检测到并修复出现的问题。

          4.平台支持
          Istio 独立于平台,被设计为可以在各种环境中运行,包括跨云、内部环境、KubernetesMesos 等等。我们可以在 Kubernetes 或是装有 ConsulNomad 环境上部署 Istio

          Istio 目前支持:

          • Kubernetes 上的服务部署
          • 基于 Consul 的服务注册
          • 服务运行在独立的虚拟机上

          5.整合和定制
          Istio 的策略实施组件可以扩展和定制,与现有的 ACL、日志、监控、配额、审查等解决方案集成。

          四、流程架构

          Istio 服务网格逻辑上分为数据平面Control Plane)和控制平面Data Plane),架构图如下所示:

          Istio 架构图

          1.数据平面Data Plane由一组以 Sidecar 方式部署的智能代理 Envoy 组成。

          Envoy 被部署为 Sidecar,和对应服务在同一个 Kubernetes pod 中。这允许 Istio 将大量关于流量行为的信号作为属性提取出来,而这些属性又可以在 Mixer 中用于执行策略决策,并发送给监控系统,以提供整个网格行为的信息。

          这些代理可以调节和控制微服务及 Mixer 之间所有的网络通信。

          2.控制平面Control Plane负责管理和配置代理来路由流量,此外配置 Mixer 以实施策略和收集遥测数据。主要包含如下几部分内容:

          • Mixer:策略和请求追踪;
          • Pilot:提供服务发现功能,为智能路由(例如 A/B 测试、金丝雀部署等)和弹性(超时、重试、熔断器等)提供流量管理功能;
          • Citadel:分发 TLS 证书到智能代理;
          • Sidecar injector:可以允许向应用中无侵入的添加功能,避免为了满足第三方需求而添加额外的代码;

          五、核心模块

          上文提到了很多技术名词,我们需要重点解释一下:

          1.什么是 Sidecar 模式?
          Sidecar 是一种将应用功能从应用本身剥离出来作为单独进程的设计模式,可以允许向应用中无侵入的添加功能,避免为了满足第三方需求而添加额外的代码。

          在软件架构中,Sidecar 附加到主应用,或者叫父应用上,以扩展、增强功能特性,同时 Sidecar 与主应用是松耦合的。

          Sidecar 是一种单节点多容器的应用设计形式,主张以额外的容器来扩展或增强主容器。

          2.Envoy 的作用是什么?
          Envoy 是一个独立的进程,旨在与每个应用程序服务器一起运行。所有 Envoy 组成了一个透明的通信网格,其中每个应用程序发送和接收来自本地主机的消息,并且不需要知道网络拓扑。

          与传统的服务通信服务的库方法相比,进程外架构有两个实质性好处:

          • Envoy 支持任何编程语言写的服务。只用部署一个 Envoy 就可以在 JavaC++GoPHPPython 等服务间形成网格。
          • 任何使用过大型面向服务的体系结构的人都知道,部署库升级可能会非常痛苦。Envoy 可以在整个基础设施中迅速部署和升级。

          Envoy 以透明的方式弥合了面向服务的体系结构使用多个应用程序框架和语言的情况。

          3.Mixer
          Mixer 是一个独立于平台的组件,负责在服务网格上执行访问控制使用策略,并从 Envoy 代理和其他服务收集遥测数据,代理提取请求级属性,发送到 Mixer 进行评估。有关属性提取和策略评估的更多信息,请参见 Mixer 配置。

          Mixer 中包括一个灵活的插件模型,使其能够接入到各种主机环境和基础设施后端,从这些细节中抽象出 Envoy 代理和 Istio 管理的服务。

          4.Pilot
          控制面中负责流量管理的组件为 Pilot,它为 Envoy Sidecar 提供服务发现功能,为智能路由(例如 A/B 测试、金丝雀部署等)和弹性(超时、重试、熔断器等)提供流量管理功能。它将控制流量行为的高级路由规则转换为特定于 Envoy 的配置,并在运行时将它们传播到 Sidecar

          5.Istio 如何保证服务通信的安全?

          • Istio 以可扩缩的方式管理微服务间通信的身份验证、授权和加密Istio 提供基础的安全通信渠道,使开发者可以专注于应用层级的安全。

          • Istio 可以增强微服务及其通信(包括服务到服务和最终用户到服务的通信)的安全性,且不需要更改服务代码。

            它为每个服务提供基于角色的强大身份机制,以实现跨集群、跨云端的互操作性。

          • 如果我们结合使用 IstioKubernetes(或基础架构)网络政策,PodPod 或服务到服务的通信在网络层和应用层都将安全无虞。IstioGoogle深度防御策略为基础构建而成,以确保微服务通信的安全。

            当我们在 Google Cloud 中使用 Istio 时,Google 的基础架构可让我们构建真正安全的应用部署。

          • Istio 可确保服务通信在默认情况下是安全的,并且我们可以跨不同协议和运行时一致地实施安全政策,而只需对应用稍作调整,甚至无需调整。

          六、Envoy 进阶

          Istio 使用 Envoy 代理的扩展版本,Envoy 是以 C++ 开发的高性能代理,用于调解服务网格中所有服务的所有入站和出站流量

          Envoy 的许多内置功能被 Istio 发扬光大,例如:

          • 动态服务发现
          • 负载均衡
          • TLS 终止
          • HTTP2 & gRPC 代理
          • 熔断器
          • 健康检查、基于百分比流量拆分的灰度发布
          • 故障注入
          • 丰富的度量指标

          Envoy 分为主线程、工作线程、文件刷新线程,其中主线程就是负责工作线程和文件刷新线程的管理和调度。而工作线程主要负责监听、过滤和转发,工作线程里面会包含一个监听器,如果收到一个请求之后会通过过滤链来进行数据过滤。前面两个都是非阻塞的,唯一一个阻塞的是这种 IO 操作的,会不断地把内存里面一些缓存进行落盘。

          总结来说,我们可以围绕如下 5 方面:

          1.服务的动态注册和发现

          Envoy 可以选择使用一组分层的动态配置 API 来进行集中管理。

          这些层为 Envoy 提供了动态更新,后端群集的主机、后端群集本身、HTTP 路由、侦听套接字和通信加密。为了实现更简单的部署,后端主机发现可以通过 DNS 解析 (甚至完全跳过) 完成,层也可以替换为静态配置文件。

          2.健康检查
          构建 Envoy 网格的建议方法是将服务发现视为最终一致的过程。 Envoy 包括一个运行状况检查子系统,该子系统可以选择对上游服务集群执行主动运行状况检查。

          然后,Envoy 使用服务发现和运行状况检查信息的联合来确定健康的负载均衡服务器。Envoy 还支持通过异常检测子系统进行被动运行状况检查。

          3.高级负载均衡

          分布式系统中不同组件之间的负载平衡是一个复杂的问题。

          由于 Envoy 是一个独立的代理而不是库,因此它能够在一个位置实现高级负载平衡技术,并使任何应用程序都可以访问。

          目前 Envoy 包括支持自动重试、断路、通过外部速率限制服务限制全局速率、请求隐藏和异常值检测。未来计划为 Request Racing 提供支持。

          4.前端/边缘系统代理支持
          虽然 Envoy 主要是为服务通信系统而设计的,但对前端/边缘系统也是很有用的,如:可观测性、管理、相同的服务发现和负载平衡算法等。

          Envoy 包含足够的功能,使其可用作大多数 Web 应用服务用例的边缘代理。这包括作为 TLS 的终点、HTTP/1.1HTTP/2 支持, 以及 HTTP L7 路由。

          5.最好的观察统计能力
          Envoy 的首要目标是使网络透明。但是在网络级别和应用程序级都无法避免的容易出现问题。Envoy 包含了对所有子系统的强有力的统计支持。 statsd 和其他兼容的数据提供程序是当前支持的统计接收器,插入不同的统计接收器也并不困难。

          Envoy 可以通过管理端口查看统计信息,还支持通过第三方供应商进行分布式追踪。

          更多详情请参考:什么是 Envoy ?

          七、方案畅想

          应用上面的原理,我们可以有很多具体的方案应用于日常开发。

          1.方案一:应用 Istio 改造微服务
          模仿在线书店的一个分类,显示一本书的信息。 页面上会显示一本书的描述,书籍的细节(ISBN、页数等),以及关于这本书的一些评论。

          应用的端到端架构:Bookinfo 应用中的几个微服务是由不同的语言编写的。 这些服务对 Istio 并无依赖,但是构成了一个有代表性的服务网格的例子:它由多个服务、多个语言构成,并且 reviews 服务具有多个版本。

          Bookinfo 架构图

          Istio 改造后架构如下:要在 Istio 中运行这一应用,无需对应用自身做出任何改变。我们只需要把 Envoy Sidecar 注入到每个服务之中。最终的部署结果将如下图所示:

          Istio 改造后架构图

          所有的微服务都和 Envoy Sidecar 集成在一起,被集成服务所有的出入流量都被 Sidecar 所劫持,这样就为外部控制准备了所需的 Hook,然后就可以利用 Istio 控制平面为应用提供服务路由、遥测数据收集以及策略实施等功能。

          更多细节,请移步 官网示例

          2.方案二:用 Istio 改造 CI/CD 流程
          Istio 架构图

          对上述流程图简单解释一下:

          • 通过 Docker 对代码进行容器化处理;
          • 通过 Gitlab 托管代码;
          • Jenkins 监听 Gitlab 下的代码,触发自动构建,并执行 Kustomize 文件;
          • Kustomize 通过配置文件,设置了 Istio 的配置(染色识别、流量分发),并启动 K8s 部署应用;
          • 最终我们通过 Rancher 来对多容器进行界面化管理;
          • 打开浏览器进行访问;

          看到这里,相信你也了解了,我们实现了一个前端多容器化部署的案例。它有什么意义呢?

          • 首先,当然是环境隔离了,研发每人一个容器开发,互不干扰;
          • 其次,我们可以做很多小流量、灰度发布等事情;
          • 自动化部署,一站式的流程体验;

          如果你对容器化还不太了解,请先看看前面两篇文章:
          Docker 边学边用
          一文了解 Kubernetes

          Istio 还是有很多可圈可点的地方,相信看到这里你也有了更全面的认识。如果你想深入了解,不妨仔细研究官方示例,并且在实际项目中不断打磨。

          八、参考资料

          1.Istio 官网
          2.什么是 Envoy
          3.微服务之 Service Mesh
          4.什么是 Service Mesh
          5.Istio 如何连接、管理和保护微服务 2.0?
          6.在 MOSN 中玩转 dubbo-go

          ❌