搭建好的Kubernetes集群中,默认是存在网络隔离的,即集群内部使用一套独立的网络,与物理网络相互隔离。
为了将内部服务暴露给外部调用,Kubernetes提供了ClusterIP等多种方式。
但在有的时候,我们能够从本地网络直接访问Kubernetes集群内网的所有结点:
- 本地调试微服务时,可能要调用许多微服务,而这些微服务所部署的Pod或Service并没有配置对外暴露的Cluster IP
- 我们需要登录Pod查看内存、日志等信息
实现这类功能,可以有两种方式:
- 在本地网络和集群内网之间,配置路由协议,从而实现互联互通。
- 通过VPN的方式,打通物理网络和集群内网。
路由配置的方式看起来最直观,但有时难以实施:
- 配置路由协议需要专业级的路由器、也需要学习路由的配置,设备采购成本、学习成本较大
- 如果本地和机房之间需要通过互联网而不是局域网访问,那么路由协议的配置需要运营商配合,基本无法实现
在本小节,我们主要介绍第二种方式,即通过VPN穿透的方式,从本地网络直接访问Kuberntes集群内网。
在深入技术细节前,我们先来了解下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所需要的密钥:
#!/bin/bash
VOLUME="$HOME/openvpn"
vpn_ip="vpn.coder4.com"
# init for first time only
rm -rf $VOLUME
mkdir -p $VOLUME
docker run -v $VOLUME:/etc/openvpn --rm kylemanna/openvpn ovpn_genconfig -u udp://$vpn_ip -s 10.4.0.0/24
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服务器了:
#!/bin/bash
# submit to tool node
NAME="openvpn"
VOLUME="$HOME/openvpn"
dns_ip="10.96.0.10"
# stop & run server (should call init_open_vpn_test.sh before)
docker ps -q -a --filter "name=$NAME" | xargs -I {} docker rm -f {}
docker run \
--name $NAME \
--network bridge \
--dns $dns_ip \
-d \
-v $VOLUME:/etc/openvpn \
-p 1194:1194/udp \
--cap-add=NET_ADMIN \
--restart always \
kylemanna/openvpn \
ovpn_run --cipher AES-128-CBC
如上所述,我们启动了VPN服务器:
- 注意我们这里是直接用的docker启动,而非放到Kubernetes集群中
- 通过bridge即NAT的方式连接物理服务器,实际上在calico网络模型下,所有的Kubernetes集群内网IP都可以通过物理机访问,所以这里我们用了桥接的方式即可实现访问。
- 绑定到物理机1194的UDP端口上
- 加密算法用的AES-128-CBC
经过上述配置后,我们可以在本地通过netcat访问,如果能成功连接而没有"Connection Refused",就说明服务启动成功了。
nc -u vpn.coder4.com 1194
配置客户端
在服务端启动后,我们需要能够从客户端真正的建立OpenVPN隧道。
OpenVPN默认是支持多用户的(即可以有不同用户连接VPN),我们首先要为用户在OpenVPN系统中创建帐号, create_vpn_user.sh:
#!/bin/bash
if [ x"$#" != x"1" ];then
echo "Usage: $0 <username>"
exit -1
fi
USERNAME="$1"
OVPN_FILE="$USERNAME.ovpn"
CIPHER="AES-128-CBC"
DNS_IP="10.96.0.10"
ROUTE_CMD="route 192.168.0.0 255.255.0.0"
VOLUME="$HOME/openvpn"
# generate client cert for username
docker run -v $VOLUME:/etc/openvpn --rm -it kylemanna/openvpn easyrsa build-client-full $USERNAME nopass
docker run -v $VOLUME:/etc/openvpn --rm kylemanna/openvpn ovpn_getclient $USERNAME > $OVPN_FILE
# post process
sed -i 's/redirect-gateway.*$//' $OVPN_FILE
cat >> $OVPN_FILE <<EOF
# disable lzo
comp-lzo no
# add this line, the k8s network route
$ROUTE_CMD
# dns update
dhcp-option DNS $DNS_IP
script-security 2
up /etc/openvpn/update-resolv-conf
down /etc/openvpn/update-resolv-conf
# security
cipher $CIPHER
EOF
如上执行"create_vpn_user.sh coder4",则会创建一个名为coder4的用户,并生成一个"coder4.ovpn"文件到本地。
我们看一下这个文件:
client
nobind
dev tun
remote-cert-tls server
comp-lzo no
remote vpn.coder4.com 1194 udp
<key>
-----BEGIN PRIVATE KEY-----
xxxx
-----END PRIVATE KEY-----
</key>
<cert>
-----BEGIN CERTIFICATE-----
xxxx
-----END CERTIFICATE-----
</cert>
<ca>
-----BEGIN CERTIFICATE-----
xxxx
-----END CERTIFICATE-----
</ca>
key-direction 1
<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
xxxx
-----END OpenVPN Static key V1-----
</tls-auth>
# add this line, the k8s network route
route 192.168.0.0 255.255.0.0
# dns update
#dhcp-option DNS 10.96.0.10
script-security 2
up /etc/openvpn/update-resolv-conf
down /etc/openvpn/update-resolv-conf
# security
cipher AES-128-CBC
如上所述:
- 证书部分已经被省略
- 禁用lzo压缩
- 远程服务器地址是vpn.code4.com,协议是udp
- VPN建立连接后,自动设置路由192.168.0.0/16,这个是Kubernets集群内网的路由
- 应用dns服务器10.96.0.10,这个也是Kubernetes集群默认的
生成文件后,我们在本地网络建立VPN连接:
openvpn ./coder4.ovpn
VPN链接建立成功后,我们尝试ping一个Kubernetes集群内的地址(假设集群内已经启动了若干容器):
ping 192.168.1.2
PING (192.168.1.2) 56(84) bytes of data.
64 bytes from 192.168.1.2: icmp_seq=1 ttl=48 time=8.39 ms
64 bytes from 192.168.1.2: icmp_seq=2 ttl=48 time=8.34 ms
64 bytes from 192.168.1.2: icmp_seq=3 ttl=48 time=8.41 ms
访问成功!至此,我们通过OpenVPN的方式,成功打通了本地网络和远程Kubernetes集群的内网。
最后,还需要再说明两点:
- Kubernetes网络模型很多,实现差别很大,本文所述的k8s + calico + docker bridge(nat)的方式 配合才能生效,其他网络模型和组合不保证能成功
- 由于一些你懂的原因,如果openvpn的server和client之间的互联网跨国了,会被断开或者无法访问,UDP也不行,不过对于微服务的应用场景,影响不大,一般都是服务器部署在国内,开发人员也在国内。
拓展与思考
- 在k8s中,POD与Service IP一般不会分配在同一网段中。如果我们的客户端也想访问Service IP,应当在哪一步、增加哪些配置?
- 本节通过OpenvVPN实现了从本地(单机)访问Kubernetes集群内网。如果想让本地局域网内,所有机器都可以访问远程Kubernetes集群的内网,应当如何配置呢?