⚠️ AI 生成标识 · 本文由 AI Agent 协助撰写,人类作者审核发布。
前言
想象这样一个场景:你有一个 AI Agent 跑在 Docker 容器里,它需要访问宿主机上的资源——查日志、看进程、管理文件、甚至部署服务。但容器默认是隔离的,怎么让它”走出”容器,触达宿主机?
最近我在部署自己的 AI Agent 时遇到了这个问题,最终通过 SSH 密钥认证 + sudo 权限配置解决了。这篇博客聊聊背后的原理和具体操作。
一、Docker 网络:容器与宿主机是邻居
1.1 默认的 Docker 网络
当我们启动一个 Docker 容器时(不指定 --network),它默认连接到 docker0 网桥:
1
2
3
4
5
6
7
8
9
10
11
12
宿主机
┌────────────────────────────────────┐
│ eth0 (公网 IP) │
│ │
│ docker0 网桥 (172.17.0.1/16) │
│ │ │
│ ├── 容器 A (172.17.0.2) │
│ ├── 容器 B (172.17.0.3) │
│ └── ... │
│ │
│ SSH 服务 (sshd) 监听 :22 │
└────────────────────────────────────┘
关键认知: 宿主机在 docker0 网桥上的 IP 永远是 172.17.0.1(网桥网关)。容器在同一个二层网络中,可以直接访问宿主机——不需要经过公网,没有 NAT 穿透问题。
1.2 实际场景:多网桥接入
但实际情况可能更复杂。在我部署的 QwenPaw 容器中,容器同时接入了多个 Docker 网桥:
1
2
3
4
5
6
7
8
9
10
# 容器内查看网络接口
$ hostname -I
172.16.0.10 172.17.0.1 172.18.0.1 172.19.0.1
# 路由表
$ cat /proc/net/route
eth0 172.16.0.0/16 直连 # 自定义网桥
docker0 172.17.0.0/16 直连 # 默认 Docker 网桥
br-... 172.18.0.0/16 直连 # 其他自定义网桥
br-... 172.19.0.0/16 直连 # 其他自定义网桥
容器的实际网络拓扑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
宿主机
┌─────────────────────────────────────────────────┐
│ docker0 (172.17.0.1/16) 自定义网桥 (172.16.0.1/28) │
│ │ │ │
│ └──────────┬─────────────────┘ │
│ │ │
│ QwenPaw 容器 │
│ ┌────────────────┐ │
│ │ eth0: 172.16.0.10 │ │
│ │ docker0: 172.17.0.x │ │
│ └────────────────┘ │
│ │
│ SSH 服务 (sshd) 监听 :22 │
└─────────────────────────────────────────────────────────┘
这意味着容器同时在多个二层网络中,通过 docker0 网桥的 172.17.0.1 直连宿主机,这是 SSH 访问的基础。
二、SSH 密钥认证:比密码更安全
2.1 为什么不用密码?
你可能会想:既然宿主机有 SSH 服务,用户名密码登录不就行了?
有两个问题:
- 密码在网络中传输(即使是加密的 SSH 协议,密码认证也需要交互式输入,不适合自动化场景)
- Agent 需要无交互登录——它不能弹出一个”请输入密码”的对话框
2.2 密钥认证的工作原理
SSH 密钥认证使用非对称加密(ED25519 或 RSA),流程如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
容器(客户端) 宿主机(服务端)
│ │
│ 1. 发起 SSH 连接请求 │
│─────────────────────────────────>│
│ │
│ 2. 查 ~/.ssh/authorized_keys │
│ 3. 生成随机数 challenge │
│<─────────────────────────────────│
│ │
│ 4. 用私钥签名 challenge │
│─────────────────────────────────>│
│ │
│ 5. 用公钥验证签名 │
│ 6. 验证通过 → 登录成功! │
│<─────────────────────────────────│
安全优势:
- 私钥永不出客户端——只在本地签名,不传输
- 公钥即使泄露——没有私钥也登不上
- 挑战-应答机制——防止重放攻击
- 比密码更安全,同时支持无交互自动化
2.3 实操:生成密钥对
容器内生成 ED25519 密钥对(比 RSA 更短、更快、更安全):
1
ssh-keygen -t ed25519 -f ~/.ssh/host_key -N ""
得到两个文件:
~/.ssh/host_key—— 私钥(权限 600,绝不能泄露)~/.ssh/host_key.pub—— 公钥(可以公开)
2.4 部署公钥到宿主机
把公钥内容追加到宿主机 ai 用户的 ~/.ssh/authorized_keys:
1
2
3
4
5
# 宿主机上执行
echo 'ssh-ed25519 AAAA... agent-friday@container' >> ~ai/.ssh/authorized_keys
chmod 600 ~ai/.ssh/authorized_keys
chmod 700 ~ai/.ssh
chown -R ai:ai ~ai/.ssh
权限很重要: authorized_keys 必须是 600,.ssh 目录必须是 700,否则 sshd 会拒绝使用。
三、SSH Config:从繁琐到优雅
3.1 没有配置的痛苦
没有配置时,每次连接要敲:
1
ssh -i ~/.ssh/host_key -o StrictHostKeyChecking=no [email protected]
参数又多又长,还容易敲错。
3.2 配置 SSH Config
在 ~/.ssh/config 中添加:
1
2
3
4
5
Host host
HostName 172.17.0.1
User ai
IdentityFile ~/.ssh/host_key
StrictHostKeyChecking no
就这样,以后在容器内只需要:
1
2
3
ssh host # 登录
ssh host "命令" # 远程执行
ssh host < script # 远程执行脚本
简洁、优雅、无交互。
四、持久化:容器重建怎么办?
4.1 Docker 容器的文件系统
Docker 容器的文件系统分为两类:
| 路径 | 持久性 | 说明 |
|---|---|---|
/tmp/ |
❌ 销毁 | 随容器销毁 |
/root/.ssh/ |
❌ overlay 层 | 重建丢失 |
挂载卷(如 /app/working/) |
✅ 持久化 | 宿主机磁盘,永久保留 |
4.2 持久化方案
把 SSH 密钥和配置放在挂载卷中,容器启动时自动恢复:
1
2
3
4
# 备份位置:/app/working/.ssh_host/
# 包含:host_key, host_key.pub, config, restore_ssh.sh
# 容器重建后执行:
bash /app/working/.ssh_host/restore_ssh.sh
甚至可以更进一步——Agent 每次需要 SSH 时自动检测连接是否可用,失效则自动恢复,对用户完全透明。
五、权限升级:从普通用户到 sudo
5.1 普通用户的限制
SSH 登录到宿主机后,ai 用户默认是受限普通用户:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ groups ai
ai users
# 不能操作 Docker
$ docker ps
permission denied while trying to connect to the Docker socket
# 不能读系统日志
$ cat /var/log/syslog
Permission denied
# 不能重启服务
$ systemctl restart xxx
Failed to restart xxx: Access denied
5.2 加入 sudo 组
要让 Agent 真正能干”运维活”,需要给 ai 用户赋予 sudo 权限:
1
2
# root 执行
usermod -aG sudo ai
但默认的 sudo 配置需要密码,Agent 的非交互式 SSH 无法输入密码。所以需要配置 NOPASSWD:
1
2
# root 执行
echo "ai ALL=(ALL) NOPASSWD:ALL" | tee /etc/sudoers.d/ai
验证:
1
2
$ sudo -n whoami
root
-n 参数表示非交互模式,如果 sudo 需要密码但没提供,会直接报错而不是挂起等待。配置 NOPASSWD 后,Agent 可以放心使用 sudo 执行任意命令。
5.3 升级后的能力对比
| 操作 | 普通用户 | 有 sudo 权限 |
|---|---|---|
| 查看系统资源 | ✅ free -h |
✅ 不变 |
| 操作 Docker | ❌ | ✅ sudo docker ps/logs/stats |
| 读系统日志 | ❌ | ✅ sudo tail -f /var/log/syslog |
| 重启服务 | ❌ | ✅ sudo systemctl restart xxx |
| 查看任意文件 | ❌ | ✅ sudo cat /etc/sudoers |
| 管理容器 | ❌ | ✅ sudo docker rm/stop/exec |
有了 sudo,Agent 的能力边界从”查看”扩展到了”操控”。
六、一些思考
6.1 为什么选择 SSH 而不是其他方式?
其实还有几种方案可以实现容器→宿主机通信:
| 方案 | 优点 | 缺点 |
|---|---|---|
| SSH | 标准、安全、无侵入 | 需要额外配置 |
| Docker Socket 挂载 | 可以直接管理 Docker | 权限过大,不安全 |
| HTTP API | 需要额外开发 | 需要部署服务端 |
| 共享 Volume | 简单直接 | 只能读写文件,不能执行命令 |
SSH 胜在标准化——几乎任何 Linux 宿主机都自带 sshd,不需要额外安装任何软件,而且支持执行任意命令、传输文件、端口转发,非常灵活。
6.2 Agent 的”延伸”之路
从 Agent 的角度看,SSH 到宿主机意味着它的能力边界从容器扩展到了整个宿主机,再配合 sudo 权限,可以做到:
1
2
3
4
5
6
7
8
9
10
11
容器内 Agent
│
├── SSH → 宿主机 shell(任意命令)
│ ├── sudo docker → 管理容器
│ ├── sudo systemctl → 管理服务
│ ├── sudo tail/vim → 读/写系统文件
│ └── sudo apt → 安装软件
│
├── SCP → 宿主机文件系统(传输文件)
│
└── 端口转发 → 宿主机网络(访问内部服务)
这是 Agent 与基础设施融合的关键一步——从”容器里的一个对话窗口”到”能真正干活的运维助手”。
总结
整个过程的核心思路是:
- 容器和宿主机在同一个网络里,这是通信的基础
- SSH 密钥认证比密码更安全、更适合自动化
- SSH Config 把繁琐的命令简化为一句
ssh host - 注意持久化,容器重建后需要恢复配置
- sudo 权限让 Agent 从”只读”升级到”可操控”,真正具备运维能力
有了这套配置,Docker 容器里的 AI Agent 就能像本地管理员一样访问和操作宿主机了。
如果你也有类似的 Agent 部署需求,或者有其他有趣的”容器跨界”方案,欢迎交流讨论!