转自:https://www.qikqiak.com/k8strain/network/ingress/traefik/

Traefik 是一个开源的可以使服务发布变得轻松有趣的边缘路由器。它负责接收你系统的请求,然后使用合适的组件来对这些请求进行处理。

Traefik - 图1

除了众多的功能之外,Traefik 的与众不同之处还在于它会自动发现适合你服务的配置。当 Traefik 在检查你的服务时,会找到服务的相关信息并找到合适的服务来满足对应的请求。
Traefik 兼容所有主流的集群技术,比如 Kubernetes,Docker,Docker Swarm,AWS,Mesos,Marathon,等等;并且可以同时处理多种方式。(甚至可以用于在裸机上运行的比较旧的软件。)
使用 Traefik,不需要维护或者同步一个独立的配置文件:因为一切都会自动配置,实时操作的(无需重新启动,不会中断连接)。使用 Traefik,你可以花更多的时间在系统的开发和新功能上面,而不是在配置和维护工作状态上面花费大量时间。

核心概念

Traefik 是一个边缘路由器,是你整个平台的大门,拦截并路由每个传入的请求:它知道所有的逻辑和规则,这些规则确定哪些服务处理哪些请求;传统的反向代理需要一个配置文件,其中包含路由到你服务的所有可能路由,而 Traefik 会实时检测服务并自动更新路由规则,可以自动服务发现。
Traefik - 图2
首先,当启动 Traefik 时,需要定义 entrypoints(入口点),然后,根据连接到这些 entrypoints 的路由来分析传入的请求,来查看他们是否与一组规则相匹配,如果匹配,则路由可能会将请求通过一系列中间件转换过后再转发到你的服务上去。在了解 Traefik 之前有几个核心概念我们必须要了解:

  • Providers 用来自动发现平台上的服务,可以是编排工具、容器引擎或者 key-value 存储等,比如 Docker、Kubernetes、File
  • Entrypoints 监听传入的流量(端口等…),是网络入口点,它们定义了接收请求的端口(HTTP 或者 TCP)。
  • Routers 分析请求(host, path, headers, SSL, …),负责将传入请求连接到可以处理这些请求的服务上去。
  • Services 将请求转发给你的应用(load balancing, …),负责配置如何获取最终将处理传入请求的实际服务。
  • Middlewares 中间件,用来修改请求或者根据请求来做出一些判断(authentication, rate limiting, headers, …),中间件被附件到路由上,是一种在请求发送到你的服务之前(或者在服务的响应发送到客户端之前)调整请求的一种方法。

    安装

    由于 Traefik 2.X 版本和之前的 1.X 版本不兼容,我们这里选择功能更加强大的 2.X 版本来和大家进行讲解,我们这里使用的镜像是 traefik:2.1.1
    在 Traefik 中的配置可以使用两种不同的方式:

  • 动态配置:完全动态的路由配置

  • 静态配置:启动配置

静态配置中的元素(这些元素不会经常更改)连接到 providers 并定义 Treafik 将要监听的 entrypoints。

在 Traefik 中有三种方式定义静态配置:在配置文件中、在命令行参数中、通过环境变量传递

动态配置包含定义系统如何处理请求的所有配置内容,这些配置是可以改变的,而且是无缝热更新的,没有任何请求中断或连接损耗。
安装 Traefik 到 Kubernetes 集群中的资源清单文件我这里提前准备好了,直接执行下面的安装命令即可:

  1. $ kubectl apply -f https://www.qikqiak.com/k8strain/network/manifests/traefik/crd.yaml
  2. $ kubectl apply -f https://www.qikqiak.com/k8strain/network/manifests/traefik/rbac.yaml
  3. $ kubectl apply -f https://www.qikqiak.com/k8strain/network/manifests/traefik/deployment.yaml
  4. $ kubectl apply -f https://www.qikqiak.com/k8strain/network/manifests/traefik/dashboard.yaml

其中 deployment.yaml 我这里是固定到 master 节点上的,如果你需要修改可以下载下来做相应的修改即可。我们这里是通过命令行参数来做的静态配置:

  1. args:
  2. - --entryPoints.web.address=:80
  3. - --entryPoints.websecure.address=:443
  4. - --api=true
  5. - --api.dashboard=true
  6. - --ping=true
  7. - --providers.kubernetesingress
  8. - --providers.kubernetescrd
  9. - --log.level=INFO
  10. - --accesslog

其中前两项配置是来定义 webwebsecure 这两个入口点的,--api=true 开启,=,就会创建一个名为 api@internal 的特殊 service,在 dashboard 中可以直接使用这个 service 来访问,然后其他比较重要的就是开启 kubernetesingresskubernetescrd 这两个 provider。
dashboard.yaml 中定义的是访问 dashboard 的资源清单文件,可以根据自己的需求修改。

  1. $ kubectl get pods -n kube-system
  2. NAME READY STATUS RESTARTS AGE
  3. traefik-867bd6b9c-lbrlx 1/1 Running 0 6m17s
  4. ......
  5. $ kubectl get ingressroute
  6. NAME AGE
  7. traefik-dashboard 30m

部署完成后我们可以通过在本地 /etc/hosts 中添加上域名 traefik.domain.com 的映射即可访问 Traefik 的 Dashboard 页面了:
Traefik - 图3

ACME

Traefik 通过扩展 CRD 的方式来扩展 Ingress 的功能,除了默认的用 Secret 的方式可以支持应用的 HTTPS 之外,还支持自动生成 HTTPS 证书。
比如现在我们有一个如下所示的 whoami 应用:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: whoami
  5. spec:
  6. ports:
  7. - protocol: TCP
  8. name: web
  9. port: 80
  10. selector:
  11. app: whoami
  12. ---
  13. kind: Deployment
  14. apiVersion: apps/v1
  15. metadata:
  16. name: whoami
  17. labels:
  18. app: whoami
  19. spec:
  20. replicas: 2
  21. selector:
  22. matchLabels:
  23. app: whoami
  24. template:
  25. metadata:
  26. labels:
  27. app: whoami
  28. spec:
  29. containers:
  30. - name: whoami
  31. image: containous/whoami
  32. ports:
  33. - name: web
  34. containerPort: 80

然后定义一个 IngressRoute 对象:

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: IngressRoute
  3. metadata:
  4. name: simpleingressroute
  5. spec:
  6. entryPoints:
  7. - web
  8. routes:
  9. - match: Host(`who.qikqiak.com`) && PathPrefix(`/notls`)
  10. kind: Rule
  11. services:
  12. - name: whoami
  13. port: 80

通过 entryPoints 指定了我们这个应用的入口点是 web,也就是通过 80 端口访问,然后访问的规则就是要匹配 who.qikqiak.com 这个域名,并且具有 /notls 的路径前缀的请求才会被 whoami 这个 Service 所匹配。我们可以直接创建上面的几个资源对象,然后对域名做对应的解析后,就可以访问应用了:
Traefik - 图4
IngressRoute 对象中我们定义了一些匹配规则,这些规则在 Traefik 中有如下定义方式:
Traefik - 图5
如果我们需要用 HTTPS 来访问我们这个应用的话,就需要监听 websecure 这个入口点,也就是通过 443 端口来访问,同样用 HTTPS 访问应用必然就需要证书,这里我们用 openssl 来创建一个自签名的证书:

  1. $ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=who.qikqiak.com"

然后通过 Secret 对象来引用证书文件:

  1. # 要注意证书文件名称必须是 tls.crt 和 tls.key
  2. $ kubectl create secret tls who-tls --cert=tls.crt --key=tls.key
  3. secret/who-tls created

这个时候我们就可以创建一个 HTTPS 访问应用的 IngressRoute 对象了:

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: IngressRoute
  3. metadata:
  4. name: ingressroutetls
  5. spec:
  6. entryPoints:
  7. - websecure
  8. routes:
  9. - match: Host(`who.qikqiak.com`) && PathPrefix(`/tls`)
  10. kind: Rule
  11. services:
  12. - name: whoami
  13. port: 80
  14. tls:
  15. secretName: who-tls

创建完成后就可以通过 HTTPS 来访问应用了,由于我们是自签名的证书,所以证书是不受信任的:
Traefik - 图6
除了手动提供证书的方式之外 Traefik 还支持使用 Let’s Encrypt 自动生成证书,要使用 Let’s Encrypt 来进行自动化 HTTPS,就需要首先开启 ACME,开启 ACME 需要通过静态配置的方式,也就是说可以通过环境变量、启动参数等方式来提供,我们这里还是直接使用启动参数的形式来开启,在 Traefik 的部署文件中添加如下命令行参数:

  1. args:
  2. ......
  3. # 使用 dns 验证方式
  4. - --certificatesResolvers.ali.acme.dnsChallenge.provider=alidns
  5. # 邮箱配置
  6. - --certificatesResolvers.ali.acme.email=ych_1024@163.com
  7. # 保存 ACME 证书的位置
  8. - --certificatesResolvers.ali.acme.storage=/etc/acme/acme.json

ACME 有多种校验方式 tlsChallengehttpChallengednsChallenge 三种验证方式,之前更常用的是 http 这种验证方式,关于这几种验证方式的使用可以查看文档:https://www.qikqiak.com/traefik-book/https/acme/ 了解他们之间的区别。要使用 tls 校验方式的话需要保证 Traefik 的 443 端口是可达的,dns 校验方式可以生成通配符的证书,只需要配置上 DNS 解析服务商的 API 访问密钥即可校验。我们这里用 DNS 校验的方式来为大家说明如何配置 ACME。
上面我们通过设置 --certificatesResolvers.ali.acme.dnsChallenge.provider=alidns 参数来指定指定阿里云的 DNS 校验,要使用阿里云的 DNS 校验我们还需要配置3个环境变量:ALICLOUD_ACCESS_KEYALICLOUD_SECRET_KEYALICLOUD_REGION_ID,分别对应我们平时开发阿里云应用的时候的密钥,可以登录阿里云后台获取,由于这是比较私密的信息,所以我们用 Secret 对象来创建:

  1. $ kubectl create secret generic traefik-alidns-secret --from-literal=ALICLOUD_ACCESS_KEY=<aliyun ak> --from-literal=ALICLOUD_SECRET_KEY=<aliyun sk>--from-literal=ALICLOUD_REGION_ID=cn-beijing -n kube-system

创建完成后将这个 Secret 通过环境变量配置到 Traefik 的应用中。还有一个值得注意的是验证通过的证书我们这里存到 /etc/acme/acme.json 文件中,我们一定要将这个文件持久化,否则每次 Traefik 重建后就需要重新认证,而 Let’s Encrypt 本身校验次数是有限制的。最后我们这里完整的 Traefik 的配置资源清单如下所示:

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: traefik
  5. namespace: kube-system
  6. labels:
  7. app: traefik
  8. spec:
  9. selector:
  10. matchLabels:
  11. app: traefik
  12. template:
  13. metadata:
  14. labels:
  15. app: traefik
  16. spec:
  17. serviceAccountName: traefik
  18. terminationGracePeriodSeconds: 60
  19. tolerations:
  20. - operator: "Exists"
  21. nodeSelector:
  22. kubernetes.io/hostname: ydzs-master
  23. volumes:
  24. - name: acme
  25. hostPath:
  26. path: /data/k8s/traefik/acme
  27. containers:
  28. - image: traefik:2.1.1
  29. name: traefik
  30. ports:
  31. - name: web
  32. containerPort: 80
  33. hostPort: 80
  34. - name: websecure
  35. containerPort: 443
  36. hostPort: 443
  37. args:
  38. - --entryPoints.web.address=:80
  39. - --entryPoints.websecure.address=:443
  40. - --api=true
  41. - --api.dashboard=true
  42. - --ping=true
  43. - --providers.kubernetesingress
  44. - --providers.kubernetescrd
  45. - --log.level=INFO
  46. - --accesslog
  47. # 使用 dns 验证方式
  48. - --certificatesResolvers.ali.acme.dnsChallenge.provider=alidns
  49. # 邮箱配置
  50. - --certificatesResolvers.ali.acme.email=ych_1024@163.com
  51. # 保存 ACME 证书的位置
  52. - --certificatesResolvers.ali.acme.storage=/etc/acme/acme.json
  53. # 下面是用于测试的ca服务,如果https证书生成成功了,则移除下面参数
  54. # - --certificatesresolvers.ali.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
  55. envFrom:
  56. - secretRef:
  57. name: traefik-alidns-secret
  58. # ALICLOUD_ACCESS_KEY
  59. # ALICLOUD_SECRET_KEY
  60. # ALICLOUD_REGION_ID
  61. volumeMounts:
  62. - name: acme
  63. mountPath: /etc/acme
  64. resources:
  65. requests:
  66. cpu: "50m"
  67. memory: "50Mi"
  68. limits:
  69. cpu: "200m"
  70. memory: "100Mi"
  71. securityContext:
  72. allowPrivilegeEscalation: true
  73. capabilities:
  74. drop:
  75. - ALL
  76. add:
  77. - NET_BIND_SERVICE
  78. readinessProbe:
  79. httpGet:
  80. path: /ping
  81. port: 8080
  82. failureThreshold: 1
  83. initialDelaySeconds: 10
  84. periodSeconds: 10
  85. successThreshold: 1
  86. timeoutSeconds: 2
  87. livenessProbe:
  88. httpGet:
  89. path: /ping
  90. port: 8080
  91. failureThreshold: 3
  92. initialDelaySeconds: 10
  93. periodSeconds: 10
  94. successThreshold: 1
  95. timeoutSeconds: 2

直接更新 Traefik 应用即可。更新完成后现在我们来修改上面我们的 whoami 应用:

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: IngressRoute
  3. metadata:
  4. name: ingressroutetls
  5. spec:
  6. entryPoints:
  7. - websecure
  8. routes:
  9. - match: Host(`who.qikqiak.com`) && PathPrefix(`/tls`)
  10. kind: Rule
  11. services:
  12. - name: whoami
  13. port: 80
  14. tls:
  15. certResolver: ali
  16. domains:
  17. - main: "*.qikqiak.com"

其他的都不变,只需要将 tls 部分改成我们定义的 ali 这个证书解析器,如果我们想要生成一个通配符的域名证书的话可以定义 domains 参数来指定,然后更新 IngressRoute 对象,这个时候我们再去用 HTTPS 访问我们的应用(当然需要将域名在阿里云 DNS 上做解析):
Traefik - 图7
我们可以看到访问应用已经是受浏览器信任的证书了,查看证书我们还可以发现该证书是一个通配符的证书。

中间件

中间件是 Traefik2.0 中一个非常有特色的功能,我们可以根据自己的各种需求去选择不同的中间件来满足服务,Traefik 官方已经内置了许多不同功能的中间件,其中一些可以修改请求,头信息,一些负责重定向,一些添加身份验证等等,而且中间件还可以通过链式组合的方式来适用各种情况。
Traefik - 图8
同样比如上面我们定义的 whoami 这个应用,我们可以通过 [https://who.qikqiak.com/tls](https://who.qikqiak.com/tls) 来访问到应用,但是如果我们用 http 来访问的话呢就不行了,就会404了,因为我们根本就没有简单80端口这个入口点,所以要想通过 http 来访问应用的话自然我们需要监听下 web 这个入口点:

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: IngressRoute
  3. metadata:
  4. name: ingressroutetls-http
  5. spec:
  6. entryPoints:
  7. - web
  8. routes:
  9. - match: Host(`who.qikqiak.com`) && PathPrefix(`/tls`)
  10. kind: Rule
  11. services:
  12. - name: whoami
  13. port: 80

注意这里我们创建的 IngressRoute 的 entryPoints 是 web,然后创建这个对象,这个时候我们就可以通过 http 访问到这个应用了。
但是我们如果只希望用户通过 https 来访问应用的话呢?按照以前的知识,我们是不是可以让 http 强制跳转到 https 服务去,对的,在 Traefik 中也是可以配置强制跳转的,只是这个功能现在是通过中间件来提供的了。如下所示,我们使用 redirectScheme 中间件来创建提供强制跳转服务:

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: Middleware
  3. metadata:
  4. name: redirect-https
  5. spec:
  6. redirectScheme:
  7. scheme: https

然后将这个中间件附加到 http 的服务上面去,因为 https 的不需要跳转:

  1. ---
  2. apiVersion: traefik.containo.us/v1alpha1
  3. kind: IngressRoute
  4. metadata:
  5. name: ingressroutetls-http
  6. spec:
  7. entryPoints:
  8. - web
  9. routes:
  10. - match: Host(`who.qikqiak.com`) && PathPrefix(`/tls`)
  11. kind: Rule
  12. services:
  13. - name: whoami
  14. port: 80
  15. middlewares:
  16. - name: redirect-https

这个时候我们再去访问 http 服务可以发现就会自动跳转到 https 去了。关于更多中间件的用法可以查看文档 Traefik Docs

灰度发布

Traefik2.0 的一个更强大的功能就是灰度发布,灰度发布我们有时候也会称为金丝雀发布(Canary),主要就是让一部分测试的服务也参与到线上去,经过测试观察看是否符合上线要求。
Traefik - 图9
比如现在我们有两个名为 appv1appv2 的服务,我们希望通过 Traefik 来控制我们的流量,将 3⁄4 的流量路由到 appv1,¼ 的流量路由到 appv2 去,这个时候就可以利用 Traefik2.0 中提供的带权重的轮询(WRR)来实现该功能,首先在 Kubernetes 集群中部署上面的两个服务。为了对比结果我们这里提供的两个服务一个是 whoami,一个是 nginx,方便测试。
appv1 服务的资源清单如下所示:(appv1.yaml)

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: appv1
  5. spec:
  6. selector:
  7. matchLabels:
  8. app: appv1
  9. template:
  10. metadata:
  11. labels:
  12. use: test
  13. app: appv1
  14. spec:
  15. containers:
  16. - name: whoami
  17. image: containous/whoami
  18. ports:
  19. - containerPort: 80
  20. name: portv1
  21. ---
  22. apiVersion: v1
  23. kind: Service
  24. metadata:
  25. name: appv1
  26. spec:
  27. selector:
  28. app: appv1
  29. ports:
  30. - name: http
  31. port: 80
  32. targetPort: portv1

appv2 服务的资源清单如下所示:(appv2.yaml)

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: appv2
  5. spec:
  6. selector:
  7. matchLabels:
  8. app: appv2
  9. template:
  10. metadata:
  11. labels:
  12. use: test
  13. app: appv2
  14. spec:
  15. containers:
  16. - name: nginx
  17. image: nginx
  18. ports:
  19. - containerPort: 80
  20. name: portv2
  21. ---
  22. apiVersion: v1
  23. kind: Service
  24. metadata:
  25. name: appv2
  26. spec:
  27. selector:
  28. app: appv2
  29. ports:
  30. - name: http
  31. port: 80
  32. targetPort: portv2

直接创建上面两个服务:

  1. $ kubectl apply -f appv1.yaml
  2. $ kubectl apply -f appv2.yaml
  3. # 通过下面的命令可以查看服务是否运行成功
  4. $ kubectl get pods -l use=test
  5. NAME READY STATUS RESTARTS AGE
  6. appv1-58f856c665-shm9j 1/1 Running 0 12s
  7. appv2-ff5db55cf-qjtrf 1/1 Running 0 12s

在 Traefik2.1 中新增了一个 TraefikService 的 CRD 资源,我们可以直接利用这个对象来配置 WRR,之前的版本需要通过 File Provider,比较麻烦,新建一个描述 WRR 的资源清单:(wrr.yaml)

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: TraefikService
  3. metadata:
  4. name: app-wrr
  5. spec:
  6. weighted:
  7. services:
  8. - name: appv1
  9. weight: 3 # 定义权重
  10. port: 80
  11. kind: Service # 可选,默认就是 Service
  12. - name: appv2
  13. weight: 1
  14. port: 80

然后为我们的灰度发布的服务创建一个 IngressRoute 资源对象:(ingressroute.yaml)

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: IngressRoute
  3. metadata:
  4. name: wrringressroute
  5. namespace: default
  6. spec:
  7. entryPoints:
  8. - web
  9. routes:
  10. - match: Host(`wrr.qikqiak.com`)
  11. kind: Rule
  12. services:
  13. - name: app-wrr
  14. kind: TraefikService

不过需要注意的是现在我们配置的 Service 不再是直接的 Kubernetes 对象了,而是上面我们定义的 TraefikService 对象,直接创建上面的两个资源对象,这个时候我们对域名 wrr.qikqiak.com 做上解析,去浏览器中连续访问 4 次,我们可以观察到 appv1 这应用会收到 3 次请求,而 appv2 这个应用只收到 1 次请求,符合上面我们的 3:1 的权重配置。
Traefik - 图10

流量复制

除了灰度发布之外,Traefik 2.0 还引入了流量镜像服务,是一种可以将流入流量复制并同时将其发送给其他服务的方法,镜像服务可以获得给定百分比的请求同时也会忽略这部分请求的响应。
Traefik - 图11
同样的在 2.0 中只能通过 FileProvider 进行配置,在 2.1 版本中我们已经可以通过 TraefikService 资源对象来进行配置了,现在我们部署两个 whoami 的服务,资源清单文件如下所示:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: v1
  5. spec:
  6. ports:
  7. - protocol: TCP
  8. name: web
  9. port: 80
  10. selector:
  11. app: v1
  12. ---
  13. kind: Deployment
  14. apiVersion: apps/v1
  15. metadata:
  16. name: v1
  17. labels:
  18. app: v1
  19. spec:
  20. selector:
  21. matchLabels:
  22. app: v1
  23. template:
  24. metadata:
  25. labels:
  26. app: v1
  27. spec:
  28. containers:
  29. - name: v1
  30. image: nginx
  31. ports:
  32. - name: web
  33. containerPort: 80
  34. ---
  35. apiVersion: v1
  36. kind: Service
  37. metadata:
  38. name: v2
  39. spec:
  40. ports:
  41. - protocol: TCP
  42. name: web
  43. port: 80
  44. selector:
  45. app: v2
  46. ---
  47. kind: Deployment
  48. apiVersion: apps/v1
  49. metadata:
  50. name: v2
  51. labels:
  52. app: v2
  53. spec:
  54. selector:
  55. matchLabels:
  56. app: v2
  57. template:
  58. metadata:
  59. labels:
  60. app: v2
  61. spec:
  62. containers:
  63. - name: v2
  64. image: nginx
  65. ports:
  66. - name: web
  67. containerPort: 80

直接创建上面的资源对象:

  1. $ kubectl get pods
  2. NAME READY STATUS RESTARTS AGE
  3. v1-77cfb86999-wfbl2 1/1 Running 0 94s
  4. v2-6f45d498b7-g6qjt 1/1 Running 0 91s
  5. $ kubectl get svc
  6. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  7. v1 ClusterIP 10.96.218.173 <none> 80/TCP 99s
  8. v2 ClusterIP 10.99.98.48 <none> 80/TCP 96s

现在我们创建一个 IngressRoute 对象,将服务 v1 的流量复制 50% 到服务 v2,如下资源对象所示:(mirror-ingress-route.yaml)

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: TraefikService
  3. metadata:
  4. name: app-mirror
  5. spec:
  6. mirroring:
  7. name: v1 # 发送 100% 的请求到 K8S 的 Service "v1"
  8. port: 80
  9. mirrors:
  10. - name: v2 # 然后复制 50% 的请求到 v2
  11. percent: 50
  12. port: 80
  13. ---
  14. apiVersion: traefik.containo.us/v1alpha1
  15. kind: IngressRoute
  16. metadata:
  17. name: mirror-ingress-route
  18. namespace: default
  19. spec:
  20. entryPoints:
  21. - web
  22. routes:
  23. - match: Host(`mirror.qikqiak.com`)
  24. kind: Rule
  25. services:
  26. - name: app-mirror
  27. kind: TraefikService # 使用声明的 TraefikService 服务,而不是 K8S 的 Service

然后直接创建这个资源对象即可:

  1. $ kubectl apply -f mirror-ingress-route.yaml
  2. ingressroute.traefik.containo.us/mirror-ingress-route created
  3. traefikservice.traefik.containo.us/mirroring-example created

这个时候我们在浏览器中去连续访问4次 mirror.qikqiak.com 可以发现有一半的请求也出现在了 v2 这个服务中:

Traefik - 图12

TCP

另外 Traefik2.0 已经支持了 TCP 服务的,下面我们以 mongo 为例来了解下 Traefik 是如何支持 TCP 服务得。

简单 TCP 服务

首先部署一个普通的 mongo 服务,资源清单文件如下所示:(mongo.yaml)

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: mongo-traefik
  5. labels:
  6. app: mongo-traefik
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: mongo-traefik
  11. template:
  12. metadata:
  13. labels:
  14. app: mongo-traefik
  15. spec:
  16. containers:
  17. - name: mongo
  18. image: mongo:4.0
  19. ports:
  20. - containerPort: 27017
  21. ---
  22. apiVersion: v1
  23. kind: Service
  24. metadata:
  25. name: mongo-traefik
  26. spec:
  27. selector:
  28. app: mongo-traefik
  29. ports:
  30. - port: 27017

直接创建 mongo 应用:

  1. $ kubectl apply -f mongo.yaml
  2. deployment.apps/mongo-traefik created
  3. service/mongo-traefik created

创建成功后就可以来为 mongo 服务配置一个路由了。由于 Traefik 中使用 TCP 路由配置需要 SNI,而 SNI 又是依赖 TLS 的,所以我们需要配置证书才行,如果没有证书的话,我们可以使用通配符 * 进行配置,我们这里创建一个 IngressRouteTCP 类型的 CRD 对象(前面我们就已经安装了对应的 CRD 资源):(mongo-ingressroute-tcp.yaml)

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: IngressRouteTCP
  3. metadata:
  4. name: mongo-traefik-tcp
  5. spec:
  6. entryPoints:
  7. - mongo
  8. routes:
  9. - match: HostSNI(`*`)
  10. services:
  11. - name: mongo-traefik
  12. port: 27107

要注意的是这里的 entryPoints 部分,是根据我们启动的 Traefik 的静态配置中的 entryPoints 来决定的,我们当然可以使用前面我们定义得 80 和 443 这两个入口点,但是也可以可以自己添加一个用于 mongo 服务的专门入口点:

  1. ......
  2. - image: traefik:2.1.1
  3. name: traefik
  4. ports:
  5. - name: web
  6. containerPort: 80
  7. hostPort: 80
  8. - name: websecure
  9. containerPort: 443
  10. hostPort: 443
  11. - name: mongo
  12. hostPort: 27017
  13. containerPort: 27017
  14. args:
  15. - --entryPoints.web.address=:80
  16. - --entryPoints.websecure.address=:443
  17. - --entryPoints.mongo.address=:27017
  18. ......

这里给入口点添加 hostPort 是为了能够通过节点的端口访问到服务,关于 entryPoints 入口点的更多信息,可以查看文档 entrypoints 了解更多信息。
然后更新 Traefik 后我们就可以直接创建上面的资源对象:

  1. $ mongo-ingressroute-tcp.yaml
  2. ingressroutetcp.traefik.containo.us/mongo-traefik-tcp created

创建完成后,同样我们可以去 Traefik 的 Dashboard 页面上查看是否生效:
Traefik - 图13
然后我们配置一个域名 mongo.local 解析到 Traefik 所在的节点,然后通过 27017 端口来连接 mongo 服务:

  1. $ mongo --host mongo.local --port 27017
  2. mongo(75243,0x1075295c0) malloc: *** malloc_zone_unregister() failed for 0x7fffa56f4000
  3. MongoDB shell version: 2.6.1
  4. connecting to: mongo.local:27017/test
  5. > show dbs
  6. admin 0.000GB
  7. config 0.000GB
  8. local 0.000GB

到这里我们就完成了将 mongo(TCP)服务暴露给外部用户了。

带 TLS 证书的 TCP

上面我们部署的 mongo 是一个普通的服务,然后用 Traefik 代理的,但是有时候为了安全 mongo 服务本身还会使用 TLS 证书的形式提供服务,下面是用来生成 mongo tls 证书的脚本文件:(generate-certificates.sh)

  1. #!/bin/bash
  2. #
  3. # From https://medium.com/@rajanmaharjan/secure-your-mongodb-connections-ssl-tls-92e2addb3c89
  4. set -eu -o pipefail
  5. DOMAINS="${1}"
  6. CERTS_DIR="${2}"
  7. [ -d "${CERTS_DIR}" ]
  8. CURRENT_DIR="$(cd "$(dirname "${0}")" && pwd -P)"
  9. GENERATION_DIRNAME="$(echo "${DOMAINS}" | cut -d, -f1)"
  10. rm -rf "${CERTS_DIR}/${GENERATION_DIRNAME:?}" "${CERTS_DIR}/certs"
  11. echo "== Checking Requirements..."
  12. command -v go >/dev/null 2>&1 || echo "Golang is required"
  13. command -v minica >/dev/null 2>&1 || go get github.com/jsha/minica >/dev/null
  14. echo "== Generating Certificates for the following domains: ${DOMAINS}..."
  15. cd "${CERTS_DIR}"
  16. minica --ca-cert "${CURRENT_DIR}/minica.pem" --ca-key="${CURRENT_DIR}/minica-key.pem" --domains="${DOMAINS}"
  17. mv "${GENERATION_DIRNAME}" "certs"
  18. cat certs/key.pem certs/cert.pem > certs/mongo.pem
  19. echo "== Certificates Generated in the directory ${CERTS_DIR}/certs"

将上面证书放置到 certs 目录下面,然后我们新建一个 02-tls-mongo 的目录,在该目录下面执行如下命令来生成证书:

  1. $ bash ../certs/generate-certificates.sh mongo.local .
  2. == Checking Requirements...
  3. == Generating Certificates for the following domains: mongo.local...

最后的目录如下所示,在 02-tls-mongo 目录下面会生成包含证书的 certs 目录:

  1. $ tree .
  2. .
  3. ├── 01-mongo
  4. ├── mongo-ingressroute-tcp.yaml
  5. └── mongo.yaml
  6. ├── 02-tls-mongo
  7. └── certs
  8. ├── cert.pem
  9. ├── key.pem
  10. └── mongo.pem
  11. └── certs
  12. ├── generate-certificates.sh
  13. ├── minica-key.pem
  14. └── minica.pem

02-tls-mongo/certs 目录下面执行如下命令通过 Secret 来包含证书内容:

  1. $ kubectl create secret tls traefik-mongo-certs --cert=cert.pem --key=key.pem
  2. secret/traefik-mongo-certs created

然后重新更新 IngressRouteTCP 对象,增加 TLS 配置:

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: IngressRouteTCP
  3. metadata:
  4. name: mongo-traefik-tcp
  5. spec:
  6. entryPoints:
  7. - mongo
  8. routes:
  9. - match: HostSNI(`mongo.local`)
  10. services:
  11. - name: mongo-traefik
  12. port: 27017
  13. tls:
  14. secretName: traefik-mongo-certs

同样更新后,现在我们直接去访问应用就会被 hang 住,因为我们没有提供证书:

  1. $ mongo --host mongo.local --port 27017
  2. MongoDB shell version: 2.6.1
  3. connecting to: mongo1.local:27017/test

这个时候我们可以带上证书来进行连接:

  1. $ mongo --host mongo.local --port 27017 --ssl --sslCAFile=../certs/minica.pem --sslPEMKeyFile=./certs/mongo.pem
  2. MongoDB shell version v4.0.3
  3. connecting to: mongodb://mongo.local:27017/
  4. Implicit session: session { "id" : UUID("e7409ef6-8ebe-4c5a-9642-42059bdb477b") }
  5. MongoDB server version: 4.0.14
  6. ......
  7. > show dbs;
  8. admin 0.000GB
  9. config 0.000GB
  10. local 0.000GB

可以看到现在就可以连接成功了,这样就完成了一个使用 TLS 证书代理 TCP 服务的功能,这个时候如果我们使用其他的域名去进行连接就会报错了,因为现在我们指定的是特定的 HostSNI:

  1. $ mongo --host mongo.k8s.local --port 27017 --ssl --sslCAFile=../certs/minica.pem --sslPEMKeyFile=./certs/mongo.pem
  2. MongoDB shell version v4.0.3
  3. connecting to: mongodb://mongo.k8s.local:27017/
  4. 2019-12-29T15:03:52.424+0800 E NETWORK [js] SSL peer certificate validation failed: Certificate trust failure: CSSMERR_TP_NOT_TRUSTED; connection rejected
  5. 2019-12-29T15:03:52.429+0800 E QUERY [js] Error: couldn't connect to server mongo.qikqiak.com:27017, connection attempt failed: SSLHandshakeFailed: SSL peer certificate validation failed: Certificate trust failure: CSSMERR_TP_NOT_TRUSTED; connection rejected :
  6. connect@src/mongo/shell/mongo.js:257:13
  7. @(connect):1:6
  8. exception: connect failed

当然我们也可以使用 ACME 来为我们提供一个合法的证书,这样在连接的使用就不需要指定证书了,如下所示:

  1. apiVersion: traefik.containo.us/v1alpha1
  2. kind: IngressRouteTCP
  3. metadata:
  4. name: mongo-traefik-tcp
  5. spec:
  6. entryPoints:
  7. - mongo
  8. routes:
  9. - match: HostSNI(`mongo.qikqiak.com`)
  10. services:
  11. - name: mongo-traefik
  12. port: 27017
  13. tls:
  14. certResolver: ali
  15. domains:
  16. - main: "*.qikqiak.com"

这样当我们连接的时候就只需要如下的命令即可:

  1. $ mongo --host mongo.qikqiak.com --port 27017 --ssl