0%

使用traefik反向代理k8s dashboard

生产环境下,k8s集群对外暴露服务主要有LoadBalancer和Ingress两种方式:

  • LoadBalancer:需要云厂商支持,使用k8s service的负载均衡能力,也就是依靠iptables/ipvs的能力,可用于各种协议
  • Ingress:相对更加灵活,通过反向代理服务器实现负载均衡,仅用于http/https协议,这种场景下需要额外的反向代理服务以及ingress controller,nginx是大家熟知的反向代理,在k8s时代,出现了nginx-ingress,就是nginx+ingress controller的组合,ingress controller负责根据ingress资源生成nginx配置,当配置有变化时重启nginx。同时也出现了云原生的反向代理traefik,它相当于把ingress controller包含到其中合为一体,并且能够动态感知路由规则变化,不需重启。

traefik是一个相对较新的反向代理,网上相关资料不是特别丰富,研究了好几天,才成功访问到k8s dashboard,将其中的关键点记录于此。

安装traefik

使用helm安装,最新chart使用的traefik 1.7.19:

1
helm install stable/traefik -f traefik-values.yaml

traefik-values.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
rbac:
enabled: true
dashboard:
enabled: true # 启用traefik dashboard
ingress:
annotations:
traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip
deployment:
hostPort:
httpEnabled: true # traefik pod所在node上开启80端口
httpsEnabled: true # traefik pod所在node上开启443端口
dashboardEnabled: true # traefik pod所在node上开启8080端口,共traefik dashboard使用
ssl:
insecureSkipVerify: true # frontend不验证https的benkend
enabled: true # 启用https入口
extraVolumes:
- name: traefik-ssl
hostPath:
path: /share/k8s/traefik/ssl # 其中存放https入口的证书和key,名字必须为tls.crt,tls.key
type: DirectoryOrCreate
extraVolumeMounts:
- name: traefik-ssl
mountPath: /ssl # traefik pod从/ssl目录读取上述tls.crt,tls.key

详细的配置方法见官方文档,上述关键点如下:

  1. 开启https入口,设置ssl.enabled=true,然后提供证书和key,上述通过从node节点本地目录mount到pod的方式,所以每个node节点要先放好证书和key,更好的方式是通过k8s secret,创建secret然后mount到pod
  2. 如何访问到入口,我是通过在node上打开端口,这时通过pod所在node就可以访问到入口,通过http://nodeiphttps://nodeip;还可以使用NodePort类型service,这样通过http://any-nodeip:http-nodeporthttps://any-nodeip:https-nodeport访问,value设置 serviceType: NodePort
  3. 路由匹配规则我使用的PathPrefixStrip,默认是host名匹配

因为启用了traefik dashboard,安装traefik会自动创建dashboard的ingress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip
labels:
app: traefik
chart: traefik-1.82.1
heritage: Tiller
release: traefik
name: traefik-dashboard
namespace: default
spec:
rules:
- host: traefik.example.com
http:
paths:
- backend:
serviceName: traefik-dashboard
servicePort: dashboard-http

traefik是通过标签app: traefik选择到需要感知的ingress。自己添加的ingress注意包含这个标签。上述annotations和host是从value而来。因为我不想配host,所以用PathPrefixStrip路由规则,我修改了上述ingress如下:

1
2
3
4
5
6
7
8
spec:
rules:
- http:
paths:
- backend:
serviceName: traefik-dashboard
servicePort: dashboard-http
path: /traefik

这样当使用http://nodeip/traefik就可以访问到dashboard,因为在node上也开启了dashboard端口,也可以通过http://nodeip:8080访问。

代理k8s dashboard

目前最新的k8s dashboard(v2.0.0-beta6)安装在kubernetes-dashboard namespace:

1
2
3
4
kubectl get svc -n kubernetes-dashboard
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dashboard-metrics-scraper ClusterIP 10.254.238.13 <none> 8000/TCP 21d
kubernetes-dashboard LoadBalancer 10.254.253.226 <pending> 443:30223/TCP 21d

增加ingress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip
labels:
app: traefik
name: kubernetes-dashboard
namespace: default
spec:
rules:
- http:
paths:
- backend:
serviceName: kubernetes-dashboard
servicePort: 443
path: /k8s

代理https后端

k8s dashboard只支持https访问,首先卡住的问题是如何代理https服务,frontend到backend的路由会出现以下几种情形:

  1. http->http

  2. http->https

  3. https->http

  4. https->https

当backend为https时,无论frontend是http或https,也就是2和4,都会报500错误,因为frontend无法验证backend,此时解决方法:

  • 要么设置insecureSkipVerify,这样比较简单,如果采用这种方式frontend最好总是采用https,也就是设置redirect
  • 要么设置ingress tls,配置host的tls证书信息

我采用的设置insecureSkipVerify的方法。一般最佳的使用方式也是入口总是用https,然后终结tls,后端是否https不重要。

代理不同namespace服务

解决上述问题后,接下来遇到k8s dashboard服务无法访问问题,在traefik dashboard中显示为红色,原因是helm安装traefik默认在default namespace中,而k8s dashboard安装在kubernetes-dashboard namespace中,不能跨namespace访问到服务,解决方法:

  • 要么将traefik安装到和k8s dashboard同一空间

  • 要么通过ExternalName将dashboard service引入到default namespace

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    apiVersion: v1
    kind: Service
    metadata:
    name: kubernetes-dashboard
    namespace: default
    spec:
    ports:
    - name: https
    port: 443
    protocol: TCP
    targetPort: 443
    sessionAffinity: None
    type: ExternalName
    externalName: kubernetes-dashboard.kubernetes-dashboard.svc.cluster.local

我采用的ExternalName方法。service的完整域名是servicename.namespace.svc.cluster.local,cluster.local是kubelet中配置的。

基于path路由

服务可以访问了,但是又出现了MIME type is not a supported stylesheet MIME type错误。

image-20191129064738766

一开始以为是traefik在reponse header中加入了 X-Content-Type-Options: nosniff,但是发现traefik默认是不加入的。

最后发现是url路径问题,我的ingress仅使用path路由,没有使用host。

当使用https://nodeip/k8s访问k8s dashboard时,因为路由规则是PathPrefixStrip,到后端的请求是https://nodeip,这时得到主页,文件名是k8s,主页面k8s中的css,js等文件路径是相对于当前文档路径的,所以request url是https://nodeip/xxx.css,这时就匹配不上路由规则,出现上述错误。

如果使用https://nodeip/k8s/访问dashboard,就一切正常了。

所以使用路径匹配路由时是存在一定风险的,和主页中的资源路径定义有关:

主页中css,js等资源路径定义方式 说明
没有定义base,资源路径不以./或…/或/开头
<base href="./">,资源路径以./开头
1.匹配/path时,只能通过https://xxxx/path/访问
2.匹配/path,并且后端重定向到sub/,这时通过https://xxxx/path/https://xxxx/path都可以访问
<base href="/">,资源路径不以./或…/或/开头 只能匹配/,其他路径匹配都无法正常工作
确实碰到这种情况,例如monocular

所以最好的方式还是通过host匹配路由。

dashboard认证

了解上述问题后,终于进入到dashboard登陆界面:

image-20191129063718980

一开始我是使用的http入口,使用Token方式登陆,没有任何响应,通过开发者工具查看,发现问题是在使用http入口时,header中没有携带jweToken,导致认证失败,必须使用https入口。

回想起之前通过kubectl proxy,即http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/也是登陆不了,其实是一样的问题。

使用http入口登陆失败:

image-20191129064020553

使用https入口时,jweToken是携带了,登陆成功:

image-20191129064137163 )

所以果断设置frontend总是https,values增加traefik.ingress.kubernetes.io/redirect-entry-point: https,然后helm upgrade,自己增加的ingress需要自己修改:

1
2
3
4
5
6
7
dashboard:
enabled: true
ingress:
annotations:
traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip
traefik.ingress.kubernetes.io/redirect-entry-point: https
# 不要使用ingress.kubernetes.io/ssl-redirect: "true",因为会丢掉path

这样无论使用http://nodeip/k8s/还是https://nodeip/k8s/都可以成功登陆。

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