0%

Lightsail自建k8s公网集群

背景

k8s的cluster管理很便利,但是它假设了所有机器都运作在一个可访问的网络中。然而,实际的应用上往往会出现不同网段的场景。

我便遇到这样的场景

有一个Lightsail的自建Cluster,然后还有另外几个不同IDC的机器。

为了让所有的服务器都更容易的部署,也为了能实现多地就近接入。

于是便有了将所有的机器,统一接入到Lightsail的Cluster中。(为什么选用Lightsail,因为便宜+安全稳定,具体原因不详述,更多是个人偏好)

然而,当正式开始部署的时候,却发现种种的麻烦与问题。

初步的思考和搜索下,大致有两个方案

  1. 初始化k8s cluster时,配置apiserver使用公网ip
  2. 通过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 的过程,是无法连接到服务器导致初始化失败。

image-20200125012402156

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

  1. 安装helm过程省略
  2. 配置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
  1. 部署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部署完成。

  1. 通过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端口转发到容器。

  1. 配置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

  1. 将刚刚的client.ovpn上传到worker机器
  2. 修改 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能提供更加专业更可靠灵活的服务。就是贵。

所以很多时候权衡了使用场景后,使用自建的方式,来降低这里的成本。