k8s 折腾记:使用 k8s 做为反向代理

前言

我们知道,k8s 有丰富的生态,例如可以自动签发证书的 cert-manager 等;同时,声明式的设计带来的易于实现 IaC 的特性。但是我们有很多应用并没有做 k8s 的适配,还是传统的部署方式。因此,就在想,能不能通过 k8s 来做一个大号的反向代理,使得集群成为统一的服务访问端点,让传统的服务也可以享受到 k8s 的各种便利。

本篇,我们聚焦于,如何让 k8s 代理外部的服务。

引入

我们知道,传统的 k8s 服务暴露主要是 Ingress、Service、Deployment 三个部分组成,大致可以用以下图来展示

graph LR
Web --> Ingress --> Service --> Deployment
  

图中的箭头代表请求的方向,其中 Ingress 由集群的 ingressclass 来实现,service 则是由集群内部实现,deployment 是我们部署的应用。

  • Web 端是网络流量入口,我们要用集群作为流量的统一入口,自然必须设置为集群的地址
  • Ingress 是集群的反代具体实现,可以选择不同的实现
  • Service 是集群内部组件,用来给具体的服务提供一个统一的入口
  • Deployment 是我们在集群上面部署的具体应用

下面来一个个分析,我们应该在上述流程的哪一步进行修改,来达到代理外部服务的目的

Ingress

参阅 官方文档,似乎没有发现可以配置外部地址的地方。

Service

参阅 官方文档,Service 比 ingress 复杂的多。

看到有个 ExternalName 类型,似乎是可以使用外部的地址的。

于是乎,就尝试创建 ExternalName 类型的 Service,然后再用 ingress 引用这个 service。

 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: external-service
  namespace: default
spec:
  externalName: suyiiyii.top
  sessionAffinity: None
  type: ExternalName
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
  name: test-ingress
  namespace: default
spec:
  ingressClassName: traefik
  rules:
    - host: test.k.suyiiyii.top
      http:
        paths:
          - backend:
              service:
                name: external-service
                port:
                  number: 80
            path: /
            pathType: Prefix

但是很遗憾,我的 ingressclass traefik 报错了

Cannot create service: externalName services not allowed: default/external-service" providerName=kubernetes serviceName=external-service servicePort="&ServiceBackendPort{Name:,Number:80,}" ingress=test-ingress namespace=default

似乎是 traefik 不支持 externalName 类型的 service,所以只能作罢。

Deployment

大不了我自己写一个应用,就只做代理的事情。配置一个地址,运行起来就把所有到应用的请求转发到配置的地址。

但是,先不说自己写一个有多麻烦,运行之前要配置代理的地址,运行时还会占用额外的资源。

所以只能作为下下策。

Service + Endpoints

最后还得在 Service 上寻找解决方法。

Service 在实现上,是通过 Endpoints,找到对应的后端服务的,例如

flowchart LR
Service --> Endpoints
Endpoints --> Deployment
  

其中,endpoints 是集群会自动配置的,所以我们平时使用的时候不需要关心。

不过,我们也可以手动配置 endpoint,来指向我们自己的外部服务。

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

这样子,当访问 minio Service 时,就会自动反代到外部的服务啦。

最后,再和以前一样,搭配上 ingress,就实现了自动管理的 https 了

 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
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minio-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
    - secretName: minio-tls
  rules:
    - host: minio.kl.suyiiyii.top
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: minio
                port:
                  name: api
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minio-console-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
    - secretName: minio-console-tls
  rules:
    - host: minio-console.kl.suyiiyii.top
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: minio
                port:
                  name: console

总结

这下子就把集群当做成了一个大号的反代了,并且可以使用集群的工作流,manifest 都上传到仓库,然后用 argo 自动更新,又把一个组件 IaC 了。🥰🥰🥰