- 你有没有遇到过串项目的问题?
- 访问任意指向集群的域名都能请求到最后一次部署的应用,不管有没有该域名的IngressRoute。
- 你有没有遇到过CRD权限问题?
ClusterRoleBinding
的ServiceAccounts
跟NameSpace走,导致ACME在新建的NameSpace里不起作用。
—— 明明看起来Traefik部署是好的。
本文将提供最佳Traefikv2.0在Kubernetes中的实践(我可是折腾了很久啊)。
概念讲解
首先,我们会使用官方提供的CRD(自定义资源定义)文件配置对Kuberntes集群访问的权限。
然后,我们就可以使用一个单例的Traefik了。Traefik在单个实例中启用LetsEncrypt时,并不会遇到任何问题,因为你只需要修改ClusterRoleBinding
的ServiceAccounts
里的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,由它去分发服务。
还记得官方的这张图例吗?这只龅牙鼠其实应该理解为有2只哦。两只分别代表两层Traefik:
- 第一层(最外层),响应Internet请求。让所有的外部访问优先抵达这一层的Traefik,由它去分发下一步的转发。同时,它还可以集中去做LetsEncrypt这些事情;
- 第二层,就是每个单例的Traefik,Belongs Private Network。
看到这里,大家是不是已经隐约感觉到是第一层的Traefik包裹着所有单例Traefik呢?可以这样理解,但也不完全对。因为Traefik是无状态的,唯有靠Service服务通过端口判断来区分内网和外网,才能达到服务分发的作用。
第一层的Traefik
一个Traefik实例由Deployment和Service组成,接下来我们准备第一层的Deployment部分。
Deployment
注意serviceAccountName
和serviceAccount
应与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 .
吧,如若配置无误,将可以看到各项服务。
至此,第一层部署结束。接下来,我们需要将所有单例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
当使用的是两层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 。
到这里,文章结束,感谢大家的阅读,欢迎留言。
发表回复