使用OpenVPN将Kubernetes集群网络暴露给本地开发网络
2019-03-08
Kubernetes集群内部是一套单独的网络,与外部网络相隔离,Kubernetes提供了NodePort、LoadBalancer、Ingress等方式用于将集群内部的Service暴露到集群外部, 但这些方式都更适用于将边缘服务暴露出去。例如,一个产品由很多微服务组成,这些微服务大多数都是内部服务,只有少数作为边缘服务需要暴露到集群外部供用户使用。 而开发人员是存在从Kubernetes外部访问和调试这些内部服务的需求的,尤其是针对测试环境的Kubernetes集群,这个需求更加必要。针对这个需求,有以下两种实现方式:
- 第一种方式:将这些内部服务以NodePort或Ingress暴露到集群外部,但是这种方式有很多缺点。对于NodePort的方式,这些内部服务会占用Kubernetes服务器节点的大量的NodePort端口,同时暴露的服务以NodeIP:NodePort的形式提供,明显不便于记录。对于以Ingress的形式,针对HTTP这类7层服务可以结合外部域名的形式暴露,但对于TCP这类4层服务,每个服务都会占用
ingress-controller
的一个端口,同样也不便于记录。 - 第二种方式:在Kubernetes集群内部部署一个OpenVPN Pod,外部网络中的开发人员通过OpenVPN客户端连接,开发人员对集群内部服务的访问都由这个OpenVPN Pod转发。现在Helm的官方Chart Repo中已经提供了一个
stable/openvpn
的Chart,使用它可以在Kubernetes集群内部完成OpenVPN的部署。本文将详细介绍OpenVPN在Kubernetes集群内的部署并打通集群内网与本地开发网络的过程。
使用Helm在K8S上部署OpenVPN #
创建持久化OpenVPN配置所需要的Local PV:
首先,在k8s集群上创建本地存储的StorageClass local-storage.yaml:
1apiVersion: storage.k8s.io/v1
2kind: StorageClass
3metadata:
4 name: local-storage
5provisioner: kubernetes.io/no-provisioner
6volumeBindingMode: WaitForFirstConsumer
7reclaimPolicy: Retain
OpenVPN的Local PV openvpn-local-pv.yaml:
1apiVersion: v1
2kind: PersistentVolume
3metadata:
4 name: data-openvpn
5spec:
6 capacity:
7 storage: 10Mi
8 accessModes:
9 - ReadWriteOnce
10 persistentVolumeReclaimPolicy: Retain
11 storageClassName: local-storage
12 local:
13 path: /home/openvpn/data
14 nodeAffinity:
15 required:
16 nodeSelectorTerms:
17 - matchExpressions:
18 - key: kubernetes.io/hostname
19 operator: In
20 values:
21 - node2
在node2上预先创建/home/openvpn/data
这个目录。
使用helm官方Chart Repo中的stable/openvpn
,编写下面的openvpn-values.yaml
:
1replicaCount: 1
2image:
3 repository: jfelten/openvpn-docker
4 tag: 1.1.0
5service:
6 type: ClusterIP
7persistence:
8 enabled: true
9 storageClass: local-storage
10 accessMode: ReadWriteOnce
11 size: 10Mi
12openvpn:
13 OVPN_K8S_POD_NETWORK: 10.244.0.0
14 OVPN_K8S_POD_SUBNET: 255.255.0.0
15 OVPN_K8S_SVC_NETWORK: 10.96.0.0
16 OVPN_K8S_SVC_SUBNET: 255.240.0.0
注意上面的openvpn-values.yaml
中:
OVPN_K8S_POD_NETWORK
与OVPN_K8S_POD_SUBNET
要与Pod的网络cluster-cidr
一致,我这里是16
OVPN_K8S_SVC_NETWORK
与OVPN_K8S_SVC_NETWORK
要与kube-apiserver
中指定的service-cluster-ip-range
一致,我这里是10.96.0.0/12
使用helm在k8s上安装openvpn:
1helm install \
2 --name openvpn --namespace kube-system -f openvpn-values.yaml stable/openvpn
安装完成后openvpn的证书会被保存在前面创建的Local PV的pki目录里,这里是/home/openvpn/data/pki/
。
可以看到openvpn在k8s中的Service如下:
1kubectl get svc -n kube-system -l app=openvpn
2NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
3openvpn ClusterIP 10.101.165.239 <none> 443/TCP 8m18s
因为这里我们选择创建的是ClusterIP类型的Service,所以还需要使用Ingress将这个Service暴露到k8s集群外部,这样就可以再集群外部使用OpenVPN的客户端连接了。
注意这里暴露的是TCP服务,与暴露HTTP服务不同,不能通过创建Ingress资源暴露。而是需要在Ingress Controller的相同Namespace下的configmap/nginx-ingress-tcp
添加nginx端口与需要暴露服务端口的对应关系。
因为这里的k8s Nginx Ingress是使用helm部署的,所以直接在Chart的value file ingress-nginx.yam中更新相关内容:
1......
2# TCP service key:value pairs
3tcp:
4 1194: "kube-system/openvpn:443"
这里相当于是通过k8s edge节点的1194端口暴露openvpn Service的443端口。
使用helm更新nginx-ingress
release:
1helm upgrade nginx-ingress stable/nginx-ingress \
2-f ingress-nginx.yaml
3
4
5Release "nginx-ingress" has been upgraded.
在k8s的edge节点上查看1194
端口已经被nginx-ingress-controller
监听:
1netstat -nltp | grep 1194
2tcp 0 0 0.0.0.0:1194 0.0.0.0:* LISTEN 16291/nginx: master
3tcp6 0 0 :::1194 :::* LISTEN 16291/nginx: master
使用客户端连接OpenVPN #
这里是Mac OS X系统,使用的客户端软件是TunnelBlick。
使用下面的脚本生成一个客户端key:
1#!/bin/bash
2
3if [ $# -ne 3 ]
4then
5 echo "Usage: $0 <CLIENT_KEY_NAME> <NAMESPACE> <HELM_RELEASE>"
6 exit
7fi
8
9KEY_NAME=$1
10NAMESPACE=$2
11HELM_RELEASE=$3
12POD_NAME=$(kubectl get pods -n "$NAMESPACE" -l "app=openvpn,release=$HELM_RELEASE" -o jsonpath='{.items[0].metadata.name}')
13SERVICE_NAME=$(kubectl get svc -n "$NAMESPACE" -l "app=openvpn,release=$HELM_RELEASE" -o jsonpath='{.items[0].metadata.name}')
14SERVICE_IP=$(kubectl get svc -n "$NAMESPACE" "$SERVICE_NAME" -o go-template='{{range $k, $v := (index .status.loadBalancer.ingress 0)}}{{$v}}{{end}}')
15kubectl -n "$NAMESPACE" exec -it "$POD_NAME" /etc/openvpn/setup/newClientCert.sh "$KEY_NAME" "$SERVICE_IP"
16kubectl -n "$NAMESPACE" exec -it "$POD_NAME" cat "/etc/openvpn/certs/pki/$KEY_NAME.ovpn" > "$KEY_NAME.ovpn"
1./gen-client-key.sh client kube-system openvpn
上面的命令会生成client.ovpn配置文件,因为前面用Helm部署openvpn时,创建的Servie是ClusterIP类型的,生成的配置文件
中的VPN服务地址会有错误,需要修改这个配置文件,将remote
配置修改如下地址为k8s边缘节点的ip,端口为前面暴露的1194端口,同时删除redirect-gateway def1
这行内容。
1remote 192.168.99.11 1194 tcp
右键点击client.ovpn,选择使用TunnelBlick打开,导入后,连接即可。连接后,下面在本地直接测试访问k8s集群中的Service或PodIP,确认网络可达。
因为这里的k8s集群的kube-proxy
使用的IPVS模式,所以直接ping ClusterIP也应该是可达的,在个人本地MacBook上连接OpenVPN后测试如下:
尝试解析kubernetes.default.svc.cluster.local
,确认可正常解析,注意这里一定要给完整的Service域名:
1nslookup kubernetes.default.svc.cluster.local
2Server: 10.96.0.10
3Address: 10.96.0.10#53
4
5Name: kubernetes.default.svc.cluster.local
6Address: 10.96.0.1
ping k8s集群中任何一个ClusterIP和PodIP确认可以ping通:
1ping 10.96.0.1
2PING 10.96.0.1 (10.96.0.1): 56 data bytes
364 bytes from 10.96.0.1: icmp_seq=0 ttl=63 time=20.327 ms
464 bytes from 10.96.0.1: icmp_seq=1 ttl=63 time=10.863 ms
564 bytes from 10.96.0.1: icmp_seq=2 ttl=63 time=12.441 ms
6
7ping 10.244.0.1
8PING 10.244.0.1 (10.244.0.1): 56 data bytes
964 bytes from 10.244.0.1: icmp_seq=0 ttl=62 time=28.051 ms
1064 bytes from 10.244.0.1: icmp_seq=1 ttl=62 time=89.588 ms
1164 bytes from 10.244.0.1: icmp_seq=2 ttl=62 time=63.754 ms
经过上面的测试可以发现从集群外部连接上VPN后,已经可以正常解析集群内Service的域名,并访问集群内部的服务。
如果收回并取消生成的客户端key,可以执行下面的脚本:
1#!/bin/bash
2
3if [ $# -ne 3 ]
4then
5 echo "Usage: $0 <CLIENT_KEY_NAME> <NAMESPACE> <HELM_RELEASE>"
6 exit
7fi
8
9KEY_NAME=$1
10NAMESPACE=$2
11HELM_RELEASE=$3
12POD_NAME=$(kubectl get pods -n "$NAMESPACE" -l "app=openvpn,release=$HELM_RELEASE" -o jsonpath='{.items[0].metadata.name}')
13kubectl -n "$NAMESPACE" exec -it "$POD_NAME" /etc/openvpn/setup/revokeClientCert.sh $KEY_NAME
总结 #
本篇通过介绍Helm chart for OpenVPN,将Kubernetes集群内网暴露到集群外部的开发网络。 实际正式使用还需要做进一步的完善和配置,例如对于每一个团队内部一般都会有一套基于LDAP的统一账号管理系统,这里的搭建的"Kubernetes VPN开发网络"也需要与这套LDAP相集成, 因此还需要做进一步的配置,关于OpenVPN如何集成LDAP可参考我之前编写的《团队环境:OpenVPN集成LDAP认证》,这个不再展开。