跳至主要内容

⬇️⬇️⬇️ 欢迎关注我的 telegram 频道和 twitter ⬇️⬇️⬇️


联系方式: Twitter Github Email Telegram

在 K8s 里搭建 WireGuard VPN 并访问集群网络

August 4, 2026 本文有 1405 个字 需要花费 3 分钟阅读

简介

公司的 K8s 集群都在内网,在家想连上去看个 Pod 状态、查个日志,要么开跳板机要么搞 VPN。之前一直用 NPS 做端口转发凑合用,但每个服务都要单独配一条隧道,端口多了管理起来很累。

干脆在 K8s 里搭个 WireGuard,连上 VPN 就能直接访问集群网络,跟在内网一样。

选型:wg-easy

WireGuard 本身配置不复杂,但管理客户端有点烦——要手动生成密钥对、写配置文件、分发给同事。wg-easy 是一个带 Web UI 的 WireGuard 管理工具,直接在浏览器里创建/删除客户端,还能下载配置文件或二维码,省事很多。

镜像地址:ghcr.io/wg-easy/wg-easy

网络拓扑

先看看我的架构:

手机/笔记本
    ↓ WireGuard 加密连接
NPS 服务器 (公网IP:1038/udp)
    ↓ UDP 隧道穿透
wg-easy Pod (1038/udp)
    ↓ wg0 解包 → SNAT
K8s 集群网络

因为集群没有公网 IP,我用了 NPS(一个内网穿透工具)把外网的 UDP 流量打到 K8s 节点的 wg-easy pod。

K8s 部署文件

Namespace

apiVersion: v1
kind: Namespace
metadata:
  name: wg-easy

PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wg-easy-storage-claim
  namespace: wg-easy
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Mi

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wg-easy
  namespace: wg-easy
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app.kubernetes.io/name: wg-easy
    spec:
      securityContext:
        sysctls:
          - name: net.ipv4.ip_forward
            value: "1"
          - name: net.ipv4.conf.all.src_valid_mark
            value: "1"
      containers:
        - name: wg-easy
          env:
            - name: WG_HOST
              value: vpn.example.com
            - name: WG_PORT
              value: "1038"
            - name: INIT_ENABLED
              value: "true"
            - name: INIT_IPV4_CIDR
              value: 10.8.0.0/24
            - name: INIT_USERNAME
              value: admin
            - name: INIT_PASSWORD
              value: 你的密码
            - name: INIT_HOST
              value: 你的NPS公网IP
            - name: INIT_PORT
              value: "1038"
            - name: DISABLE_IPV6
              value: "true"
          image: ghcr.io/wg-easy/wg-easy:15.3.0
          ports:
            - containerPort: 51820
              name: wg
              protocol: UDP
            - containerPort: 51821
              name: http
              protocol: TCP
          securityContext:
            capabilities:
              add:
                - NET_ADMIN
                - SYS_MODULE
          volumeMounts:
            - mountPath: /etc/wireguard
              name: config
      volumes:
        - name: config
          persistentVolumeClaim:
            claimName: wg-easy-storage-claim

Service(HTTP Web UI)

apiVersion: v1
kind: Service
metadata:
  name: wg-easy-http
  namespace: wg-easy
spec:
  ports:
    - name: http
      port: 51821
      protocol: TCP
      targetPort: http
  selector:
    app.kubernetes.io/name: wg-easy
  type: ClusterIP

再配个 Ingress 就能通过域名访问 Web UI 了。

踩坑记录

坑1:INIT_ENABLED 必须开

我最开始只配了一堆 INIT_* 变量,以为它们会自动生效。结果发现 wg0.conf 里压根没按我写的来。

去翻文档才发现——INIT_ENABLED=true 不写,所有 INIT_ 变量都是摆设。

坑2:Pod 内 IPv6 被禁

容器起来后 Web UI 能访问,但点进去就报 500:

Unable to access interface: No such device

wg show 看不到 wg0 接口。手动 wg-quick up wg0 发现:

ip -6 address add fdcc:ad94:bacf:61a4::cafe:1/112 dev wg0
RTNETLINK answers: Permission denied

集群的 Pod 里 IPv6 默认是禁用的disable_ipv6=1),但 wg-easy 生成的 wg0.conf 默认带了 IPv6 地址,加不上去导致整个 wg-quick up 失败。

解决方法:加环境变量 DISABLE_IPV6=true,让 wg-easy 只生成 IPv4 配置。

坑3:缺少 sysctl

WireGuard 需要内核开启 IP 转发,不然客户端连上也没法转发流量到集群网络。在 Pod spec 里加:

spec:
  securityContext:
    sysctls:
      - name: net.ipv4.ip_forward
        value: "1"
      - name: net.ipv4.conf.all.src_valid_mark
        value: "1"

网络连通原理

客户端连上 VPN 后(分配 IP 10.8.0.2),想去访问集群里的某个 Pod(比如 10.4.x.x),流量是这样的:

  1. 客户端查路由表,10.4.0.0/16 走 WG 隧道
  2. 加密后发给 NPS 公网 IP
  3. NPS 转发到 K8s wg-easy Pod 的 1038/udp
  4. 解密后从 wg0 接口出来
  5. 内核做 SNAT(MASQUERADE),源 IP 变成 Pod 自己的 IP
  6. 请求到达目标 Pod,正常回包

关键就是 wg-easy Pod 里的 iptables 规则(自动生成的):

iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

这行把所有从 VPN 出来的流量 SNAT 成 Pod 自己的 IP,这样在 K8s 网络里就跟普通 Pod 通信一样,不需要额外的路由配置。

客户端配置

在 wg-easy Web UI 创建客户端后,下载的配置文件大概这样:

[Interface]
PrivateKey = xxxx
Address = 10.8.0.2/32
DNS = 1.1.1.1
MTU = 1420

[Peer]
PublicKey = xxxx
PresharedKey = xxxx
AllowedIPs = 你的集群网段, 10.8.0.0/16
Endpoint = 你的NPS公网IP:1038
PersistentKeepalive = 25

注意几点:

  • AllowedIPs 要包含集群网段,不然客户端不会把访问集群的流量送进 VPN 隧道
  • PersistentKeepalive 建议设成 25,UDP 穿透长时间空闲会断,保活包能维持连接
  • Endpoint 配的是 NPS 的外网地址和端口

验证连接

服务端看连接状态:

kubectl exec -n wg-easy <pod> -- wg show

连上了会显示:

peer: xxxx
  endpoint: 客户端公网IP:端口
  allowed ips: 10.8.0.2/32
  latest handshake: 1 minute ago
  transfer: 1.23 KiB received, 456 B sent

没连上就只看到 endpoint: (none)latest handshake: (never)

客户端连上后,直接在终端 ping 一个集群内的 Pod IP 试试:

ping 10.4.x.x

通了就说明大功告成。

欢迎关注我的博客 www.bboy.app
Have Fun