Traefik 2.0 两层Traffic应用实践 - konakona
konakona
Dream Afar.
konakona

Traefik 2.0 两层Traffic应用实践

Traefik 2.0  两层Traffic应用实践
  • 你有没有遇到过串项目的问题?
    • 访问任意指向集群的域名都能请求到最后一次部署的应用,不管有没有该域名的IngressRoute。
  • 你有没有遇到过CRD权限问题?
    • ClusterRoleBindingServiceAccounts跟NameSpace走,导致ACME在新建的NameSpace里不起作用。

—— 明明看起来Traefik部署是好的。

本文将提供最佳Traefikv2.0在Kubernetes中的实践(我可是折腾了很久啊)。


概念讲解

首先,我们会使用官方提供的CRD(自定义资源定义)文件配置对Kuberntes集群访问的权限。

然后,我们就可以使用一个单例的Traefik了。Traefik在单个实例中启用LetsEncrypt时,并不会遇到任何问题,因为你只需要修改ClusterRoleBindingServiceAccounts里的Namespace与Traefik实例一致,就可以让LetsEncrypt正常运行。

然而,当我们想要在多个Traefik实例中同时启用LetsEncrypt,就会出现无法正确接收质询请求了。

When using a single instance of Traefik with LetsEncrypt, no issues should be encountered, however this could be a single point of failure. Unfortunately, it is not possible to run multiple instances of Traefik 2.0 with LetsEncrypt enabled, because there is no way to ensure that the correct instance of Traefik will receive the challenge request, and subsequent responses.

https://docs.traefik.io/providers/kubernetes-crd/

为了解决这一类权限问题,我们要嵌套Traefik。让所有的外部访问优先抵达第一层的Traefik,由它去分发服务。

https://blog.img.crazyphper.com/2020/03/architecture-800x437.png

还记得官方的这张图例吗?这只龅牙鼠其实应该理解为有2只哦。两只分别代表两层Traefik:

  • 第一层(最外层),响应Internet请求。让所有的外部访问优先抵达这一层的Traefik,由它去分发下一步的转发。同时,它还可以集中去做LetsEncrypt这些事情;
  • 第二层,就是每个单例的Traefik,Belongs Private Network。

看到这里,大家是不是已经隐约感觉到是第一层的Traefik包裹着所有单例Traefik呢?可以这样理解,但也不完全对。因为Traefik是无状态的,唯有靠Service服务通过端口判断来区分内网和外网,才能达到服务分发的作用。


第一层的Traefik

一个Traefik实例由Deployment和Service组成,接下来我们准备第一层的Deployment部分。

Deployment

注意serviceAccountNameserviceAccount应与CRD创建时设定一致,默认就是traefik-ingress-controller
注意namespace应与serviceAccount创建的namesapce一致,默认是default,我这里指定的是kube-system,因为CRD中我已修改default为kube-system。
启用了LetsEncrypt,并挂载了一个traefik-acme-data的PVC配置,用于将ACME数据存放在宿主机上。

# traefik-ds.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: kube-system
  name: traefik
  labels:
    app: traefik
spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      serviceAccount: traefik-ingress-controller

      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: traefik-acme-data
      containers:
        - args:
          - --api
          - --accesslog
          - --global.sendanonymoususage
          - --entrypoints.web.address=:8000
          - --entrypoints.websecure.address=:4443
          - --providers.kubernetescrd
          - --certificatesresolvers.default.acme.tlschallenge
          - --certificatesresolvers.default.acme.email=konakona.xiong@gmail.com
          - --certificatesresolvers.default.acme.httpChallenge.entryPoint=web
          - --certificatesresolvers.default.acme.storage=/data/acme.json
          image: traefik:v2.0
          imagePullPolicy: IfNotPresent
          name: traefik
          ports:
          - containerPort: 8000
            name: web
            protocol: TCP
          - containerPort: 4443
            name: websecure
            protocol: TCP
          volumeMounts:
            - name: data
              mountPath: /data

PV/PVC

创建一个PV,将ACME数据存放至宿主机的/data/k8s/kube-system/traefik/acme-data目录下,并声明1G的空间,理论上来说10M就够了。

# traefik-pv.yaml

kind: PersistentVolume
apiVersion: v1
metadata:
  name: traefik-acme-data
spec:
  capacity:
    storage: "1Gi"
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: "/data/k8s/kube-system/traefik/acme-data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: traefik-acme-data
  namespace: kube-system
spec:
  volumeName: traefik-acme-data
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: "1Gi"

Service

分别创建对外的service:traefik,以及对内的service:traefik-internal

# traefik-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: traefik
  namespace: kube-system
spec:
  ports:
  - name: web
    nodePort: 32001
    port: 8000
    protocol: TCP
    targetPort: 8000
  - name: websecure
    nodePort: 32002
    port: 4443
    protocol: TCP
    targetPort: 4443
  selector:
    app: traefik
  type: NodePort
---
apiVersion: v1
kind: Service
metadata:
  name: traefik-internal
  namespace: kube-system
spec:
  ports:
  - name: web
    port: 8000
    protocol: TCP
    targetPort: 80
  - name: websecure
    port: 4443
    protocol: TCP
    targetPort: 443
  selector:
    app: traefik
  type: ClusterIP

service:traefik负责将所有外部访问32001和32002端口的请求转发给service:traefik-internal的8000和4443。


执行kubectl apply -f . 吧,如若配置无误,将可以看到各项服务。

https://blog.img.crazyphper.com/2020/03/image-2-10-800x200.png

至此,第一层部署结束。接下来,我们需要将所有单例Traefik中LetsEncrypt的部分去除,就可以享受到第一层主动为你去申请LetsEncrypt的功劳了。


单例Traefik 借鉴

如果好奇我的单例Traefik是如何部署的,那么接下来我将进一步展示它们。

Deployment

spec:
    containers:
        - name: traefik
          image: traefik:v2.0
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          args:
            - --api
            - --accesslog
            - --global.sendanonymoususage
            - --entrypoints.web.address=:80
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

Services

apiVersion: v1
kind: Service
metadata:
  name: traefik
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: traefik
    app.kubernetes.io/instance: traefik

因第一层的Traefik下的service:traefik-internal会将所有Traefik的80转发到8000端口,再由service:traefik将8000转发到NodePort:32001供外部访问。

开发者无需修改自己docker-compose文件去适应部署的需要,直接按照最初的80提供给CICD,保证了大部分代码的部署是无感知、无改动的。

同时,LetsEncrypt的工作已经完整的交由第一层Traefik处理,单例的Traefik无需再写args cli,除非你不需要。

traefik 获取访问者的真实IP

从Kubernetes 1.5 开始,默认情况下,发送到 Type=NodePort 的服务数据包来自 DNAT 。 traefik的Service默认使用的 externalTrafficPolicy:Cluster,代表其他节点的Pod流量可以转发过来,这意味着traefik Pod拿到的是转发过来的ClusterIP,并非访问者的真实IP。就好比张三在访问网站,但是服务器以为是李四来了。

我们可以通过设置 externalTrafficPolicy: Local 将请求代理到本地端点,而不是其他节点Pod转发来的ClusterIP,以保留最原始的请求IP地址。

当你的traefik是单部署的时候,在 Server 中 spec externalTrafficPolicy: Local 就大功告成了:

apiVersion: v1
kind: Service
metadata:
  name: traefik
  namespace: kube-system
spec:
  externalTrafficPolicy: Local  # <-- 此处
  type:NodePort # 或LoadBalancer才可以设置externalTrafficPolicy
https://blog.img.crazyphper.com/2021/01/企业微信20210108-163549-150x44.png

当使用的是两层traefik,不仅要在第一层的traefik server中设置 externalTrafficPolicy: Local ,还需要在应用层的单例traefik Deployment中增加command(用args啊!喂)来传递X-Forwarded-For的IP:

args:
  - --entryPoints.web.forwardedHeaders.trustedIPs=0.0.0.0/0

应用层的traefik Service是ClusterIP,所以不需要改变也无法改变 externalTrafficPolicy

到这里,文章结束,感谢大家的阅读,欢迎留言。

赞赏

团哥

文章作者

继续玩我的CODE,让别人说去。 低调,就是这么自信。

发表回复

textsms
account_circle
email

konakona

Traefik 2.0 两层Traffic应用实践
你有没有遇到过串项目的问题?访问任意指向集群的域名都能请求到最后一次部署的应用,不管有没有该域名的IngressRoute。你有没有遇到过CRD权限问题?ClusterRoleBinding的ServiceAccounts…
扫描二维码继续阅读
2020-03-09