KubeEdge 边缘实战:给 Pod 穿上“隐形斗篷”——基于 Sidecar 的透明代理 FRP 穿透方案

作者:Administrator 发布时间: 2025-11-25 阅读量:10 评论数:0

在边缘计算(KubeEdge)场景下,网络环境往往比云端复杂得多。我们经常会遇到这样的“地狱难度”开局:

  • 边缘节点深藏在内网,没有公网 IP。

  • 防火墙策略极严,出站流量被封死。

  • 唯一的出路:必须通过指定的 HTTP 代理(Proxy)才能访问互联网。

最近我就碰到了这个需求:我需要在边缘节点上部署 FRP 客户端(FRPC),把内网服务暴露出去。但问题是,FRPC 本身虽然支持 HTTP 代理设置,但在某些复杂的 K8s 网络环境下,或者需要更底层的流量控制时,直接改代码配置并不够灵活。

于是,我搞了一套“基于 Sidecar + iptables 透明代理”的方案。简单来说,就是把 Pod 的网络栈劫持了,强制让它走代理,而应用本身毫无感知。

这套方案不仅完美解决了单一出口问题,我还顺手实现了多链路并发——在同一个节点上,通过不同的代理出口(电信/联通/专用线路)拉起多条隧道。

今天就把这套方案的实现细节分享出来。


💡 核心思路:把流量“骗”进代理

我们要实现的效果是:FRPC 以为自己在直连公网的 FRPS,但实际上它的 TCP 包被我们在操作系统底层“偷梁换柱”,封装进了 HTTP 代理隧道。

这需要 K8s 的两个大杀器:

  1. Init Container:在主程序启动前干坏事(修改 iptables)。

  2. Sidecar (Gost):负责把被劫持的流量搬运到 HTTP 代理服务器。

流量是怎么走的?

想象一下 FRPC 发起了一个连接请求:

  1. FRPC: "我要去连接 43.133.xx.xx:7000!"(发起 TCP 握手)

  2. Iptables (看门大爷): "等会儿,去这个 IP 的流量被标记了,你不能直连,去隔壁找老王(Sidecar)。"(DNAT 重定向到 localhost:12345)

  3. Sidecar (Gost): "收到!我把这个 TCP 包包一层 HTTP CONNECT 头,发给公司的代理服务器 192.168.6.1。"

  4. 真实代理: 收到 HTTP 请求,解包,转发给公网的 FRPS

整个过程对 FRPC 是透明的,它甚至不知道代理的存在。


🛠️ 实战配置

环境说明:

  • 服务端 (FRPS): 部署在云端公网节点 (jp-gateway),IP: 43.133.xx.xx

  • 客户端 (FRPC): 部署在边缘节点 (ning-virtual-node1)。

  • 代理出口: 有三条线路,分别是 192.168.6.1 (默认), 100.1 (线路B), 233.1 (线路C)。

1. 服务端:HostNetwork 直通

服务端没啥花哨的,为了性能和端口映射方便,直接用 hostNetwork: true 霸占宿主机端口。

# 截取自 YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jp-frps
spec:
  template:
    spec:
      hostNetwork: true # 直接监听宿主机网卡
      nodeSelector:
        kubernetes.io/hostname: jp-gateway # 钉死在有公网 IP 的节点
      containers:
      - name: frps
        image: snowdreamtech/frps:0.58.0
        # ...

2. 客户端:Sidecar 魔法 (核心部分)

这是本文的精华。我们以“线路一”为例。

第一步:Init Container 设置“路障”

我们需要一个特权容器来修改 Pod 的 iptables 表。注意这里用了白名单模式——我们只劫持去往 FRPS IP 的流量

为什么要白名单? 因为如果全流量劫持,Pod 访问 K8s API Server 或 CoreDNS 的流量也会被转发走,导致 Pod 启动失败。

      initContainers:
      - name: init-iptables
        image: docker.io/library/alpine:3.18
        command: ["/bin/sh", "-c"]
        args:
          - |
            apk add iptables;
            # 1. 新建一个链
            iptables -t nat -N PROXY_CHAIN
            # 2. 【关键】如果目标是 FRPS 服务器 IP,就重定向到 12345 端口
            iptables -t nat -A PROXY_CHAIN -p tcp -d 43.133.xx.xx -j REDIRECT --to-ports 12345
            # 3. 挂载到 OUTPUT 链
            iptables -t nat -A OUTPUT -p tcp -j PROXY_CHAIN
        securityContext:
          capabilities:
            add: ["NET_ADMIN"] # 必须有改网络的权限
          privileged: true

第二步:Gost Sidecar “偷渡”

使用 gost 作为本地透明代理端点,它极其轻量且支持协议丰富。

      containers:
      - name: sidecar-gost
        image: ginuerzh/gost:2.11.5
        args:
          - "-L=red://:12345" # 监听重定向过来的流量 (red = redirect)
          - "-F=http://DirectUser:bad10.89@192.168.6.1:7890" # 转发给上游 HTTP 代理
        securityContext:
          runAsUser: 1000
          runAsGroup: 1337 # 这里的 Group ID 其实可以配合 iptables 规避死循环,但在白名单模式下不是必须的

第三步:FRPC 业务容器 “坐享其成”

这里有两个优化点值得注意:

  1. 启动延时 (sleep 5):Sidecar 启动需要时间,让主进程等一等,防止上来就报错。

  2. loginFailExit = false:FRP 默认连不上就退出了。在 K8s 里 Pod 反复重启(CrashLoopBackOff)很难看,配置这个让它在后台默默重试,更优雅。

      - name: frpc
        image: snowdreamtech/frpc:0.58.0
        command: ["/bin/sh", "-c"]
        # 先睡 5 秒,等 Sidecar 准备好
        args: ["sleep 5; /usr/bin/frpc -c /etc/frp/frpc.toml"]
        volumeMounts:
        - name: config
          mountPath: /etc/frp

🚀 进阶玩法:单节点多线路并发

既然容器隔离了网络栈,我们完全可以在同一个边缘节点上,部署三套一模一样的架构,唯一的区别是 Sidecar 指向的上游代理 IP 不同

这在测试网络质量或做链路主备时非常有用。

我在配置里定义了三套 Deployment:

  1. jp-frpc: 走 192.168.6.1 (默认) -> 映射端口 6080

  2. jp-frpc-100: 走 192.168.100.1 (专线A) -> 映射端口 6081

  3. jp-frpc-233: 走 192.168.233.1 (专线B,带特殊密码) -> 映射端口 6082

所有的流量最终都打到了边缘节点的同一个内网服务 192.168.6.201:80 上。


📝 避坑指南

在折腾这套方案时,踩过几个坑,顺便提醒大家:

  1. Privileged 权限init-iptables 必须要有 privileged: true。如果在企业级 K8s 或 OpenShift 上,可能需要调整 PSP (Pod Security Policy) 或 OPA 策略。

  2. 宿主机网络连通性:虽然是在 Pod 里做代理,但前提是宿主机本身能 Ping 通那个代理 IP(比如 192.168.6.1)。如果宿主机路由表里都找不到这个内网 IP,Pod 里怎么折腾都没用。

  3. KubeEdge 的污点:边缘节点通常有 NoSchedule 污点,别忘了在 Deployment 里加上 tolerations,否则 Pod 会一直 Pending。

      tolerations:
        - key: "kubeedge.io/dedicated"
          operator: "Equal"
          value: "edge"
          effect: "NoSchedule"

📦 拿来主义

完整的 YAML 文件太长,我把它整合到了一个 All-in-One 的文件里,包含 Namespace、ConfigMap 和所有的 Deployment。

复制保存为 edge-frp-stack.yaml,然后执行 kubectl apply -f edge-frp-stack.yaml 即可一键拉起。

apiVersion: v1
kind: ConfigMap
metadata:
  name: jp-frps-config
  namespace: edge-frp
data:
  frps.toml: |
    bindPort = 7000
    auth.method = "token"
    auth.token = "xxxx"

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: jp-frpc-config
  namespace: edge-frp
data:
  frpc.toml: |
    serverAddr = "43.133.xx.xx"
    serverPort = 7000
    auth.method = "token"
    auth.token = "xxxx"
    
    # 【优化点1】连接失败不退出,持续重试 (关键!)
    loginFailExit = false

    [[proxies]]
    name = "fnos-gateway"
    type = "tcp"
    localIP = "192.168.6.201"
    localPort = 80
    remotePort = 6080

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: jp-frps
  namespace: edge-frp
  labels:
    app: frps
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jp-frps
  template:
    metadata:
      labels:
        app: jp-frps
    spec:
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      tolerations:
        - key: "kubeedge.io/dedicated"
          operator: "Equal"
          value: "edge"
          effect: "NoSchedule"
      nodeSelector:
        kubernetes.io/hostname: jp-gateway
      containers:
      - name: frps
        image: snowdreamtech/frps:0.58.0
        command: ["/usr/bin/frps", "-c", "/etc/frp/frps.toml"]
        volumeMounts:
        - name: config
          mountPath: /etc/frp
        ports:
        - containerPort: 7000
          protocol: TCP
      volumes:
      - name: config
        configMap:
          name: jp-frps-config

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jp-frpc
  namespace: edge-frp
  labels:
    app: frpc
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jp-frpc
  template:
    metadata:
      labels:
        app: jp-frpc
    spec:
      tolerations:
        - key: "kubeedge.io/dedicated"
          operator: "Equal"
          value: "edge"
          effect: "NoSchedule"
      nodeSelector:
        kubernetes.io/hostname: ning-virtual-node1
      initContainers:
      - name: init-iptables
        image: docker.io/library/alpine:3.18
        command: ["/bin/sh", "-c"]
        args:
          - |
            apk add iptables;
            iptables -t nat -N PROXY_CHAIN
            # 仅拦截去往 FRPS 的流量
            iptables -t nat -A PROXY_CHAIN -p tcp -d 43.133.xx.xx -j REDIRECT --to-ports 12345
            iptables -t nat -A OUTPUT -p tcp -j PROXY_CHAIN
        securityContext:
          capabilities:
            add: ["NET_ADMIN"]
          privileged: true

      containers:
      - name: sidecar-gost
        image: ginuerzh/gost:2.11.5
        args:
          - "-L=red://:12345"
          - "-F=http://DirectUser:xxx@192.168.6.1:7890"
        securityContext:
          runAsUser: 1000
          runAsGroup: 1337
          
      - name: frpc
        image: snowdreamtech/frpc:0.58.0
        command: ["/bin/sh", "-c"]
        # 【优化点3】启动延时:等待 5 秒,确保 Sidecar 已经就绪,减少首次报错
        args:
          - "sleep 5; /usr/bin/frpc -c /etc/frp/frpc.toml"
        volumeMounts:
        - name: config
          mountPath: /etc/frp
      
      volumes:
      - name: config
        configMap:
          name: jp-frpc-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: jp-frpc-config-100
  namespace: edge-frp
data:
  frpc.toml: |
    serverAddr = "43.133.xx.xx"
    serverPort = 7000
    auth.method = "token"
    auth.token = "xxxx"
    
    # 【优化点1】连接失败不退出,持续重试 (关键!)
    loginFailExit = false

    [[proxies]]
    name = "fnos-gateway-100"
    type = "tcp"
    localIP = "192.168.6.201"
    localPort = 80
    remotePort = 6081

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jp-frpc-100
  namespace: edge-frp
  labels:
    app: jp-frpc-100
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jp-frpc-100
  template:
    metadata:
      labels:
        app: jp-frpc-100
    spec:
      tolerations:
        - key: "kubeedge.io/dedicated"
          operator: "Equal"
          value: "edge"
          effect: "NoSchedule"
      nodeSelector:
        kubernetes.io/hostname: ning-virtual-node1
      initContainers:
      - name: init-iptables
        image: docker.io/library/alpine:3.18
        command: ["/bin/sh", "-c"]
        args:
          - |
            apk add iptables;
            iptables -t nat -N PROXY_CHAIN
            # 仅拦截去往 FRPS 的流量
            iptables -t nat -A PROXY_CHAIN -p tcp -d 43.133.xx.xx -j REDIRECT --to-ports 12345
            iptables -t nat -A OUTPUT -p tcp -j PROXY_CHAIN
        securityContext:
          capabilities:
            add: ["NET_ADMIN"]
          privileged: true

      containers:
      - name: sidecar-gost
        image: ginuerzh/gost:2.11.5
        args:
          - "-L=red://:12345"
          - "-F=http://DirectUser:xxx@192.168.100.1:7890"
        securityContext:
          runAsUser: 1000
          runAsGroup: 1337
          
      - name: frpc
        image: snowdreamtech/frpc:0.58.0
        command: ["/bin/sh", "-c"]
        # 【优化点3】启动延时:等待 5 秒,确保 Sidecar 已经就绪,减少首次报错
        args:
          - "sleep 5; /usr/bin/frpc -c /etc/frp/frpc.toml"
        volumeMounts:
        - name: config
          mountPath: /etc/frp
      
      volumes:
      - name: config
        configMap:
          name: jp-frpc-config-100

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: jp-frpc-config-233
  namespace: edge-frp
data:
  frpc.toml: |
    serverAddr = "43.133.xx.xx"
    serverPort = 7000
    auth.method = "token"
    auth.token = "xxx"
    
    # 【优化点1】连接失败不退出,持续重试 (关键!)
    loginFailExit = false

    [[proxies]]
    name = "fnos-gateway-233"
    type = "tcp"
    localIP = "192.168.6.201"
    localPort = 80
    remotePort = 6082

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jp-frpc-233
  namespace: edge-frp
  labels:
    app: jp-frpc-233
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jp-frpc-233
  template:
    metadata:
      labels:
        app: jp-frpc-233
    spec:
      tolerations:
        - key: "kubeedge.io/dedicated"
          operator: "Equal"
          value: "edge"
          effect: "NoSchedule"
      nodeSelector:
        kubernetes.io/hostname: ning-virtual-node1
      initContainers:
      - name: init-iptables
        image: docker.io/library/alpine:3.18
        command: ["/bin/sh", "-c"]
        args:
          - |
            apk add iptables;
            iptables -t nat -N PROXY_CHAIN
            # 仅拦截去往 FRPS 的流量
            iptables -t nat -A PROXY_CHAIN -p tcp -d 43.133.xx.xx -j REDIRECT --to-ports 12345
            iptables -t nat -A OUTPUT -p tcp -j PROXY_CHAIN
        securityContext:
          capabilities:
            add: ["NET_ADMIN"]
          privileged: true

      containers:
      - name: sidecar-gost
        image: ginuerzh/gost:2.11.5
        args:
          - "-L=red://:12345"
          - "-F=http://DirectUser:xxx@192.168.233.1:7890"
        securityContext:
          runAsUser: 1000
          runAsGroup: 1337
          
      - name: frpc
        image: snowdreamtech/frpc:0.58.0
        command: ["/bin/sh", "-c"]
        # 【优化点3】启动延时:等待 5 秒,确保 Sidecar 已经就绪,减少首次报错
        args:
          - "sleep 5; /usr/bin/frpc -c /etc/frp/frpc.toml"
        volumeMounts:
        - name: config
          mountPath: /etc/frp
      
      volumes:
      - name: config
        configMap:
          name: jp-frpc-config-233


总结:通过 Kubernetes 的 Sidecar 模式和 iptables 劫持,我们成功把传统的网络代理配置从“应用层”下沉到了“基础设施层”。这不仅解耦了业务逻辑,还让复杂的边缘网络穿透变得像搭积木一样简单。

评论