OpenVPN访问Kubernetes集群内网

搭建好的Kubernetes集群中,默认是存在网络隔离的,即集群内部使用一套独立的网络,与物理网络相互隔离。

为了将内部服务暴露给外部调用,Kubernetes提供了ClusterIP等多种方式。

但在有的时候,我们能够从本地网络直接访问Kubernetes集群内网的所有结点:

  • 本地调试微服务时,可能要调用许多微服务,而这些微服务所部署的Pod或Service并没有配置对外暴露的Cluster IP
  • 我们需要登录Pod查看内存、日志等信息

实现这类功能,可以有两种方式:

  • 在本地网络和集群内网之间,配置路由协议,从而实现互联互通。
  • 通过VPN的方式,打通物理网络和集群内网。

路由配置的方式看起来最直观,但有时难以实施:

  • 配置路由协议需要专业级的路由器、也需要学习路由的配置,设备采购成本、学习成本较大
  • 如果本地和机房之间需要通过互联网而不是局域网访问,那么路由协议的配置需要运营商配合,基本无法实现

在本小节,我们主要介绍第二种方式,即通过VPN穿透的方式,从本地网络直接访问Kuberntes集群内网。

在深入技术细节前,我们先来了解下VPN的原理,如下图所示:

VPN原理

如上图所示,VPN通过互联网链接,建立其一条本地网络到远程内网之间的隧道。远程内网的含义是:有一台具有公网IP的网关,但内网本身没有暴露给公网。当VPN连接建立后,就可以直接从本地网络访问远程内网中的所有资源。

在本小节,我们选用OpenVPN,这是一个开源的VPN实现,选用理由有:

  • 客户端支持广泛:Windows, Linux, Mac, Android,iOS等几乎所有主流平台
  • 传输层协议同时支持TCP和UDP
  • 协议流行,在一些软路由(如DD-WRT)上都有直接实现,方便了日后拓展

OpenVPN路由服务的配置

配置OpenVPN路由主要分为两大步骤:

  • 配置服务端
  • 客户端连接

在配置OpenVPN服务前,我们再回顾一下这节的标题”OpenVPN访问Kubernetes集群内网”,我们要做的是访问Kubernetes集群内网。

我们假设你已经架设了Kubernetes集群,并使用了calico网络模型。

我们先来看一下服务端的配置,在启动服务前,先要生成Open VPN所需要的密钥:

  1. #!/bin/bash
  2. VOLUME="$HOME/openvpn"
  3. vpn_ip="vpn.coder4.com"
  4. # init for first time only
  5. rm -rf $VOLUME
  6. mkdir -p $VOLUME
  7. docker run -v $VOLUME:/etc/openvpn --rm kylemanna/openvpn ovpn_genconfig -u udp://$vpn_ip -s 10.4.0.0/24
  8. docker run -v $VOLUME:/etc/openvpn --rm -it kylemanna/openvpn ovpn_initpki

如上所示,我们直接使用了封装好的kylemanna/openvpn这个Docker镜像。

  • 上述请在集群的任意一台物理机上执行,这台物理机之后会作为OpenVPN的接入点,所以一定要有公网IP
  • 生成配置到本地HOME目录的openvpn文件夹
  • vpn内网范围是10.4.0.0/24,这只得是VPN隧道所使用的网段,一定要注意,不要和已有物理网络、Kubernetes网络冲突。
  • 通信协议是UDP,我们假设这台物理机有DNS指向vpn.coder4.com

生成配置后,就可以启动VPN服务器了:

  1. #!/bin/bash
  2. # submit to tool node
  3. NAME="openvpn"
  4. VOLUME="$HOME/openvpn"
  5. dns_ip="10.96.0.10"
  6. # stop & run server (should call init_open_vpn_test.sh before)
  7. docker ps -q -a --filter "name=$NAME" | xargs -I {} docker rm -f {}
  8. docker run \
  9. --name $NAME \
  10. --network bridge \
  11. --dns $dns_ip \
  12. -d \
  13. -v $VOLUME:/etc/openvpn \
  14. -p 1194:1194/udp \
  15. --cap-add=NET_ADMIN \
  16. --restart always \
  17. kylemanna/openvpn \
  18. ovpn_run --cipher AES-128-CBC

如上所述,我们启动了VPN服务器:

  • 注意我们这里是直接用的docker启动,而非放到Kubernetes集群中
  • 通过bridge即NAT的方式连接物理服务器,实际上在calico网络模型下,所有的Kubernetes集群内网IP都可以通过物理机访问,所以这里我们用了桥接的方式即可实现访问。
  • 绑定到物理机1194的UDP端口上
  • 加密算法用的AES-128-CBC

经过上述配置后,我们可以在本地通过netcat访问,如果能成功连接而没有”Connection Refused”,就说明服务启动成功了。

  1. nc -u vpn.coder4.com 1194

配置客户端

在服务端启动后,我们需要能够从客户端真正的建立OpenVPN隧道。

OpenVPN默认是支持多用户的(即可以有不同用户连接VPN),我们首先要为用户在OpenVPN系统中创建帐号, create_vpn_user.sh:

  1. #!/bin/bash
  2. if [ x"$#" != x"1" ];then
  3. echo "Usage: $0 <username>"
  4. exit -1
  5. fi
  6. USERNAME="$1"
  7. OVPN_FILE="$USERNAME.ovpn"
  8. CIPHER="AES-128-CBC"
  9. DNS_IP="10.96.0.10"
  10. ROUTE_CMD="route 192.168.0.0 255.255.0.0"
  11. VOLUME="$HOME/openvpn"
  12. # generate client cert for username
  13. docker run -v $VOLUME:/etc/openvpn --rm -it kylemanna/openvpn easyrsa build-client-full $USERNAME nopass
  14. docker run -v $VOLUME:/etc/openvpn --rm kylemanna/openvpn ovpn_getclient $USERNAME > $OVPN_FILE
  15. # post process
  16. sed -i 's/redirect-gateway.*$//' $OVPN_FILE
  17. cat >> $OVPN_FILE <<EOF
  18. # disable lzo
  19. comp-lzo no
  20. # add this line, the k8s network route
  21. $ROUTE_CMD
  22. # dns update
  23. dhcp-option DNS $DNS_IP
  24. script-security 2
  25. up /etc/openvpn/update-resolv-conf
  26. down /etc/openvpn/update-resolv-conf
  27. # security
  28. cipher $CIPHER
  29. EOF

如上执行”create_vpn_user.sh coder4”,则会创建一个名为coder4的用户,并生成一个”coder4.ovpn”文件到本地。

我们看一下这个文件:

  1. client
  2. nobind
  3. dev tun
  4. remote-cert-tls server
  5. comp-lzo no
  6. remote vpn.coder4.com 1194 udp
  7. <key>
  8. -----BEGIN PRIVATE KEY-----
  9. xxxx
  10. -----END PRIVATE KEY-----
  11. </key>
  12. <cert>
  13. -----BEGIN CERTIFICATE-----
  14. xxxx
  15. -----END CERTIFICATE-----
  16. </cert>
  17. <ca>
  18. -----BEGIN CERTIFICATE-----
  19. xxxx
  20. -----END CERTIFICATE-----
  21. </ca>
  22. key-direction 1
  23. <tls-auth>
  24. #
  25. # 2048 bit OpenVPN static key
  26. #
  27. -----BEGIN OpenVPN Static key V1-----
  28. xxxx
  29. -----END OpenVPN Static key V1-----
  30. </tls-auth>
  31. # add this line, the k8s network route
  32. route 192.168.0.0 255.255.0.0
  33. # dns update
  34. #dhcp-option DNS 10.96.0.10
  35. script-security 2
  36. up /etc/openvpn/update-resolv-conf
  37. down /etc/openvpn/update-resolv-conf
  38. # security
  39. cipher AES-128-CBC

如上所述:

  • 证书部分已经被省略
  • 禁用lzo压缩
  • 远程服务器地址是vpn.code4.com,协议是udp
  • vpn建立连接后,自动设置路由192.168.0.0/16,这个是Kubernets集群内网的路由
  • 应用dns服务器10.96.0.10,这个也是Kubernetes集群默认的

生成文件后,我们在本地网络建立vpn连接:

  1. openvpn ./coder4.ovpn

vpn链接建立成功后,我们尝试ping一个Kubernetes集群内的地址(假设集群内已经启动了若干容器):

  1. ping 192.168.1.2
  2. PING (192.168.1.2) 56(84) bytes of data.
  3. 64 bytes from 192.168.1.2: icmp_seq=1 ttl=48 time=8.39 ms
  4. 64 bytes from 192.168.1.2: icmp_seq=2 ttl=48 time=8.34 ms
  5. 64 bytes from 192.168.1.2: icmp_seq=3 ttl=48 time=8.41 ms

访问成功!至此,我们通过OpenVPN的方式,成功打通了本地网络和远程Kubernetes集群的内网。

最后,还需要再说明两点:

  1. Kubernetes网络模型很多,实现差别很大,本文所述的k8s + calico + docker bridge(nat)的方式 配合才能生效,其他网络模型和组合不保证能成功
  2. 由于一些你懂的原因,如果openvpn的server和client之间的互联网跨国了,会被断开或者无法访问,UDP也不行,不过对于微服务的应用场景,影响不大,一般都是服务器部署在国内,开发人员也在国内。

拓展与思考

  1. 在k8s中,POD与Service IP一般不会分配在同一网段中。如果我们的客户端也想访问Service IP,应当在哪一步、增加哪些配置?
  2. 本节通过OpenvVPN实现了从本地(单机)访问Kubernetes集群内网。如果想让本地局域网内,所有机器都可以访问远程Kubernetes集群的内网,应当如何配置呢?