Kubernetes Deployment 实战:部署、滚动更新与回滚的完整演示

把应用跑上集群:Deployment 的一堂实战课 上一篇搭好了 kind 三节点集群,但这只是"系统盘"——真正的 K8s 学习从把应用跑上去才刚开始。这篇文章用一次完整的实战演示:部署 3 副本 nginx、观察调度器怎么分配节点、扩容、滚动更新发新版、故意发布坏版本看集群卡死、最后回滚救回来。全程真实操作和真实输出,每个环节都解释"为什么是这样"。 📌 前置知识:建议先读过本系列前一篇(kind 搭建一主二从集群),至少知道控制面/工作节点/kubelet 是什么。这篇文章的操作都在那个集群上进行。 这次要做什么 目标:在 kind 一主二从集群上,跑通 Deployment 的完整生命周期 流程:部署 → 观察调度 → 查看对象层级 → 扩容 → 滚动更新 → 坏版本发布 → 回滚 收获:亲眼验证"期望状态/控制器循环/调度器/滚动更新"这些概念 前置条件与环境准备 项 本次实测 集群 kind learn ,1 控制面 + 2 工作节点,K8s v1.36.1 工具 kubectl v1.36.4 镜像 nginx:1.25 与 nginx:1.27(预载入节点) 关键一步:镜像怎么进节点 kind 集群里拉镜像有个容易忽略的坑:节点内部的 containerd 不走宿主机 Docker 的代理配置,直接从 Docker Hub 拉。国内网络直连大概率超时。所以先把镜像拉到宿主机(走代理),再一次性导入所有节点: # 宿主机拉镜像(Docker daemon 已配代理) docker pull nginx:1.25 docker pull nginx:1.27 # 导入集群所有节点(等价于"把镜像送进每个节点") kind load docker-image nginx:1.25 nginx:1.27 --name learn “加载进入每个节点"到底是什么意思? kind 的"容器即节点"是嵌套结构:节点是一个 Docker 容器,容器内跑着 containerd(节点自己的容器运行时)——kubelet 只认 containerd。于是镜像存储有两层、互不相通: ...

十一月 18, 2023 · 6 分钟 · 1152 字 · yaomingye

内网穿透实战:SSH 反向隧道让外网随时随地访问家里的服务器

一条隧道,把 NAT 后的服务器接到公网 家里有台 debian 服务器,跑着 mihomo、Docker、kind 集群,人在公司或外地的时候想连上去干活——但它在家庭局域网后面,没有公网 IP,外面根本摸不到它。某开发者的解法:让它自己主动"爬"出去,在唯一有公网 IP 的阿里云 ECS 上挂一个隧道入口。从此不管在哪,一条命令直达家里的服务器,而且安全到脚本小子无从下手。 这篇文章完整记录这个方案:先讲透原理(NAT 为什么挡人、反向隧道为什么能钻出去),再对比工具选型,然后给出全部配置过程和真实踩过的三个坑。 这次要做什么 目标:通过公网 ECS 中转,实现在任意网络 SSH 访问位于家庭 NAT 后的 debian 服务器 产出:ssh debian-lan 一条命令直达;局域网内原有直连不受影响 安全:至少挡住脚本小子——不暴露额外公网端口、隧道账号无 shell、全链路密钥认证 原理:NAT 挡住了什么,隧道就钻什么 第一步:理解 NAT 的"单向门" NAT(Network Address Translation,网络地址转换)让内网设备共享一个公网出口,但它是一扇单向门:内网设备主动出站,路由器放行并记住映射;公网侧想主动连进来,路由器没有对应记录,直接丢弃。 flowchart TD subgraph WAI["公网侧"] U["笔记本\n(任意网络)"] S["ECS 公网 IP"] end subgraph LAN["家庭局域网 (NAT 后)"] D["debian 服务器\n192.168.x.x"] end U -->|"① 出站可达"| S S -.->|"② 入站被 NAT 丢弃"| D D -->|"③ 出站可达"| S style S fill:#0f172a,stroke:#3b82f6,stroke-width:2.5px,color:#ffffff,font-weight:bold style U fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#ffffff style D fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#ffffff 图中 ② 那条虚线就是死路:别人永远无法主动找到你。但注意 ① 和 ③——出站永远是通的。这就是全部突破口。 ...

十一月 16, 2023 · 4 分钟 · 735 字 · yaomingye

PLG 可观测栈生产化:Prometheus/Loki/Grafana 上线前必须补的参数清单

PLG 能跑 ≠ 能生产,这 40 个参数决定它会不会炸 第 1 步:目标——别把"能跑"当成"能上线" 某开发者第一次搭 Prometheus + Loki + Grafana 的时候,docker-compose 一把梭,数据能出图、日志能搜到,觉得"这不就完了吗"。 直到有一天:磁盘写满、Loki 摄入速率爆了、Prometheus 查询超时、Grafana 裸奔在公网被扫——才意识到玩具和生产是两回事。 这篇把 PLG 生产化的参数和注意事项一次性讲透,分四块: 组件 生产化核心问题 Prometheus 数据存多久?查询会不会拖垮?挂了怎么办? Loki 日志会不会把磁盘写爆?摄入限额?标签会不会爆炸? Promtail 标签设计红线 + 采集可靠性 Grafana 认证、数据库、配置管理、备份 📌 定位:写给后端开发兼职运维的人——不追求架构极客,只求上线后别半夜被磁盘告警叫醒。 第 2 步:前置——先理解 PLG 各自的生产"命门" 四件套的生产风险完全不一样,先建立直觉: flowchart LR APP["应用/主机"] -->|"指标 scrape"| P["Prometheus时序数据库"] APP -->|"日志 push"| PT["Promtail日志采集"] PT -->|"HTTP push"| L["Loki日志存储"] P --> G["Grafana可视化"] L --> G classDef root fill:#0f172a,stroke:#3b82f6,stroke-width:2.5px,color:#bfdbfe,font-weight:bold; classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb; classDef data fill:#052e16,stroke:#16a34a,stroke-width:2px,color:#bbf7d0,font-weight:bold; class APP root; class P,L data; class PT,G process; 组件 生产命门 典型事故 Prometheus 内存(TSDB 缓存)、磁盘(时序数据)、查询并发 OOM、磁盘写满、查询拖垮 Loki 磁盘(日志量)、摄入速率、流数量(标签基数) 磁盘爆、摄入拒绝、流爆炸 Promtail 标签设计、位置文件、网络缓冲 流爆炸、重启重采日志 Grafana 认证、数据库、配置漂移 裸奔被入侵、配置丢失 第 3 步:Prometheus 生产参数——存储、查询、高可用 3.1 数据保留期(默认 15 天,必须显式设置) # docker-compose 启动参数 prometheus: command: - '--storage.tsdb.retention.time=30d' # 按时间保留(推荐) - '--storage.tsdb.retention.size=50GB' # 按大小保留(和上面二选一或都用) 为什么:默认 15 天,生产一般要 30 ~ 90 天。但保留期越长磁盘越大——50GB 磁盘 + 30 天保留,要提前算好每台机器的指标量(一般每 target 每小时几百 KB,几十个 target 一天约 1 ~ 2GB)。 ...

十一月 12, 2023 · 5 分钟 · 904 字 · yaomingye

微服务架构下 nginx 运维:后端开发如何借 AI 守住流量入口

nginx 不再是"服务器"了,它是你家微服务的门卫 第 1 步:目标——搞懂 nginx 在微服务里到底干嘛 先说个某开发者的真实转变。以前写单体应用,nginx 的活就是:把静态资源甩给浏览器,把动态请求转发给 Tomcat。配置嘛,抄一份改改就能跑。 后来上了微服务,Spring Cloud Gateway 成了统一入口,nginx 的位置就尴尬了——它既不用管静态资源,也不直接碰业务,但它卡在所有流量的最前面。 浏览器 ↓ nginx ← 我们这篇的主角:流量大门 ↓ Spring Cloud Gateway ← 路由/鉴权/限流 ↓ 微服务 A 微服务 B 微服务 C nginx 在这条链路上的职责变成了: 职责 说明 TLS 终结 HTTPS 证书在 nginx 上解掉,网关不用管证书 流量分发 把请求转发给 gateway(可能不止一个实例) 连接管理 复用客户端连接,减少网关压力 基础防护 隐藏版本号、限流、挡扫描器 日志入口 记录所有进站请求(网关日志不覆盖 nginx 这一层) 📌 关键认知:nginx 挂 = 整个系统挂。它前面没有别的挡箭牌,所以 nginx 的配置质量直接决定入口的稳定性。这也是为什么值得花一篇的篇幅讲清楚。 这篇的目标很实在:让你(或让 AI 代你)改 nginx 配置时,知道哪些是命门、哪些是锦上添花,别把入口配成瓶颈。 第 2 步:前置条件——先弄懂 nginx 的"体力"从哪来 动手配之前,必须理解 nginx 的工作模型。它跟 Tomcat 那种"一连接一线程"完全不同: ...

十一月 10, 2023 · 5 分钟 · 931 字 · yaomingye

给 1核2G 的阿里云 ECS 换上免费 HTTPS:从装证书到踩坑全记录

博客裸奔 HTTP 大半年,我给它上了个免费锁 第 1 步:目标——让博客地址栏出现小锁 事情是这样的。某开发者的博客在阿里云 1核2G 的小 ECS 上跑了大半年,一直用 http://yaocat.cloud 裸奔。也不是没想过上 HTTPS,但总觉得"麻烦"、“要花钱”、“反正没人看”。 直到有天朋友发来一个链接,浏览器地址栏赫然一个大红叉:“不安全”。虽然博客确实没什么人看,但顶着这个红叉自己心里也膈应。 查了一圈发现:HTTPS 现在完全免费,Let’s Encrypt 发的证书不要钱,还能自动续期。那还等什么,搞它。 目标拆一下: 事项 说明 ① 申请免费证书 Let’s Encrypt,用 acme.sh 工具 ② nginx 配置挂载 配置从容器里挪出来,重建不丢 ③ HTTPS 配置 443 端口 + HTTP 自动跳转 ④ 顺带优化 gzip 压缩 + 静态缓存 第 2 步:前置条件——这台机器长什么样 先交代一下环境: 项 值 服务器 阿里云 ECS,1核2G,Debian 11 (bullseye) 网站 Docker 容器跑 nginx:alpine,挂载 /var/www/blog 域名 yaocat.cloud,已解析到服务器公网 IP 端口 80 已开(HTTP 正常访问) 动手前先确认两件事能不能通: ...

十一月 8, 2023 · 4 分钟 · 681 字 · yaomingye

Prometheus 告警体系搭建:从 Alertmanager 到 AI ChatOps 一条龙

告警别只发通知,让 AI 替你干活 第 1 步:目标——从"看面板"到"手机响" 上一篇把 Prometheus + Grafana 搭起来,指标也采进来了。但有个问题:指标不会自己说话。 某开发者当时的状态是:白天盯着 Grafana 面板看 CPU 曲线,晚上睡觉心里发毛——万一凌晨 3 点服务挂了,谁叫我? 这篇文章的目标很直白: ❌ 旧世界:出问题 → 用户投诉 → 你爬起来开电脑 → SSH → 查日志 → 修复 ✅ 新世界:出问题 → 手机响 → 聊天框里说"查一下" → AI 排查完给你结论 具体拆成三个里程碑: 里程碑 内容 产出 ① 告警规则 Prometheus 检测 CPU/内存/磁盘异常 8 条可用的告警规则 ② Alertmanager 告警去重分组、统一出口 一个能收敛告警的中枢 ③ ChatOps 告警 → webhook → AI → Telegram 手机上收告警 + 动嘴指挥 📌 前置知识:假设你已经跑通了上一篇的 Prometheus + Grafana + node-exporter,知道 up 、 rate() 这些基本 PromQL。 ...

十一月 6, 2023 · 5 分钟 · 1062 字 · yaomingye

RocketMQ 实战避坑指南:从消息丢失到高可用,9大核心问题一网打尽

RocketMQ 避坑全攻略 0. 前言 0.1 为什么写这篇博客 消息中间件是分布式系统的必修课——这话没错,但很多团队引入 MQ 的时候只看到了"解耦"和"削峰填谷"的好处,却没意识到它同时带来了消息丢失、重复消费、积压、顺序错乱等一系列新问题。坦白说,踩过这些坑的开发者不在少数。 某开发者在生产环境第一次遇到 RocketMQ 积压十几万条消息的时候,第一反应是重启消费者——结果毫无悬念地失败了。后来花了一整天排查,问题竟然只是消费逻辑里多了一个 Thread.sleep(200) 。这种教训值得记下来。 0.2 读者需要的基础知识 用过 RocketMQ(至少本地跑过 Demo,知道 Producer / Consumer / Topic / Broker 是什么) 知道什么是生产者、消费者、Topic、Broker、NameServer 了解基本的分布式系统概念(如 CAP、最终一致性) 0.3 文章结构说明 按"问题 → 原因 → 原理 → 解决方案"的结构展开,每个问题独立成章。读者可以按需跳读,也可以从头串下来形成体系。 0.4 一句话总结 本文不是教"怎么用",而是教"怎么用好、怎么避坑"。 默认配置在生产环境就是定时炸弹。 1. 消息丢失 消息丢失是 MQ 使用中最致命的问题之一——订单丢了就是资损,通知丢了就是客诉。先按链路拆解一下丢消息的三个位置。 1.1 丢失场景分类 flowchart TD start([消息发送]) --> prod{生产端是否可靠?} prod -->|网络超时| prodLoss[生产端丢失] prod -->|异步未回调| prodLoss prod -->|重试耗尽| prodLoss prod -->|发送成功| broker[Broker 存储] broker --> persist{持久化策略?} persist -->|异步刷盘| brokerLoss[Broker 端丢失] persist -->|异步复制| brokerLoss persist -->|磁盘故障| brokerLoss persist -->|同步落盘| consume[消费端拉取] consume --> ack{ACK 策略?} ack -->|自动提交| consLoss[消费端丢失] ack -->|并发异常| consLoss ack -->|手动确认成功| done([消息可靠送达]) classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2.5px,color:#fce7f3,font-weight:bold; classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:2px,color:#ede9fe,font-weight:bold; classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb; classDef reject fill:#450a0a,stroke:#dc2626,stroke-width:2px,color:#fecaca,font-weight:bold; classDef data fill:#052e16,stroke:#16a34a,stroke-width:2px,color:#bbf7d0,font-weight:bold; class start,done startEnd; class prod,persist,ack condition; class prodLoss,brokerLoss,consLoss reject; class broker,consume process; 1.1.1 生产端丢失 场景 原因 网络超时 客户端以为发送成功,实际 Broker 未收到。网络抖动 + 超时配置不合理导致 异步发送未回调 producer.send(msg) 后直接 return,异常被吞掉 重试耗尽 RetryTimesWhenSendFailed 次数用完,消息被丢弃 1.1.2 Broker 端丢失 场景 原因 异步刷盘 消息写入 PageCache 后返回成功,但尚未落盘,断电即丢 主从异步复制 Master 宕机时 Slave 未同步到最新数据 磁盘故障 物理损坏导致已落盘数据不可恢复 1.1.3 消费端丢失 场景 原因 自动提交 Offset consumeMessageBatchMaxSize 拉了一批消息,自动 ACK 后业务处理失败 并发消费异常 多线程中某条消息处理失败,但整体无法回滚 1.2 原理深度剖析 1.2.1 存储架构 RocketMQ 的存储核心是 CommitLog(顺序写文件)+ ConsumeQueue(按 Topic+Queue 建索引)+ IndexFile(按 Key 查询)。所有消息先追加到 CommitLog,再异步构建 ConsumeQueue 索引。 ...

十一月 4, 2023 · 6 分钟 · 1239 字 · yaomingye

小程序支付后端从零构建:wx.pay 全流程、幂等实践与客户端定位

支付后端从 0 到 1:流程、幂等和那位备胎 提起微信支付,不少后端新同学的第一反应是"这不就是调个 API 嘛"。真上手才发现,光一个异步回调就能把人折腾到怀疑人生:明明用户付了钱,订单状态却一直不更新;回调来了两次,积分发了双份;个人主体小程序连商户号都申请不下来,只能对着文档干瞪眼。 这篇文章把 wx.pay 的后端流程从头到尾拆开:登录拿 openid、统一下单换 prepay_id、二次签名、异步回调验签与解密,再到怎么用乐观锁把幂等做扎实。最后用一点篇幅聊聊"备胎信使"理论——搞明白客户端在支付里到底说了不算什么,很多困惑会迎刃而解。 📌 前置知识:会写 Spring Boot 接口,看得懂 SQL,理解基本的 HTTP 与 JSON。不需要任何支付经验,本文的代码保证从空项目能直接搭起来。 第 1 步 目标说明:这一篇到底讲什么 1.1 为什么写这篇文章 支付是少数几个"看起来简单、出错要命"的领域。某开发者的第一版支付代码只有一百多行,跑起来却发现三个大坑: 客户端调起支付后立刻回调了 success,后端却还没收到微信的异步通知,订单一直挂在"待支付"; 通知重试机制下同一个回调被处理了两次,用户积分翻倍; 本地调得好好的,上线后微信的回调根本进不来——因为内网地址微信访问不了。 这三件事分别对应流程、幂等、回调三个话题,也是本文的主线。提前把这些想明白,能省下大把试错时间。 1.2 小程序开发的"三驾马车" 一个完整的小程序业务,后端主要跟三样东西打交道: 能力 前端 API 后端职责 类比 身份识别 wx.login 用 code 换 openid,建立用户账号 进门刷脸 交易闭环 wx.requestPayment 统一下单、签名、回调处理 柜台结账 消息触达 wx.requestSubscribeMessage + 服务端发送 存 access_token,发订阅消息 售后电话 三者独立又协作:登录建立身份,支付产生交易,订阅消息把交易结果送达用户。 1.3 本文核心议题 围绕上面三驾马车,重点回答四个问题: wx.pay 的完整后端流程是什么?预支付、二次签名、异步回调各是干什么的。 如何保证支付幂等,防止重复扣款、重复加积分? 客户端在支付中到底扮演什么角色?哪些事它说了不算? 个人开发者没有商户资质,怎么照样把后端逻辑练熟? 1.4 阅读本文的收获 读完你会得到一份可以直接抄的 Java 实现:登录接口、统一下单、二次签名、回调处理器,以及一套幂等的三层防御。还会得到一个重要认知:支付后端的核心逻辑与商户号无关,没资质也能先把逻辑写对,拿到商户号只是替换一个 API 地址的事。 ...

十月 23, 2023 · 15 分钟 · 3017 字 · yaomingye

把 React + Vite 项目搬到 Electron 去:改造、踩坑与调试

把 React 项目搬到 Electron,顺便让 PDF 导出不再闹心 某开发者手头有个 Vite + React + TypeScript 的简历编辑器项目,浏览器里跑得好好的,但每次要导出 PDF 都得先开新窗口、再唤出浏览器打印对话框、再手动取消「页眉和页脚」——这套操作重复多了真的会烦躁。于是决定把它改成 Electron 桌面应用。 这篇文章记录了改造全过程,顺便解决了国内下载 Electron 二进制的问题,以及几个常用的调试命令。 第 1 步:先确认你从什么起点出发 这次改造的对象是一个标准的 Vite + React + TypeScript 前端项目,没有用任何奇怪的自定义配置。如果你也是类似的项目结构,可以直接照着操作。 改造前后的技术栈对比: 改造前 改造后 Vite 8 开发服务器 Vite 8 + Electron 43 React 18 + TypeScript 不变 MUI v9 组件库 不变 window.print() 导出 PDF webContents.printToPDF() 原生导出 浏览器 localStorage 持久化 不变 + 原生「另存为」对话框 验证入口: 项目根目录应该有一个 package.json,内含 "scripts": { "dev": "vite" },项目本身能通过 npm run dev 正常启动。 ...

四月 5, 2023 · 5 分钟 · 974 字 · yaomingye

微服务文档重构:API 分组、BFF 聚合与文档拆迁

今天干了啥:分组、聚合、拆文档 今天没写啥牛逼的业务代码,大部分时间在跟文档和接口结构较劲。记个流水账。 一、发现 doc.html 不对劲 打开一个微服务的 Knife4j 文档页,下拉框里赫然列着七八个其他服务的分组。一点就 404,明摆着是隔壁服务跑这儿串门了。 第一反应是 knife4j 的配置问题,加了一堆 enableXxx: false ,重启——纹丝不动。后来发现 knife4j 基础 starter 压根没有跨服务聚合功能。真正的原因藏在 SpringDoc 的配置链路里,某些共享配置文件往 swagger-config 端点的 urls 字段里塞了外部的分组 URL。 最后在每个服务的本地 application.yml 把 springdoc 配置全写死,用本地覆盖干掉了注入。顺便把相关的配置说明写进系统设计文档了。 产出: 一篇踩坑博客 + 配置修复。 二、给接口文档分了三个层 之前所有微服务的接口都在一个组里,前端要看、后端也要看、微服务 Feign 调用也混在一起。这次给每个服务分了三个 Swagger 分组: 前端接口(给前端的) 后台接口(给管理后台的) 内部接口(给其他微服务调用的) 每个服务各自独立,不再相互干扰。 三个分组的接口在代码上也做了物理隔离——新建了 controller/internal/ 包,只给微服务间 Feign 调用用。顺便把原来跟前端接口冲突的方法也挪过去了,URL 统一加 /v1/internal/ 前缀,从根上避免路由冲突。 三、把前端需要的接口聚合到 BFF 层 之前前端写个页面经常要调好几个微服务,商品详情页调了 5 次接口。虽然 BFF 层之前就已经做了一些聚合(首页聚合、商品详情聚合、下单预览聚合),但管理后台这边基本还是透传状态。 给管理后台 BFF 加了两个聚合接口: 用户编辑页:一次查出用户信息 + 角色列表 + 部门树 + 岗位列表 商品编辑页:一次查出商品详情 + 分类树(品牌和单位的数据等后续补 Feign 客户端) 同时给两个 BFF 服务都加上了 Swagger 文档。以后前端重写,只看这两个 BFF 的文档就够了,不用再翻 8 个微服务的接口。 ...

三月 16, 2023 · 1 分钟 · 127 字 · yaomingye
Cat Radio