古人学问无遗力，少壮工夫老始成。 纸上得来终觉浅，绝知此事要躬行。
最近群里一直在讨论OpenClaw这个工具，说是AI编程助手，能帮写代码、改bug，听着挺玄乎的。我寻思着，这玩意儿真有那么神？反正闲着也是闲着，不如搞个服务器试试看。不过说实话，一想到要在服务器上装这一堆东西就头大。Node.js、npm、各种依赖……光是想想那些命令行敲来敲去就够呛。之前装个Jenkins都折腾了大半天，各种报错，各种查文档，最后好不容易弄好了，人都快累趴下了。那天正好看到有人推荐Wisdom SSH这个工具，说是集成了AI的SSH终端，能自动生成命令。我一开始还不信，心想这不就是换个壳的SSH客户端嘛，能有多大的区别。结果用了一次之后，真香。Wisdom SSH这玩意儿官网在wisdomheart.cn，下载安装挺简单的。装好之后打开，界面左边是服务器列表，右边有个AI对话区，就像咱们平时用的聊天窗口一样。连接服务器的方式跟普通SSH工具差不多，填个IP、用户名、密码就能连上。官网地址：https://www.wisdomheart.cn/我这次用的是一台debain13的服务器，配置不算高，2核4G，跑个OpenClaw应该够用。连上服务器之后，我就在AI对话区里敲了一句话：帮我安装OpenClaw。就这么简单的一句话。AI那边停顿了几秒，然后开始给我列步骤。它会先检测系统环境，看看有没有装Node.js，版本是多少，npm有没有，Python环境怎么样。这些要是自己手动查，得敲一堆命令，现在AI自动就帮你搞定了。检测完之后，AI告诉我这台服务器上Node.js版本太老，需要升级到18以上才能装OpenClaw。然后它直接给出了安装命令，我点一下"执行"，命令就自动在终端里跑了。curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs看着命令一行行跑过去，心里还挺踏实的。以前装这些东西，命令记不住，还得去百度，复制粘贴还怕弄错。现在好了，AI直接生成，我只需要确认执行就行。Node.js装好之后，AI又提示说需要安装pnpm，因为OpenClaw推荐用pnpm来管理依赖。这个我之前还真不知道，一直用的npm。AI说pnpm比npm快，而且节省磁盘空间，行吧，那就装。npm install -g pnpm装完pnpm，AI让我克隆OpenClaw的仓库。这个步骤我倒是挺熟悉的，git clone嘛，谁不会。不过AI生成的命令比我想的周全，它还加了个参数，指定克隆深度，这样下载速度快很多。git clone --depth 1 https://github.com/openclaw/openclaw.git克隆完之后进入目录，AI提示说要用pnpm安装依赖。我点执行，然后就开始了漫长的等待。依赖包挺多的，下了得有十几分钟。中间网络有点波动，我还以为要挂，结果pnpm自己重试了几次，最后还是装完了。依赖装好之后，AI说需要配置API密钥。这个是OpenClaw的核心，得有个大模型的API才能用。我之前在OpenAI注册过账号，还有点余额，就把API Key填进去了。配置文件在项目目录下的.env文件里，AI直接帮我打开编辑，我把Key粘贴进去，保存。然后就是启动了。AI给出的命令是：pnpm run dev敲回车，终端里开始刷日志。看着那些绿色的"success"字样，心里还是挺激动的。最后显示服务启动在localhost:3000端口，我本地浏览器访问服务器的IP:3000，果然看到了OpenClaw的界面。说实话，整个过程比我想象的顺利多了。要是以前，光是查各种依赖的安装方法就得折腾好久，还不一定能一次成功。这次用Wisdom SSH，基本上就是跟着AI的提示走，点几下执行按钮，就搞定了。不过中间也遇到点小插曲。启动的时候报了个错，说是端口被占用。我一看，原来服务器上之前跑了个测试服务，占用了3000端口。我正准备去查怎么杀进程，AI已经给出了命令：lsof -i:3000 kill -9 <PID>直接把占端口的进程干掉了。然后重新启动，一切正常。还有个问题是环境变量。OpenClaw需要设置一些环境变量，AI提示说最好写到.bashrc里，这样每次登录都会自动加载。它生成的命令是：echo 'export OPENCLAW_API_KEY=你的key' >> ~/.bashrc source ~/.bashrc这种细节要是自己弄，很容易忘，到时候服务跑起来找不到Key，又得排查半天。用下来感觉Wisdom SSH最大的优势就是省心。以前装东西，得自己查文档，看教程，还得注意版本兼容性，稍不留神就踩坑。现在AI帮你把这些问题都考虑到了，生成的命令也比较靠谱，至少我这次装OpenClaw没遇到什么大问题。当然，也不是说AI生成的命令就百分百正确。中间有个地方，AI建议我用nvm来管理Node.js版本，结果装的时候发现服务器上没有curl，nvm的安装脚本跑不起来。这种情况下，还是得自己判断一下，先装curl，再装nvm。AI虽然智能，但也不是万能的，有些特殊情况还是得靠人。不过Wisdom SSH有个好处，就是它生成的命令你可以先看看，觉得不对可以改，或者选择不执行。不像有些自动化脚本，一跑起来就停不了，出了问题都不知道在哪。装完OpenClaw之后，我简单试用了一下。确实挺好用的，能帮我写一些简单的函数，还能解释代码。不过这就是另一个话题了，今天主要想说的是Wisdom SSH这个工具。其实仔细想想，Wisdom SSH解决的是运维工作中一个很痛点的问题：命令太多，记不住。特别是那些不常用的命令，每次用都得查，查完还得验证，费时费力。有了AI辅助，至少能省掉查命令这个环节，效率提升还是挺明显的。而且Wisdom SSH不只是能装软件，日常的服务器管理也能用。比如查看系统状态、分析日志、配置防火墙，这些操作都可以通过AI对话来完成。我试了一下让它帮我分析nginx的访问日志，AI直接给出了统计命令，还把结果可视化了一下，看着挺直观的。对了，Wisdom SSH还有个文件传输的功能，也挺实用的。以前传文件得用scp或者sftp，命令一长串，路径还得写对。现在AI对话里直接说"把本地的xxx文件传到服务器的yyy目录"，它就帮你生成命令，确认一下就能传。虽然底层还是scp，但至少不用记那些参数了。说到这儿，想起之前有次传文件，把路径写反了，结果把服务器上的文件给覆盖了，还好有备份。要是当时有这种AI辅助，估计就不会犯这种低级错误了。不过话说回来，工具再好，还是得懂原理。AI能帮你生成命令，但你至少得知道这个命令是干什么的，会不会有什么副作用。完全依赖AI，万一哪天AI抽风了，给你生成了个rm -rf /，你也不看就执行，那就真完了。所以我觉得Wisdom SSH适合的人群，是那些已经有一定运维基础，但不想在查命令上浪费太多时间的人。如果你是纯小白，连cd、ls都不会，那还是先学学基础吧。AI是辅助工具，不是保姆。这次装OpenClaw的经历，让我对AI辅助运维有了新的认识。以前总觉得AI离我们很远，都是些高大上的东西。现在发现，AI已经能实实在在地帮我们解决工作中的问题了。虽然还不够完美，但至少是个好的开始。装完OpenClaw之后，我又用Wisdom SSH折腾了点别的，比如装了个Docker，配了个Nginx反向代理，都挺顺利的。感觉有了这个工具，以后服务器上装东西，应该不会再那么头疼了。对了，Wisdom SSH还支持保存命令模板，常用的操作可以存下来，下次直接用。这个功能对于重复性的运维工作特别有用，比如部署应用、备份数据库之类的。最后说一下，Wisdom SSH目前是免费的，官网上就能下载[2]。有兴趣的可以试试，反正不要钱，不好用卸了就是。不过我估计你用了之后，大概率会跟我一样，再也回不去那些传统的SSH客户端了。好了，今天的分享就到这儿。Wisdom SSH配合OpenClaw，确实能让运维工作轻松不少。如果你也经常跟服务器打交道，不妨试试这个组合，说不定会有惊喜。有什么问题欢迎在评论区留言，我看到会回复的。如果觉得这篇文章对你有帮助，点个赞再走呗，码字不易，需要鼓励。公众号：运维躬行录个人博客：躬行笔记觉得有用的话，转发给更多需要的朋友，让咱们运维人都能早点下班！
上周三凌晨两点，我被一个电话吵醒。电话那头是开发小哥，声音有点抖：「哥，线上缓存挂了，用户登录全部失败。」我脑子瞬间清醒了一半，爬起来开电脑一看，监控图上那条线直接躺平了。Memcached 集群某个节点内存爆了，触发淘汰策略，连带着把会话数据全给清了。那一晚我算是彻底明白了，缓存选型这事儿，真不是随便拍脑袋就能定的。今天就想跟大家聊聊 Redis 和 Memcached 这两个老家伙，到底有什么区别，实际生产里该怎么选。网上文章一大堆，但很多都是照本宣科，我尽量用实际踩过的坑来聊。先说数据结构这事儿Memcached 这东西，设计思路特别纯粹，就是 key-value 存储。你存什么，它就拿什么，不会问你太多问题。代码写起来也简单：import memcache mc = memcache.Client(['127.0.0.1:11211']) mc.set('user:1001', '{"name": "张三", "age": 28}') data = mc.get('user:1001')看着挺清爽对吧。但问题也在这儿，你想存个列表怎么办？想存个哈希怎么办？只能自己序列化，存成字符串。取出来的时候再反序列化。有一次我们做商品库存管理，每个商品有多个规格，每个规格又有不同的库存数量。用 Memcached 的话，我得把整个结构序列化存进去，每次修改某个规格库存，都得把整个对象取出来，改完再存回去。高并发的时候，这操作简直是灾难。后来换到 Redis 就舒服多了：import redis r = redis.Redis(host='127.0.0.1', port=6379) # 用 hash 结构存商品规格 r.hset('product:1001:stock', 'size:M', 100) r.hset('product:1001:stock', 'size:L', 50) r.hset('product:1001:stock', 'size:XL', 30) # 修改单个规格库存，不用动其他数据 r.hincrby('product:1001:stock', 'size:M', -1) # 直接拿某个规格 stock = r.hget('product:1001:stock', 'size:M')Redis 支持的数据结构多了去了，字符串、哈希、列表、集合、有序集合，还有位图、HyperLogLog 这些高级货。有时候想想，这玩意儿哪是缓存啊，简直就是个多功能工具箱。持久化，这个差别太大了Memcached 有个特点，数据全在内存里，重启就没了。听起来挺极端，但人家设计思路就是这样——缓存嘛，本来就可以丢。可实际生产里，有些数据你真不敢让它丢。比如用户会话、购物车、还有一些配置信息。有一次公司做活动，预热阶段把活动配置缓存在 Memcached 里，结果半夜机器重启，配置全没了，第二天活动直接开天窗。Redis 就不一样，它支持持久化。两种方式：RDB 快照：隔一段时间把内存数据快照到磁盘# redis.conf 配置 save 900 1 # 900 秒内至少 1 个 key 变化就保存 save 300 10 # 300 秒内至少 10 个 key 变化就保存 save 60 10000 # 60 秒内至少 10000 个 key 变化就保存AOF 日志：把每个写操作都记下来，重启时重放appendonly yes appendfsync everysec # 每秒同步一次，性能和安全性折中我一般建议生产环境两个都开着。RDB 做备份恢复快，AOF 保证数据不丢。虽然会牺牲点性能，但比起数据丢失的损失，这点性能真不算什么。当然也不是说 Memcached 就不能用。有些场景数据丢了就丢了，比如页面缓存、临时计算结果。这种用 Memcached 反而更合适，毕竟少了很多持久化的开销。内存管理，这里面的坑不少说到内存管理，这两个家伙的思路完全不一样。Memcached 用的是 slab 分配机制。它预先分配好不同大小的内存块，数据来了就找合适大小的块放进去。好处是内存碎片少，分配速度快。但问题也很明显——如果你存的数据大小参差不齐，内存浪费会比较严重。有次我们存用户信息，有的用户数据几百字节，有的几十字节。Memcached 按最大那块分配，结果内存利用率连 60% 都不到。监控看着内存还剩好多，实际已经存不进新数据了。Redis 的内存管理更灵活一些，它会根据数据实际大小动态分配。而且可以配置内存淘汰策略：# 内存满了之后怎么处理 maxmemory 2gb maxmemory-policy allkeys-lru # 淘汰最近最少使用的 key淘汰策略有好几种：volatile-lru：只淘汰有过期时间的 keyallkeys-lru：所有 key 里淘汰最近最少使用的volatile-ttl：淘汰即将过期的noeviction：不淘汰，写操作直接报错这个配置得根据业务来。我们有个场景是用户会话缓存，必须保证在线用户的数据不丢，就用了 volatile-lru，给会话设置过期时间，优先淘汰这些。性能对比，没那么简单网上很多文章说 Memcached 性能比 Redis 好，因为它是多线程的。这个说法在早期确实成立，但现在情况变了。Redis 6.0 之后引入了多线程 IO 处理，虽然命令执行还是单线程，但网络读写已经可以并行处理了。实际测试下来，在高并发场景下，Redis 的性能并不比 Memcached 差，很多时候还更好。我自己在测试环境做过对比，用 sysbench 压测：# Memcached 测试 memtier_benchmark --server=127.0.0.1:11211 --protocol=memcache \ --clients=50 --threads=4 --requests=100000 # Redis 测试 memtier_benchmark --server=127.0.0.1:6379 --protocol=redis \ --clients=50 --threads=4 --requests=100000结果挺有意思。简单 get/set 操作，两者差距不大，Memcached 稍微快一点点，大概 5% 左右。但一旦涉及复杂操作，比如哈希、列表，Redis 的优势就出来了。而且别忘了，性能不是唯一指标。有时候为了那 5% 的性能提升，牺牲掉数据结构、持久化这些功能，到底值不值，得好好算算账。集群和高可用，这个真不能忽视早期用 Memcached 的时候，集群是个头疼事儿。它本身不支持集群，得靠客户端做分片。# 客户端分片示例 servers = ['192.168.1.10:11211', '192.168.1.11:11211', '192.168.1.12:11211'] mc = memcache.Client(servers) # 客户端根据 key 的 hash 值决定存到哪台机器这种方式问题很多。某台机器挂了，那部分数据就访问不了了。扩容的时候，大量 key 需要重新分布，缓存基本等于失效。后来我们有个项目，Memcached 集群从 3 台扩到 5 台，缓存命中率直接从 95% 掉到 40%，数据库差点被打穿。Redis 的集群方案就成熟多了，原生支持分片和主从复制：# 创建 6 节点集群（3 主 3 从） redis-cli --cluster create \ 192.168.1.10:6379 192.168.1.11:6379 192.168.1.12:6379 \ 192.168.1.13:6379 192.168.1.14:6379 192.168.1.15:6379 \ --cluster-replicas 1主节点挂了，从节点自动顶上来。扩容的时候，数据可以在线迁移，业务基本无感知。虽然配置起来稍微麻烦点，但长远来看省心多了。实际选型，我的建议说了这么多，到底该怎么选？我总结了几条经验：选 Memcached 的场景：纯缓存，数据丢了无所谓只需要简单 key-value 存储追求极致性能，操作简单已有架构基于 Memcached，迁移成本高选 Redis 的场景：需要持久化，数据不能丢需要复杂数据结构需要事务、发布订阅这些高级功能需要成熟的集群方案未来业务可能扩展说实话，现在新项目我基本都直接上 Redis 了。除非是特别明确的简单缓存场景，否则 Redis 的灵活性真的香太多。而且社区活跃，文档齐全，遇到问题容易找到解决方案。有个事儿挺有意思，我们有个老系统用 Memcached 好多年，一直挺稳定。后来想加个排行榜功能，发现 Memcached 搞不定，最后还是加了个 Redis 专门做这个。结果现在两套缓存并存，运维成本反而上去了。早知道当初直接全用 Redis，也没这些麻烦事儿。一些实战配置建议最后给几个实际配置建议，都是踩过坑总结出来的：Redis 配置：# 内存限制一定要设，防止吃光内存 maxmemory 4gb maxmemory-policy allkeys-lru # 持久化根据业务需求 appendonly yes appendfsync everysec # 连接数调大点 maxclients 10000 # 慢查询日志，方便排查 slowlog-log-slower-than 10000 slowlog-max-len 128Memcached 配置：# 启动参数 memcached -m 4096 -c 10240 -t 4 -p 11211 # -m 内存大小（MB） # -c 最大连接数 # -t 线程数 # -p 端口监控一定要做好。我见过太多缓存出问题，都是因为监控没到位，等发现的时候已经晚了。至少要把内存使用率、命中率、连接数这些指标盯紧了。写到这儿差不多该收尾了。缓存这事儿说简单也简单，说复杂也复杂。关键还是得结合自己业务来，别盲目跟风，也别固守成规。有个心得分享给大家：技术选型没有绝对的对错，只有适不适合。有时候看似完美的方案，到了实际生产里可能各种问题。多测试，多观察，出了问题快速响应，比什么都重要。要是这篇文章对你有帮助，或者你也有缓存选型的踩坑经历，欢迎在评论区聊聊。大家互相学习，少走点弯路。公众号：运维躬行录个人博客：躬行笔记记得关注，后续会持续分享更多运维实战经验。转发给身边需要的同事，说不定哪天就能帮上忙。咱们下期见。
小白也能懂，手把手教你 Windows 10 部署 OpenClaw 并接入飞书，打造你的私人 AI 助理！
最近几个月，OpenClaw 这个开源项目真的火出圈了。身边好几个朋友都在问：“这玩意儿到底怎么装？能不能在飞书里用？”说实话，一开始我也被它的文档绕晕过——一会儿要配 Node.js，一会儿又是 Git、PowerShell 权限、飞书回调地址……但折腾几次之后，发现其实只要踩对坑，整个过程比想象中简单得多。今天我就把我在 Windows 10 上从零开始部署 OpenClaw，并成功接入飞书的全过程，毫无保留地写出来。不玩虚的，全是实操步骤，连“为什么卡在这一步”的坑我都给你标清楚。哪怕你是第一次接触命令行的小白，照着做也能跑起来。先说点实在的：你得准备啥？别急着开干，先看看你的电脑符不符合基本要求：操作系统：Windows 10 或 11（64位），我用的是 Win10 专业版 22H2内存：至少 4GB，建议 8GB 以上（OpenClaw 跑起来会吃点内存）磁盘空间：留出 2GB 以上空闲（C 盘别太满）网络：能正常访问 GitHub、Node.js 官网，最好有稳定的网络（国内用户可能需要耐心等下载）另外，你需要：一个 飞书开发者账号（个人免费注册就行）一个 大模型 API Key（比如豆包、Minimax、火山引擎等，国内可用），这里我用阿里百炼 首月只要7块钱第一步：装好基础环境（Node.js + Git）OpenClaw 是基于 Node.js 开发的，所以第一步必须装好它。1. 安装 Node.js打开 Node.js 官网 1，点击绿色的 “Windows Installer (.msi)” 下载安装包（推荐 LTS 版或最新版，目前我用的是 v24.13.0）。安装时最关键的一点：✅ 务必勾选 “Add to PATH” 和 “Automatically install the necessary tools”这两个选项决定了后面能不能顺利运行命令。其他一路默认下一步就行。装完后，打开 PowerShell（以管理员身份运行），输入：node -v npm -v如果看到类似 v24.13.0 和 10.5.0 的版本号，说明装好了。⚠️ 注意：有些老教程说用 Node.js 18 或 20，但实测 OpenClaw 最新版（2026.3）在 Node 24 上最稳，低于 22 可能直接报错。2. 安装 Git去 Git 官网 2 下载安装包。国内网速慢是常态，耐心等。一路下一步安装过程中，在 “Adjusting your PATH environment” 这一步，选择：Git from the command line and also from 3rd-party software这样 PowerShell 才能识别 git 命令。装完同样验证一下：git --version看到版本号就 OK。第二步：解除 PowerShell 脚本限制Windows 默认禁止执行远程脚本，所以我们得先放行。在 管理员权限的 PowerShell 中执行：Set-ExecutionPolicy RemoteSigned -Scope CurrentUser Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass系统会问你是否确认，输入 Y 回车就行。这一步做完，后面才能顺利运行安装脚本。第三步：安装 OpenClaw现在主角登场。iwr -useb https://openclaw.ai/install.ps1 | iex装完后验证：openclaw --version看到版本号（比如 2026.3.8）就说明成功了。第四步：首次配置（关键！选 QuickStart）接下来运行配置向导：openclaw onboard --install-daemon这里有几个选择，务必按我说的选，不然容易翻车：配置项配置内容I understand this is powerful and inherently risky. Continue?选择 ”Yes”Onboarding mode选择 “QuickStart”Model/auth provider选择 “Skip for now”，后续可以配置Filter models by provider选择 “All providers”Default model使用默认配置Select channel (QuickStart)选择 “Skip for now”，后续可以配置Configure skills now? (recommended)选择 “No”，后续可以配置。Enable hooks?按空格键选中，选择“Skip for now”，按回车键进入下一步。How do you want to hatch your bot?选择 “Hatch in TUI”。注册阿里百炼1、打开网址：https://bailian.console.aliyun.com/cn-beijing/#/home2、登录账号3、订阅基础套餐，首月7块开始配置龙虾复制以下内容到Raw JSONS输入框，替换已有内容。若需保留已有配置，请勿直接全量替换，详见已有配置如何安全修改？将YOUR_API_KEY替换为Coding Plan 专属 API Key。{ "models": { "mode": "merge", "providers": { "bailian": { "baseUrl": "https://coding.dashscope.aliyuncs.com/v1", "apiKey": "YOUR_API_KEY", "api": "openai-completions", "models": [ { "id": "qwen3.5-plus", "name": "qwen3.5-plus", "reasoning": false, "input": ["text", "image"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 1000000, "maxTokens": 65536 }, { "id": "qwen3-max-2026-01-23", "name": "qwen3-max-2026-01-23", "reasoning": false, "input": ["text"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 262144, "maxTokens": 65536 }, { "id": "qwen3-coder-next", "name": "qwen3-coder-next", "reasoning": false, "input": ["text"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 262144, "maxTokens": 65536 }, { "id": "qwen3-coder-plus", "name": "qwen3-coder-plus", "reasoning": false, "input": ["text"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 1000000, "maxTokens": 65536 }, { "id": "MiniMax-M2.5", "name": "MiniMax-M2.5", "reasoning": false, "input": ["text"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 196608, "maxTokens": 32768 }, { "id": "glm-5", "name": "glm-5", "reasoning": false, "input": ["text"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 202752, "maxTokens": 16384 }, { "id": "glm-4.7", "name": "glm-4.7", "reasoning": false, "input": ["text"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 202752, "maxTokens": 16384 }, { "id": "kimi-k2.5", "name": "kimi-k2.5", "reasoning": false, "input": ["text", "image"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 262144, "maxTokens": 32768 } ] } } }, "agents": { "defaults": { "model": { "primary": "bailian/qwen3.5-plus" }, "models": { "bailian/qwen3.5-plus": {}, "bailian/qwen3-max-2026-01-23": {}, "bailian/qwen3-coder-next": {}, "bailian/qwen3-coder-plus": {}, "bailian/MiniMax-M2.5": {}, "bailian/glm-5": {}, "bailian/glm-4.7": {}, "bailian/kimi-k2.5": {} } } }, "gateway": { "mode": "local" } }养虾成功切换模型在当前会话切换模型（临时有效）在终端输入openclaw tui，进入 TUI 界面，使用 /model <模型名称> 在当前会话中切换模型。/model qwen3-coder-next界面返回提示“model set to qwen3-coder-next” 即表示生效。切换默认模型（永久有效）如需在每次新会话中使用指定模型，修改 agents.defaults.model.primary 字段为目标模型。请参考修改配置文件。{ "agents": { "defaults": { "model": { "primary": "bailian/qwen3.5-plus" } } } }第五步：正式对接飞书（重点来了！）现在我们来手动接入飞书。1. 在飞书开放平台创建应用打开 飞书开放平台 3登录后，点击「创建企业自建应用」填写应用名称（比如 “我的 AI 助理”），上传个图标，确定权限管理 → 添加以下权限：im:messageim:resourcecontact:user.read3. 在 OpenClaw 中添加飞书通道回到 PowerShell，运行：openclaw configure输入飞书的AppID，AppSecrect：保存退出。然后重启 OpenClaw 服务（或者直接关掉 Web UI 再重新运行 openclaw onboard）。最后说两句OpenClaw 不是玩具，它真能帮你干活。我上周让它自动整理了三个月的会议纪要，省了我至少五个小时。而且所有数据都在你本地，不像某些云服务偷偷传数据。当然，它也有局限——比如需要一直开机、依赖大模型 API（可能有费用）、操作电脑有风险（所以它才让你确认“yes”）。但只要你理解这些，它就是你数字生活里的“贾维斯”。如果你照着这篇教程走通了，不妨试试给它装几个 Skill（比如邮件处理、日程管理），玩法多得很。觉得有用？别藏着掖着，转发给那个总喊“AI 太难搞”的同事，让他也体验一把什么叫“一句话搞定”。关注我，带你玩转更多硬核又实用的运维/AI 工具！公众号：运维躬行录个人博客：躬行笔记 4
手把手教你用 cephadm 在 Ubuntu 22.04 上部署生产级 Ceph 集群（Quincy/Reef 版本通用）
最近公司要上一套分布式存储，想要用 Ceph。说实话，我之前虽然接触过 Ceph，但都是测试环境玩玩，这次可是真刀真枪要上生产环境，压力山大。不过好在现在 Ceph 的部署工具已经成熟很多了，尤其是 cephadm 这套基于容器的部署方式，比早年手动编译、配置一堆服务省心太多了。今天这篇文章就把我从零开始搭建 Ceph 集群的过程完整记录下来，全程基于 Ubuntu 22.04 LTS，使用的是官方推荐的 cephadm 工具。无论是 Quincy（17.2.x）还是 Reef（18.2.x）版本，流程基本一致，你可以直接照着操作，部署完就能跑业务。一、先搞清楚你要搭什么在动手部署之前，先花一分钟搞清楚：Ceph 到底是个啥？简单说，Ceph 是一个开源的 软件定义存储（SDS）系统，它能把你一堆普通服务器的硬盘“揉”成一个超大、高可用、自动修复的统一存储池。最牛的是，它一套系统同时支持三种存储接口：块存储（RBD）：像云硬盘一样挂给虚拟机或容器用；文件存储（CephFS）：提供标准的 POSIX 文件系统，多个客户端可同时读写；对象存储（RGW）：兼容 AWS S3 和 OpenStack Swift，适合存图片、视频、备份等海量非结构化数据。这种“三合一”的设计，在业内非常少见。那有没有同类产品？有，比如：GlusterFS：也是开源分布式文件系统，主打文件存储，架构比 Ceph 简单，但扩展性和一致性稍弱，大规模场景下容易出现元数据瓶颈。MinIO / OpenIO：专注对象存储，轻量、易用，但不支持块和文件存储，功能单一。商业方案如 VMware vSAN、Dell PowerScale：功能强、服务好，但贵得离谱，还得绑定硬件。而 Ceph 的优势就很明显了：✅ 真正去中心化：没有元数据服务器单点瓶颈，靠 CRUSH 算法智能分布数据； ✅ 线性扩展：加机器就能扩容，性能和容量同步涨； ✅ 自愈能力强：硬盘坏了？节点宕了？Ceph 自己默默重建副本，业务无感； ✅ 完全开源免费：社区活跃，Red Hat、SUSE、Canonical 都深度集成，连 OpenStack 和 Kubernetes 原生都支持它。当然，Ceph 也不是没缺点——配置复杂、学习曲线陡、对网络要求高。但一旦跑稳了，那就是“省心又省钱”的代名词。尤其在私有云、边缘计算、AI 训练这些需要 EB 级存储的场景里，Ceph 几乎成了事实标准。所以，别被它吓住。只要方法对，部署其实没那么难。接下来，咱们就一步步把它跑起来。Ceph 不是随便几台机器就能跑起来的。它有几个核心角色：Monitor（mon）：负责维护集群状态，至少3个节点（奇数），不能挂。Manager（mgr）：提供监控、仪表盘等功能，一般和 mon 混部或单独部署2个。OSD（osd）：真正存数据的，每个节点挂一块或多块硬盘，越多越好。可选：MDS（用于 CephFS 文件系统）、RGW（对象存储网关）。我们这次的目标是：3 节点最小高可用集群，每台既是 mon/mgr，也跑 OSD，适合中小规模生产环境。硬件建议（别抠门）：CPU：每台至少 4 核内存：OSD 节点建议 8GB 起，最好 16GB+硬盘：每台至少 1 块 ≥1TB 的数据盘（SSD/HDD 都行，但别用系统盘）网络：千兆起步，万兆更佳，所有节点互通二、系统准备（所有节点都要做）1. 设置主机名和 hosts假设三台机器 IP 分别是：192.168.1.10 → ceph1192.168.1.11 → ceph2192.168.1.12 → ceph3在每台机器上执行（以 ceph1 为例）：sudo hostnamectl set-hostname ceph1然后编辑 /etc/hosts，三台机器都加上：192.168.1.10 ceph1 192.168.1.11 ceph2 192.168.1.12 ceph3⚠️ 注意：主机名必须和 hosts 里一致，否则后面 cephadm 添加 host 会报错。2. 关闭防火墙和 SELinuxUbuntu 默认没开 SELinux，但防火墙可能开着：sudo ufw disable检查一下：sudo ufw status # 应该显示 inactive3. 时间同步（非常重要！）Ceph 对时间敏感，不同步会出大问题：sudo apt update sudo apt install -y ntp sudo systemctl enable --now ntp验证：timedatectl # 看 NTP synchronized: yes4. 创建统一用户并配 sudo 免密虽然 cephadm 支持 root，但官方建议用普通用户。我们在三台机器都创建 cephadmin 用户：sudo useradd -m -s /bin/bash cephadmin echo "cephadmin ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/cephadmin再给这个用户设个密码（可选，主要是为了 ssh 登录）：sudo passwd cephadmin三、配置 SSH 免密登录（从管理节点到其他节点）我们把 ceph1 当作管理节点，需要能无密码登录 ceph2 和 ceph3。切换到 cephadmin 用户：su - cephadmin生成密钥（一路回车）：ssh-keygen -t rsa -N ""把公钥复制到其他两台机器：ssh-copy-id cephadmin@ceph2 ssh-copy-id cephadmin@ceph3测试：ssh ceph2 ssh ceph3 # 应该不用输密码直接进💡 如果提示主机名不匹配，可能是 DNS 或 hosts 没配好，回头检查。四、安装 Ceph 依赖（所有节点）回到 root 或用 sudo：sudo apt update sudo apt install -y python3-pip git docker.io然后添加 Ceph 官方源（这里以 Reef 18.2.4 为例，Quincy 把 reef 换成 quincy 即可）：wget -q -O- 'https://download.ceph.com/keys/release.asc' | sudo apt-key add - echo "deb https://download.ceph.com/debian-reef/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/ceph.list sudo apt update📌 小技巧：国内访问慢？可以换成阿里云镜像：echo "deb http://mirrors.aliyun.com/ceph/debian-reef/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/ceph.list五、在管理节点初始化集群只在 ceph1 上操作：sudo apt install -y cephadm ceph-common然后初始化集群（注意替换 IP）：sudo cephadm bootstrap --mon-ip 192.168.1.10 --cluster-network 192.168.1.0/24参数说明：--mon-ip：当前管理节点的 IP--cluster-network：OSD 之间通信的网段（建议和 public network 分开，这里图省事用了同一个）执行后会看到一堆日志，最后输出类似：Ceph Dashboard is now available at: URL: https://ceph1:8443/ User: admin Password: xxxxxxxx记下这个密码！后面要登录 Web 控制台。此时 /etc/ceph/ 下会生成 ceph.conf 和 client.admin.keyring，这就是集群的“身份证”。六、添加其他节点到集群继续在 ceph1 上操作：ceph orch host add ceph2 ceph orch host add ceph3✅ 注意：这里用的是主机名，不是 IP！所以前面 hosts 配置很关键。然后指定 mon 跑在哪几个节点（必须奇数）：ceph orch apply mon ceph1,ceph2,ceph3mgr 一般自动部署两个，不用额外操作。七、部署 OSD（关键步骤！）Ceph 不会自动用你的硬盘，得手动告诉它哪些盘可以用来存数据。先看看每台机器有哪些盘（在 ceph1 上远程查）：ceph orch device ls输出类似：HOST PATH TYPE SIZE AVAILABLE REJECT REASONS ceph1 /dev/sdb hdd 1000G Yes None ceph2 /dev/sdb hdd 1000G Yes None ceph3 /dev/sdb hdd 1000G Yes None如果显示 AVAILABLE: No，可能是盘有分区或文件系统，得先清掉：sudo wipefs -a /dev/sdb sudo sgdisk --zap-all /dev/sdb确认可用后，一键部署所有 OSD：ceph orch apply osd --all-available-devices这条命令的意思是：“把所有标记为可用的硬盘都变成 OSD”。等几分钟，再看：ceph -s应该能看到：3 mons2 mgrs3 osds up and in集群状态 HEALTH_OK八、验证和使用1. 查看集群状态ceph -s ceph df ceph osd tree2. 访问 Dashboard浏览器打开 https://ceph1:8443，用前面 bootstrap 给的账号密码登录。首次登录会让你改密码，改完就能看到漂亮的监控界面：OSD 状态、IOPS、容量、告警……一目了然。3. 创建 RBD 块设备（测试用）# 创建 pool ceph osd pool create rbd_pool 32 32 # 初始化 RBD rbd pool init rbd_pool # 创建一个 10GB 的镜像 rbd create rbd_pool/testimg --size 10G # 映射到本地（需安装 rbd-nbd 或内核支持） sudo rbd map rbd_pool/testimg搞定！你现在已经有一个能跑虚拟机磁盘、K8s PV 的块存储了。九、生产环境注意事项网络分离：Public Network（客户端访问）和 Cluster Network（OSD 同步）最好走不同网卡，避免互相干扰。硬盘规划：如果有 NVMe，可以用作 BlueStore 的 DB/WAL，大幅提升小文件性能。副本数：默认是 3 副本，如果你只有 3 个 OSD，那就刚好；如果更多，可以调 CRUSH 规则。监控告警：Dashboard 自带 Prometheus + Grafana，记得配置邮件或钉钉告警。备份 keyring：/etc/ceph/client.admin.keyring 是命根子，丢了就管不了集群了！最后说两句Ceph 看似复杂，其实只要按步骤来，用 cephadm 部署并不难。我这套流程已经在多个客户现场跑了几个月，稳定得很。关键是前期规划要做好：网络、硬盘、主机名，这些细节一错，后面全是坑。如果你觉得这篇文章对你有帮助，欢迎点赞、转发，让更多运维兄弟少走弯路。也欢迎关注我的公众号，我会持续分享真实生产环境的踩坑经验和优化技巧。公众号：运维躬行录个人博客：躬行笔记
从理念到架构：一文彻底讲透 DevOps 的本质与落地实践
很多人第一次听说 “DevOps”，脑子里立刻蹦出一堆工具名：Jenkins、Docker、K8s、GitLab CI、Argo CD……好像不用这些，就不配谈 DevOps。但干了几年一线运维后，我越来越清楚一件事：DevOps 不是工具堆砌，而是一场协作方式的革命。它要解决的，从来不是“怎么自动化”，而是“为什么开发和运维总在互相甩锅”。今天这篇文章，我就用最接地气的方式，从核心理念讲起，再结合真实生产环境中的经典架构模式，带你完整理解 DevOps 到底是什么、为什么重要、以及该怎么落地。不吹概念，只讲能用的。一、DevOps 的起点：那堵看不见的墙想象一个场景：开发写完代码，本地测试通过，兴高采烈地交给运维：“上线吧！”运维拿到代码，发现没写依赖清单、没配环境变量、连日志路径都没统一。部署失败，系统崩了。开发说：“我本地能跑啊！”运维回怼：“你本地又不是生产环境！”这种扯皮，在传统模式下太常见了。开发只对“功能实现”负责，运维只对“系统稳定”负责——目标割裂，责任模糊，交付自然又慢又脆。更惨的是，有些公司上线全靠“四大金刚”（四个资深运维手动操作），脚本五花八门，流程全靠口传。疫情期间开发可以居家轮班，运维却必须24小时待命——人累，系统也慢。这就是 DevOps 要打破的局面。二、DevOps 的核心：不是工具，是文化DevOps 的本质，就一句话：谁构建，谁负责。它要求开发不再只关心“代码能不能跑”，还要关心“代码能不能在线上安全、稳定、快速地跑起来”；也要求运维不再只是“守门人”，而是提供标准化、自助化的平台能力，让开发能安全地自主发布。这不是岗位合并，而是责任共担、目标对齐。当开发开始主动写 Dockerfile、配健康检查、看监控告警，当运维开始参与需求评审、理解业务逻辑，那堵墙才算真正拆了。而工具，只是实现这种协作的“加速器”。没有文化，光有 Jenkins，照样是“伪 DevOps”——流程自动化了，但沟通还是断的。三、DevOps 的落地：四种经典架构模式理念明白了，接下来就是怎么干。不同规模、不同行业的团队，适合的 DevOps 架构完全不同。下面这四种，都是我在生产环境中见过、用过、甚至亲手搭过的。1. 轻量自助型（适合创业公司 / 小团队）典型场景：3~10人团队，既要快又要省成本，没人专职搞平台。架构特点：代码托管：GitLab（自带 CI，省事）流水线：GitLab CI 或 GitHub Actions部署方式：Docker + Ansible 脚本部署到云服务器（如阿里云 ECS）环境：dev / staging / prod 三套，配置通过环境变量注入监控：Sentry（错误追踪）+ 云厂商基础监控流程：开发提交代码 → 自动跑测试 → 构建镜像 → 推送到镜像仓库 → 手动点击“发布到生产” → 自动执行部署脚本。关键设计：所有部署脚本统一管理，避免“一人一套脚本”的混乱prod 发布需二次确认，防止误操作保留历史镜像，支持一键回滚我帮一个做 SaaS 工具的创业团队搭过这套，三人团队每天能发3~5次版。开发自己点发布，运维只负责兜底。虽然“土”，但效率翻倍。2. 微服务 + GitOps 型（中大型互联网企业主流）典型场景：业务已拆成几十上百个微服务，需要强一致性、高可观测性。架构特点：代码仓库：GitHub（业务代码） + GitLab（K8s Manifests）CI：GitHub Actions（构建镜像）CD：Argo CD（实现 GitOps）编排：Kubernetes配置管理：Helm / Kustomize可观测性：Prometheus + Loki + Grafana（全链路）核心思想：集群的期望状态由 Git 仓库定义。比如，你在 Git 里把 user-service 的镜像 tag 从 v1.1 改成 v1.2，Argo CD 会自动检测变更，并将线上集群“纠正”到目标状态。优势：环境完全一致，告别“开发正常、生产报错”所有变更可追溯（看 Git 提交记录就行）回滚秒级完成，权限可控某跨境电商用这套支撑大促，50+ 微服务每天部署上百次。他们甚至把数据库迁移脚本也纳入 GitOps，合规审计直接拉 Git 日志，省了无数人力。3. 稳敏双态型（金融 / 政务行业专属）典型场景：既要满足银保监会等强监管，又要支持互联网业务快速迭代。架构设计：稳态业务（如核心交易系统）：走传统流程，多级审批、人工部署、强审计，发布频率低但绝对安全敏态业务（如活动页、营销系统）：走标准 DevOps 流水线，自动化部署、灰度发布、快速试错统一平台层：共用代码仓库（按项目打标签区分）共用监控、日志、安全扫描能力统一权限与审计中心某股份制银行实施后，敏态业务迭代周期从2周缩到3天，稳态系统连续三年零重大事故。关键是——组织接受了“不同业务不同节奏”，不再一刀切。4. DevSecOps 型（高安全要求场景）典型场景：医疗、金融、政府等对安全合规要求极高的领域。核心做法：安全左移 + 平铺到全流程代码提交：SAST 扫描（如 SonarQube）构建阶段：镜像漏洞扫描（Trivy）部署前：IaC 安全检查（Checkov 扫 Terraform）运行时：RASP + 网络策略限制关键机制：安全作为“质量门禁”嵌入流水线高危漏洞自动阻断发布安全团队提供规则，但不干预流程有个医疗 SaaS 客户，因 HIPAA 合规要求，把 Trivy 扫描加到 CI 最后一步。一次开发用了带 CVE 的 base 镜像，流水线直接 fail，PR 被卡住。后来他们干脆在模板里预置安全基线——让安全成为默认选项，而不是额外负担。四、别被工具绑架：先问问题，再选架构看完这些架构，你可能会想：“那我该选哪个？”我的建议是：别急着上 K8s，先回答三个问题：当前最大的协作瓶颈是什么？ 是部署慢？环境不一致？还是上线总出问题？故障平均修复时间（MTTR）多久？ 如果超过30分钟，说明反馈机制太弱。开发敢不敢自己点“发布”按钮？ 如果不敢，说明平台能力或信任机制没建好。答案会告诉你该往哪个方向走。小团队别盲目追新，先把自动化脚本和回滚机制做扎实；大企业别一味求快，先解决“100+系统耦合导致连锁延期”的结构性问题。五、DevOps 的终极目标：对业务负责最后说句实在话：技术再酷，如果不能帮公司赚钱或留住用户，都是白搭。DevOps 的真正价值，不是让你少加班，也不是让你用上 Argo CD，而是——让业务能更快试错、更快响应市场变化。当产品提一个需求，你能当天上线验证；当大促流量突增，你能自动扩容扛住；当出现故障，你能5分钟内定位并恢复……这才是 DevOps 赢得老板信任的根本原因。总结DevOps 不是魔法，也不是银弹。它是一套以协作为核心、以自动化为手段、以业务价值为导向的工作方式。小团队可以从轻量自助做起，重点在“解放人力”；中大型企业要追求一致性与可观测性，避免“黑盒部署”；金融政务需兼顾合规与敏捷，稳敏双态是出路；高安全场景必须把安全内嵌到每个环节。无论哪种，底层逻辑都一样：打破壁垒、快速反馈、责任共担。如果你觉得这篇文章帮你理清了 DevOps 的全貌，欢迎转发给正在踩坑的同事。也欢迎关注我的公众号 @运维躬行录，我会持续分享一线实战经验，不画饼，只讲真话。公众号：运维躬行录个人博客：躬行笔记 1
别再傻傻分不清了！Containerd 和 Docker 到底有啥区别？命令、架构全拆解
前阵子有个刚入行的小伙伴跑来问我：“哥，我听说现在 Kubernetes 都不用 Docker 了，改用 Containerd，那是不是以后 Docker 就废了？我学 Docker 还有意义吗？”我当时差点一口老血喷出来——这问题问得，就跟“微信被抖音干掉了，我还用不用装微信”一样离谱。其实啊，Docker 没死，只是它的一部分功能被“剥离”出去了，而那个被剥离出来的核心组件，就是 Containerd。今天咱们就掰开揉碎了讲讲：Containerd 和 Docker 到底有啥区别？从命令行操作到底层架构，一条条给你捋清楚。这篇文章不灌鸡汤，全是我在生产环境里踩过的坑、调过的参数、看过的日志堆出来的干货。先说个前提：Docker 其实是个“全家桶”。你平时用 docker run、docker build，背后其实是一整套工具链在干活。这套工具链里，真正负责和 Linux 内核打交道、创建容器、管理镜像的，并不是 Docker 本身，而是它内部的一个叫 containerd 的守护进程。你可以把 Docker 想象成一个“外卖平台”——你点个“宫保鸡丁”，平台接单（Docker CLI），然后派单给后厨（containerd），后厨炒菜（拉镜像、启容器），最后骑手送餐（网络、存储等）。而 Containerd 呢，就是那个后厨，只管做菜，不管接单也不管送餐。所以，Kubernetes 把 Docker “踢出局”，其实是说：“我不需要你这个外卖平台了，我自己会接单（Kubelet 直接调 containerd），你把后厨交出来就行。”于是 Docker 把 containerd 开源给了 CNCF（云原生基金会），现在它是个独立项目，谁都能用。那从命令行角度看，差别大吗？如果你习惯了 docker ps、docker images、docker exec，那你直接切到 containerd 上，会有点懵——因为 containerd 根本没有这些命令！Containerd 提供的是 ctr 和 crictl 两个命令行工具。注意，这两个不是一回事：ctr 是 containerd 自带的调试工具，功能齐全但不稳定，官方明确说“别在生产环境用”。crictl 是 Kubernetes CRI（容器运行时接口）标准下的命令行工具，专为 K8s 设计，推荐在集群节点上使用。举个例子：你想看当前有哪些容器在跑：# Docker 方式 docker ps # ctr 方式（不推荐生产用） ctr containers list # crictl 方式（K8s 节点推荐） crictl ps再比如拉个镜像：# Docker docker pull nginx # ctr ctr image pull docker.io/library/nginx:latest # crictl（注意：crictl 不能直接 pull，得通过 pod sandbox 或 runtime 接口间接拉） # 所以通常 crictl 不用来拉镜像，而是配合 kubelet 自动拉看到没？ctr 的命令更底层，参数也更啰嗦。而且 ctr 拉下来的镜像，默认命名空间是 default，而 K8s 用的是 k8s.io。如果你用 ctr 拉了个镜像，K8s 可能根本看不到——因为不在同一个命名空间里！这就导致一个经典翻车现场：运维同学在 K8s 节点上用 ctr pull 了一个镜像，然后纳闷“为啥 Pod 还在 ImagePullBackOff”？结果一查，镜像在 default 命名空间，而 kubelet 在 k8s.io 里找，当然找不到。正确的做法是：ctr -n k8s.io image pull nginx:latest加个 -n k8s.io 指定命名空间，这样 kubelet 才能认。再说架构差异，这才是重点。Docker 的架构是这样的（简化版）：用户 → Docker CLI → Docker Daemon (dockerd) → containerd → runc → Linux Kernel其中：Docker CLI：你敲的 docker 命令Docker Daemon (dockerd)：后台服务，负责解析命令、管理镜像、网络、卷等containerd：负责容器生命周期管理（创建、启动、停止等）runc：OCI 标准的容器运行时，真正调用 clone()、mount() 等系统调用创建容器而纯 Containerd 的架构（比如在 K8s 中）是：Kubelet → CRI Plugin → containerd → runc → Linux Kernel这里少了 Docker Daemon 这一层。Kubelet 通过 CRI（Container Runtime Interface）直接和 containerd 对话。containerd 内置了 CRI 插件（以前是单独的 cri-containerd，现在合并了），所以不需要 dockerd。这意味着什么？更轻量：少了 dockerd，内存占用更低，启动更快。我们线上一个节点，切换到 containerd 后，系统负载降了 10% 左右。更稳定：dockerd 曾经出过不少 bug，比如镜像清理异常、网络配置冲突。containerd 架构简单，故障面小。更标准：CRI 是 Kubernetes 定义的标准接口，任何符合 CRI 的运行时（比如 containerd、CRI-O）都能无缝接入 K8s。而 Docker 并不原生支持 CRI，得靠 dockershim（一个适配层），这个 shim 在 K8s 1.20 之后就被废弃了。不过，Docker 也不是一无是处。它的最大优势是开发体验好。本地开发时，docker build、docker-compose 一套下来丝滑流畅。而 containerd 没有 build 功能——它只能运行容器，不能构建镜像。所以现实中的分工往往是：开发阶段：用 Docker 构建镜像、本地测试生产部署：用 containerd（或 CRI-O）跑在 K8s 上两者根本不冲突，反而互补。说到这儿，可能有人要问：“那我能不能在生产环境继续用 Docker？”技术上当然可以。K8s 1.24 之前都支持 dockershim，就算现在，也有第三方维护的 shim 可以用。但我们团队早就切到 containerd 了，原因很简单：少一层抽象，少一个故障点。记得去年有一次，dockerd 卡死，导致整个节点上的容器无法更新，Pod 重启失败。排查半天才发现是 dockerd 的镜像元数据锁住了。换成 containerd 后，这种“中间商赚差价”导致的问题基本消失了。另外，containerd 的配置也更透明。它的配置文件是 /etc/containerd/config.toml，所有参数一目了然。而 Docker 的配置散落在 daemon.json、systemd unit、甚至环境变量里，调起来头疼。比如你想改镜像仓库地址（mirror），Docker 要改 /etc/docker/daemon.json：{ "registry-mirrors": ["https://xxx.mirror.aliyuncs.com"] }而 containerd 要在 config.toml 里找到 [plugins."io.containerd.grpc.v1.cri".registry] 这一段，加上：[plugins."io.containerd.grpc.v1.cri".registry.mirrors] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] endpoint = ["https://xxx.mirror.aliyuncs.com"]虽然写法麻烦点，但结构清晰，不容易出错。再聊聊镜像存储。Docker 默认用 overlay2 存储驱动，镜像存在 /var/lib/docker 下，目录结构复杂，手动清理容易误删。Containerd 用的是 content store + snapshotter 架构。镜像内容（layers）存在 content store（通常是 /var/lib/containerd/io.containerd.content.v1.content），快照（容器可写层）由 snapshotter 管理（比如 overlayfs）。好处是：镜像和容器的存储完全解耦，支持多 snapshotter（overlayfs、btrfs、zfs 等），扩展性更强。不过对运维来说，最直观的感受是：containerd 的磁盘占用更可控。我们曾经遇到 Docker 节点磁盘爆满，查了半天发现是 /var/lib/docker/containers 下的日志文件没轮转。而 containerd 默认不接管日志，日志由 kubelet 或 systemd 管理，更容易统一处理。最后说点实用技巧。如果你在 K8s 节点上想调试容器，推荐用 crictl：# 查看所有 Pod crictl pods # 查看容器 crictl ps -a # 进入容器（类似 docker exec） crictl exec -it <container_id> sh # 查看容器日志 crictl logs <container_id>但要注意，crictl 的 container_id 是长 ID，不像 docker ps 显示短 ID。可以用 crictl ps | grep your-app 找。另外，containerd 支持插件机制。比如你可以替换默认的 runc，换成 kata-runtime 实现安全容器；或者换 snapshotter 为 devmapper 做块设备隔离。这种灵活性是 Docker 很难做到的。总结一下：Docker 是“全家桶”，适合开发和单机部署；Containerd 是“核心引擎”，适合生产环境尤其是 K8s。命令上，Docker 有 docker CLI，Containerd 用 ctr/crictl，后者更底层但更贴近 K8s。架构上，Docker 多了一层 dockerd，Containerd 直接对接 CRI，更轻量、更标准。两者不是替代关系，而是分工协作：Docker 构建，Containerd 运行。别再纠结“该学哪个”了——都得会。开发用 Docker，运维搞 K8s 就绕不开 Containerd。理解它们的关系，才能在容器世界里游刃有余。如果你觉得这篇文章帮你理清了思路，不妨点个赞、转发给还在混淆这两者的同事。容器化这条路，踩坑不可怕，可怕的是不知道坑在哪。关注我，带你少走弯路，多填干货。公众号：运维躬行录个人博客：躬行笔记
别再折腾 iptables 了！用 cproxy + wstunnel 轻松搞定 Linux 透明代理，内网穿透稳如老狗
最近有个朋友找我帮忙，说他们公司有个老旧的 Linux 服务器，跑着一堆内部服务，现在想让这些服务能通过公网访问，但又不能直接暴露端口，防火墙策略也卡得死死的。常规的反向代理方案搞不定，因为应用层根本没做任何代理适配，总不能一个个改代码吧？这时候我就想到了“透明代理”——对应用完全无感，流量在系统层面就被劫持转发，上层程序压根不知道自己走的是代理。听起来很玄乎？其实没那么复杂，关键就俩工具：cproxy 和 wstunnel。今天这篇，我就手把手带你把这套组合拳打出来，顺便聊聊我在生产环境里踩过的坑、调过的参，保证你照着做就能跑起来。先说说 cproxy 是啥cproxy 不是那种大厂开源的明星项目，它是个轻量级的透明代理工具，专门干一件事：把本地发出的 TCP 流量重定向到指定的 SOCKS5 或 HTTP 代理。它底层用的是 Linux 的 TPROXY 模块（不是 REDIRECT！），所以能保留原始目的地址，特别适合做透明代理。很多人一听到“透明代理”就想到 iptables + redsocks / proxychains，但那些方案要么配置复杂，要么兼容性差。cproxy 的优势在于：配置简单、性能不错、支持 UDP（虽然我们这次主要用 TCP），而且它能和 wstunnel 完美配合——后者能把任意 TCP 流量封装成 WebSocket，轻松绕过企业防火墙。场景还原：为什么需要这个组合？假设你有台内网机器 A（192.168.1.100），上面跑了个 Web 服务（比如 8080 端口）。现在你想让外网用户通过你的公网服务器 B（比如 example.com）访问这个服务，但：服务器 A 不能直接开公网 IP防火墙只允许出站 443（HTTPS）你不想改 Web 应用的代码或配置这时候，常规思路是用 frp/ngrok，但它们依赖中心节点，而且有些环境连 frp 的自定义端口都不让连。而 wstunnel 可以把流量伪装成正常的 WebSocket（走 443），再配合 cproxy 做透明劫持，整个链路就通了。实操开始：部署 wstunnel先在公网服务器 B 上起一个 wstunnel 服务端。wstunnel 的 GitHub 仓库是 https://github.com/erebe/wstunnel，安装很简单，直接下二进制就行：# 在服务器 B（公网）上执行 wget https://github.com/erebe/wstunnel/releases/latest/download/wstunnel-linux-amd64 -O /usr/local/bin/wstunnel chmod +x /usr/local/bin/wstunnel然后启动服务端，监听 443 端口（假装是 HTTPS）：wstunnel server --port 443 --restrictTo "127.0.0.1:8080" wss://example.com--restrictTo 是安全限制，只允许转发到本机的 8080 端口（后面我们会把内网流量打到这里）。注意：如果你已经有 Nginx 占用 443，可以用 --port 8443，然后用 Nginx 反代 /wstunnel 路径到 8443，这里为了简化先直接占 443。内网机器 A：部署 cproxy + wstunnel 客户端现在回到内网机器 A（192.168.1.100）。首先装 cproxy。它没有官方 release，但源码编译非常快：git clone https://github.com/txthinking/cproxy.git cd cproxy make sudo cp cproxy /usr/local/bin/接着，在 A 上启动 wstunnel 客户端，连接到公网服务器 B：wstunnel client wss://example.com --udp --tcp -L 127.0.0.1:1080这行命令的意思是：把本地 1080 端口作为 SOCKS5 代理，所有流量通过 wss://example.com 隧道转发。--udp --tcp 表示同时支持两种协议（虽然我们主要用 TCP）。现在，1080 端口就是一个“出口”，所有发往这里的流量都会被 wstunnel 打包成 WebSocket，从 443 出去。关键一步：用 cproxy 把流量劫持到 1080接下来就是透明代理的核心了。cproxy 的作用是：监听一个本地端口（比如 12345），把所有发往它的 TCP 连接，通过 SOCKS5 代理（127.0.0.1:1080）转发出去。启动 cproxy：cproxy -l 12345 -a 127.0.0.1:1080-l 是本地监听端口，-a 是上游 SOCKS5 地址。现在，只要把目标流量重定向到 12345，它就会自动走代理。用 iptables 把流量“骗”到 cproxy别慌，这里的 iptables 规则其实就两行，比你想的简单多了。我们要劫持的是本机发出的、目标为内网 Web 服务（192.168.1.100:8080）的流量？不对！等等，逻辑反了。实际上，我们的目标是：当有人访问公网服务器 B 的 443 时，流量最终要打到内网 A 的 8080。但 A 本身并不知道这个请求，所以我们需要在 A 上“假装”自己收到了这个请求。更准确地说：公网 B 收到请求后，通过 wstunnel 把数据转发到 A 的 127.0.0.1:8080（由 wstunnel server 的 --restrictTo 决定）。但 A 上并没有服务监听 127.0.0.1:8080 啊！这时候，我们需要在 A 上做一件事：把发往 127.0.0.1:8080 的流量，重定向到真正的 Web 服务（比如 192.168.1.100:8080）？也不对。我把自己绕晕了，重新理一遍：用户访问 https://example.com请求到达服务器 B 的 443（wstunnel server）wstunnel server 把流量转发到 本机的 127.0.0.1:8080（因为 --restrictTo=127.0.0.1:8080）但我们希望这个流量最终到达 内网机器 A 的 8080所以，服务器 B 上还需要一个本地转发！比如用 socat 或 nginx 把 127.0.0.1:8080 的流量打到 wstunnel client 的某个端口？太乱了。其实更优雅的做法是：让 wstunnel server 直接把流量转发到内网 A 的公网隧道出口。但 A 没有公网 IP 啊。啊！我犯了个经典错误——应该让 wstunnel client 主动建立反向隧道。正确姿势：在内网 A 上，wstunnel client 启动时，不仅要开 SOCKS5（1080），还要开一个 本地端口映射，把远程的某个端口映射到本地服务。比如：# 在内网 A 上执行 wstunnel client wss://example.com -L 127.0.0.1:1080 -R 8080:127.0.0.1:8080-R 8080:127.0.0.1:8080 表示：在 server 端监听 8080，所有流量转发到 client 的 127.0.0.1:8080。但 wstunnel 的 -R 参数默认是绑定 127.0.0.1，外部无法访问。所以我们需要改 server 启动命令：# 服务器 B 上 wstunnel server --port 443 --hostReverseProxy wss://example.com然后 client 用：wstunnel client wss://example.com -L 1080 -R :8080:127.0.0.1:8080这样，server 会把 8080 绑定到 0.0.0.0，外部就能访问了。但问题来了：如果我想透明代理本机的所有出站流量呢？比如让 curl、wget 自动走代理？这才是 cproxy 的主战场。回归主题：用 cproxy 做本机透明代理假设你现在在内网机器 A 上，想让所有对外的 HTTP/HTTPS 请求都走代理（比如访问 GitHub、Docker Hub），但又不想每个命令都加 proxy 参数。这时候，cproxy + iptables 就派上用场了。步骤：启动 wstunnel client，开 SOCKS5（1080）启动 cproxy，监听 12345，上游指向 1080用 iptables 把本机的出站流量重定向到 12345具体命令：# 清空规则（测试环境） iptables -t nat -F # 跳过本地回环和局域网 iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN iptables -t nat -A OUTPUT -d 192.168.0.0/16 -j RETURN iptables -t nat -A OUTPUT -d 10.0.0.0/8 -j RETURN iptables -t nat -A OUTPUT -d 172.16.0.0/12 -j RETURN # 把其他所有 TCP 流量重定向到 cproxy iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-port 12345注意：这里用的是 REDIRECT，不是 TPROXY。因为 cproxy 默认支持 REDIRECT 模式（虽然它也支持 TPROXY，但 REDIRECT 更简单）。现在，你在 A 上执行：curl https://httpbin.org/ip返回的 IP 就是公网服务器 B 的 IP！说明流量已经通过 wstunnel 走出去了。但有个坑：DNS 请求不会被重定向！因为 DNS 是 UDP，而上面的规则只处理 TCP。解决办法有两个：强制使用 TCP DNS（比如 curl --dns-servers 8.8.8.8 不行，得改 resolv.conf 加 options use-vc）用 cproxy 的 UDP 支持（启动时加 -u），再配 iptables 重定向 UDP 53不过大多数场景下，只要 TCP 能代理，DNS 可以单独配 DoH 或 DoT，或者直接用 8.8.8.8（如果网络允许）。性能和稳定性怎么样？我在一台 2 核 4G 的云服务器上跑过这个组合，单连接下载速度能达到 80Mbps（受限于 WebSocket 封装开销），延迟增加约 30~50ms。对于管理后台、API 调用完全够用。稳定性方面，wstunnel 支持自动重连，cproxy 也很轻量（内存占用 <10MB）。唯一要注意的是：不要用在高并发场景，毕竟它不是为百万连接设计的。为什么不用 clash / sing-box？有朋友问：现在 clash meta、sing-box 这么强，还搞 cproxy 干嘛？道理很简单：轻量、无依赖、一行命令搞定。clash 要配 YAML，sing-box 更复杂，而 cproxy + wstunnel 总共就三个命令，适合嵌入脚本、容器、临时调试。而且，很多老旧系统（比如 CentOS 6）跑不了 Rust 程序，但 cproxy 是 Go 编译的静态二进制，扔哪都能跑。最后提醒几个坑SELinux / AppArmor：某些系统会阻止 iptables 或 socket 操作，记得关掉或加规则。内核版本：TPROXY 需要较新的内核（>=3.0），但 REDIRECT 模式基本通吃。wstunnel 的证书验证：如果用自签名证书，client 要加 --insecurecproxy 默认只监听 127.0.0.1，如果想让局域网其他机器用，得改源码或加参数（目前不支持 bind 0.0.0.0）总结一下wstunnel：把 TCP/UDP 流量封装成 WebSocket，轻松穿透防火墙cproxy：轻量级透明代理，把本地流量重定向到 SOCKS5iptables：最后一公里，把流量“骗”到 cproxy三者结合，你就能在几乎任何 Linux 环境下实现“无感代理”，无论是内网穿透、科学上网，还是 CI/CD 中的依赖下载，都稳得很。下次再遇到“只能出 443”、“不能改应用配置”的需求，别再抓耳挠腮了，试试这个组合，说不定十分钟就搞定。如果你觉得这篇文章帮你省了三天调试时间，不妨点个赞、转给那个还在手动配 iptables 的同事。也欢迎留言告诉我你的使用场景，说不定下期就写你遇到的奇葩问题！公众号：运维躬行录个人博客：躬行笔记
让AI替你SSH进服务器，5分钟搞定故障排查！运维人终于能睡整觉了
最近我遇到了一个特别有意思的场景，让我对AI运维有了全新的认识。事情是这样的：我有一台Rocky Linux服务器需要部署WordPress，但我不想手动敲那些繁琐的命令。于是我就想，能不能让AI直接帮我操作服务器？结果发现，现在真的有工具能做到这一点——通过sshmcp协议。什么是sshmcp？这玩意儿到底有多神奇说实话，第一次听说sshmcp的时候我还以为是什么新的加密协议。后来才知道，这是基于MCP（Model Context Protocol）协议的一个SSH工具，专门为了让AI能够安全地远程操作服务器而设计的。简单来说，sshmcp就像是给AI配了一个"数字手"，让它能够通过SSH连接到你的服务器，执行命令、传输文件，甚至帮你排查问题。但最关键的是，它不会把你的SSH密码直接暴露给AI模型，所有的凭据都在本地管理，从根源上保证了安全性[5]。如何开始使用sshmcp项目地址：https://github.com/classfang/ssh-mcp-server/blob/main/README_CN.md我使用的ai工具是：Cherry Studio项目地址：https://www.cherry-ai.com/安装完成后配置大模型大模型配置好后安装mcp如果你也想体验这种AI运维的感觉，其实并不复杂。根据官方文档，你可以通过NPX直接运行ssh-mcp-server：npm i @fangjunjie/ssh-mcp-server或者如果你更喜欢Python版本，也可以使用：pip install ssh-mcp-server ssh-mcp-server --config your-config.json添加mcp服务器配置文件通常包含你的SSH连接信息：{ "mcpServers": { "ssh-mcp-server": { "command": "npx", "args": [ "-y", "@fangjunjie/ssh-mcp-server", "--host", "192.168.198.133", "--port", "22", "--username", "root", "--password", "pwd123456" ] } } }一旦配置完成，任何支持MCP协议的AI助手（比如Cursor、Trae等）就可以通过这个服务器来操作你的远程机器了[3]。sshmcp的核心优势：安全与效率的完美平衡说到sshmcp，我觉得它最厉害的地方在于解决了AI运维的最大痛点——安全性。传统的做法要么是把SSH密钥直接给AI（极其危险），要么就是完全手动操作（效率低下）。而sshmcp通过以下机制保证了安全：凭据隔离：SSH凭据完全在本地管理，永远不会暴露给AI模型命令控制：可以通过黑白名单机制精确控制允许执行的命令范围标准化接口：遵循MCP协议规范，可与所有支持该协议的AI助手无缝集成双向文件传输：不仅支持命令执行，还支持安全的文件上传下载这意味着你可以放心地让AI帮你处理日常运维任务，而不必担心服务器被黑或者数据泄露。操作演示-让ai安装lnmp环境，部署wordpress发现端口占用处理实际应用场景：不止是部署WordPress虽然我用WordPress部署作为例子，但sshmcp的应用场景远不止于此：应急响应：当服务器出现故障时，AI可以立即连接并执行诊断命令，快速定位问题所在[2]。批量操作：如果你管理着多台服务器，AI可以同时连接所有服务器并执行相同的维护任务。配置管理：AI可以帮助你标准化服务器配置，确保所有服务器都遵循相同的安全策略。日志分析：AI可以直接读取服务器日志，分析异常模式并提供优化建议。安全审计：定期检查服务器的安全配置，发现潜在的风险点。安全考虑：别让便利成为隐患虽然sshmcp看起来很美好，但我们还是要保持谨慎。毕竟让AI直接操作生产服务器是有风险的。以下几点建议供参考：最小权限原则：为AI创建专用的SSH用户，只授予必要的权限命令限制：使用黑白名单严格控制AI可以执行的命令审计日志：记录AI执行的所有操作，便于事后审查测试环境先行：先在测试环境中验证AI的操作逻辑人工审核关键操作：对于删除、重启等高危操作，要求人工确认未来展望：AI运维的新时代说实话，体验过这种AI辅助的运维方式后，我真的觉得传统手动运维有点过时了。想象一下，以后我们可能只需要告诉AI："帮我优化这台服务器的性能"，它就能自动分析瓶颈、调整配置、应用最佳实践。当然，这并不意味着运维工程师会被取代。相反，我们的角色会从"操作工"转变为"策略制定者"和"质量监督员"。我们需要设计更好的自动化流程，制定更智能的运维策略，同时确保AI的行为符合我们的预期和安全要求。总结回到我最初的那个WordPress部署案例，虽然我是手动完成的，但整个过程让我深刻体会到AI运维的巨大潜力。sshmcp这样的工具正在模糊人机协作的边界，让我们能够以更高效、更安全的方式管理基础设施。如果你还在手动敲命令管理服务器，不妨试试sshmcp。它可能会彻底改变你对运维工作的认知。毕竟，在这个AI时代，我们应该把重复性的工作交给机器，把创造力留给自己。最后我想说，未来已来！让我们积极拥抱ai，适应ai，学会使用ai工具来提升自己的效率。勇敢的去适应吧，只有改变才是永恒不变！！！如果你觉得这篇文章对你有帮助，欢迎关注@运维躬行录，我会持续分享更多关于AI运维、自动化工具和DevOps实践的干货内容。也欢迎在评论区分享你的AI运维体验，我们一起探讨这个充满可能性的新时代！公众号：运维躬行录个人博客：躬行笔记
别再瞎猜了！K8s生产环境排错实战手册：从Pod卡住到服务503，这些坑我都替你踩过了
说实话，K8s这玩意儿，部署起来可能半小时搞定，但真到了生产环境出问题，那可真是“一小时定位，三小时复盘，五小时背锅”。今天这篇文章，不讲理论，不画架构图，就聊点实在的——生产环境真实遇到的排错案例 + 详细处理过程 + 踩过的坑。内容全来自我亲手处理过的故障，有些甚至半夜三点被电话叫醒去救火。如果你也在搞K8s，建议收藏，说不定哪天就用上了。🧯 场景一：Pod 卡在 Pending，你以为是资源不够？前几天，测试团队反馈新上线的服务一直起不来，kubectl get pod 看到状态是 Pending。第一反应：是不是节点资源不够？赶紧 kubectl describe pod xxx 一看：Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 2m default-scheduler 0/5 nodes are available: 5 Insufficient cpu.看起来确实是 CPU 不够。但奇怪的是，集群明明还有两台空闲节点，CPU 使用率不到30%。这时候很多人就懵了：“资源明明够啊，为啥调度不过去？”真相其实是：QoS 和 PriorityClass 在背后搞鬼。我们项目里为了保障核心服务，给某些 Pod 设置了高优先级（PriorityClass）。而这个新上线的服务没配，结果被调度器直接“降级”了——即使有资源，也得等高优先级任务跑完再说。排查命令：# 查看当前集群所有 PriorityClass kubectl get priorityclass # 查看 Pod 是否绑定了 PriorityClass kubectl get pod <pod-name> -o jsonpath='{.spec.priorityClassName}'解决办法：如果是非核心业务，可以临时调低其他高优 Pod 的副本数；或者给这个新服务也配上合适的 PriorityClass；更彻底的做法是：在命名空间级别设置 ResourceQuota + LimitRange，避免资源被无限制抢占。💡 小提醒：别只看 kubectl describe 的 Events 表面信息。有时候“Insufficient cpu”只是障眼法，真正原因是亲和性（affinity）、污点（taint）或调度插件策略。🌪️ 场景二：服务间歇性 503，日志却一切正常？这是最让人抓狂的问题之一。用户访问接口，偶尔返回 503 Service Unavailable，但 kubectl logs 看应用日志，完全正常，连 error 都没有。重启 Pod？暂时好了，几小时后又来。一开始以为是应用 Bug，后来发现：根本不是应用的问题，而是 conntrack 表溢出了！K8s 默认用 iptables 做 Service 转发，而 iptables 依赖内核的 conntrack 模块记录连接状态。当并发连接数暴增（比如大促、压测、爬虫攻击），conntrack 表满了，新连接直接被丢弃，表现为“服务不可达”，但容器本身毫无感知。怎么确认？登录到 Pod 所在节点，执行：# 查看当前 conntrack 连接数 sysctl net.netfilter.nf_conntrack_count # 查看最大限制 sysctl net.netfilter.nf_conntrack_max如果 count 接近 max（默认一般是 65536），基本就是它了。解决方案：临时扩容（应急）：sysctl -w net.netfilter.nf_conntrack_max=262144长期优化：改用 IPVS 模式（性能更好，不依赖 conntrack）；或者调整 kube-proxy 配置，启用 strictARP: true + 合理设置 conntrack 参数；对外暴露的服务，前面加一层 Nginx 或 LVS 做连接收敛。📌 血泪教训：有一次我们没注意，大促当天凌晨 conntrack 满了，导致支付回调失败，损失几十万订单。从此以后，监控里必加 nf_conntrack_count 指标。🔌 场景三：PVC 挂载失败，Pod 卡在 ContainerCreating这种情况太常见了。你写了个 StatefulSet，挂了个 PVC，结果 Pod 一直卡在 ContainerCreating，describe 一看：MountVolume.SetUp failed for volume "pvc-xxx" : mount failed: exit status 32或者：Unable to attach or mount volumes: unmounted volumes=[data], unattached volumes=[...]: timed out waiting for the condition这时候很多人第一反应是：“存储后端挂了？” 但其实，90% 的情况是 StorageClass 配置问题或权限不对。举个真实例子：我们用的是 NFS 作为后端存储，StorageClass 里写了 mountOptions: ["vers=4.1"]，但某台 NFS 服务器只支持 vers=3。结果新 Pod 调度到那台机器上就挂载失败。排查步骤：看 PV 和 PVC 是否绑定成功：kubectl get pvc -n your-ns kubectl get pv如果 PVC 状态是 Pending，说明没找到匹配的 PV。检查 StorageClass 是否存在且配置正确：kubectl get storageclass kubectl describe storageclass your-sc-name手动在节点上测试挂载（关键！）：登录到目标节点，用 root 执行：mount -t nfs -o vers=4.1,nolock,proto=tcp your-nfs-server:/path /mnt/test如果报错，就能定位是网络、权限还是协议问题。如果是云厂商（如 AWS EBS、阿里云云盘），还要检查：节点是否在同一可用区（AZ）？IAM 权限是否允许挂载？磁盘是否已被其他实例占用？修复建议：统一 StorageClass 的 mountOptions；对 NFS 存储，建议用 nolock,intr,soft 参数避免死锁；生产环境尽量用 CSI 驱动，而不是 in-tree 插件（已废弃）。🌐 场景四：DNS 解析时有时无，nslookup 正常但 curl 失败？这个问题特别诡异。你在 Pod 里执行 nslookup kubernetes.default，返回 IP 正常；但 curl http://my-service 却超时。很多人以为是 CoreDNS 崩了，其实更可能是 DNS 缓存 + ndots 陷阱。K8s 默认给 Pod 的 /etc/resolv.conf 加了 ndots:5，意思是：如果域名中点少于 5 个，就先尝试拼接 search domain（比如 my-service.namespace.svc.cluster.local）。但如果 search domain 太多，或者网络延迟高，就会导致 DNS 查询超时重试，最终失败。验证方法：# 进入 Pod kubectl exec -it your-pod -- sh # 查看 resolv.conf cat /etc/resolv.conf # 手动测试带完整域名的解析（绕过 ndots） nslookup my-service.namespace.svc.cluster.local # 对比短域名 nslookup my-service如果短域名慢或失败，长域名正常，基本就是 ndots 问题。解决方案：应用层改用 FQDN（完整域名）：比如代码里写 http://my-service.namespace.svc.cluster.local:8080；调整 Pod 的 dnsConfig：spec: dnsConfig: options: - name: ndots value: "1" - name: timeout value: "2"升级 CoreDNS 到最新版，并开启缓存插件（cache 30）；监控 CoreDNS 的 latency 和 error rate，用 Prometheus + Grafana 做告警。🧠 冷知识：K8s 1.27+ 已支持 dnsPolicy: ClusterFirstWithHostNet，对 hostNetwork Pod 更友好。⚙️ 场景五：节点 NotReady，kubelet 日志全是 “PLEG is not healthy”某天早上收到告警：多个节点状态变成 NotReady。登录节点一看，systemctl status kubelet 显示 active (running)，但 kubectl get node 就是 NotReady。查 kubelet 日志：journalctl -u kubelet -n 100 | grep -i pleg输出：PLEG is not healthy: pleg was last seen active 3m agoPLEG（Pod Lifecycle Event Generator） 是 kubelet 用来监听容器生命周期变化的组件。它卡住，通常是因为 容器运行时响应太慢。我们当时用的是 containerd，排查发现：节点上跑了太多 Pod（200+）；containerd 的 oom_score_adj 没调，被系统 OOM killer 干掉过一次；磁盘 IO 高，导致 containerd 状态同步延迟。处理流程：先隔离节点，防止调度新 Pod：kubectl cordon node-name kubectl drain node-name --ignore-daemonsets --delete-local-data --force重启 containerd 和 kubelet：systemctl restart containerd systemctl restart kubelet优化配置：给 containerd 设置更高的 oom_score_adj（比如 -999）；限制单节点 Pod 数量（通过 kubelet 的 --max-pods=110）；使用 SSD 磁盘，避免 IO 瓶颈。✅ 经验：生产环境单节点 Pod 数别超过 100，否则 kubelet 容易卡死。别信“理论上支持 110”，那是理想值。🧨 场景六：镜像拉取失败，但本地 docker pull 正常？kubectl describe pod 显示：Failed to pull image "registry.corp.com/app:v1": rpc error: code = Unknown desc = failed to pull and unpack image: failed to resolve reference "...": pull access denied但你在节点上手动 crictl pull registry.corp.com/app:v1 却成功了！原因：Pod 没配 imagePullSecrets！K8s 拉镜像时，会用 Pod 所在命名空间下的 secret 来认证。如果你没显式指定 imagePullSecrets，它就用默认的（通常是空的）。解决办法：创建 secret：kubectl create secret docker-registry regcred \ --docker-server=registry.corp.com \ --docker-username=user \ --docker-password=pass \ -n your-ns在 Deployment 中引用：spec: imagePullSecrets: - name: regcred💥 坑点：Helm chart 里经常漏掉 imagePullSecrets，导致内网部署失败。建议在 values.yaml 里统一配置。🛠️ 我的私藏排错工具箱最后分享几个我常用的“暗黑”命令，关键时刻能救命：绕过容器直接抓包（不用进 Pod）：kubectl debug node/<node> -it --image=nicolaka/netshoot -- tcpdump -i eth0 port 80查看 Pod 被哪个控制器管理：kubectl get pod <pod> -o jsonpath='{.metadata.ownerReferences}'快速导出所有事件（按时间排序）：kubectl get events --sort-by=.metadata.creationTimestamp -A检查 kubelet 是否上报心跳：kubectl get --raw='/api/v1/nodes/<node>/proxy/stats/summary'诊断镜像（自己打包的）：FROM alpine:3.18 RUN apk add --no-cache curl jq bind-tools net-tools iproute2 tcpdump CMD ["sleep", "infinity"]用法：kubectl run diag --image=my-diag-tools -it --rm --restart=Never -- sh总结：排错不是玄学，是系统工程K8s 排错，说难也难，说简单也简单。关键在于：别慌：先看状态（STATUS），再看事件（Events），最后看日志（Logs）；分层排查：从 Pod → Node → Network → Storage → Control Plane，一层层剥；善用工具：kubectl + journalctl + tcpdump + Prometheus，组合拳打满；预防大于治疗：做好监控、告警、资源限制、健康检查，很多问题根本不会发生。生产环境没有“不可能”，只有“还没遇到”。你今天省下的一个探针配置，可能就是明天半夜三点的夺命连环 call。如果你觉得这篇文章帮你避开了一个坑，欢迎转发给你的同事（尤其是那个总说“K8s 很稳定”的开发）。也别忘了 关注 @运维躬行录，我会持续更新更多硬核实战经验，带你少走弯路，多睡好觉。毕竟，运维的终极目标，不是解决问题，而是——让问题别发生。
K3s保姆级实战：从单机“点亮”到生产集群搭建，别再对着官方文档发呆了
上一篇咱们聊了K3s的定位，后台有不少兄弟私信我：“老哥，道理我都懂，但到底怎么装？官方文档那一堆参数看得我脑仁疼，能不能来点直接能抄作业的？”没问题。今天这篇咱们不整虚的，直接开搞。我把话放这儿：只要你有一台能联网的Linux机器（甚至虚拟机），跟着我这篇走，半小时内你要是跑不起来一个带Ingress、带存储的K8s集群，我把键盘吃了。但这不仅仅是简单的安装教程。我是做运维的，我知道咱们在生产环境会遇到什么——内网隔离、镜像拉不动、证书过期、节点死活加不进去。这些坑，我都替你们填平了。准备好了吗？打开你的终端，咱们开始。第一阶段：环境准备（别跳过，这是血泪史）很多新手上来就跑安装脚本，结果报错报得怀疑人生。其实90%的问题都是环境没收拾干净。在跑任何K3s命令之前，你先检查这几样东西：1. 你的 Hostname（主机名）K3s（以及K8s）对主机名非常敏感。别用 localhost 这种名字，也别用带下划线 _ 的奇葩名字。乖乖地，改成标准的DNS兼容格式，比如 node-1、k3s-master。hostnamectl set-hostname k3s-master-01 # 改完记得退出重进一下终端，或者 source /etc/profile2. 关闭防火墙和 Swap虽然K3s声称支持Swap，但为了稳妥（特别是新手），我建议还是关了。防火墙是大坑。如果你不是网络专家，搞不定iptables那些复杂的转发规则，就把 firewalld 或者 ufw 关了。systemctl stop firewalld systemctl disable firewalld swapoff -a # 记得去 /etc/fstab 里把 swap 那行注释掉，不然重启又回来了3. 时间同步这个我强调过无数次了。集群节点之间时间误差超过几秒，认证证书直接失效，节点状态全是 NotReady。装个 chrony 也就是一分钟的事儿。第二阶段：单机急速部署（Hello World 级别）如果你只是想在笔记本虚拟机或者开发环境试水，用这个模式。官方给的脚本是 curl -sfL https://get.k3s.io | sh -。但是！ 如果你在国内，直接跑这个脚本，你可能会卡在下载二进制文件那一步，或者卡在拉取 pause 镜像那一步。咱们用个“魔改版”的命令，利用国内的镜像加速：curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | \ INSTALL_K3S_MIRROR=cn \ sh -这就完了？对，这就完了。但是，作为一个老鸟，我通常会加几个参数，让它更符合我的使用习惯：curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | \ INSTALL_K3S_MIRROR=cn \ INSTALL_K3S_EXEC="--write-kubeconfig-mode 644 --disable-cloud-controller" \ sh -解释一下这几个参数（这都是经验）：--write-kubeconfig-mode 644：这个太重要了。默认情况下，生成的 kubeconfig 文件只有 root 能读。你稍微切个普通用户想跑 kubectl，直接 Permission denied。加上这个，省去后续 chmod 的麻烦。--disable-cloud-controller：因为咱们是在裸机或者虚拟机跑，不需要去连AWS或者阿里云的云控制器，禁用了能省点内存。验证一下：装完后，等个几十秒（取决于你的网速拉镜像快慢），运行：kubectl get nodes如果看到状态是 Ready，恭喜你，你的单节点K3s集群已经活了。第三阶段：生产级集群搭建（Server + Agent 模式）好，现在咱们来点真格的。假设你有三台机器：192.168.1.10 (Master/Server)192.168.1.11 (Worker/Agent)192.168.1.12 (Worker/Agent)1. 启动 Master 节点在 1.10 上执行：curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | \ INSTALL_K3S_MIRROR=cn \ INSTALL_K3S_EXEC="--node-ip 192.168.1.10 --flannel-iface eth0" \ sh ---node-ip：指定节点IP，防止多网卡环境K3s识别错了。--flannel-iface：指定Flannel网络走哪个网口。这个极其重要！我看过太多集群不通，就是因为Flannel自动选了个虚拟网卡或者错误的内网卡。2. 获取 TokenMaster 起来后，我们需要一个“通关密令”才能让 Worker 加入进来。在 Master 上执行：cat /var/lib/rancher/k3s/server/node-token你会得到一串长长的字符串，类似 K10xxxxx::server:xxxx。复制它。3. 加入 Worker 节点在 1.11 和 1.12 上，分别执行：# 替换下面的 IP 和 TOKEN export K3S_URL=https://192.168.1.10:6443 export K3S_TOKEN=上面复制的那串字符 curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | \ INSTALL_K3S_MIRROR=cn \ sh -这时候，你回到 Master 节点，狂敲 kubectl get nodes，你会看到那两个兄弟慢慢地从 NotReady 变成 Ready。这就组建完成了一个分布式的K8s集群。是不是比 kubeadm 简单了一万倍？第四阶段：实战使用（别光看着，动起来）集群搭好了如果不跑应用，那就是浪费电。咱们来一套标准的组合拳：部署 Nginx + 发布服务 + 挂载存储。1. 部署一个简单的 Nginx别整那些复杂的 Helm Chart，咱们手写个 YAML，这才是运维的基本功。创建一个文件 my-nginx.yaml：apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo labels: app: nginx spec: replicas: 2 # 跑两个副本，看看负载均衡 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:alpine ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: nginx ports: - protocol: TCP port: 80 targetPort: 80执行部署：kubectl apply -f my-nginx.yaml查看状态：kubectl get pods -o wide你应该能看到两个 Pod 分布在不同的节点上（如果调度正常的话）。2. 暴露服务（Traefik Ingress 的妙用）K3s 默认集成了 Traefik Ingress Controller。这玩意儿好用就好用在，你不需要额外安装 Nginx Ingress 了。直接写一个 Ingress 规则文件 ingress.yaml：apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress annotations: # 这一行很重要，告诉K8s不要用默认的class，而是明确指定traefik kubernetes.io/ingress.class: "traefik" spec: rules: - http: paths: - path: / pathType: Prefix backend: service: name: nginx-service port: number: 80执行 kubectl apply -f ingress.yaml。现在，神奇的事情发生了。打开你浏览器的，输入 任意一个节点 的 IP 地址（比如 http://192.168.1.10）。你应该能看到 Nginx 的“Welcome”页面。为什么？因为 K3s 的 Service LoadBalancer 默认也是开的（svclb），Traefik 默认监听宿主机的 80 和 443 端口。这就是“开箱即用”的爽点。你不需要搞 NodePort 那个丑陋的端口号（比如 30080）。3. 存储实战（Local Path Provisioner）K8s 最头疼的是存储。你得有 NFS、Ceph 或者云盘。但在 K3s 里，它自带了一个 local-path 的 StorageClass。说白了，就是把 Pod 的数据存在 宿主机的某个目录下。咱们来试一下。创建一个 PVC（持久化卷声明）文件 pvc.yaml：apiVersion: v1 kind: PersistentVolumeClaim metadata: name: local-path-pvc spec: accessModes: - ReadWriteOnce storageClassName: local-path resources: requests: storage: 1GiApply 它，然后 kubectl get pvc。你会发现状态立马变成了 Bound。不需要你手动创建 PV，不需要你配置 NFS Server。数据存在哪？默认在宿主机的 /var/lib/rancher/k3s/storage/ 下面。这对于跑数据库测试、或者简单的文件存储，简直不要太方便。第五阶段：那些让你怀疑人生的“坑”（进阶必读）要是文章只写到上面，那也就是个入门教程。下面这些才是我在生产环境摸爬滚打出来的经验。1. 离线安装（Airgap）怎么搞？企业内网通常是不通外网的。你跑那个 install.sh 脚本绝对报错。你需要做三件事：去 GitHub Releases 页面下载 k3s 二进制文件。下载 k3s-airgap-images-amd64.tar 镜像包。下载 install.sh 脚本。关键步骤：把 k3s 二进制扔到 /usr/local/bin/ 并给执行权限。把镜像包扔到 /var/lib/rancher/k3s/agent/images/ 目录下（目录不存在就创建）。然后执行脚本的时候加个参数：INSTALL_K3S_SKIP_DOWNLOAD=true。这样，K3s 启动时会自动导入那个 tar 包里的镜像，完全不需要联网。2. Docker 还是 Containerd？K3s 默认用 Containerd。很多兄弟习惯了 docker ps，进来一看傻眼了，命令没了。你可以用 crictl 代替。crictl ps 就是 docker ps。crictl images 就是 docker images。如果你非要用 Docker（比如有些老旧的 CI/CD 脚本依赖 docker.sock），你可以在安装时加上 --docker 参数。但我强烈建议你适应 Containerd，这才是 K8s 的未来。3. 证书过期了怎么办？K3s 的证书有效期默认是一年。以前这是个大坑，集群跑了一年突然挂了，日志全是 x509 报错。好消息是，现在的 K3s 版本在服务重启时会自动轮转证书。只要你不是一年都不重启 K3s 服务，基本上不会遇到这个问题。如果不放心，可以在 crontab 里加个计划任务，每个月重启一次 systemctl restart k3s。4. 卸载（重头再来）折腾坏了？环境乱了？K3s 极其贴心地提供了一键卸载脚本。Master 节点运行：/usr/local/bin/k3s-uninstall.shAgent 节点运行：/usr/local/bin/k3s-agent-uninstall.sh跑完之后，干干净净，就像什么都没发生过一样。总结：动起来才是硬道理兄弟们，K3s 真的是个好东西。它把 K8s 拉下了神坛，让咱们每个人都能在哪怕几百块钱的服务器上玩转云原生。别光收藏不练。今晚回去，找个虚拟机，按照我上面的步骤跑一遍。当你看到 Nginx 页面弹出来的那一刻，你会发现 K8s 其实也没那么可怕。如果在安装过程中遇到什么奇葩报错，或者想聊聊更高级的玩法（比如 K3s 高可用数据库对接），欢迎在评论区留言。我是那个在机房里给你填坑的老哥，咱们下篇见！公众号：运维躬行录个人博客：躬行笔记