k3s笔记-02-在Oracle Cloud部署集群
Overview
1. 前言
卖油翁说过:我亦无他,惟手熟尔。掌握工具的最好办法就是每天使用,笔者的小集群在Oracle免费虚拟机上已经运行超过100天了,前些天误操作删掉了master,恢复时顺手升级到最新的v1.22.7版本。
今天请了病假,在跑完几趟医院后,傍晚得空在家休息,闲着也是闲着,就记录了下如何使用Cloudflare和Oracle Cloud的免费资源搭建个人集群。
2. 可用的免费资源
2.1 Cloudflare
注册Cloudflare并将域名的NS设置完成后,我们可以获得以下资源:
1. 泛域名TLS证书
主域名及子域名都可以得到TLS加密,并获取到一份由cloudflare签发的泛域名源证书,证书部署在集群后,cloudflare与我们集群之间的通信也能得到TLS加密。
2. CDN加速
使用域名访问时,所有流量都会经过cloudflare的边缘节点转发,并支持以下协议:
- HTTP/1.1
- HTTP/2
- HTTP/3
- gRPC(基于HTTP/2)
- WebSocket(基于HTTP/1.1)
这意味着我们可以部署常见的HTTP应用,例如s3、DNS over HTTPS等,以及利用WebSocket/gRPC作为数据通道的代理。
如果是使用TCP或UDP协议的应用,还是需要访问宿主机端口,再转发到对应服务。
2.2 Oracle Cloud
关于Oracle Cloud免费资源,笔者在之前的博客中已经详细记录:Oracle Cloud笔记
笔者在这里使用了以下资源:
- 一台4核CPU,24G内存,50G系统盘的arm64虚拟机作为master,具备IPv4和IPv6公网地址
- 两台1核CPU,2G内存,50G系统盘的x86虚拟机作为worker,具备IPv4和IPv6公网地址
- 使用S3协议接入免费的对象存储备份etcd
- 一台AMD虚拟机挂载50G块存储供S3服务使用
服务暴露使用traefik和k3s自带的servicelb实现,不依赖Oracle Cloud的负载均衡器。
3. 集群架构
1➜ ~ kubectl get node
2NAME STATUS ROLES AGE VERSION
3instance-20211118-1925 Ready 10.0.0.33,doh,goproxy,s3 2d22h v1.22.7+k3s1
4instance-20211118-1935 Ready 10.0.0.220,proxy 2d22h v1.22.7+k3s1
5oracle Ready 10.0.0.184,control-plane,etcd,master 2d22h v1.22.7+k3s1
如上,笔者的集群中有三台虚拟机,每台使用 node-role.kubernetes.io 附加了额外的角色用于标记宿主机。
集群是简单的一主二从结构,由于混合部署了arm64和x86,需要注意使用的镜像也支持不同架构,否则服务漂移后会因为架构不同导致启动失败。
VPC地址:10.0.0.184/24
集群网段:10.253.0.0/24] B[节点B
VPC地址:10.0.0.33/24
集群网段:10.253.1.0/24] C[节点C
VPC地址:10.0.0.220/24
集群网段:10.253.2.0/24] A--->B B--->C C--->A end
所有节点处于同一个VPC,而集群网络基于VPC内网搭建,k3s默认使用flannel+vxlan作为集群网络方案,笔者在这里选择了 10.253.0.0/16 与10.254.0.0/16 作为cluster-cidr与service-cidr,避免与原有的VPC网段冲突。
集群IP:10.253.0.2] end subgraph 节点B B1[容器组B1
集群IP:10.253.1.2] end end
服务IP:10.254.10.10] SC--->|iptables转发|C1 SC--->|iptables转发|C2 subgraph 节点A A1[容器组A1
集群IP:10.253.0.2] end subgraph 节点C C1[容器组C1
集群IP:10.253.2.2] C2[容器组C2
集群IP:10.253.2.4] end end
PodIP->
节点内网IP->
节点外网EIP] B--->C1 B--->C2 subgraph VPC subgraph 节点A A1[容器组A1
集群IP:10.253.0.2] end end subgraph 公网 C1[谷歌
域名:google.com] C2[苹果
域名:apple.com] end end
每个节点上的容器访问Pod和Service时使用集群IP,访问外网时使用宿主机的公网IP,如果是代理类的应用,需要注意绑定宿主机避免源IP漂移。
4. 应用部署与服务暴露
集群的服务暴露一直都是个难题,而k3s选择自带附加组件servicelb与traefik充当了LoadBalancer与Ingress Controller的角色。
集群部署完成后,会启动traefik并创建一个关联的service,所有宿主机的IP会被加入该service的loadBalancer的ingress的IP列表中,访问宿主机80和443端口的流量首先转发到service,接着进一步转发到后端的traefik容器组。
1➜ ~ kubectl -n kube-system describe svc traefik
2Name: traefik
3Namespace: kube-system
4Labels: app.kubernetes.io/instance=traefik
5 app.kubernetes.io/managed-by=Helm
6 app.kubernetes.io/name=traefik
7 helm.sh/chart=traefik-10.14.100
8Annotations: meta.helm.sh/release-name: traefik
9 meta.helm.sh/release-namespace: kube-system
10Selector: app.kubernetes.io/instance=traefik,app.kubernetes.io/name=traefik
11Type: LoadBalancer
12IP Family Policy: SingleStack
13IP Families: IPv4
14IP: 10.254.142.204
15IPs: 10.254.142.204
16LoadBalancer Ingress: 10.0.0.184, 10.0.0.220, 10.0.0.33
17Port: web 80/TCP
18TargetPort: web/TCP
19NodePort: web 31348/TCP
20Endpoints: 10.253.0.2:8000
21Port: websecure 443/TCP
22TargetPort: websecure/TCP
23NodePort: websecure 32684/TCP
24Endpoints: 10.253.0.2:8443
25Session Affinity: None
26External Traffic Policy: Cluster
27Events: <none>
在请求进入traefik后,就可以使用内部域名访问集群中的服务了。以之前部署的S3服务为例,我们可以画出以下的请求流程:
s3.wbuntu.com]--->CF CF--->NAT subgraph 公网 CF[Cloudflare CDN] end subgraph VPC NAT[dnat转换
节点外网EIP->
节点内网IP] NAT--->|iptables转发|A1 subgraph 节点A A1[容器组traefik
集群IP:10.253.0.2] end end end
集群IP:10.253.0.2] end SB[Service doh] SC[Service minio] SB--->B1 SC--->C1 SC--->C2 subgraph 节点B B1[容器组doh
集群IP:10.253.1.2] end subgraph 节点C C1[容器组minio
集群IP:10.253.2.2] C2[容器组minio
集群IP:10.253.2.4] end end
如上所示,要暴露服务到公网时,只需创建路由规则即可,可以把多个路由规则合并到一个Ingressroute中,就像使用nginx一样按域名和路径转发请求。
下面是将minio部署在集群中,并暴露服务到公网的相关yaml配置。
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4 name: minio
5spec:
6 replicas: 1
7 selector:
8 matchLabels:
9 app: minio
10 template:
11 metadata:
12 labels:
13 app: minio
14 spec:
15 nodeSelector:
16 node-role.kubernetes.io/s3: "true"
17 containers:
18 - name: minio
19 image: minio/minio
20 args: ["server", "/data", "--address", ":9000", "--console-address", ":9001"]
21 env:
22 - name: MINIO_ROOT_USER
23 value: "xxxxxxxxxxxxxxxxx"
24 - name: MINIO_ROOT_PASSWORD
25 value: "xxxxxxxxxxxxxxxxx"
26 - name: MINIO_SERVER_URL
27 value: "https://s3.wbuntu.com"
28 - name: MINIO_BROWSER_REDIRECT_URL
29 value: "https://s3-console.wbuntu.com"
30 imagePullPolicy: IfNotPresent
31 ports:
32 - containerPort: 9000
33 - containerPort: 9001
34 volumeMounts:
35 - name: minio
36 mountPath: /data
37 volumes:
38 - name: minio
39 hostPath:
40 path: /data/minio
41 type: DirectoryOrCreate
上述yaml中,使用deployment部署minio,挂载宿主机目录存储数据。
1apiVersion: v1
2kind: Service
3metadata:
4 name: minio
5spec:
6 type: ClusterIP
7 selector:
8 app: minio
9 ports:
10 - name: s3
11 port: 9000
12 targetPort: 9000
13 - name: s3-console
14 port: 9001
15 targetPort: 9001
上述yaml中,使用service将minio暴露给traefik。
1apiVersion: traefik.containo.us/v1alpha1
2kind: IngressRoute
3metadata:
4 name: ingress
5spec:
6 entryPoints:
7 - websecure
8 routes:
9 - match: Host(`s3.wbuntu.com`)
10 kind: Rule
11 services:
12 - name: minio
13 port: 9000
14 - match: Host(`s3-console.wbuntu.com`)
15 kind: Rule
16 services:
17 - name: minio
18 port: 9001
19 tls:
20 secretName: tls-cert
上述yaml中,创建了一个ingressroute,将Host匹配 s3.wbuntu.com 的请求转发到minio服务的9000端口,Host匹配 s3-console.wbuntu.com 的请求转发到minio的9001端口。
这里引用了一个密钥 tls-cert,包含前面提到由Cloudflare签发的泛域名证书,我们可以继续在这个ingress中添加更多的路由规则,将其他的集群服务暴露到公网,这里就不做详细展开了。
5. 获取用户IP
以代理应用为例,收到请求时会打印来源用户IP,默认配置下我们只能看到经过转发的内网IP,如下:
12022/04/01 10:24:59 10.253.0.1:0 accepted tcp:www.google.com:443 [direct] email: [email protected]
执行 kubectl -n kube-system edit svc traefik 编辑traefik的svc,设置为 externalTrafficPolicy 为 Local,让外部流量直接进入traefik,如下:
1spec:
2 ...
3 externalTrafficPolicy: Local
4 internalTrafficPolicy: Cluster
5 ...
因为此时我们的请求是经过Cloudflare的CDN转发的,再次检查代理应用日志,可以发现用户IP变成了Cloudclare的IP,如下:
12022/04/01 10:40:00 172.70.93.35:0 accepted tcp:www.google.com:443 [direct] email: [email protected]
因为Cloudflare转发请求时,已经通过X-Forwarded-For头部设置了真实用户IP,我们可以修改traefik容器的启动参数,信赖来自Cloudflare的IP地址段,将请求中的用户IP转发到后端。
执行 kubectl -n kube-system edit deploy traefik 编辑traefike的deployment,为两个entrypoint分别添加forwardedHeaders.trustedIPs,对应的value为Cloudflare的IP段,如下:
1 spec:
2 containers:
3 - args:
4 ...
5 - --entrypoints.web.address=:8000/tcp
6 - --entrypoints.web.forwardedHeaders.trustedIPs=103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,104.16.0.0/13,104.24.0.0/14,108.162.192.0/18,131.0.72.0/22,141.101.64.0/18,162.158.0.0/15,172.64.0.0/13,173.245.48.0/20,188.114.96.0/20,190.93.240.0/20,197.234.240.0/22,198.41.128.0/17
7 - --entrypoints.websecure.address=:8443/tcp
8 - --entrypoints.websecure.forwardedHeaders.trustedIPs=103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,104.16.0.0/13,104.24.0.0/14,108.162.192.0/18,131.0.72.0/22,141.101.64.0/18,162.158.0.0/15,172.64.0.0/13,173.245.48.0/20,188.114.96.0/20,190.93.240.0/20,197.234.240.0/22,198.41.128.0/17
9 ...
Cloudflare的IPv4地址段可以通过这个命令获取:
1curl -s https://www.cloudflare.com/ips-v4 | sort -h
重启traefik容器组后,再次检查代理应用日志,这时可以看到真实的用户IP了,如下:
12022/04/01 10:57:20 106.52.172.214:0 accepted tcp:content-autofill.googleapis.com:443 [direct] email: [email protected]
需要注意的是,这种场景下必须将相关域名解析到traefik所在宿主机的公网IP,因为 externalTrafficPolicy 为 Local 时,外部流量通过NodePort进入集群时只能发给本机的Pod,无法转发到其他节点上的Pod。