更新于 2023.2.12

版本 v1.0.0

背景

计划搭建 K8s 集群用于提供比较稳定的服务。

我拥有多台云厂商的机器,稳定性非常高,但是内存都比较低,尤其是运行 Spring Boot 服务时会不够用。我也拥有多台内网设备,计算性能和内存都比云服务器高得多,但是不稳定。所以计划借助 K8s,使用云服务器作为控制平面和外部流量入口,后端服务运行在内网机器上。当某一台内网机器挂掉的时候,K8s 可以及时将流量切到另一台机器的服务上,从而保障服务整体的可用性。

因为云服务器的内存实在太小了,所以使用 K3s。K3s 作为轻量级的 K8s 发行版,对资源的占用非常低,适合边缘计算等场景。

因为只有一台云服务器,所以这台服务器将成为整个集群的单点。但是云服务器一般还是比较靠谱的,整体可用性也还能接受。

因为云服务器只有 1Mbps 的带宽,所以集群只能跑一些后端应用。更大带宽、负载均衡等方法尽管能解决这个问题,但是开销是难以承受的。不过可以把前端应用部署在 OSS 之类的地方。

机器信息

控制节点: 阿里云ECS 2c2g, 1Mbps
工作节点: 8c8g 及以上
操作系统: ArchLinux

Tailscale 打通网络

因为集群中存在内网节点,云服务器无法直接和内网机器通讯。为了解决这个问题,需要用到 VPN 搭建虚拟的局域网。这里我选用 Tailscale, 它具有高性能的同时,易用性比 ZeroTier 更好。我还自己搭建了 Derper 服务器,用于协助握手和流量转发。

通过 Tailscale,云服务器和每台内网机器都会分配到一个虚拟的 IP,机器之间通过这个 IP 即可直接通信。

K3s 部署

在云服务器上执行下列安装命令。其中 $MY_TAILSCALE_IP 是云服务器的 Tailscale 虚拟 IP。--disable traefik 表示禁用默认安装的 Traefik 网关,因为我想安装 APISIX。--node-taint node-role.kubernetes.io/control-plane:NoSchedule 为云服务器打上了污点,避免容器被调度到云服务器上。这是因为云服务器的内存较小,尽量不要运行多余的容器。

1
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh -s - --node-external-ip $MY_TAILSCALE_IP --flannel-backend=wireguard-native --flannel-external-ip  --node-taint node-role.kubernetes.io/control-plane:NoSchedule --disable traefik

然后执行 cat /var/lib/rancher/k3s/server/node-token,获取 K3s 的 token。

执行 export KUBECONFIG=/etc/rancher/k3s/k3s.yaml,可以考虑把这句命令放进 .zshrc

在内网机器上执行下列安装命令。其中 $CLOUD_TAILSCALE_IP 是云服务器的 Tailscale 虚拟 IP, $MY_TAILSCALE_IP 是内网机器的 Tailscale 虚拟 IP,$NODE_TOKEN 是 K3s 集群的 token。

1
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn K3S_URL=https://$CLOUD_TAILSCALE_IP:6443 K3S_TOKEN=$NODE_TOKEN sh -s - --node-external-ip $MY_TAILSCALE_IP

安装 APISIX 及 dashboard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
helm repo add apisix https://charts.apiseven.com
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install apisix apisix/apisix \
--set gateway.type=LoadBalancer \
--set gateway.tls.enabled=true \
--set ingress-controller.enabled=true \
--create-namespace \
--namespace ingress-apisix \
--set ingress-controller.config.apisix.serviceNamespace=ingress-apisix \
--set ingress-controller.config.apisix.adminAPIVersion=v3 \
--set ingress-controller.config.kubernetes.enableGatewayAPI=true \
--kubeconfig /etc/rancher/k3s/k3s.yaml

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.5.0/standard-install.yaml --namespace ingress-apisix

helm install apisix-dashboard apisix/apisix-dashboard --create-namespace --namespace ingress-apisix

将自己的域名 DNS 解析到云服务器的 ip 上。比如自己拥有的域名是 a.com,云服务器域名是 ali.a.com。还需要进行泛域名解析,将 *.ali.a.com 解析到 ali.a.com 上。这样,可以用不同的 HTTP Host 区分不同的服务。比如 apisix.ali.a.com 用来表示 APISIX Dashboard, whoami.ali.a.com 表示访问 whoami 服务。

准备 *.ali.a.com 证书。可以使用 ACME.sh 脚本进行免费申请。写入 ~/.k8s/apisix/cert.yaml。需要把 cert 和 key 经过 base64 编码后放入对应的位置。

1
2
3
4
5
6
7
8
apiVersion: v1
data:
cert: 123
key: 456
kind: Secret
metadata:
name: apisix-secret
namespace: ingress-apisix

定义 apisix.ali.a.com 到 APISIX Dashboard 的路由。写入 ~/.k8s/apisix/dashboard.yaml。其中 apisix.ali.a.com 替换成自己的域名。

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
30
31
32
33
34
35
36
37
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
name: apisix-dashboard
namespace: ingress-apisix
spec:
loadbalancer:
type: ewma
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: apisix-dashboard
namespace: ingress-apisix
spec:
http:
- name: apisix-dashboard
match:
hosts:
- apisix.ali.a.com
paths:
- /*
backends:
- serviceName: apisix-dashboard
servicePort: 80
---
apiVersion: apisix.apache.org/v2
kind: ApisixTls
metadata:
name: apisix-dashboard
namespace: ingress-apisix
spec:
hosts:
- apisix.ali.a.com
secret:
name: apisix-secret
namespace: ingress-apisix

然后执行 kubectl apply -f ~/.k8s/apisix 就可以通过 https://apisix.ali.a.com 访问 APISIX Dashboard 了。

最后部署一个 Whoami 服务。其他后端服务的部署也比较类似。

写入 ~/.k8s/whoami/whoami.yaml。其中 apisix.ali.a.com 替换成自己的域名。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
spec:
replicas: 3
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
type: NodePort
ports:
- name: http
targetPort: 80
port: 80
nodePort: 30163
selector:
app: whoami
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
name: whoami
spec:
loadbalancer:
type: ewma
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: whoami
spec:
http:
- name: whoami
match:
hosts:
- whoami.ali.a.com
paths:
- /*
backends:
- serviceName: whoami
servicePort: 80
---
apiVersion: apisix.apache.org/v2
kind: ApisixTls
metadata:
name: whoami
spec:
hosts:
- "whoami.ali.a.com"
secret:
name: apisix-secret
namespace: ingress-apisix

然后执行 kubectl apply -f ~/.k8s/whoami 就可以通过 https://whoami.ali.a.com 访问 Whoami 服务了。

参考

https://icloudnative.io/posts/deploy-k3s-cross-public-cloud/
https://docs.k3s.io/zh/