traefik v2.x部署
Traefik 2.x 介绍
Traefik 2.0几个值得关注的功能
- 使用 CRD 来完成之前 Ingress + 注解的功能
- 支持多协议的 TCP 端口路由
- 引入了 MiddleWare,使用中间件完全自定义路由
- 金丝雀发布
Traefik 2.0 TCP 路由
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP # 类型
metadata:
name: ingressroutetcpfoo.crd # 元数据 name
spec:
entryPoints:
- footcp
routes:
# Match is the rule corresponding to an underlying router.
- match: HostSNI(`*`)
services: # 对应着 k8s 的某个路由的 service
- name: whoamitcp
port: 8080
上面这个配置就是配置一个 TCP 路由规则,上面就是说每个以 footcp 为结尾的请求都会被路由到 whoamitcp 这个服务上去
Traefik 2.0 MiddleWare
Traefik 2.0 中引入了中间件功能,可以用于将请求路由到目的地之前或之后来调整请求
首先我们可以声明一个中间件,在任意数量的路由上面都可以重用它们,下面我们来演示下如何配置中间件,声明一个 BasicAuth 中间件来控制对我们服务的访问(这次使用 TOML 来配置):
# 为两个用户声明一个 basicauth 的中间件
[http.middlewares.test-auth.basicAuth]
users = ["user1:hashed", "user2:hashed"]
# 将上面的中间件应用到路由上面去
[http.routers.my-router.to-service]
rule = "host(`my-protected.domain`)"
middlewares = ["test-auth"]
service = "service1"
此外可以声明一个链来组合绑定这些中间件,并反复使用它们,对于 Kubernetes 用户来说,还可以使用 Traefik 的新 CRD 来进行更加清晰明了的配置,而不需要复杂的注解。(可以在文档中找到有关 IngressRoute 对象的更多信息)如下所示:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`mydomain`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: secured
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: secured
spec:
chain:
middlewares:
- name: https-only
- name: known-ips
- name: auth-users
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: auth-users
spec:
basicAuth:
secret: secretUsers # 兼容 K8S secrets 对象
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: https-only
spec:
redirectScheme:
scheme: https
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: known-ips
spec:
ipWhiteList:
sourceRange:
- 192.168.1.7
- 127.0.0.1/32
上面示例中 secured 这个中间件就是有 https-only、know-ips、auth-users 这3个中间件组成的一个链式中间件
而且在 Traefik 中内置了许多中间件:路径操作、多种身份验证机制、缓冲、断路器、重试、压缩、错误处理、headers、IP 白名单、限速、重定向等。此外官方还重新设计了代码架构,可以让开发者更加容易提供第三方的中间件
金丝雀发布
Traefik 2.0 中以服务负载均衡的形式支持金丝雀发布,可以将服务负载均衡器看成负责将请求转发到实际服务的虚拟服务,下面让我们来看一个经典的场景,现在有一个路由到一个 API:
http:
routers:
my-route:
rule: "Host(`my.domain`)"
service: my-api-v1
services:
my-api-v1:
loadBalancer:
servers:
- url: "http://private-ip-server-1/"
现在我们要部署该服务的一个新版本,但是希望可以逐步部署,比如先部署大约1/3的请求,我们这里就需要使用一个新的 ID(这里可以用 my-api-v2)来部署一个新的服务:
http:
services:
my-api-v2:
loadBalancer:
servers:
- url: "http://private-ip-server-2/"
然后我们需要定义一个服务负载均衡器(我们这里叫 cannary-api),并定义每个版本的比例,而不是直接指向新版本:
http:
services:
canary-api:
weighted:
services:
- name: my-api-v1
weight: 3
- name: my-api-v2
weight: 1
最后,记得把路由指向这个 canary-api 服务:
http:
routers:
my-route:
rule: "Host(`my.domain`)"
service: canary-api
之后,我们不需要重新部署真正的服务就可以更新权重,当然还可以对它们进行扩容,这都不会对金丝雀部署本身产生任何的影响
流量复制
金丝雀部署并不是服务系列功能中唯一的一种可以使用的功能,Traefik 2.0 还引入了镜像服务,一种可以将流入流量复制并同时将其发送给其他服务的方法,镜像服务可以获得给定百分比的请求同时也会忽略这部分请求的响应:
[http.services.mirrored-api]
[http.services.mirrored-api.mirroring]
service = "api"
[[http.services.mirrored-api.mirroring.mirrors]]
name = "api-v2"
percent = 10
[http.services.api]
[http.services.api.loadBalancer]
[[http.services.api.loadBalancer.servers]]
url = "http://private-ip-server-1/"
[http.services.api-v2]
[http.services.api-v2.loadBalancer]
[[http.services.api-v2.loadBalancer.servers]]
url = "http://private-ip-server-2/"
上面这个示例中,我们就可以复制10%的请求发送给镜像
部署traefik v2.x
本文以2.2版本为例
DaemonSet方式部署
创建CRD资源
在 Traefik v2.0版本后,开始使用 CRD(Custom Resource Definition)来完成路由配置等,所以需要提前创建CRD资源
[root@k8s-master traefik]# cat traefik.crd.yaml
## IngressRoute
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutes.traefik.containo.us
spec:
scope: Namespaced
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRoute
plural: ingressroutes
singular: ingressroute
---
## IngressRouteTCP
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutetcps.traefik.containo.us
spec:
scope: Namespaced
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteTCP
plural: ingressroutetcps
singular: ingressroutetcp
---
## Middleware
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: middlewares.traefik.containo.us
spec:
scope: Namespaced
group: traefik.containo.us
version: v1alpha1
names:
kind: Middleware
plural: middlewares
singular: middleware
---
## TLSOption
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsoptions.traefik.containo.us
spec:
scope: Namespaced
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSOption
plural: tlsoptions
singular: tlsoption
---
## TraefikService
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: traefikservices.traefik.containo.us
spec:
scope: Namespaced
group: traefik.containo.us
version: v1alpha1
names:
kind: TraefikService
plural: traefikservices
singular: traefikservice
---
## TLSStore
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsstores.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSStore
plural: tlsstores
singular: tlsstore
scope: Namespaced
---
## IngressRouteUDP
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressrouteudps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteUDP
plural: ingressrouteudps
singular: ingressrouteudp
scope: Namespaced
[root@k8s-master traefik]# kubectl apply -f traefik.crd.yaml -n kube-system
customresourcedefinition.apiextensions.k8s.io/ingressroutes.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/ingressroutetcps.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/middlewares.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/tlsoptions.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/traefikservices.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/tlsstores.traefik.containo.us created
customresourcedefinition.apiextensions.k8s.io/ingressrouteudps.traefik.containo.us created
创建 RBAC 权限
Kubernetes在 1.6 版本中引入了基于角色的访问控制(RBAC)策略,方便对 Kubernetes资源和API进行细粒度控制,Traefik 需要一定的权限,所以,这里提前创建好 Traefik ServiceAccount 并分配一定的权限
[root@k8s-master traefik]# cat traefik.rbac.yaml
## ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: kube-system
name: traefik-ingress-controller
---
## ClusterRole
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"]
- apiGroups: ["extensions"]
resources: ["ingresses/status"]
verbs: ["update"]
- apiGroups: ["traefik.containo.us"]
resources: ["middlewares","ingressroutes","ingressroutetcps","tlsoptions","ingressrouteudps","traefikservices","tlsstores"]
verbs: ["get","list","watch"]
---
## ClusterRoleBinding
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
[root@k8s-master traefik]# kubectl apply -f traefik.rbac.yaml -n kube-system
serviceaccount/traefik-ingress-controller created
clusterrole.rbac.authorization.k8s.io/traefik-ingress-controller created
clusterrolebinding.rbac.authorization.k8s.io/traefik-ingress-controller created
创建 Traefik 配置文件
由于Traefik配置很多,通过 CLI 定义不是很方便,一般时候都会通过配置文件配置Traefik参数,然后存入ConfigMap,将其挂入Traefik中
[root@k8s-master traefik]# cat traefik-config.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: traefik-config
data:
traefik.yaml: |-
ping: "" ## 启用 Ping
serversTransport:
insecureSkipVerify: true ## Traefik 忽略验证代理服务的 TLS 证书
api:
insecure: true ## 允许 HTTP 方式访问 API
dashboard: true ## 启用 Dashboard
debug: false ## 启用 Debug 调试模式
metrics:
prometheus: "" ## 配置 Prometheus 监控指标数据,并使用默认配置
entryPoints:
web:
address: ":80" ## 配置 80 端口,并设置入口名称为 web
websecure:
address: ":443" ## 配置 443 端口,并设置入口名称为 websecure
providers:
kubernetesCRD: "" ## 启用 Kubernetes CRD 方式来配置路由规则
kubernetesIngress: "" ## 启动 Kubernetes Ingress 方式来配置路由规则
log:
filePath: "" ## 设置调试日志文件存储路径,如果为空则输出到控制台
level: error ## 设置调试日志级别
format: json ## 设置调试日志格式
accessLog:
filePath: "" ## 设置访问日志文件存储路径,如果为空则输出到控制台
format: json ## 设置访问调试日志格式
bufferingSize: 0 ## 设置访问日志缓存行数
filters:
#statusCodes: ["200"] ## 设置只保留指定状态码范围内的访问日志
retryAttempts: true ## 设置代理访问重试失败时,保留访问日志
minDuration: 20 ## 设置保留请求时间超过指定持续时间的访问日志
fields: ## 设置访问日志中的字段是否保留(keep 保留、drop 不保留)
defaultMode: keep ## 设置默认保留访问日志字段
names: ## 针对访问日志特别字段特别配置保留模式
ClientUsername: drop
headers: ## 设置 Header 中字段是否保留
defaultMode: keep ## 设置默认保留 Header 中字段
names: ## 针对 Header 中特别字段特别配置保留模式
User-Agent: redact
Authorization: drop
Content-Type: keep
#tracing: ## 链路追踪配置,支持 zipkin、datadog、jaeger、instana、haystack 等
# serviceName: ## 设置服务名称(在链路追踪端收集后显示的服务名)
# zipkin: ## zipkin配置
# sameSpan: true ## 是否启用 Zipkin SameSpan RPC 类型追踪方式
# id128Bit: true ## 是否启用 Zipkin 128bit 的跟踪 ID
# sampleRate: 0.1 ## 设置链路日志采样率(可以配置0.0到1.0之间的值)
# httpEndpoint: http://localhost:9411/api/v2/spans ## 配置 Zipkin Server 端点
[root@k8s-master traefik]# kubectl apply -f traefik-config.yaml -n kube-system
configmap/traefik-config created
部署Traefik
下面将用 DaemonSet 方式部署 Traefik,便于在多服务器间扩展,用 hostport 方式绑定服务器 80、443 端口,方便流量通过物理机进入 Kubernetes 内部
[root@k8s-master traefik]# cat traefik-ds.yaml
apiVersion: v1
kind: Service
metadata:
name: traefik
spec:
ports:
- name: web
port: 80
- name: websecure
port: 443
selector:
app: traefik
---
apiVersion: v1
kind: Service
metadata:
name: traefik-ui
spec:
ports:
- name: admin
port: 8080
selector:
app: traefik
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: traefik-ingress-controller
labels:
app: traefik
spec:
selector:
matchLabels:
app: traefik
template:
metadata:
name: traefik
labels:
app: traefik
spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 1
containers:
- image: traefik:v2.2.8
name: traefik-ingress-lb
ports:
- name: web
containerPort: 80
hostPort: 80 ## 将容器端口绑定所在服务器的 80 端口
- name: websecure
containerPort: 443
hostPort: 443 ## 将容器端口绑定所在服务器的 443 端口
- name: admin
containerPort: 8080 ## Traefik Dashboard 端口
resources:
limits:
cpu: 2000m
memory: 1024Mi
requests:
cpu: 1000m
memory: 1024Mi
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
args:
- --configfile=/config/traefik.yaml
volumeMounts:
- mountPath: "/config"
name: "config"
readinessProbe:
httpGet:
path: /ping
port: 8080
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
livenessProbe:
httpGet:
path: /ping
port: 8080
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
volumes:
- name: config
configMap:
name: traefik-config
[root@k8s-master traefik]# kubectl apply -f traefik-ds.yaml -n kube-system
service/traefik created
service/traefik-ui created
daemonset.apps/traefik-ingress-controller created
到此 Traefik v2.2 应用已经部署完成
配置路由规则
Traefik 应用已经部署完成,但是想让外部访问 Kubernetes 内部服务,还需要配置路由规则,上面部署 Traefik 时开启了 Traefik Dashboard,这是 Traefik 提供的视图看板,所以,首先配置基于 HTTP 的 Traefik Dashboard 路由规则,使外部能够访问 Traefik Dashboard。然后,再配置基于 HTTPS 的 Kubernetes Dashboard 的路由规则,这里分别使用 CRD 和 Ingress 两种方式进行演示
方式一:使用 CRD 方式配置 Traefik 路由规则
1.配置 HTTP 路由规则 (Traefik Dashboard 为例)
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard-route
spec:
entryPoints:
- web
routes:
- match: Host(`traefik.axhome.com.cn`)
kind: Rule
services:
- name: traefik
port: 8080
2.配置 HTTPS 路由规则(Kubernetes Dashboard 为例)
这里我们创建 Kubernetes 的 Dashboard 看板创建路由规则,它是 Https 协议方式,由于它是需要使用 Https 请求,所以我们配置基于 Https 的路由规则并指定证书
# 创建自签名证书
$ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=cloud.mydlq.club"
# 将证书存储到 Kubernetes Secret 中
$ kubectl create secret generic cloud-mydlq-tls --from-file=tls.crt --from-file=tls.key -n kube-system
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: kubernetes-dashboard-route
spec:
entryPoints:
- websecure
tls:
secretName: cloud-mydlq-tls
routes:
- match: Host(`traefik.axhome.com.cn`)
kind: Rule
services:
- name: kubernetes-dashboard
port: 443
方式二:使用 Ingress 方式配置 Traefik 路由规则
注意:使用该方式需要 Traefik 版本 ≥ 2.2
1.配置 HTTP 路由规则 (Traefik Dashboard 为例)
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: traefik-dashboard-ingress
namespace: kube-system
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: traefik.axhome.com.cn
http:
paths:
- path: /
backend:
serviceName: traefik
servicePort: 8080
2.配置 HTTPS 路由规则(Kubernetes Dashboard 为例)
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubernetes-dashboard-ingress
namespace: kube-system
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
tls:
- secretName: cloud-mydlq-tls
rules:
- host: traefik.axhome.com.cn
http:
paths:
- path: /
backend:
serviceName: kubernetes-dashboard
servicePort: 443
本地添加host或者域名解析,可以通过域名访问
Deployment方式部署
创建crd rbac
[root@k8s-master traefik]# cat traefik-crd.yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutes.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRoute
plural: ingressroutes
singular: ingressroute
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: middlewares.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: Middleware
plural: middlewares
singular: middleware
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutetcps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteTCP
plural: ingressroutetcps
singular: ingressroutetcp
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressrouteudps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteUDP
plural: ingressrouteudps
singular: ingressrouteudp
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsoptions.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSOption
plural: tlsoptions
singular: tlsoption
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsstores.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSStore
plural: tlsstores
singular: tlsstore
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: traefikservices.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TraefikService
plural: traefikservices
singular: traefikservice
scope: Namespaced
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: traefik-ingress-controller
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- traefik.containo.us
resources:
- middlewares
- ingressroutes
- traefikservices
- ingressroutetcps
- ingressrouteudps
- tlsoptions
- tlsstores
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
deploy部署traefik
[root@k8s-master traefik]# cat traefik-deploy.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: default
name: traefik-ingress-controller
---
kind: Deployment
apiVersion: apps/v1
metadata:
namespace: default
name: traefik
labels:
app: traefik
spec:
replicas: 1
selector:
matchLabels:
app: traefik
template:
metadata:
labels:
app: traefik
spec:
serviceAccountName: traefik-ingress-controller
containers:
- name: traefik
image: traefik:v2.2
args:
- --api.insecure
- --accesslog
- --entrypoints.web.Address=:80
- --entrypoints.websecure.Address=:443
- --providers.kubernetescrd
- --certificatesresolvers.myresolver.acme.tlschallenge
- --certificatesresolvers.myresolver.acme.email=foo@you.com
- --certificatesresolvers.myresolver.acme.storage=acme.json
# Please note that this is the staging Let's Encrypt server.
# Once you get things working, you should remove that whole line altogether.
- --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
ports:
- name: web
containerPort: 80
hostport: 80
- name: websecure
containerPort: 443
hostport: 443
- name: admin
containerPort: 8080
创建svc
[root@k8s-master traefik]# cat traefik-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: traefik
spec:
ports:
- protocol: TCP
name: web
port: 80
- protocol: TCP
name: websecure
port: 443
selector:
app: traefik
---
apiVersion: v1
kind: Service
metadata:
name: traefik-ui
spec:
ports:
- protocol: TCP
name: admin
port: 8080
nodePort: 30808
selector:
app: traefik
type: NodePort
创建ingressroutes
[root@k8s-master traefik]# cat ingressroutes.yml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test-ingressroute
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`test.axhome.com.cn`)
kind: Rule
services:
- name: whoami
port: 80
- match: Host(`traefik.axhome.com.cn`)
kind: Rule
services:
- name: traefik-ui
port: 8080
[root@k8s-master traefik]# cat whoami.yaml
---
kind: Deployment
apiVersion: apps/v1
metadata:
namespace: default
name: whoami
labels:
app: whoami
spec:
replicas: 2
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: containous/whoami
ports:
- name: web
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
ports:
- protocol: TCP
name: web
port: 80
selector:
app: whoami
本地添加host或者域名解析,可以通过域名访问