k8s 折腾记:使用一个集群暴露另一个集群

背景

由于家境贫寒没钱,只有一台 2c4g 的云服务器,基本部署不了什么服务。但是,作为一个垃圾佬,本地的资源可以说是非常过剩。因此,就想着,能不能通过部署在公网的集群,把在内网的集群也暴露到公网上。

目标

主要分为 http 服务和 tcp 服务,比如说网页服务和数据库服务。

Http

由于 http 服务使用 80 和 443 端口,原集群和本地集群必然使用同一个端口,所以需要做到合理的分流。

分类显然就是基于域名的了,和虚拟主机一个道理。我规定 k.suyiiyii.top将会路由到公网的集群,而 kl.suyiiyii.top 将会路由到本地的集群。

TCP

这个就主要靠端口区分了,虽然有根据 tls sni 嗅探做分流的技术,但毕竟不是每一个应用都会用标准的 tls,所以还是要基于更底层的端口。

打通内网

实现转发之前,总要让云端的集群能够访问本地的集群吧。这里我使用的是 tailscale,在本地的一台机器上面配置 subnet,然后其他机器就可以直接访问内网了,参考文档

Http

转发的方法还是使用 Service + Endpoints 来实现,下面给出 manifest:

 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
apiVersion: v1
kind: Endpoints
metadata:
  name: k65 # 端点名称,可以根据需要修改
subsets:
  - addresses:
      - ip: 10.21.22.65 # 外部服务的 IP 地址
    ports:
      - port: 443 # 外部服务的端口
        protocol: TCP # 协议类型
        name: https
      - port: 80 # 外部服务的端口
        protocol: TCP # 协议类型
        name: http
---
apiVersion: v1
kind: Service
metadata:
  name: k65
spec:
  ports:
    - protocol: TCP
      port: 443
      name: https
      targetPort: 443
    - protocol: TCP
      port: 80
      name: http
      targetPort: 80
  type: ClusterIP

然后是 ingress,这里我使用了 traefik 的 crds,IngressRoute,多了一个可以使用正则匹配域名的功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: external-service-route-http
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`kl.suyiiyii.top`) || HostRegexp(`{subdomain:[a-z0-9-]+}.kl.suyiiyii.top`)
      kind: Rule
      services:
        - name: k65
          port: 80

这样,再把 kl.suyiiyii.top*.kl.suyiiyii.top 都解析到云端的集群,就可以实现 http 协议的自动转发了了。

Https

事情到了 https 这里,就变得复杂起来了。

谁加密请求

首先,要问自己一个问题,tls 加密的部分,应该是谁来做处理?是云端集群还是本地集群?

  • 云端集群:那么本地集群就只暴露 http 服务,云端集群配置域名相关 tls
  • 本地集群:本地集群配置 tls,并且同时暴露 http 和 https 服务,云端集群只提供转发服务

这里我采用的是后者。如果采用的是前者,那么配置本地集群服务的同时需要在云端集群配置要转发的域名。而采取的后者,只需要在本地集群配置即可。同时,应用和应用入口的配置在同一个集群,也方便应用的迁移(不需要修改 manifest)。

怎么分流流量

http 协议可以通过读取请求头中的 Host 字段来判断请求的主机,但 https 是 http 的加密版本,整个请求是全部加密的,根本读取不到 Host 字段,我们需要其他的手段来实现 https 请求分流。

https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview

现在,http 协议由更下次的 tls 协议包裹,所以我们要把目光聚焦于 tls 协议了

https://anushadasari.medium.com/the-https-protocol-explained-under-the-hood-c7bd9f9aaa7b

我们查询相关资料,发现 tls 协议有一个 sni 扩展,似乎可以解决这个问题。

简单来说,就是在 https 刚被发明出来的一段时间,人们也发现了 https 无法根据主机名称进行分流这个问题,所以既然 Host 字段被放到内层加密了,那我就在外层再添加一个 Host 不就行了。

sni 扩展就是做这个事情的,目前 sni 已经被广泛使用,所以我们可以基于 sni 做分流。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
  name: external-service-route-https
spec:
  entryPoints:
    - websecure
  routes:
    - match: HostSNI(`kl.suyiiyii.top`) || HostSNIRegexp(`{subdomain:[a-z0-9-]+}.kl.suyiiyii.top`)
      services:
        - name: k65
          port: 443
  tls:
    passthrough: true 

记得开了 tls passthrough,表示云端集群不处理 tls 相关逻辑,而是直接转发。

有的朋友可能有疑问,现在不都是 https 了吗,还有必要配置 http 的转发吗?

其实是有的,因为本地集群也需要签发证书等操作,let’s encrypt 的服务器需要通过 http 服务检查我们对服务器的控制权,所以还是要开启 http 转发。

TCP

这个比较简单,就参考前一篇文章即可

总结

通过上述的方式,成功将本地的集群也暴露到了公网上面,并且各种访问方式与原生公网集群无异,又是一次酣畅淋漓的折腾。🥰🥰🥰