浅谈Traefik

Ingress

Ingress 是授权入站连接到达集群服务的规则集合。

  • 从外部流量调度到 NodePort 上的 Service
  • 从 Service 调度到 Ingress Controller
  • Ingress Controller 根据 Ingress 中定义的虚拟主机或后端URL
  • 根据虚拟主机名调度到后端的一组 Pod

Ingress Controller

Ingress Controller是一个统称,是一类工具的集合。

  • Ingress Nginx:Kubernetes 官方维护方案,魔改版的 Nginx
  • Traefik:开源 HTTP 方向代理与负载均衡,支持 Ingress
  • Ingress Kong:开源 API gateway 解决方案所开发的 Kubernetes Ingress Controller
  • F5 BIG-IP Controller:F5 所开发的 Ingress Controller

Ingress的引入

目前Kubernetes暴露服务的方式只有三种:LoadBalancer Service、ExternalName、NodePort Service,而 Cluster IP 更不能被集群外的主机所访问,Service 虽然解决了服务发现和负载均衡,但只支持4层负载均衡,并不支持7层。NodePort 不易于管理庞大的端口、Rolling update 效率低下,需要额外搭建额外的负载均衡,只建议用于测试;LoadBalancer 必须运行在支持Cloud Provider 之上,而且需要花费额外的费用购买弹性 IP。我们运行的大多数服务都是应用层 HTTP(S),从长远角度考虑,想把集群内部的 Service 暴露到集群外,Ingress Controller 都是比较好的方案,它反向代理集群内的七层服务,从而通过 vhost 子域名那样路由到后端的服务。

Traefik

先贴一张 Traefik 官方图,api.domain.com 将流量路由到集群内的 API pod,backoffice.domain.com 流量路由到一组名为 backoffice 的 pod 上。试想一想,在传统的 Nginx 上要想额外再增加 admin 服务该如何处理?Nginx 可以通过虚拟主机域名区分不同的服务,而每个服务通过 upstream 进行定义不同的负载均衡池,再加上 location 进行负载均衡的反向代理,只需要修改 nginx.conf 即可实现,但是在 Kubernetes 中又该如何实现这种方式调度呢?Ingress 就是把以上动作抽象出来,变为一个 Ingress 对象,创建和更新 YAML 即可,然后 Ingress Controller 通过与 API Server 交互,动态的去感知集群中 Ingress 规则的变化来生成 Nginx 配置文件,最后 reload 生效更改后的配置。
upload successful

Traefik部署详解

RBAC

Kubernetes 在1.6+中引入了基于角色的访问控制(RBAC),以允许对 Kubernetes 资源和 API 进行细粒度控制。

如果群集配置了 RBAC,则需要授权 Traefik 使用 Kubernetes API。有两种方法可以设置适当的权限:通过特定于命名空间的 RoleBindings 或单个全局 ClusterRoleBinding。

每个命名空间的 RoleBinding 可以限制授予权限,只有 Traefik 正在监视的名称空间才能使用,从而遵循最小权限原则。如果 Traefik 不应该监视所有名称空间,并且名称空间集不会动态更改,那么这是首选方法。否则,必须使用单个 ClusterRoleBinding。

traefik-rbac.yaml

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
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: traefik-ingress-controller
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- list
- watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: traefik-ingress-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: traefik-ingress-controller
subjects:
- kind: ServiceAccount
name: traefik-ingress-controller
namespace: kube-system

通过Deployment或DaemonSet部署

  • 当使用 Deployment 部署时,可伸缩性更好。因为当使用 DaemonSet 时,每个节点会有一个 traefik-ingress-controller,当然你可以通过标签去控制它在哪些节点上启用。
  • DaemonSet 可以使用 NET_BIND_SERVICE 功能运行,这将允许它绑定到每个主机上的端口80/443。这将允许绕过 kube-proxy,并减少流量跳跃。请注意,这违反了 Kubernetes 最佳实践指南,并提出了调度/扩展问题的可能性。尽管存在潜在问题,但这仍然是大多数入口控制器的选择,关于单点的问题,如何做 Ingress Controller 将再往后讨论。

traefik-deployment.yaml

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik-ingress-controller
namespace: kube-system
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: traefik-ingress-controller
namespace: kube-system
labels:
k8s-app: traefik-ingress-lb
spec:
replicas: 1
selector:
matchLabels:
k8s-app: traefik-ingress-lb
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
containers:
- image: traefik
name: traefik-ingress-lb
ports:
- name: http
containerPort: 80
- name: admin
containerPort: 8080
args:
- --api
- --kubernetes
- --logLevel=INFO
---
kind: Service
apiVersion: v1
metadata:
name: traefik-ingress-service
namespace: kube-system
spec:
selector:
k8s-app: traefik-ingress-lb
ports:
- protocol: TCP
port: 80
name: web
- protocol: TCP
port: 8080
name: admin
type: NodePort

  • 将公开两个允许访问入口和 Web 界面的 NodePort。

traefik-ds.yaml

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik-ingress-controller
namespace: kube-system
---
kind: DaemonSet
apiVersion: extensions/v1beta1
metadata:
name: traefik-ingress-controller
namespace: kube-system
labels:
k8s-app: traefik-ingress-lb
spec:
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
containers:
- image: traefik
name: traefik-ingress-lb
ports:
- name: http
containerPort: 80
hostPort: 80
- name: admin
containerPort: 8080
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
args:
- --api
- --kubernetes
- --logLevel=INFO
nodeSelector:
edgenode: "true"
---
kind: Service
apiVersion: v1
metadata:
name: traefik-ingress-service
namespace: kube-system
spec:
selector:
k8s-app: traefik-ingress-lb
ports:
- protocol: TCP
port: 80
name: web
- protocol: TCP
port: 8080
name: admin

  • 这将创建一个在主机上使用特权端口80/8080的守护进程。这可能不适用于所有提供程序,但说明了静态(非NodePort)hostPort绑定。仍然可以在群集内部使用 traefik-ingress-service 来访问 DaemonSet Pod。

如果有多个边缘节点,使用 nodeSelector 选择边缘节点来调度 traefik-ingress-controller 运行在多个节点,在边缘节点前放置一个负载均衡器,如:nginx、keepalived,将所有边缘节点作为负载均衡器的后端。

1
2
kubectl label nodes k8s-n1 edgenode=true
kubectl label nodes k8s-n2 edgenode=true

验证效果

1
2
3
kubectl get ds -n kube-system 
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
traefik-ingress-controller 2 2 2 2 2 edgenode=true 13m

Deployment 部署与 DaemonSet 部署之间的差异:

  • 前者具有更容易的向上和向下扩展性。它可以实现完整的 Pod 生命周期,并支持 Kubernetes 1.2 的滚动更新。运行部署至少需要一个 Pod。
  • 后者会自动扩展到满足特定选择器的所有节点,并保证一次填充一个节点。 Kubernetes 1.7 也完全支持滚动更新,适用于 DaemonSets。

Ingress 对象

上面通过 NodePort 来访问 Traefik 的 Dashboard,如何通过 ingress 访问?首先需要创建一个 ingress 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: traefik-web-ui
namespace: kube-system
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: traefik.demo.com
http:
paths:
- backend:
serviceName: traefik-ingress-service
servicePort: admin

最关键的是 rules 部分,这里为 Traefik 的 Dashboard 定义一个 ingress 对象,serviceName 对应的是上面创建的 traefik-ingress-service,为了避免端口更改,对应的 servicePort 则是 admin。

验证:

  • 如果无 DNS Server,可以修改本地计算机的 /etc/hosts 文件,添加 traefik.demo.com 对应的 Ingress Controller 所在的 Node IP,或者是 Ingress Controller 的 VIP。
  • 在浏览器访问 http://traefik.demo.com,hostPort 保证了直接用域名访问,而不用增加端口号访问的问题。

upload successful

TLS 认证

目前大部分场景都会使用 https 来访问我们的服务,可以使用自签名或者从正规机构购买的 CA 证书,保证任何人访问的时候,浏览器会显示受信任的证书,也就是 Chrome 的小绿盾。

1
$ openssl req -newkey rsa:2048 -nodes -keyout tls.key -x509 -days 365 -out tls.crt

创建 secret 对象

1
kubectl create secret generic traefik-cert --from-file=tls.crt --from-file=tls.key -n kube-system

配置 Traefik

1
2
3
4
5
6
7
8
9
10
11
12
13
defaultEntryPoints = ["http", "https"]

[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "/ssl/tls.crt"
KeyFile = "/ssl/tls.key"

上面的配置文件中,配置了 http 和 https 两个入口,并且配置了 http 强制跳转到 https 服务,访问 https 服务,证书是必不可少的,CertFile 和 KeyFile 指定了两个文件,但是在 Traefik Pod 中并不存在这两个证书,可以通过 ConfigMap 将 traefik.toml 配置文件挂载到 Traefik Pod 中。

1
$ kubectl create configmap traefik-conf --from-file=traefik.toml -n kube-system

改造 Traefik Pod 配置清单

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
42
43
44
45
46
47
48
49
50
51
52
53
kind: DaemonSet
apiVersion: extensions/v1beta1
metadata:
name: traefik-ingress-controller
namespace: kube-system
labels:
k8s-app: traefik-ingress-lb
spec:
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
volumes:
- name: ssl
secret:
secretName: traefik-cert
- name: config
configMap:
name: traefik-conf
containers:
- image: traefik
name: traefik-ingress-lb
volumeMounts:
- mountPath: "/ssl"
name: "ssl"
- mountPath: "/config"
name: "config"
ports:
- name: http
containerPort: 80
hostPort: 80
- name: https
containerPort: 443
hostPort: 443
- name: admin
containerPort: 8080
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
args:
- --configfile=/config/traefik.toml
- --api
- --kubernetes
- --logLevel=INFO
nodeSelector:
edgenode: "true"

与之前相比,增加了 443 端口,启动参数中指定了 traefik 的配置文件,并通过 volume 把相关的 secret 和 configmap 挂载进来。更新完 Traefik Pod 后,再次访问 traefik 的 dashboard 会跳转到 https 的地址,并会提示相关证书相关的报警信息,这是因为证书是自签发的,并不受浏览器新人,如果从正规机构购买的证书则不会出现报警信息,而是小绿盾。点击下面的高级,就可以强制跳转,这样就能正常访问 traefik 的 dashboard 了。
upload successful

配置 ingress

创建三个 websites 示例 (cheese-deployments.yaml)

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: stilton
labels:
app: cheese
cheese: stilton
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: stilton
template:
metadata:
labels:
app: cheese
task: stilton
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:stilton
ports:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: cheddar
labels:
app: cheese
cheese: cheddar
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: cheddar
template:
metadata:
labels:
app: cheese
task: cheddar
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:cheddar
ports:
- containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: wensleydale
labels:
app: cheese
cheese: wensleydale
spec:
replicas: 2
selector:
matchLabels:
app: cheese
task: wensleydale
template:
metadata:
labels:
app: cheese
task: wensleydale
version: v0.0.1
spec:
containers:
- name: cheese
image: errm/cheese:wensleydale
ports:
- containerPort: 80

为每个 cheese pods 创建相关的 Service (cheese-services.yaml)

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: v1
kind: Service
metadata:
name: stilton
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: stilton
---
apiVersion: v1
kind: Service
metadata:
name: cheddar
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: cheddar
---
apiVersion: v1
kind: Service
metadata:
name: wensleydale
annotations:
traefik.backend.circuitbreaker: "NetworkErrorRatio() > 0.5"
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: cheese
task: wensleydale

-------------本文结束感谢您的阅读-------------