背景
k8s的cluster管理很便利,但是它假设了所有机器都运作在一个可访问的网络中。然而,实际的应用上往往会出现不同网段的场景。
我便遇到这样的场景
有一个Lightsail的自建Cluster,然后还有另外几个不同IDC的机器。
为了让所有的服务器都更容易的部署,也为了能实现多地就近接入。
于是便有了将所有的机器,统一接入到Lightsail的Cluster中。(为什么选用Lightsail,因为便宜+安全稳定,具体原因不详述,更多是个人偏好)
然而,当正式开始部署的时候,却发现种种的麻烦与问题。
初步的思考和搜索下,大致有两个方案
- 初始化k8s cluster时,配置apiserver使用公网ip
- 通过vpn,将worker与master拉到同一个网络中
于是,马上去试验,Let’s go ~
使用公网IP初始化apiserver
一般来说,初始化k8s cluster时,指定 apiserver-advertise-address=publicip
则可。
例如:
1
| $ kubeadm init --pod-network-cidr=10.244.0.0/16 --service-cidr=10.96.0.0/16 --apiserver-advertise-address=18.18.18.18
|
然而,Lightsail是与EC2一样,使用的是 Elastic IP。
本地的网卡是不存在公网IP的。是通过VPC的路由过来的。kubeadm init 的过程,是无法连接到服务器导致初始化失败。
github上有类似的issue:https://github.com/kubernetes/kubeadm/issues/1390
有讨论说可以通过端口转发的方式来让网络可访问,但这样的解决方式并不优雅,我认为不是一个好的解决方法。
也有说为什么要使用ElasticIP,这种场景本就不应该使用。对于这个,确实是这样,然而,我们更多的时候是无法选择,因为服务器只提供这样的方式,在经济成本下可选型上。找不到更合适的,因此才有这样的麻烦与问题。
对于ElasticIP,有点的优点,也有麻烦的地方。
不扯远了,回归到问题本身,公网ip初始化apiserver,目前还走不通,解决方法不优雅,不确定是否会有更多的潜在问题。这方案走不通,放弃了,尝试另一个方案吧。
k8s部署OpenVPN
既然要部署vpn让worker机器与master处于同一网络。
于是有个想法便是,在k8s中部署vpn,然后这样机器连接上vpn后,边能直接访问k8s的内部服务,再通过配置路由表,让worker机器可以直连到master的子网。看着应该是可行的。
vpn的方案找寻了一下,OpenVPN的部署比较方便,便选用它了。
通过helm部署OpenVPN
- 安装helm过程省略
- 配置OpenVPN需要的资源
local-storage.yaml
1 2 3 4 5 6 7 8
| apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage namespace: gzdg provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Retain
|
openvpn-local-pv.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| apiVersion: v1 kind: PersistentVolume metadata: name: data-openvpn namespace: gzdg spec: capacity: storage: 10Mi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-storage local: path: /PATH/openvpn/data nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - storage-host
|
其中一些参数解释
/PATH/openvpn/data 指定存放OpenVPN配置文件的目录,初始化OpenVPN后,密钥等信息会在里面。
storage-host是nodeSelector指定,在哪台机器机器部署这个资源。
然后通过 kubectl 创建资源
1 2
| $ kubectl apply -f local-storage.yaml $ kubectl apply -f openvpn-local-pv.yaml
|
- 部署OpenVPN
创建 openvpn-values.yaml 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| replicaCount: 1 image: repository: jfelten/openvpn-docker tag: 1.1.0 service: type: ClusterIP persistence: enabled: true storageClass: local-storage accessMode: ReadWriteOnce size: 10Mi openvpn: OVPN_K8S_POD_NETWORK: 10.244.0.0 OVPN_K8S_POD_SUBNET: 255.255.0.0 OVPN_K8S_SVC_NETWORK: 10.96.0.0 OVPN_K8S_SVC_SUBNET: 255.255.0.0
|
然后执行
1
| $ helm install openvpn --namespace gzdg -f openvpn-values.yaml stable/openvpn
|
小Tips:
部署vpn过程如果出错,可以通过执行下面指令清理
$ helm delete openvpn -n gzdg
$ kubectl patch pv data-openvpn -p ‘{“spec”:{“claimRef”: null}}’
删除openvpn后,之前已经使用的pvc会被置为Release状态,需要重新激活才能使用。
1 2 3
| $ kubectl get svc openvpn -n gzdg NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE openvpn ClusterIP 10.96.179.22 <none> 443/TCP 8h
|
确认openvpn部署完成。
- 通过ingress-nginx暴露服务
在 ingress-nginx 的配置文件 service-nodeport.yaml 上追加信息
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
| apiVersion: v1 kind: Service metadata: name: ingress-nginx namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx spec: type: NodePort ports: - name: http port: 80 targetPort: 80 protocol: TCP nodePort: 30080 - name: https port: 443 targetPort: 443 protocol: TCP nodePort: 30443 - name: proxied-tcp-1194 port: 1194 targetPort: 1194 protocol: TCP nodePort: 31194 selector: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx
|
将1194端口映射到31194. 宿主机器可通过31194访问容器服务。
这里有个小tips,可以通过宿主的nginx做4层转发,做一些权限管理之类的。这里不展开。
在 ingress-nginx.yaml 的 ConfigMap tcp-service配置中追加配置
1 2 3 4 5 6 7 8 9 10
| kind: ConfigMap apiVersion: v1 metadata: name: tcp-services namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx data: 1194: "gzdg/openvpn:443"
|
将tcp 1194端口转发到容器。
- 配置OpenVPN客户端信息
创建 gen-client-key.sh 脚本,用来生成客户端配置信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #!/bin/bash
if [ $# -ne 3 ] then echo "Usage: $0 <CLIENT_KEY_NAME> <NAMESPACE> <HELM_RELEASE>" exit fi
KEY_NAME=$1 NAMESPACE=$2 HELM_RELEASE=$3 POD_NAME=$(kubectl get pods -n "$NAMESPACE" -l "app=openvpn,release=$HELM_RELEASE" -o jsonpath='{.items[0].metadata.name}') SERVICE_NAME=$(kubectl get svc -n "$NAMESPACE" -l "app=openvpn,release=$HELM_RELEASE" -o jsonpath='{.items[0].metadata.name}') SERVICE_IP=$(kubectl get svc -n "$NAMESPACE" "$SERVICE_NAME" -o go-template='{{(.spec.clusterIP)}}') kubectl -n "$NAMESPACE" exec -it "$POD_NAME" /etc/openvpn/setup/newClientCert.sh "$KEY_NAME" "$SERVICE_IP" kubectl -n "$NAMESPACE" exec -it "$POD_NAME" cat "/etc/openvpn/certs/pki/$KEY_NAME.ovpn" > "$KEY_NAME.ovpn"
|
然后执行
1
| $ ./gen-client-key.sh client gzdg openvpn
|
成功后会生成 client.ovpn
文件。到此,服务端配置完成了。
连接VPN,并加入cluster
- 将刚刚的client.ovpn上传到worker机器
- 修改 client.ovpn 校正一些连接信息
1 2 3 4 5 6 7 8 9 10 11
| # 生成的默认远程IP是cluster的IP与端口,这里需要改成vpn服务器的公网ip与访问端口 # remote 10.96.179.22 443 tcp remote 18.18.18.18 31194 tcp
# 去掉 redirect-gateway def1 # redirect-gateway def1 # 不去掉的情况,客户端连接vpn后,全局流量都走vpn了。
# 追加新的路由,让vpn网络可以访问vpn服务器宿主网络 # 根据具体情况设置 route 172.26.24.0 255.255.255.0
|
1 2 3 4 5 6 7 8 9 10 11 12
| $ openvpn --config client.ovpn --daemon $ route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default ip-172-26-0-1.u 0.0.0.0 UG 100 0 0 eth0 10.96.0.0 10.240.0.5 255.255.0.0 UG 0 0 0 tun0 10.240.0.1 10.240.0.5 255.255.255.255 UGH 0 0 0 tun0 10.240.0.5 0.0.0.0 255.255.255.255 UH 0 0 0 tun0 10.244.0.0 10.240.0.5 255.255.255.0 UG 0 0 0 tun0 10.244.0.0 10.240.0.5 255.255.0.0 UG 0 0 0 tun0 172.26.0.0 0.0.0.0 255.255.255.0 U 100 0 0 eth0 172.26.24.0 ip-10-240-0-5.u 255.255.255.0 UG 0 0 0 tun0
|
看到路由表,成功了。
这时候ping内网 172.26.24.209, 还是cluster服务的 10.244.2.2。都能成功访问。
最后,通过 kubectl join 加入到cluster中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $ route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default ip-172-26-0-1.u 0.0.0.0 UG 0 0 0 eth0 10.96.0.0 ip-10-240-0-5.u 255.255.0.0 UG 0 0 0 tun0 ip-10-240-0-1.u ip-10-240-0-5.u 255.255.255.255 UGH 0 0 0 tun0 ip-10-240-0-5.u 0.0.0.0 255.255.255.255 UH 0 0 0 tun0 10.244.0.0 ip-10-240-0-5.u 255.255.255.0 UG 0 0 0 tun0 10.244.0.0 ip-10-240-0-5.u 255.255.0.0 UG 0 0 0 tun0 10.244.2.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0 link-local 0.0.0.0 255.255.0.0 U 1002 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 172.26.0.0 0.0.0.0 255.255.240.0 U 0 0 0 eth0 172.26.24.0 ip-10-240-0-5.u 255.255.255.0 UG 0 0 0 tun0
|
结语
当然,这样的方式是能将不同区域的服务器整合在一起,但是,毕竟不是专线网络,这里的跨地区访问速度是有非常大的限制的。在实际业务中,并不能通过这样的方式进行数据交换。在部署pod的时候需要注意并区分worker的所在地。(可通过标签来区分)
实际上,这样的vpn方式实际上就是aws提供的 site to site vpn的方式是一样的。只不过,aws提供的,通过vpc来建立路由表,能更直观更简单灵活配置不同的网络策略。通过 site to site vpn来建立专线连接,让不同区域的服务器可以直连。从效果来说,aws的是更好的。
那为什么不直接使用aws的site to site vpn?
一个字
贵!!!
https://aws.amazon.com/cn/vpn/pricing/
定价示例
定价示例 1 没有加速的站点到站点 VPN
- 您可以在美国东部(俄亥俄)创建一个与您的 Amazon VPC 相连的 AWS 站点到站点 VPN 连接。此连接活跃期为 30 天,且每天 24 小时都处于活跃状态。通过该连接传出了 1000 GB 数据,传入了 500 GB 数据。
- AWS 站点到站点 VPN 连接费用:对于处于活跃状态的连接,您需要按小时支付 AWS 站点到站点 VPN 连接费用。对于该 AWS 区域,费率为每小时 0.05 USD。
- 数据传输费:首 GB 数据是免费的,对于 499 GB,将按每 GB 0.09 USD 的费率向您收费。您需要付费 44.91 USD。
- 您需要付费 80.91 USD。
其实不只是 AWS VPN, 还有 ELK, ELB 等等等一系列的服务。aws能提供更加专业更可靠灵活的服务。就是贵。
所以很多时候权衡了使用场景后,使用自建的方式,来降低这里的成本。