讓任何 OCI image 都能擁有 VPN Kill Switch 的功能

前一陣子發現一個滿熱門的 BT client 專案 haugene/docker-transmission-openvpn,裡面有各種常見 VPN Providers 的設定,而且他還有用 iptables 寫好 Kill Switch 的功能,覺得還滿懶人的,很好上手。

後來仔細想想如果只是要讓 container 能跑在 VPN 的環境就要弄出一個新的 image 感覺好像沒辦法把服務切的很乾淨,於是就開始著手研究有沒有辦法可以做到這件事但是不需要重新 build image。

動機

主要是因為之前 VPN 都是讓 router (Asus RT-AC66U) 來做,也有做到 policy route 還有 kill switch 的功能,但是因為這台 router 的 CPU 不是說特別好,CPU 跑滿的情況還只能達到 1MB/s 實在是慢到有點受不了。

另外,因為現在住的宿舍每天有限制對外流量,超過流量後就會限速,有時候就必須轉成手機的網路,但是不知道為什麼目前 router 用的韌體 (Merlin) 透過 USB 換到手機的網路的時候有機率會重開機,導致每次做這件事都會有大概 5 分鐘的 down time。

後來想說還是把 VPN 搬到機器上來做效率可能會比較好,而且要切換網路還有路由也比較方便一點,因此就來研究怎麼不修改 image 就能做到這件事。

Sidecar Pattern

透過建立 vpn 的 sidecar container 來附加 vpn 功能到 image 上。

Docker

翻了一下 docker 的文件,發現 network 這個設定有 container 這個選項可以用,說明是這樣的

‘container:<name|id>’: reuse another container’s network stack

原理就是讓宣告這個選項的 container 能夠跟指定的 container 共享 linux 上的 network namespace 來共享 container 間的網路資源。使用 docker-compose 時,要使用 network_mode 這個關鍵字,除了 container 這個 keyword,也能使用 service 指定相同 compose 下的服務名稱。

以下是測試的範例,使用了 dperson/openvpn-client 這個 OpenVPN client:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
version: '3.4'
services:
  vpn:
    container_name: vpn
    image: dperson/openvpn-client
    cap_add:
      - net_admin
    environment:
      TZ: "Asia/Taipei"
      FIREWALL: ''
    networks:
      - default
    read_only: true
    tmpfs:
      - /run
      - /tmp
    restart: unless-stopped
    security_opt:
      - label:disable
    stdin_open: true
    tty: true
    volumes:
      - /dev/net:/dev/net:z
      - ./vpn:/vpn
  test:
    depends_on:
      - vpn
    image: praqma/network-multitool
    network_mode: "service:vpn"

container 起來後,用 docker-compose exec test /bin/sh attach 進去,可以看到 test 這個 container 也有 vpn 這個 container 設定的 tun0 interface 以及 default route:

/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 100
    link/none
    inet 10.8.8.10/24 brd 10.8.8.255 scope global tun0
       valid_lft forever preferred_lft forever
102: eth0@if103: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.19.0.2/16 brd 172.19.255.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # ip r
0.0.0.0/1 via 10.8.8.1 dev tun0
default via 172.19.0.1 dev eth0
10.8.8.0/24 dev tun0 proto kernel scope link src 10.8.8.10
128.0.0.0/1 via 10.8.8.1 dev tun0
172.19.0.0/16 dev eth0 proto kernel scope link src 172.19.0.2
212.102.42.84 via 172.19.0.1 dev eth0

缺點

但是這樣做不是完美的,我遇到了幾個比較煩的問題

port binding

如果要在 test 這個 container expose port 的話,會遇到這個問題:

ERROR: for vpn_test_1  Cannot create container for service test: conflicting options: port publishing and the container type network mode

network_mode: service 是無法跟 port publishing 共存的,因此如果要 expose port 的話,必需要在 vpn 這個 container 上做設定,但這樣整份 config 看起來就會很奇怪。

dependency recreation

如果 vpn 這個 container 因為一些原因重新建立了,像是更改環境變數或是升級 image,test 這個 container 不會重新建立,於是就會遇到這個問題:

ERROR: for vpn_test_1  Cannot restart container 9345feb04c10564c0a9443891bd4e6dd67d01a7d822f855b5b901d7a618fba56: No such container: cce530d075a16267da10213949622244cdbbcff40070b38c1a12ff1f9c40325f

因為 vpn 這個 container 重新建立後 container id 變了,原先 test 依賴的 container 消失了所以才會遇到這個問題。雖然可以透過 docker-compose up --always-recreate-deps -d 來強制重新建立所有有 dependency 關係的 container,但這樣實在是不太方便。

bug

不知道為什麼重開的時候有時候會遇到這個錯誤,要再重新下一次指令才會成功:

ERROR: for vpn_test_1  Cannot start service vpn_test_1: Container bb98419511b1aaa72a897bfcb8b61d01ff8508dbd7aa9af9136569b075d3c073 is restarting, wait until the container is running

Podman

後來發現 podman 的 pod 滿符合我想要達到的目的,pod 對網路資源的定義是:

By definition, all containers in the same Podman pod share the same network namespace. Therefore, the containers will share the IP Address, MAC Addresses and port mappings. You can always communicate between containers in the same pod, using localhost.

透過建立 pod 來讓 container 共享 network namespace,但是 podman 的缺點是雖然有 podman-compose 這樣的專案,但是無法在文件中定義 pod。只能使用像是 podman play kube ... 這個指令來跑 k8s 的 config,而且支援的範圍有限。既然都要用到 k8s 的 config,這樣我就直接使用 kubectl 了,也比較省事一點。

針對上面的提到問題,在使用 pod 後都能夠有辦法解決:

  • podman 的 port binding 是針對 pod 的,所以不會有 docker 的問題
  • network namespace 是透過 pod 而不是 container,因此 vpn 的 container 重新建立後不會有依賴問題
  • 透過 livenessProbe 以及 startupProbe 來檢查 vpn 的狀態,如果掉了就 kill container,並透過 restartPolicy 決定是否要重啟 container

因為這個使用情景滿簡單的,k3s 提供的功能就已經能做到了,而且安裝也比較輕鬆,所以就拿 k3s 當作例子。

支援 livenessProbe 的 podman 最近有新的 PR 並且已經被 merge,應該過不久就會 release

K3S

定義好 pod 以及 container 的資訊後,透過 kubectl apply -f test.yml 來建立 pod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# test.yml
---
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2021-08-04T10:45:39Z"
  labels:
    app: vpn-test
  name: vpn-test
spec:
  containers:
  - env:
    - name: FIREWALL
      value: ""
    - name: TZ
      value: "Asia/Taipei"
    image: docker.io/dperson/openvpn-client:latest
    name: vpn
    securityContext:
      privileged: true
    volumeMounts:
    - mountPath: /vpn
      name: vpn-config
    - mountPath: /dev/net
      name: dev-net
    livenessProbe:
      exec:
        command:
        - sh
        - -c
        - if [ $(wget -q -O- https://ipinfo.io/country) == 'HK' ]; then exit 0; else exit $?; fi
      failureThreshold: 1
      initialDelaySeconds: 10
      periodSeconds: 10
    startupProbe:
      exec:
        command:
        - sh
        - -c
        - if [ $(wget -q -O- https://ipinfo.io/country) == 'HK' ]; then exit 0; else exit $?; fi
      failureThreshold: 5
      initialDelaySeconds: 5
      periodSeconds: 5
  - image: docker.io/praqma/network-multitool:latest
    name: network-multitool
  restartPolicy: Always
  volumes:
  - hostPath:
      path: # vpn config path
      type: Directory
    name: vpn-config
  - hostPath:
      path: /dev/net
      type: Directory
    name: dev-net

livenessProbe 會在 startupProbe 成功後接手,可以透過 ifconfig tun0 down 來檢查 livenessProbe。

更換 image 的測試,可以把設定中的 openvpn-client:latest 換成 openvpn-client:amd64,接著再執行一次 kubectl apply -f test.yml,pod 中的 container 會更換為新的 image。

缺點

缺少 dependency 機制

如果沒有 dependency 機制,就不能確保 vpn 這個 container 在做 tunnel 的設定時其他 container 不會有流量漏出。

雖然說 k8s 有 initContainers 可以用,但是 initContainers 會在最初執行完就結束。檢查 dependency 在 k3s 相較 docker 提供的 depends_on 關鍵字就會麻煩許多。

每開一個新的 pod 就要建立一個新的 VPN connection

由於是透過共享 network namespace 實現的,因此如果有其他 pod 也想使用 VPN 時,必須要再重複完全一樣的 service。

VPN gateway with sidecar pattern

後來覺得比較好的實作方式還是應該要有一個負責做 vpn 的 container,然後讓其他需要有 vpn 功能的 container 把路由導過去。

k8s@home/pod-gateway

找了一下發現已經有人做好我想要的所有功能了,但是搞懂設定怎麼調還是花了不少力氣,總之運作原理大概就像下面這個動畫:

pod-gateway

首先會有一個 namespace 是負責做 VPN,在這裡取名為 VPN-GW,裡面有包含一個 OpenVPN (或是 WireGuard) 的 pod,以及一個用來實作 admission webhook 的 pod。在建立這個 namespace 時可以選擇要讓哪些 namespace 的 pod 有 VPN 的功能,這裡為了簡單只有一個叫做 VPN 的 namespace,裡面放著各種要通過 VPN 才能出去的服務的 pod。

在 VPN 這個 namespace 建立 pod 的時候,webhook 會把一個 initContainer client-init 以及一個 container client-sidecar 同時放進這個 pod 中,用來處理介面以及路由。

在最初 VPN-GW 這個 pod 啟動時,會有一個 initContainer gateway-init 負責建立 VXLAN 的 tunnel 通到所有指定的 namespace,同時處理 iptables 的白名單,僅允許通往 VPN provider 的流量,以及設定 NAT。在 gateway-init 執行完初始化後,OpenVPN 會負責建立 VPN tunnel 以及 kill switch 功能,讓流量僅能透過 tun 以及 tap interface 出去。最後 sidecar container 會負責維護 DHCP 以及 DNS server。

在 service 的 pod 啟動時,client-init 會負責 DHCP 以及設定 gateway 的工作,接著服務及 sidecar 啟動後,會定期檢查 gateway 的連線,如果掉了會再重新執行一次 DHCP。

VPN-GW 這個 pod 可以使用 livenessProbe 來檢查 VPN 的連線,在 namespace 的層級也可以使用 networkpolicies resource 來限制流量只能通往 VPN 服務的 port 或是限制 VPN namespace 僅能與 k8s 內部的 ip 進行溝通。

安裝

首先需要安裝 helm 以及 cert-manager(這個 helm chart 的 dependency)

kubectl apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml

建立 vpn-gw 的 namespace:

kubectl create ns vpn-gw

建立 vpn 的 namespace,規定是要有 routed-gateway 這個 label:

1
2
3
4
5
6
7
8
# vpn-ns.yml
---
apiVersion: v1
kind: Namespace
metadata:
  name: vpn
  labels:
    routed-gateway: "true"
kubectl apply -f vpn-ns.yml

初始設定

首先先檢查一下沒開 VPN 的情況 gateway 能不能正常工作,DNS IP 可能需要根據不同的環境設定:

這邊的其他可以使用的設定參考 k8s@home/library-charts 以及 k8s@home/charts

1
2
3
4
5
6
7
8
9
# values.yml
---
env:
  TZ: "Asia/Taipei"
DNS: 10.43.0.10
routed_namespaces:
- vpn
settings:
  NOT_ROUTED_TO_GATEWAY_CIDRS: "10.43.0.10"

接著透過 helm 建立 pod-gateway 在 vpn-gw 這個 namespace:

helm install pod-gateway k8s-at-home/pod-gateway \
	--kubeconfig /etc/rancher/k3s/k3s.yaml \
	-n vpn-gw \
	-f values.yml

接著建立一個測試用的 pod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# vpn-test.yml
---
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2021-08-04T10:45:39Z"
  labels:
    app: vpn-test
  name: vpn-test
spec:
  containers:
  - image: docker.io/praqma/network-multitool:latest
    name: terminal
  restartPolicy: Always
kubectl create -f vpn-test.yml -n vpn

檢查 vpn 的 namespace,可以看到 hook 幫我們建立了除了 service 的兩個 container:

> kubectl describe pod/vpn-test -n vpn
Init Containers:
  gateway-init:
  	...
Containers:
  network-multitool:
  	...
  gateway-sidecar:
    ...

進到 service 的 container 後,可以發現多了 vxlan0 這個 interface 通到 pod-gateway,並且路由也設好了:

> kubectl exec --stdin --tty -n vpn vpn-test -c terminal -- /bin/sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
3: eth0@if145: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 8e:d0:ed:fc:02:c5 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.42.0.74/24 brd 10.42.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::8cd0:edff:fefc:2c5/64 scope link 
       valid_lft forever preferred_lft forever
4: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1400 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 1a:ef:44:af:f4:ad brd ff:ff:ff:ff:ff:ff
    inet 172.16.0.237/24 brd 172.16.0.255 scope global vxlan0
       valid_lft forever preferred_lft forever
    inet6 fe80::18ef:44ff:feaf:f4ad/64 scope link 
/ # ip r
default via 172.16.0.1 dev vxlan0 
10.42.0.0/24 dev eth0 proto kernel scope link src 10.42.0.74 
10.42.0.0/16 via 10.42.0.1 dev eth0 
10.43.0.10 via 10.42.0.1 dev eth0 
172.16.0.0/24 dev vxlan0 proto kernel scope link src 172.16.0.237 

Service Namespcae Network Policy

在負責放 service 的 namespace 可以加上這個 network policy,確保 ingress 以及 egress 僅能通往 k8s 內部的 ip:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# vpn-networkpolicy.yaml
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: vpn-namespace
spec:
  podSelector: {}
  ingress:
  - from:
    # Only allow ingress from K8S
    - ipBlock:
        cidr: 10.0.0.0/8
  egress:
  - to:
    # Only allow egress to K8S
    - ipBlock:
        cidr: 10.0.0.0/8
  policyTypes:
    - Ingress
    - Egress
kubectl apply -f vpn-networkpolicy.yaml -n vpn

VPN 設定

雷點:因為我之前有用過 dperson/openvpn-client 這個 container 實作的 kill switch 功能,所以不知道什麼時候 vpn 設定目錄下就有 .firewall 還有 .firewall6 的檔案,導致我就算沒有宣告說要用 container 提供的 kill switch 還是會被強制開啟。讓我一開始設定的時候一直搞不懂為什麼 iptables 會整組換新,還用了很 hack 的方法改寫原本 OpenVPN 的 up script。

雷點2:NOT_ROUTED_TO_GATEWAY_CIDRS 必須包含 k3s Service 的 IP range,不然 DNS 會無法使用,pod 之間也會無法溝通。

總之最後是自己去看他的 code 才發現有這些問題,pod-gateway 在開啟 OpenVPN 後的設定如下,要再視自己的環境還有 VPN Provider 做調整:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# values.yml
---
env:
  TZ: "Asia/Taipei"
DNS: 10.43.0.10
routed_namespaces:
- vpn
settings:
  NOT_ROUTED_TO_GATEWAY_CIDRS: "10.43.0.0/16"
  VPN_BLOCK_OTHER_TRAFFIC: true
  VPN_TRAFFIC_PORT: 1194
addons:
  vpn:
    enabled: true
    type: openvpn
    additionalVolumeMounts:
    - mountPath: /vpn
      name: vpn-config
    - mountPath: /dev/net
      name: dev-net
    env:
      TZ: "Asia/Taipei"
    livenessProbe:
      exec:
        command:
          - sh
          - -c
          - if [ $(wget -q -O- https://ipinfo.io/country) == 'HK' ]; then exit 0; else exit $?; fi
      initialDelaySeconds: 30
      periodSeconds: 60
      failureThreshold: 1
    networkPolicy:
      enabled: true
      egress:
        - to:
          - ipBlock:
              cidr: 0.0.0.0/0
          ports:
          # VPN traffic port - change if your provider uses a different port
          - port: 1194
            protocol: UDP
        - to:
          # Allow traffic within K8S - change if your K8S cluster uses a different CIDR
          - ipBlock:
              cidr: 10.0.0.0/8
persistence:
  vpn-config:
    enabled: true
    type: hostPath
    hostPath: # vpn config path
  dev-net:
    enabled: true
    type: hostPath
    hostPath: /dev/net

最後檢查一下 iptables:

/ # iptables-save
*filter
:INPUT ACCEPT [15:3212]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
-A INPUT -d 10.42.0.193/32 -j ACCEPT
-A FORWARD -o tun0 -j ACCEPT
-A FORWARD -i tun0 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i tun0 -j REJECT --reject-with icmp-port-unreachable
-A OUTPUT -p udp -m udp --dport 1194 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 1194 -j ACCEPT
-A OUTPUT -d 10.0.0.0/8 -j ACCEPT
-A OUTPUT -d 192.168.0.0/16 -j ACCEPT
-A OUTPUT -o tun0 -j ACCEPT
-A OUTPUT -o vxlan0 -j ACCEPT
COMMIT

*nat
:PREROUTING ACCEPT [19:2392]
:INPUT ACCEPT [17:2248]
:OUTPUT ACCEPT [13:1713]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -j MASQUERADE
COMMIT

接著 attach 進去 service 的 container 檢查自己的 ip,可以發現是連上 VPN 後拿到的 ip 了。

使用範例

這裡放一個 aria2 的 service 範例,在使用 pod-gateway 後,基本上已經跟調 VPN 沒有關係了,只需要寫好 Deployment、Service、Ingress 後將服務佈署在 vpn 這個 namespace 就好,剩下的事情 hook 都會幫我們處理好:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: aria2
  labels:
    app: aria2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: aria2
  template:
    metadata:
      labels:
        app: aria2
      name: aria2
    spec:
      hostname: aria2
      containers:
      - env:
        # ...
        - name: TZ
          value: "Asia/Taipei"
        image: docker.io/p3terx/aria2-pro:latest
        name: aria2-pro
        ports:
        - containerPort: 6800
          protocol: TCP
        - containerPort: 6888
          protocol: TCP
        - containerPort: 6888
          protocol: UDP
        volumeMounts:
        - mountPath: /config
          name: aria2-config
        - mountPath: /downloads
          name: downloads
      - image: docker.io/p3terx/ariang:latest
        name: ariang
        ports:
        - containerPort: 6880
          protocol: TCP
      restartPolicy: Always
      volumes:
      - hostPath:
          path: # aria2 config path
          type: Directory
        name: aria2-config
      - hostPath:
          path: # aria2 download path
          type: Directory
        name: downloads
---
apiVersion: v1
kind: Service
metadata:
  name: aria2-service
spec:
  selector:
    app: aria2
  ports:
    - name: aria2-pro-6800-tcp
      port: 6800
      targetPort: 6800
      protocol: TCP
    - name: aria2ng-6880-tcp
      port: 6880
      targetPort: 6880
      protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: aria2-ingress
spec:
  rules:
    - host: # domain name
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: aria2-service
                port:
                  number: 6880
          - path: /jsonrpc
            pathType: Prefix
            backend:
              service:
                name: aria2-service
                port:
                  number: 6800

也可以建立一個 proxy daemon 的 pod 在 vpn namespace 中,不過如果直接使用 hostPort 要注意 routing 問題,因為 default route 會走 vxlan0,很有可能流量會送不回去,需要額外用 initContainers 處理,除此之外如果不使用 Ingress resource 當作入口,前面設定的 networkpolicy 中的 ingress ip range 也要做修改:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---
apiVersion: v1
kind: Pod
metadata:
  name: tinyproxy
spec:
  initContainers:
  - image: docker.io/praqma/network-multitool:latest
    name: test
    command: ['sh', '-c', 'ip route add 192.168.1.0/24 via 10.42.0.1 dev eth0']
    securityContext:
      capabilities:
        add: ["NET_ADMIN"]
  containers:
  - image: docker.io/vimagick/tinyproxy
    name: tinyproxy
    ports:
    - containerPort: 8888
      hostPort: 8888
    volumeMounts:
    - mountPath: /etc/tinyproxy
      name: config
  restartPolicy: Always
  volumes:
  - hostPath:
      path: # config dir
      type: Directory
    name: config

補充

Multiple namespace

如果要多個 namespace 的話,要設定 webhook.namespaceSelector.label,否則預設會是都叫 routed-gateway,這樣不同 namespace 的 deployment 都會被選到,導致 deployment 建立失敗。

1
2
3
4
5
webhook:
  namespaceSelector:
    type: label
    label: "routed-gateway-jp"
    custom: {}

k8s-at-home deprecated

k8s-at-home 在 2022/08/01 已經 deprecated 了,之後更新可能要看 angelnu/pod-gateway 這個 repo 了。

gluetun

gluetun 比自己手動設定 OpenVPN 方便多了,可以研究一下怎麼設定

結語

原本是想說直接拿 kubectl 當 docker-compose 用就算了,但後來還是覺得單純用 pod 共享 network namespace 的解法不是很完美,所以又花了一點時間研究了 pod-gateway,設定完後覺得真心覺得不錯用,整個架構看起來也滿乾淨的。

最後把全部有在用的服務都順勢搬到 k3s 上了,而且這樣把網速跑滿 (100Mbps) 後 CPU 大概也才佔 20% 而已,相較之前卡在 router 的速度好上不少。

參考資料

https://github.com/dperson/openvpn-client

https://podman.io/getting-started/network

https://artifacthub.io/packages/helm/k8s-at-home/pod-gateway

https://docs.k8s-at-home.com/guides/pod-gateway/

comments powered by Disqus