Why: CT124 observed "Connecting" from iPhone for hours while CT124 self-reported Connected. Root cause: DNS timeout ~7h earlier broke management gRPC, daemon didn't auto-recover after DNS returned. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
163 lines
9.1 KiB
Markdown
163 lines
9.1 KiB
Markdown
# NetBird 部署摘要
|
||
|
||
## 部署結果
|
||
|
||
**NetBird Self-Hosted 已部署於 `192.168.42.127`**
|
||
|
||
- 部署日期:2026-04-17
|
||
- 存取方式:`http://192.168.42.127`
|
||
- 部署模式:HTTP-only(無 TLS)、IP-only(無域名)
|
||
|
||
## 部署時的關鍵決策
|
||
|
||
### 1. 為何不用官方 `getting-started.sh` 腳本?
|
||
|
||
官方腳本是互動式的(讀取 `/dev/tty`),且預設選項需要公網域名與 Let's Encrypt。本次為內網 IP 部署,手動產生設定檔更直接。
|
||
|
||
腳本原始碼中支援 `NETBIRD_DOMAIN=use-ip` 模式(line 308),會自動偵測 IP 並切換為 HTTP、port 80、`rel://` relay 協定。本次部署採用了相同的邏輯但以手動設定檔實現。
|
||
|
||
### 2. 為何選 Caddy 而非 Traefik 作為反向代理?
|
||
|
||
- Traefik 內建 Let's Encrypt ACME,但無域名時會反覆嘗試失敗
|
||
- Caddy 設定檔更精簡(約 10 行),純 HTTP 路由對本場景足夠
|
||
- 兩者都能正確處理 gRPC (h2c) 後端
|
||
|
||
### 3. 為何需要 crun 取代 runc?
|
||
|
||
**遇到的錯誤**:
|
||
|
||
```
|
||
OCI runtime create failed: runc create failed: unable to start container process:
|
||
error during container init: open sysctl net.ipv4.ip_unprivileged_port_start file:
|
||
reopen fd 8: permission denied
|
||
```
|
||
|
||
**根因分析**:
|
||
- Proxmox 非特權 LXC 將 `/proc/sys` 掛載為唯讀(`ro`)
|
||
- runc 1.2+ 為防止 TOCTOU 攻擊,會用「reopen fd」機制存取 sysctl 檔案
|
||
- 此機制在 LXC 的 `/proc` mount namespace 限制下失敗
|
||
|
||
**嘗試但無效的方法**:
|
||
- `--privileged` flag
|
||
- `--security-opt apparmor=unconfined --security-opt seccomp=unconfined`
|
||
- `mount -o remount,rw /proc/sys`
|
||
- apt 安裝的 crun 1.14.1(OCI spec 太舊,Docker 不接受)
|
||
|
||
**最終解法**:
|
||
下載 GitHub 最新 crun binary (1.20),並設為 Docker 預設 runtime。
|
||
|
||
## 安裝步驟摘要
|
||
|
||
```
|
||
1. apt 安裝前置套件 (curl, jq, openssl) ← 約 1 分鐘
|
||
2. 安裝 Docker Engine ← 約 2 分鐘(網速慢)
|
||
3. 下載 crun binary,設定為 Docker 預設 runtime ← 約 30 秒
|
||
4. 建立 /opt/netbird/ 下四個設定檔 ← 約 30 秒
|
||
5. docker compose pull + up -d ← 約 1 分鐘
|
||
6. 等待 netbird-server 下載 GeoLite2 資料庫 ← 約 2-5 分鐘
|
||
```
|
||
|
||
總耗時約 10 分鐘(含 GeoIP 下載)。
|
||
|
||
## 服務驗證
|
||
|
||
所有服務已驗證正常運作:
|
||
|
||
| 端點 | 狀態 | 說明 |
|
||
|------|------|------|
|
||
| `http://192.168.42.127/` | 200 | Dashboard Web UI |
|
||
| `http://192.168.42.127/oauth2/.well-known/openid-configuration` | 200 | OIDC Discovery |
|
||
| STUN `:3478/udp` | | NAT 穿透 |
|
||
| 內部 gRPC (Signal + Management) | | h2c via Caddy |
|
||
|
||
## 疑難排解記錄
|
||
|
||
| 問題 | 原因 | 解法 |
|
||
|------|------|------|
|
||
| 初次 apt install 卡住 | 背景執行的 apt 行程未結束就被觸發第二個,導致 lock 競爭 | 清除 `/var/lib/dpkg/lock*` 與 `/var/lib/apt/lists/lock`,重啟 apt |
|
||
| `curl: command not found` | apt 仍在安裝中 | 等待安裝完成 |
|
||
| 所有 Docker 容器啟動失敗 | LXC `/proc/sys` 唯讀 + runc 1.3 的 reopen fd 機制 | 改用 crun 1.20 |
|
||
| Docker Compose `crun` runtime 未知 | `/etc/docker/daemon.json` 設定未生效 | `systemctl restart docker` |
|
||
| apt 的 crun 1.14.1 報 `unknown version specified` | crun 的 OCI spec 1.0.0 與 Docker 29.4 不相容 | 從 GitHub 下載 crun 1.20 |
|
||
| 502 Bad Gateway(所有 API 端點) | netbird-server 下載 GeoLite2 中,尚未開始監聽 port 80 | 等待 2-5 分鐘 |
|
||
| 手機 App:`server closed the stream without sending trailers` | 透過 NPM 對外公開時,NPM 用 `proxy_pass` 而非 `grpc_pass` 轉 gRPC;加上 Caddy `:80` 預設不支援 h2c | NPM 加 `advanced_config`(gRPC 路由)+ Caddy 全域啟用 `protocols h1 h2 h2c`;見 [npm-grpc-fix.md](./npm-grpc-fix.md) |
|
||
| `PUT /api/setup-keys/{id}` 回 200 但 `name` 沒改 | NetBird API 只更新 `auto_groups` 和 `revoked`,`name` 是 read-only | 改 `store.db` 的 `setup_keys.name` → 重啟 `netbird-server`;見 [setup-keys-and-pat.md](./setup-keys-and-pat.md) |
|
||
| peer 之間一直走 Relay,`[stun:...:3478] is Checking...` | STUN port 3478 被 Headscale (`.126`) 搶先佔用 DNAT | NetBird 改用 3479/udp(server config + OpenWrt DNAT);見 [stun-port-conflict.md](./stun-port-conflict.md) |
|
||
| STUN 都通了,仍然 `Connection type: Relayed`、`ICE candidate: -/-` | 客戶端 **Force Relay Connection** 開關被打開,跳過 ICE gathering | App Settings → Advanced → 關掉 Force Relay;見 [client-troubleshooting.md](./client-troubleshooting.md) |
|
||
| ping RTT 尖峰到 2-4 秒 | 當下 WiFi/ISP 有 bufferbloat | 用 `networkQuality -v` 確認(看 RPM),與 NetBird 無關 |
|
||
| Mac `netbird status` 卡在 `Management: Disconnected, rpc error` | daemon 本身卡死,`netbird up`/`down` 救不回 | `sudo launchctl kickstart -k system/netbird`(服務名是 `netbird` 而非 `io.netbird.client`);見 [peer-deployment-ops.md](./peer-deployment-ops.md) |
|
||
| LXC CT100 SSH root 密碼被擋(`Permission denied`) | sshd_config 內有 `PermitRootLogin prohibit-password` 比 `yes` 先出現,first-match-wins | 加 `/etc/ssh/sshd_config.d/99-allow-root.conf` 覆蓋;socket activation 不需重啟 sshd |
|
||
| 新 peer `netbird status` 顯示 Connected 但互 ping 都不通 | 主機同時跑 Tailscale kernel-mode,`iptables-legacy ts-input` 把 `100.64.0.0/10` 非 tailscale0 封包全 drop,NetBird `100.71.0.0/16` 中彈 | `iptables-legacy -I INPUT 1 -i wt0 -j ACCEPT` + FORWARD 同理;systemd 持久化;詳見 [peer-deployment-ops.md](./peer-deployment-ops.md) |
|
||
| 加 subnet route 後部分裝置拿不到路由(`Networks: -`、`Status: Connecting`) | 既有 NetBird daemon 不會自動重新套用 route,卡在舊 state | 各裝置重連 daemon:macOS `launchctl kickstart`、iOS App Disconnect→Connect、Docker `compose restart`;見 [network-routes.md](./network-routes.md) |
|
||
| iPhone RTSP app 連不到 LAN 攝影機(Safari 打 HTTP 可以) | 某些 iOS app 不走系統 VPN 路由 | 換支援 VPN 的 app(VLC 等),或改用 NetBird FQDN |
|
||
| Dashboard 顯示 "SSH Access is disabled" 但 `ssh` 指令照通 | Dashboard 的 "SSH Access" 是 NetBird 內建 SSH 通道,不等於目標機器的 OpenSSH | 兩種 SSH 是不同東西,不用理會那個提示;見 [ssh-access.md](./ssh-access.md) |
|
||
| 某 peer 一直顯示 Connecting,但其本機 status 看起來 Connected | DNS 短暫 timeout → daemon 的 management gRPC 連線卡死,不自癒(Signal 還 Connected,但 Management Disconnected) | 直接重啟 daemon / container;可選加 watchdog;見 [peer-deployment-ops.md](./peer-deployment-ops.md) |
|
||
|
||
## 檔案位置
|
||
|
||
### 本機(/Users/timmy/dev/42_127/)
|
||
|
||
```
|
||
├── README.md ← 專案總覽
|
||
├── QUICKSTART.md ← 快速部署步驟
|
||
├── SUMMARY.md ← 本檔(部署摘要)
|
||
├── netbird-selfhosted-setup.md ← 完整安裝指南
|
||
├── npm-grpc-fix.md ← 經 NPM 對外公開時的 gRPC 修復
|
||
├── setup-keys-and-pat.md ← Setup Keys / PAT 操作與 API 限制繞道
|
||
├── stun-port-conflict.md ← STUN 改用 3479 (3478 被 Headscale 佔用)
|
||
├── client-troubleshooting.md ← 客戶端 Force Relay / bufferbloat 排錯
|
||
├── peer-deployment-ops.md ← Docker 部署客戶端、改名、Mac daemon 重啟
|
||
├── groups-and-policies.md ← Groups 與 Access Control 設計與操作
|
||
├── network-routes.md ← Subnet Router 讓 peer 存取 LAN 非 NetBird 裝置
|
||
└── ssh-access.md ← 一般 SSH vs NetBird 內建 SSH 的差別
|
||
```
|
||
|
||
### 遠端伺服器(192.168.42.127:/opt/netbird/)
|
||
|
||
```
|
||
├── docker-compose.yml ← 服務編排
|
||
├── Caddyfile ← 反向代理路由
|
||
├── config.yaml ← NetBird Server 設定
|
||
└── dashboard.env ← Dashboard 環境變數
|
||
```
|
||
|
||
### Docker Volume
|
||
|
||
- `netbird_netbird_data`:NetBird SQLite 資料庫與 GeoIP 資料
|
||
|
||
## 後續維運
|
||
|
||
### 日常指令
|
||
|
||
```bash
|
||
ssh root@192.168.42.127
|
||
cd /opt/netbird
|
||
|
||
# 查看日誌
|
||
docker compose logs -f
|
||
|
||
# 重啟
|
||
docker compose restart
|
||
|
||
# 更新映像
|
||
docker compose pull && docker compose up -d
|
||
|
||
# 備份資料庫
|
||
docker compose exec netbird-server sqlite3 /var/lib/netbird/store.db ".backup /tmp/backup.db"
|
||
docker cp netbird-server:/tmp/backup.db ./backup-$(date +%F).db
|
||
```
|
||
|
||
### 可能的擴展
|
||
|
||
- **加上 TLS**:若之後取得域名,可切換為 Traefik + Let's Encrypt(或本機自簽憑證)
|
||
- **外部 IdP**:需支援 OIDC 的 IdP(如 Authentik、Keycloak、Google)可取代內建 Dex
|
||
- **高可用性**:SQLite 不支援叢集,需改用 PostgreSQL;Relay 可獨立部署
|
||
- **監控**:已暴露 `metricsPort: 9090` 的 Prometheus 指標
|
||
|
||
## 參考資料
|
||
|
||
- 官方文件:https://docs.netbird.io/selfhosted/selfhosted-quickstart
|
||
- Getting Started 腳本:https://github.com/netbirdio/netbird/releases/latest/download/getting-started.sh
|
||
- crun release:https://github.com/containers/crun/releases
|
||
- Proxmox LXC Docker 相容性:https://pve.proxmox.com/wiki/Linux_Container#_features
|