为什么 cAdvisor 在部分节点采集不到容器的内存数据?

最近在搞跨云节点的容器监控,遇到一个非常诡异的问题:在 Grafana 面板上,有很多容器拉不到内存信息。

具体表现是,看 container_memory_max_usage_bytes{image!=""} 这个指标时,老节点的数据一切正常,能读到具体的字节数;但新节点的数据全都是 0

仔细对比了一下指标的 id 标签:

  • 有数据的节点/docker/5282f5...
  • 没数据的节点/system.slice/docker-153d1e...scope

路径明显不一致。为了解决这个”断流”问题,踩了几个坑,在这里记录一下排查和修复的全过程。

坑一:盲改 cAdvisor 启动参数

一开始我以为是 cAdvisor 的 cgroup 挂载路径偏移导致的。因为在新节点里,Docker 使用了 systemd 作为 Cgroup Driver。

于是我尝试修改 cadvisor.service 文件,加入了 --runtime_full_cgroup_path=true 参数。结果改完重启,Systemd 直接报错 Failed with result 'exit-code'

查看 journalctl -u cadvisor -f 发现两个致命错误:

  1. 参数语法错误:我把 KillMode=process 写在了 ExecStart 后面,导致解析失败。
  2. 版本弃用:我用的是 v0.46.0 版本的 cAdvisor,这个版本已经完全弃用-runtime_full_cgroup_path 参数!强行加进去只会触发 flag provided but not defined 报错。

坑二:寻找消失的物理路径

既然参数没法调,我决定直接去宿主机上看看 cgroup 的实际目录层级。 按照以往的经验,我执行了:

ls /sys/fs/cgroup/memory/system.slice/docker-*.scope

结果提示:文件不存在。

为了精准定位,采用反向溯源的方法,先拿到容器内主进程的 PID,然后直接看它挂载在哪里:

# 拿到容器的 PID
docker inspect -f '{{.State.Pid}}' iperf-server

# 查看对应 PID 的 cgroup (假设 PID 是 12345)
cat /proc/12345/cgroup

输出结果: 0::/system.slice/docker-153d1e883aa123826...scope

真相大白:Cgroup v1 与 v2 的代沟

看到开头的 0::,懂了。这是 Cgroup v2(统一层级)的特征签名。

根本不是 cAdvisor 挂载错了,也不是参数配错了,而是底层操作系统架构变了:

  • 老节点跑的是 Cgroup v1,有单独的 /memory/ 目录,内核提供了最大内存使用量的统计接口,所以 cAdvisor 能读到数据。
  • 新节点跑的是 Cgroup v2,底层去掉了 memory 目录。在 Cgroup v2 环境下,cAdvisor 获取不到历史极值,于是强行给 container_memory_max_usage_bytes 这个指标塞了一个 0

最终方案:在 PromQL 层面无缝缝合

既然底层都不提供这个数据了,死磕 cAdvisor 配置毫无意义。解决办法是直接在查询层更换跨版本兼容的”真实内存”指标:container_memory_working_set_bytes

但我希望能保留老节点上 max_usage 的历史数据。

利用 PromQL 的 or 逻辑,实现新老数据的缝合替换。修改 Grafana 的查询语句如下:

(container_memory_max_usage_bytes{image!=""} > 0)
or
(container_memory_working_set_bytes{image!=""})

执行逻辑:

  1. 首先尝试获取老指标。因为新节点这个值全是 0,所以 > 0 的条件会把新节点直接过滤掉。
  2. 对老节点来说,左侧条件成立,画出老指标的线。
  3. 对新节点来说,左侧为空,触发 or 逻辑,自动替补使用右侧的新指标 working_set 并在图表上画线。

保存查询,刷新面板。

总结:排查监控数据缺失问题,不要一上来就去猜参数。顺藤摸瓜,从 PID 反查 cgroup 挂载点,直接看操作系统的底层到底给了什么,从根本上解决问题。


Mac 上安装 OrbStack 替代 Docker Desktop

什么是 OrbStack

OrbStack 是一个轻量级的 Docker 替代工具,相比 Docker Desktop:

  • 启动更快
  • 资源占用更低
  • 有原生 macOS GUI
  • 免费(基本功能)

安装过程

1. 下载安装

https://orbstack.dev/download

下载完成后,双击打开 .dmg 文件,将 OrbStack 拖入 Applications 文件夹即可。

2. 启动 Docker

打开 OrbStack 应用,点击 “Start” 按钮启动 Docker 环境。

验证安装:

$ orbctl status
Running

$ docker version --format '{{.Server.Version}}'
28.5.2

配置国内镜像源

配置 Docker 的镜像源来加速拉取。

1. 创建配置文件

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": [
    "https://registry.docker-cn.com",
    "https://mirror.ccs.tencentyun.com",
    "https://docker.mirrors.ustc.edu.cn"
  ]
}
EOF

2. 重启 Docker

在 OrbStack 菜单中:

  • Stop Docker
  • Start Docker

测试运行

$ docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

完美!🎉

参考


Mac Studio 安装 OpenClaw 多实例共存实录

最近在研究多 Agent 编排,本机已经跑着 copaw。今天心血来潮,决定把底层的 openclaw 单独拎出来跑个 UI 界面看看。

做个记录,备忘。

1. Opencode 辅助安装:一场优雅的权限绕过

为了省事,我直接唤醒了终端里的 AI 助手(Opencode,挂的 MiniMax 模型)帮我装。AI 的排错逻辑挺有意思:

  1. 它首先尝试了官方的无脑脚本:curl -fsSL https://openclaw.ai/install.sh | bash
  2. 发现走不通,立刻切成了全局 NPM 安装:npm install -g openclaw@latest
  3. 随后触发了经典报错:/usr/local/bin/openclaw 文件已存在,引发软链接冲突。
  4. 它的第一反应是 sudo rm 强删,但被系统的交互式密码输入卡住了(终端里的 AI 无法直接帮你输密码)。
  5. 接着它立刻放弃提权,直接用普通权限 rm -f /usr/local/bin/openclaw && npm install -g openclaw@latest。因为我当前账户对该目录有写权限,直接物理抹除并重新接管成功。

最终拿到了 OpenClaw 2026.3.28 版本。AI 时代,连写环境配置都变成了一种结对编程。

image-20260330下午31734247

image-20260330下午31826704

image-20260330下午31858691

image-20260330下午32135388

image-20260330下午32433093

2. 端口冲突

装完后,按常规流程初始化:

openclaw setup
openclaw gateway

结果直接吃了个闭门羹:日志提示 pid 539 kelu: openclaw-gateway (*:18789)。18789 端口已经被占用了。

因为我本机还在跑着 copaw,copaw 也是将 OpenClaw 作为底层通信引擎,早就已经在后台静默拉起了一个 Gateway 守护进程,用了 18789 端口和默认的 ~/.openclaw 工作区。

3. 开启 Dev 沙盒模式

我想同时保留 copaw 的运行状态,又想自己独立玩 OpenClaw 的 Dashboard。既然不能同归于尽,那就只能物理隔离。

OpenClaw 官方提供了一个 --dev 参数,可以解决了多实例共存的问题。

启动独立网关:

openclaw --dev gateway

这行命令直接开辟了一个平行宇宙:

  • 配置文件被隔离到了 ~/.openclaw-dev/openclaw.json
  • 数据工作区被重定向到了 ~/.openclaw/workspace-dev
  • 网关端口自动偏移到了 19001
  • 甚至连局域网的 Bonjour 广播名字,都聪明地加上了后缀 kelu的Mac Studio (OpenClaw) (2) 以防冲突

image-20260330下午30028713

4. 唤醒 UI 控制台

后台引擎在 19001 端口平稳怠速后,新开一个终端页,带上 dev 参数直连:

openclaw --dev dashboard

浏览器瞬间弹出。虽然目前版本的 Web UI 纯英文,没有任何 zh-CN 的汉化选项,但这并不妨碍。

image-20260401上午95833705


清理 Docker 残留网络:解决 ifconfig 中陌生的网桥接口

最近在服务器上查看网络配置,发现 ifconfig 里多了两个陌生的网桥接口:

br-112c7faa4714 br-8f522120d6b1

不记得自己创建过这些。查了一下,原来是 Docker 留下的”网络垃圾”。

发生了什么?

用 Docker Compose 跑过容器后,即使容器已经删除了,Docker 网络可能还留着。

比如之前部署 n8n,用 docker-compose up -d 会自动创建两个网络:

  • n8n_default
  • n8n_n8n-network

后来 n8n 容器不跑了,但这两个网络还在系统里拖着,网桥接口自然就出现了。

怎么查看?

# 查看所有 Docker 网络
docker network ls

# 查看具体网络的连接情况
docker network inspect <网络名>

Containers 字段,如果是空的 {},说明没有容器连这个网络了:

"Containers": {}

怎么清理?

# 删除指定网络
docker network rm n8n_n8n-network
docker network rm n8n_default

删掉之后,ifconfig 里那些陌生的网桥接口就消失了。

更好的习惯

下次用 docker-compose down 的时候,加 --remove-orphans 参数:

docker-compose down --remove-orphans

这样会顺便把 Compose 创建的网络一起清理掉,避免留下垃圾。

一键清理脚本

如果想偷懒,可以定期跑这个,清理所有未使用的网络:

docker network prune -f

会删除所有没有被容器使用的网络。

一句话总结:容器删了不等于网络会删,定期 docker network prune 能让服务器干净点。


RSSHub 安装笔记

之前一直用 Feedly 订阅各类 RSS,但发现有些网站没有原生 RSS,不能直接订阅。听说 RSSHub 可以给这些网站生成 RSS,于是决定自己装一个。

环境

  • 服务器:Linux
  • Docker 已安装
  • 想把服务跑在 1200 端口

安装步骤

1. 创建目录

mkdir -p /root/docker/rsshub
cd /root/docker/rsshub

2. 配置 docker-compose

version: '3.3'

services:
  rsshub:
    image: ghcr.io/diygod/rsshub:latest
    container_name: rsshub
    restart: unless-stopped
    network_mode: host
    environment:
      - NODE_ENV=production
      - PORT=1200
      - CACHE_TYPE=memory
      - CACHE_EXPIRE=60
    volumes:
      - ./data:/tmp/rsshub

这里用了 host 网络模式,端口直接映射到宿主机。数据目录挂载到 ./data,方便持久化。

3. 启动服务

mkdir -p data
docker-compose up -d

4. 验证

curl http://localhost:1200/

看到 Welcome 页面就说明装好了。

小结

RSSHub 装起来比想象中简单,Docker 化部署基本上就是写个配置文件的事。

参考

  • RSSHub 官网:https://rsshub.app
  • Docker Hub:https://hub.docker.com/r/diygod/rsshub

n8n Docker 部署指南

最近在搭自动化工具,选了 n8n。网上教程不少,但要么版本太旧,要么踩坑没写清楚。

我把自己实际部署的过程记录下来,配上完整的配置文件。你跟着做一遍,应该能跑起来。

n8n 是什么?

简单说,n8n 是个开源的工作流自动化工具。可以理解为:

  • IFTTT 的开源替代品
  • 支持可视化编排
  • 能连上百种服务(Slack、GitHub、数据库、API 等等)
  • 自托管版完全免费,无限工作流,无限商用

官方提供云服务(收费),也支持自己部署(免费)。本文讲的是自己部署。

总结

核心就这几步:

  1. 准备目录
  2. 生成密钥密码
  3. 写配置文件
  4. 修复权限
  5. 启动

环境准备

必备条件

# 1. 确保 Docker 安装好了
docker --version
# 推荐:Docker 20.10+

# 2. 确保 Docker Compose 有
docker-compose --version
# 推荐:v2.0+

准备目录

# 创建部署目录
mkdir -p /root/n8n/{data,backups}
cd /root/n8n

第一步:生成密码和密钥

这一步会生成两个关键配置:

  1. 管理员密码 - 登录 Web 界面用
  2. 加密密钥 - n8n 用来加密存储的凭证信息(非常重要,丢了就解密不了)
# 生成 64 位十六进制加密密钥
openssl rand -hex 32
# 复制输出,后面要用。示例:
# a155a297a3cf9f3a9fbb4eadf3b4be08adcb9cff7f3e152e2575b7b23f1b3ade

# 生成管理员密码(20位,包含大小写、数字、特殊字符)
openssl rand -base64 24 | tr -dc 'A-Za-z0-9!@#$%^&*' | head -c 20
# 复制输出。示例:
# dgYN71mMEbkS3uD5djSi

# 获取服务器 IP(用于 Webhook URL)
hostname -I | awk '{print $1}'
# 示例输出:10.0.12.8

第二步:创建配置文件

.env 文件

创建一个 .env 文件,把上面的值填进去:

# /root/n8n/.env

# ========== 认证配置 ==========
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
# 把刚才生成的密码填这里
N8N_BASIC_AUTH_PASSWORD=dgYN71mMEbkS3uD55555

# ========== 加密密钥 ==========
# 把刚才生成的 64 位密钥填这里
N8N_ENCRYPTION_KEY=a155a297a3cf9f3a9fbb4eadf3b4be08adcb9cff7f3e152e2575b7b23f666666

# ========== 网络配置 ==========
N8N_HOST=0.0.0.0
N8N_PORT=5678
N8N_PROTOCOL=http
TZ=Asia/Shanghai
# 改成你的服务器 IP
WEBHOOK_URL=http://10.0.12.8:5678/

# ========== 数据库配置 ==========
# 用 SQLite,最简单
DB_TYPE=sqlite

# ========== 执行配置 ==========
# 生产环境别保存执行数据,省空间
EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168

# ========== 安全设置 ==========
# HTTP 访问时关闭安全 Cookie 检查
N8N_SECURE_COOKIE=false

# ========== 跳过账户创建 ==========
N8N_SKIP_CREATION_DURING_STARTUP=true

# ========== 禁用版本通知(可选) ==========
# 关闭后不会弹 "Your license key is on the way" 提示
N8N_VERSION_NOTIFICATIONS_ENABLED=false
N8N_DIAGNOSTICS_ENABLED=false

设置权限:

chmod 600 /root/n8n/.env

docker-compose.yml 文件

# /root/n8n/docker-compose.yml
version: '3.3'

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    # 用 host 网络模式,容器直接用宿主机网络
    # 好处:内网能访问,不会把端口暴露到外网
    network_mode: host
    volumes:
      # 挂载数据目录
      - ./data:/home/node/.n8n:rw
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - N8N_HOST=0.0.0.0
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
      - TZ=Asia/Shanghai
      - WEBHOOK_URL=http://10.0.12.8:5678/
      
      - DB_TYPE=sqlite
      - EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
      - EXECUTIONS_DATA_PRUNE=true
      - EXECUTIONS_DATA_MAX_AGE=168
      
      - N8N_SECURE_COOKIE=false
      - N8N_SKIP_CREATION_DURING_STARTUP=true
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider http://localhost:5678/healthz || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    container_name: n8n

第三步:修复权限

这是很多人会踩的坑。

Docker 容器里的 n8n 是用 UID 1000 这个用户运行的。如果你直接把 ./data 目录挂载进去,而目录是 root:root 权限,容器就没法写东西。

# 修复权限
chown -R 1000:1000 /root/n8n/data
chmod 755 /root/n8n/data

第四步:启动

# 启动容器
docker-compose up -d

# 等待几秒让容器启动
sleep 15

# 检查状态
docker ps | grep n8n
# 应该看到 n8n 这个容器,状态是 Up

# 检查健康状态
docker inspect --format='{{.State.Health.Status}}' n8n
# 应该输出:healthy

第五步:访问测试

# 本地测试
curl -s -o /dev/null -w "%{http_code}" http://localhost:5678
# 应该返回 200

# 内网测试(把 IP 换成你的服务器 IP)
curl -s -o /dev/null -w "%{http_code}" http://10.0.12.8:5678
# 应该返回 200

打开浏览器访问:http://你的服务器IP:5678

首次访问会让你创建管理员账户,用之前在 .env 里配置的用户名密码登录即可。

我踩过的坑

坑一:PostgreSQL 版本太旧

最初我想用已有的 PostgreSQL 9.4:

syntax error at or near "NOT"
CREATE INDEX IF NOT EXISTS IDX_xxx

原因:n8n 用的 IF NOT EXISTS 语法,PostgreSQL 9.4 不支持。11 以上版本才支持。

解决方案:改用 SQLite,n8n 原生支持,不需要额外部署数据库。

坑二:权限被拒绝

Error: EACCES: permission denied, open '/home/node/.n8n/config'

原因:挂载目录权限是 root:root,容器里用 1000 用户跑,没权限写。

解决方案:见「第三步:修复权限」

浏览器打开时提示安全 Cookie 问题。

原因:n8n 检测到 HTTP 访问但配置了安全 Cookie。

解决方案:在环境变量加 N8N_SECURE_COOKIE=false

坑四:收到许可证邮件

收到邮件说 “Your license key is on the way!”。

原因:n8n Cloud 的营销邮件。自托管版完全免费,不需要许可证。

解决方案一:禁用版本检查(推荐)

如果你不想处理邮件,直接禁用版本通知:

# 追加到 .env 文件
echo "N8N_VERSION_NOTIFICATIONS_ENABLED=false" >> /root/n8n/.env
echo "N8N_DIAGNOSTICS_ENABLED=false" >> /root/n8n/.env

# 重启
./n8n.sh restart

重启后 “Your license key is on the way” 的提示会消失。

解决方案二:激活(可选)

  1. 查邮箱
  2. 找 n8n 发来的邮件
  3. 复制 License Key 填入页面

结论:自托管版完全免费,不影响使用。禁用版本通知最简单。

常用命令

# 启动
./n8n.sh start

# 停止
./n8n.sh stop

# 重启
./n8n.sh restart

# 查看日志
./n8n.sh logs          # 所有日志
./n8n.sh logs -f      # 实时日志
./n8n.sh logs n8n     # 只看 n8n 日志

# 查看状态
./n8n.sh status

# 更新版本
./n8n.sh update

备份与恢复

备份命令

./backup.sh

备份文件会保存在 /root/n8n/backups/ 目录下,文件名类似 n8n_backup_20260212_153321.tar.gz

定时备份

加到 crontab,每天凌晨 2 点自动备份:

crontab -e

# 添加这行
0 2 * * * /root/n8n/backup.sh > /dev/null 2>&1

恢复备份

# 假设有个备份文件
tar xzf /root/n8n/backups/n8n_backup_20260212_153321.tar.gz -C /tmp/

# 恢复数据
docker cp /tmp/. n8n:/home/node/.n8n/

# 重启容器
./n8n.sh restart

最终目录结构

/root/n8n/
├── docker-compose.yml    # Docker 配置
├── .env                 # 敏感配置(权限 600)
├── n8n.sh              # 管理脚本
├── backup.sh           # 备份脚本
├── README.md           # 说明文档
├── data/               # SQLite 数据库文件
│   ├── database.sqlite
│   ├── database.sqlite-shm
│   └── database.sqlite-wal
└── backups/            # 备份文件
    └── n8n_backup_20260212_153321.tar.gz

常见问题

Q: 数据存在哪?

SQLite 数据库文件在 /root/n8n/data/database.sqlite

Q: 能改密码吗?

直接改 .env 文件里的 N8N_BASIC_AUTH_PASSWORD,然后重启容器:

./n8n.sh restart

Q: 加密密钥能改吗?

不能。这个密钥是用来加密存储的凭证的。如果改了,之前的凭证就解密不开了。

参考链接

  • n8n 官方文档:https://docs.n8n.io/
  • 自托管安装指南:https://docs.n8n.io/hosting/installation/self-hosted/
  • Docker 镜像:https://hub.docker.com/r/docker.n8n.io/n8nio/n8n

Chrome Gemini 强制开启指南

Gemini 已经内置于 Chrome,但 Google 对其开启增加了繁琐的“地理围栏”和灰度测试。以下是我在macOS下的开启的步骤,做个记录。

0x01 前置准备

  1. 节点伪装: 将代理切换至 美国节点 (US)

  2. 语言环境: 进入 Chrome 设置,将 界面语言 (Display Language) 切换为 English (United States),并重启浏览器。

    image-20260211下午70706177

  3. 身份定位: 确保你的 Google 账号设置或搜索设置中的 Region 也是 US(部分情况非必须,但建议对齐)。

    image-20260211下午71005399

    选择住址:

    image-20260211下午71121578

  4. 关闭chrome。

0x02 注入指令

open -n -a "Google Chrome" --args --variations-override-country=us
  • 原理:强制 Chrome 在启动时覆盖国家代码为 US,绕过服务端检测。

0x03 验证

操作完成后,打开 Chrome:

  1. 输入 chrome://components

  2. 查找 Optimization Guide On Device Model

  3. 如果看到版本号不为 0.0.0.0,说明本地模型已下载成功。

    image-20260211下午70850521

    还可以访问这个页面看关于Gemini更多的配置: <chrome://settings/ai/gemini>

  4. 开始使用,可以直接和Gemini对话了。

    image-20260211下午75702693

    image-20260211下午75822704

0x04 设置快捷命令

打开终端,编辑配置文件:vim ~/.zshrc

alias chrome='open -n -a "Google Chrome" --args --variations-override-country=us'

保存并执行 source ~/.zshrc