1 Service
1.1 Service概念
Service是kubernetes标准的API资源类型之一,存在于集群中的各节点之上,kubernetes会为它创建一个稳定不变且可用的 Cluster_IP,为pod提供固定的流量入口。
- 服务发现:通过标签选择器,在同一名称空间下发现一组提供相同服务的pod,并筛选符合条件的pod
- 实际上并非由Service资源自己完成,是由与Service同名的Endpoint或EndpointSlice资源及控制器完成的
- 当创建一个Service时,Kubernetes会为该Service创建一个同名的Endpoints资源。这个Endpoints资源包含了一个或多个后端Pod的IP地址和端口号,是否有该pod取决于这个pod有没有通过readiness探针测试。当Service收到请求时,它会将请求转发给Endpoints中的后端Pod
- EndpointSlice可以支持更大规模的集群
- 四层负载均衡:这组筛选出来的pod的ip地址,将作为service的后端服务器,其流量调度规则是由运行在各工作节点的kube-proxy根据配置的模式生成,可以是iptables或ipvs
- 客户端可以是来自集群之上的Pod,也可以是集群外部的其它端点
- 该节点之上的进程,可通过该Service的Cluster IP进入(集群内部,东西向流量)
- 该节点之外的端点,可经由该Service的NodePort进入(集群外部,南北向流量,两级调度)
Service资源主要是为了解决Pod在服务提供上的不足
- Pod的 IP 具有动态性,当因为故障或重启而更换时,IP地址也会随之变化
- Pod的 IP 只能在kubernetes集群内部访问
1.2 Service类型
1.2.1 ClusterIP
访问范围:集群内部
依赖条件:无
典型场景:微服务内部通信
支持Service_IP:Service_Port
接入,Client --> Service_IP:Service_Port --> Pod_IP:Pod_Port
kind: Service
apiVersion: v1
metadata:
name: demoapp-svc
spec:
type: ClusterIP # 类型标识,默认即为ClusterIP;
clusterIP: 10.97.72.1 # 建议不要指定,自动分配
selector:
app: demoapp # 指定pod的标签,pod上必须有app=demoapp的标签
ports:
- name: http # 端口名称标识
protocol: TCP # 协议,支持TCP、UDP和SCTP
port: 80 # Service的端口号
targetPort: 80 # 目标端口号,即后端端点提供服务的监听端口号
[root@master1 services]#kubectl create deployment demoapp --image=ikubernetes/demoapp:v1.0 --replicas=2 -n demo
[root@master1 services]#kubectl get pods -n demo --show-labels
NAME READY STATUS RESTARTS AGE LABELS
demoapp-7c58cd6bb-2zbb8 1/1 Running 0 20s app=demoapp,pod-template-hash=7c58cd6bb
demoapp-7c58cd6bb-t8dwm 1/1 Running 0 20s app=demoapp,pod-template-hash=7c58cd6bb
[root@master1 services]# kubectl get pods -o wide -n demo
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demoapp-7c58cd6bb-2zbb8 1/1 Running 0 3h19m 10.244.2.10 node1.wang.org <none> <none>
demoapp-7c58cd6bb-t8dwm 1/1 Running 0 3h19m 10.244.1.8 node2.wang.org <none> <none>
[root@master1 services]#kubectl get services -n demo
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demoapp-svc ClusterIP 10.101.226.122 <none> 80/TCP 14s
[root@master1 services]#kubectl get services -n demo demoapp-svc -o yaml
spec:
internalTrafficPolicy: Cluster #内部流量策略
type: ClusterIP
#创建自主式Pod测试,可以调度到两个节点上的pod
[root@master1 services]#kubectl run client-$RANDOM --image=ikubernetes/admin-box:v1.2 -it --restart=Never --rm --command -- /bin/sh
root@client-5400 ~# curl 10.101.226.122
ikubernetes admin-box:v1.2 !! ClientIP: 10.244.1.9,Servername: demoapp-7c58cd6bb-2zbb8,ServerIP: 10.244.2.10
root@client-5400 ~# curl 10.101.226.122
ikubernetes admin-box:v1.2 !! ClientIP: 10.244.1.9,Servername: demoapp-7c58cd6bb-t8dwm,ServerIP: 10.244.1.8
#也能利用名字,注意要指定名称空间
root@client-5400 ~# curl demoapp-svc.demo
1.2.2 NodePort
访问范围:集群外部(通过节点)
依赖条件:节点需要有与外部通信的IP
典型场景:开发测试,临时访问
支持Node_IP:Node_Port
接入,Client --> Node_IP:NodePort --> Pod_IP:Pod_Port,这里的Node_IP可以集群中任一节点
节点会监听一个端口与service的端口映射,这就是NodePort,一般会分配一个在30000-32767之间的端口
kind: Service
apiVersion: v1
metadata:
name: demoapp
namespace: demo
spec:
type: NodePort # 必须明确给出Service类型
selector:
app: demoapp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
nodePort: 30080 # 可选,为避免冲突,建议由系统动态分配
# externalTrafficPolicy: Local
[root@master1 services]#kubectl get svc -n demo
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demoapp-clusterip-svc ClusterIP 10.97.72.1 <none> 80/TCP 26m
demoapp-nodeport-svc NodePort 10.96.45.106 <none> 80:32697/TCP 2s
#集群内部
[root@master1 services]#kubectl run client-$RANDOM --image=ikubernetes/admin-box:v1.2 -it --restart=Never --rm --command -- /bin/sh
[root@client-3546 /]# curl 10.96.45.106
iKubernetes demoapp v1.0 !! ClientIP: 10.244.1.22, ServerName: demoapp-7c58cd6bb-9g92g, ServerIP: 10.244.1.20!
[root@client-3546 /]# curl 10.96.45.106
iKubernetes demoapp v1.0 !! ClientIP: 10.244.1.22, ServerName: demoapp-7c58cd6bb-mzgqk, ServerIP: 10.244.2.13!
#集群外部,集群内部任一节点
[root@rocky8 ~]#curl 10.0.0.183:32697
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-7c58cd6bb-mzgqk, ServerIP: 10.244.2.13!
[root@rocky8 ~]#curl 10.0.0.183:32697
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-7c58cd6bb-9g92g, ServerIP: 10.244.1.20!
1.2.3 LoadBalancer
访问范围:集群外部(通过LB)
依赖条件:云平台支持
典型场景:生产环境对外暴露服务
由于NodePort不是正常服务的端口,比如用户访问nginx,只知道是80端口,但是我们是30080端口,用户就不知道了,所以直接让用户访问Node不现实,需要在集群外部多加一级代理LoadBalancer,将接入的流量转发至工作节点上的NodePort
支持通过外部的LoadBalancer的LB_IP:LB_Port
接入,Client --> LB_IP:LB_PORT --> Node_IP:NodePort --> Pod_IP:Pod_Port
云服务商一般会提供一个负载均衡器,当集群创建一个LoadBalancer类型的service资源时,会自动帮我们关联一个负载均衡器,,生成 EXTERNAL-IP(LB_IP) 供外部客户端使用
若外部LB把流量转发给并非目标Pod所在的节点时,该节点就要把该流量转发给拥有这个pod的Node上,报文转发的路径中就会存在跃点,可以通过 externalTrafficPolicy.Local
设置外部LB只能把流量转发给运行有该Service关联的Pod的Node之上
kind: Service
apiVersion: v1
metadata:
name: demoapp-loadbalancer-svc
spec:
type: LoadBalancer
selector:
app: demoapp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
[root@master1 MetalLB]#kubectl get svc -n demo
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demoapp-clusterip-svc ClusterIP 10.97.72.1 <none> 80/TCP 48m
demoapp-loadbalancer-svc LoadBalancer 10.98.160.147 10.0.0.51 80:31179/TCP 15m
demoapp-nodeport-svc NodePort 10.96.45.106 <none> 80:32697/TCP 21m
[root@rocky8 ~]#curl 10.0.0.51
iKubernetes demoapp v1.0 !! ClientIP: 10.244.2.0, ServerName: demoapp-7c58cd6bb-9g92g, ServerIP: 10.244.1.20!
[root@rocky8 ~]#curl 10.0.0.51
iKubernetes demoapp v1.0 !! ClientIP: 10.244.2.1, ServerName: demoapp-7c58cd6bb-mzgqk, ServerIP: 10.244.2.13!
1.2.4 ExternalName
访问范围:集群内部
依赖条件:外部服务需要有DNS名称
典型场景:代理外部服务
DNS 重定向机制,用于将集群外部的服务引入到集群中,将集群内的服务名称映射到集群外的域名,通过externalName字段进行设置。当集群内的 Pod 访问这个服务时,DNS 解析会将服务名称解析为外部域名。
ServiceName --> external Service DNS Name
kind: Service
apiVersion: v1
metadata:
name: externalname-redis-svc #Service名称
namespace: demo
spec:
type: ExternalName
externalName: redis.ik8s.io #DNS解析的外部目标域名
ports:
- protocol: TCP
port: 6379
targetPort: 6379
nodePort: 0
selector: {} #选择器部分为空,表示这个Service不与任何特定的Pod关联
[root@master1 services]#kubectl get pods -n demo
NAME READY STATUS RESTARTS AGE
demoapp-7c58cd6bb-bldpv 1/1 Running 0 48s
demoapp-7c58cd6bb-bls8m 1/1 Running 0 48s
[root@master1 services]#kubectl get svc -n demo
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
externalname-redis-svc ExternalName <none> redis.ik8s.io 6379/TCP 15s
[root@master1 services]#kubectl exec -it -n demo demoapp-7c58cd6bb-bldpv -- /bin/sh
[root@demoapp-7c58cd6bb-bldpv ~]# host -t A externalname-redis-svc
externalname-redis-svc.demo.svc.cluster.local is an alias for redis.ik8s.io.
redis.ik8s.io has address 1.2.3.4
1.2.5 Headless
访问范围:集群内部
依赖条件:无
典型场景:有状态应用,直接访问Pod
那些没有ClusterIP的Service则称为Headless Service,它们又可以为分两种情形
- 有标签选择器的有状态应用,或者没有标签选择器但有着与Service对象同名的Endpoint资源。Service的DNS名称直接解析为后端各就绪状态的Pod的IP地址,各Pod IP相关PTR记录将解析至Pod自身的名称,不会解析至Service的DNS名称
- 无标签选择器且也没有与Service对象同名的Endpoint资源,即ExternalName,Service的DNS名称将会生成一条CNAME记录,对应值由Service对象上的
spec.externalName
字段指定
范例:有标签选择器
kind: Service
apiVersion: v1
metadata:
name: demoapp-headless-svc
spec:
clusterIP: None
selector:
app: demoapp #解析到这个标签下的后端各就绪状态的Pod的IP地址
ports:
- port: 80
targetPort: 80
name: http
[root@master1 ~]#kubectl get pods -n demo -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demoapp-7c58cd6bb-bldpv 1/1 Running 0 10m 10.244.1.4 node1.wang.org <none> <none>
demoapp-7c58cd6bb-bls8m 1/1 Running 0 10m 10.244.2.4 node2.wang.org <none> <none>
[root@master1 services]#kubectl get svc -n demo
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demoapp-headless-svc ClusterIP None <none> 80/TCP 8s
externalname-redis-svc ExternalName <none> redis.ik8s.io 6379/TCP 12m
[root@master1 services]#kubectl exec -it -n demo demoapp-7c58cd6bb-bldpv -- /bin/sh
#解析至Pod自身的IP
[root@demoapp-7c58cd6bb-bldpv ~]# host -t A demoapp-headless-svc
demoapp-headless-svc.demo.svc.cluster.local has address 10.244.2.4
demoapp-headless-svc.demo.svc.cluster.local has address 10.244.1.4
#反向解析至Pod自身的名称
[root@demoapp-7c58cd6bb-bldpv ~]# host -t PTR 10.244.1.4
4.1.244.10.in-addr.arpa domain name pointer 10-244-1-4.demoapp-headless-svc.demo.svc.cluster.local.
范例:没有标签选择器,但有着与Service对象同名的Endpoint资源
apiVersion: v1
kind: Endpoints
metadata:
name: mysql-external
namespace: default
subsets:
- addresses: #指定集群外部的后端服务器的IP地址
- ip: 172.29.9.51
- ip: 172.29.9.52
ports: #定义Endpoints的端口映射
- name: mysql
port: 3306
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: mysql-external #必须与Endpoints一致
namespace: default #必须与Endpoints一致
spec:
type: ClusterIP
ports:
- name: mysql
port: 3306
targetPort: 3306
protocol: TCP
[root@master1 services]#kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mysql-external ClusterIP 10.103.90.57 <none> 3306/TCP 17s
[root@master1 services]#kubectl describe svc mysql-external
Name: mysql-external
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.103.90.57
IPs: 10.103.90.57
Port: mysql 3306/TCP
TargetPort: 3306/TCP
Endpoints: 172.29.9.51:3306,172.29.9.52:3306 #这两个集群外部的服务器,与节点在同一网段,Pod就可以正常访问
Session Affinity: None
Events: <none>
2 CoreDNS
2.1 CoreDNS概念
CoreDNS是Kubernetes集群的必备附件(DNS服务器),负责为Kubernetes提供名称解析和服务发现,对于每个Service,自动生成一个或多个A、PTR和SRV(端口名称解析)记录,将Service的name与Service的Cluster_IP地址做一个DNS域名映射,可以基于名称的方式去访问该组Pod上的服务.
每个Service资源对象,在CoreDNS上都会自动生成一个遵循 “<service>.<ns>.svc.<zone>”
格式的名称,围绕该名称会生成一些DNS格式的资源记录
<service>
:当前Service对象的名称<ns>
:当前Service对象所属的名称空间<zone>
:当前Kubernetes集群使用的域名后缀,默认为“cluster.local”
,是 Kubernetes 集群中所有域名的根域名
2.2 CoreDNS插件架构
CoreDNS是一种灵活、可扩展的 DNS 服务器,借助插件架构,允许用户根据需求自定义功能。与其他 DNS 服务器BIND、Knot、PowerDNS不同,CoreDNS 非常灵活,几乎所有功能都交给插件去实现。
默认的 CoreDNS 安装中包含了大约 30 个插件,还可以编译许多外部插件到 CoreDNS 中,以扩展其功能。
CoreDNS的插件可大致分为两类:
- 负责处理请求的常规插件,这些插件才是插件链中的有效组成部分,例如errors、kubernetes和forward等,而他们又分为两类
- 负责以某种方式处理请求的插件:这类插件不是区域数据源,它们的运行方式是在自身处理完成后,会将查询传递给下一个插件
- 后端插件:用于配置区域数据的来源,例如etcd、file和kubernetes等
- 这类插件通常要为其负责的区域生成最终结果,它要么能正常响应查询,要么返回NXDOMAIN
- 但也可以使用fallthrough改变这种响应行为:未发现查询的目标资源记录时,将DNS查询请求转交给插件链中的下一个插件,随后终结于另一个后端插件,或再次由 fallthrough 改变这种行为
- 不处理请求的特殊插件,它们仅用来修改Server或Server Block中的配置,例如health、tls、startup、shutdown和root等插件,这类插件不会作为服务器配置段的插件链中的有效组成部署
2.3 CoreDNS在kubernetes下的工作原理
CoreDNS 在 Kubernetes 中作为一个 Deployment 运行,通常会部署两个或多个副本以确保高可用性。它主要通过以下步骤工作:
- 启动与配置:CoreDNS 读取 ConfigMap 配置文件,根据配置启动相应的插件。
- 监听 DNS 请求:CoreDNS 监听来自 Kubernetes 集群内部的 DNS 请求。
- 解析 DNS 请求:根据请求的类型,CoreDNS 调用相应的插件进行解析。
- 返回解析结果:将解析结果返回给请求方。
2.4 Pod上的DNS解析策略
由 spec.dnsPolicy
设置
- Default:Pod直接继承其所在节点的名称解析配置,就无法解析集群内部的服务,csi-nfs-driver默认使用
- ClusterFirst:使用集群内部的CoreDNS服务进行域名解析。如果集群内的服务无法解析请求的域名,那么才将查询转发给从所在节点继承的上游名称服务器
- ClusterFirstWithHostNet:专用于在设置了hostNetwork的Pod对象上使用的ClusterFirst策略。当Pod与宿主机共用同一个网络命名空间时,这类Pod无法访问集群内的服务,所以该策略让这类Pod可以利用集群的DNS服务进行域名解析
- None:用于忽略Kubernetes集群的默认设定,而仅使用由dnsConfig自定义的配置
Pod内解析一个域名时,请求送到CoreDNS的Pod中的流程
1、DNS配置初始化
Pod启动时,kubelet根据集群DNS配置( --cluster-dns
参数)将CoreDNS的Service ClusterIP写入Pod的 /etc/resolv.conf
文件
2、DNS查询发起
应用程序发起域名查询(如my-service)
- 若为非FQDN(如my-service),系统按search域依次补全(如my-service..svc.cluster.local)并尝试解析
- 若为FQDN(如my-service.ns.svc.cluster.local),直接发送DNS请求
3、请求路由至CoreDNS服务
Pod的DNS客户端将请求通过UDP/TCP协议发送至nameserver指定的CoreDNS Service ClusterIP(如10.96.0.10)的53端口,然后节点上的kube-proxy实现流量转发,通过iptables/IPVS规则将目标为CoreDNS ServiceIP的流量负载均衡到后端CoreDNS Pod的Endpoint IP
4、CoreDNS处理请求
- CoreDNS Pod接收请求后,根据配置(corefile)执行插件链
- kubernetes插件:解析集群内Service、Pod域名,通过API Server查询Service/Pod的IP
- forward插件:非集群域名(如baidu.com)转发至上游DNS(如节点/etc/resolv.conf中的外部DNS服务器
- 返回解析结果:CoreDNS将解析后的IP地址按原路径返回至Pod,完成域名解析
Pod应用→ /etc/resolv.conf → CoreDNS Service → kube-proxy→ CoreDNS Pod→ API Server/外部DNS
3 Ingress
3.1 Ingress的概念
Service作为四层负载均衡,不支持基于URL等机制对HTTP/HTTPS协议进行高级路由、超时/重试、基于流量的灰度等高级流量治理机制,也难以将多个Service流量统一管理,所以有了Ingress作为七层负载均衡
- 可配置域名和路径规则,为多个服务提供统一入口
- 支持多种高级功能,如 TLS、路径重写等
- 仅支持 HTTP 和 HTTPS
Ingress是Kubernetes上的标准API资源类型之一,由Ingress API和Ingress Controller共同组成
- 前者负责以k8s标准的资源格式定义流量调度、路由等规则
- 后者负责监视(watch)Ingress并生成自身的配置,并据此完成流量转发
Ingress Controller非为内置的控制器,需要额外部署(用户空间的代理进程),实现方案有很多,包括Ingress-Nginx、HAProxy、Envoy、Traefik、Gloo、Contour和Kong等
- 通常以Pod形式运行于Kubernetes集群之上
- 一般应该由专用的LB Service负责为其接入集群外部流量
- Kubernetes支持同时部署二个或以上的数量的Ingress Controller,所以在创建Ingress资源时,应该指明其所属的Ingress Controller
Ingress、Ingress Controller和Service的关系
- Ingress需要借助于Service资源来发现后端端点
- Ingress Controller会基于Ingress的定义将流量直接发往其相关Service的后端端点,该转发过程并不会再经由Service进行
- Client --> LB Service --> Ingress Controller Pod --> Upstream Service --> Upstream Pod
- LB Service是NodePort类型或者是LoadBalancer类型
- Upstream Service仅发挥服务发现功能,来发现后端Pod,并不需要负载均衡功能,这样做可以减少一层代理
- 所以流量流向可以认为是Client --> LB Service --> Ingress Controller Pod --> Upstream Pod
- 当然对那些未通过Ingress Controller进入的流量service会执行负载均衡
3.2 Ingress的类型
3.2.1 Simple fanout
在同一个FQDN下通过不同的URI完成不同应用间的流量分发
- 基于单个虚拟主机接收多个应用的流量
- 常用于将流量分发至同一个应用下的多个不同子应用,同一个应用内的流量由调度算法分发至该应用的各后端端点
- 不需要为每个应用配置专用的域名
基于URI方式代理不同应用的请求时,后端应用的URI若与代理时使用的URI不同,则需要启用URL Rewrite完成URI的重写,Ingress-Nginx支持使用 “annotation nginx.ingress.kubernetes.io/rewrite-target”
注解
[root@master1 ~]#kubectl create deployment nginx --image=nginx:1.22-alpine --replicas=2 -n dev
[root@master1 ~]#kubectl create service clusterip nginx --tcp=80:80 -n dev
[root@master1 ~]#kubectl get ep -n dev
NAME ENDPOINTS AGE
demoapp 10.244.1.57:80,10.244.2.53:80 56m
nginx 10.244.1.58:80,10.244.2.54:80 10s
#有个问题:location映射的时候,请求前面带上/demoapp或/nginx,代理的时候也会带上
#即client --> web.wu.com/demoapp --> demoapp:80/demoapp
#但是我们应该想的是代理到后端是demoapp:80/
#所以Ingress-Nginx支持使用“annotation nginx.ingress.kubernetes.io/rewrite-target”注解进行URI的重写
#下面例子是仅将请求的path简单重写为另一个path
[root@master1 ~]#kubectl create ingress demoapp-nginx --rule="web.wu.com/demoapp/*"=demoapp:80 --rule="web.wu.com/nginx/*"=nginx:80 --class=nginx --annotation nginx.ingress.kubernetes.io/rewrite-target="/" -n dev --dry-run=client -o yaml > ingress-simple-fanout.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
creationTimestamp: null
name: demoapp-nginx
namespace: dev
spec:
ingressClassName: nginx
rules:
- host: web.wu.com
http:
paths:
- backend:
service:
name: demoapp
port:
number: 80
path: /demoapp/
pathType: Prefix
- backend:
service:
name: nginx
port:
number: 80
path: /nginx/
pathType: Prefix
status:
loadBalancer: {}
[root@master1 ~]#kubectl apply -f ingress-simple-fanout.yaml
[root@master1 ~]#kubectl describe ingress -n dev demoapp-nginx
Name: demoapp-nginx
Labels: <none>
Namespace: dev
Address: 10.0.0.52
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
web.wu.com
/demoapp/ demoapp:80 (10.244.1.57:80,10.244.2.53:80)
/nginx/ nginx:80 (10.244.1.58:80,10.244.2.54:80)
Annotations: nginx.ingress.kubernetes.io/rewrite-target: / #URL重写
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 70s (x2 over 103s) nginx-ingress-controller Scheduled for sync
[root@rocky8 ~]#vim /etc/hosts
10.0.0.52 demoapp.wu.com web.wu.com
[root@rocky8 ~]#curl web.wu.com/demoapp/
iKubernetes demoapp v1.0 !! ClientIP: 10.244.2.52, ServerName: demoapp-7c58cd6bb-h6vqs, ServerIP: 10.244.1.57!
[root@rocky8 ~]#curl web.wu.com/nginx/
<title>Welcome to nginx!</title>
[root@rocky8 ~]#curl web.wu.com/demoapp/version
iKubernetes demoapp v1.0 !! ClientIP: 10.244.2.52, ServerName: demoapp-7c58cd6bb-h6vqs, ServerIP: 10.244.1.57!
root@rocky8 ~]#curl web.wu.com/nginx/version
<title>Welcome to nginx!</title>
#上面有个问题:/*的话,什么路径都能匹配到根上,所以有更复杂的写法
#下面例子是将请求的path重写为另一个path的命令,移除客户请求时使用的path前缀
[root@master1 ~]#kubectl create ingress demoapp-nginx --rule="web.wu.com/demoapp(/|$)(.*)"=demoapp:80 --rule="web.wu.com/nginx(/|$)(.*)"=nginx:80 --class=nginx --annotation nginx.ingress.kubernetes.io/rewrite-target='/$2' -n dev --dry-run=client -o yaml > ingress-simple-fanout.yaml
#默认这么写pathType为Exact,Exact不支持正则表达式,会报错
#需要手动指定pathType为ImplementationSpecific,表示由具体的Ingress Controller决定如何处理路径(NGINX 支持正则表达式)
[root@master1 ~]#kubectl apply -f ingress-simple-fanout.yaml
[root@rocky8 ~]#curl web.wu.com/demoapp
iKubernetes demoapp v1.0 !! ClientIP: 10.244.2.52, ServerName: demoapp-7c58cd6bb-k7w4j, ServerIP: 10.244.2.53!
[root@rocky8 ~]#curl web.wu.com/demoapp/version
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
#client --> web.wu.com/demoapp/hostname --> demoapp:80/hostname
[root@rocky8 ~]#curl web.wu.com/demoapp/hostname
ServerName: demoapp-7c58cd6bb-k7w4j
3.2.2 Name based virtual hosting
为每个应用使用一个专有的主机名,并基于这些名称完成不同应用间的流量转发
- 每个FQDN对应于Ingress Controller上的一个虚拟主机的定义
- 同一组内的应用的流量,由Ingress Controller根据调度算法完成请求调度
基于FQDN名称代理不同应用的请求时,需要事先准备好多个域名,且确保对这些域名的解析能够到达Ingress Controller
[root@master1 ~]#kubectl create ingress demoapp-nginx --rule="demoapp.wu.com/(.*)"=demoapp:80 --rule="nginx.wu.com/(.*)"=nginx:80 --class=nginx --annotation nginx.ingress.kubernetes.io/rewrite-target='/$1' -n dev --dry-run=client -o yaml > ingress-name-based-virtual-hosting.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
creationTimestamp: null
name: demoapp-nginx
namespace: dev
spec:
ingressClassName: nginx
rules:
- host: demoapp.wu.com
http:
paths:
- backend:
service:
name: demoapp
port:
number: 80
path: /(.*)
pathType: Exact #改成ImplementationSpecific
- host: nginx.wu.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /(.*)
pathType: Exact
status:
loadBalancer: {}
[root@rocky8 ~]#vim /etc/hosts
10.0.0.52 demoapp.wu.com nginx.wu.com
[root@rocky8 ~]#curl demoapp.wu.com
iKubernetes demoapp v1.0 !! ClientIP: 10.244.2.52, ServerName: demoapp-7c58cd6bb-h6vqs, ServerIP: 10.244.1.57!
[root@rocky8 ~]#curl demoapp.wu.com/hostname
ServerName: demoapp-7c58cd6bb-k7w4j
[root@rocky8 ~]#curl nginx.wu.com
<title>Welcome to nginx!</title>
[root@rocky8 ~]#curl nginx.wu.com/version
<head><title>404 Not Found</title></head>
3.2.3 TLS
Ingress也可以提供TLS通信机制,但仅限于443/TCP端口
- 若TLS配置部分指定了不同的主机,则它们会根据通过SNI TLS扩展指定的主机名
- 前提:Ingress控制器支持SNI在同一端口上复用
- TLS Secret必须包含名为tls.crt和 的密钥tls.key,它们分别含有TLS的证书和私钥
基于TLS的Ingress要求事先准备好专用的 “kubernetes.io/tls”
类型的Secret对象
启用tls后,该域名下的所有URI默认为强制将http请求跳转至https,若不希望使用该功能,可以使用如下注解选项:--annotation nginx.ingress.kubernetes.io/ssl-redirect=false
[root@master1 wordpress]#kubectl create namespace blog
[root@master1 wordpress]#kubectl apply -f mysql-ephemeral/ -n blog
[root@master1 wordpress]#kubectl apply -f wordpress-apache-ephemeral/ -n blog
#创建私钥
[root@master1 wordpress]#(umask 077; openssl genrsa -out blog.wu.com.key 2048)
#创建自签名证书
[root@master1 wordpress]#openssl req -new -x509 -key blog.wu.com.key -out blog.wu.com.crt -subj /C=CN/ST=Beijing/L=Beijing/O=DevOps/CN=blog.wu.com
[root@master1 wordpress]#kubectl create secret tls blog.wu.com --cert=./blog.wu.com.crt --key=./blog.wu.com.key -n blog
#创建常规的虚拟主机代理规则,同时将该主机定义为TLS类型
#tls指定的是用来配置https主机的证书和私钥的名字,即secret.tls的名字
[root@master1 wordpress]#kubectl create ingress wordpress --rule='blog.wu.com/*=wordpress:80,tls=blog.wu.com' --class=nginx -n blog
#做好域名解析后,浏览器访问blog.wu.com
3.3 Canary规则
Ingress Nginx Annotations支持的Canary规则
nginx.ingress.kubernetes.io/canary-by-header
:基于该Annotation中指定Request Header进行流量切分,适用于灰度发布以及A/B测试- 在请求报文中,若存在该Header且其值为always时,请求将会被发送到Canary版本
- 若存在该Header且其值为never时,请求将不会被发送至Canary版本
- 对于任何其它值,将忽略该Annotation指定的Header,并通过优先级将请求与其他金丝雀规则进行优先级的比较
nginx.ingress.kubernetes.io/canary-by-header-value
:基于该Annotation中指定的Request Header的值进行流量切分,标头名称则由前一个Annotation(nginx.ingress.kubernetes.io/canary-by-header)进行指定- 请求报文中存在指定的标头,且其值与该Annotation的值匹配时,它将被路由到Canary版本
- 对于任何其它值,将忽略该Annotation
nginx.ingress.kubernetes.io/canary-by-header-pattern
- 同canary-by-header-value的功能类似,但该Annotation基于正则表达式匹配Request Header的值
- 若该Annotation与canary-by-header-value同时存在,则该Annotation会被忽略
nginx.ingress.kubernetes.io/canary-weight
:基于服务权重进行流量切分,适用于蓝绿部署或灰度发布,权重范围0 - 100按百分比将请求路由到Canary Ingress中指定的服务- 权重为 0 意味着该金丝雀规则不会向Canary入口的服务发送任何请求
- 权重为100意味着所有请求都将被发送到 Canary 入口
nginx.ingress.kubernetes.io/canary-by-cookie
:基于 cookie 的流量切分,适用于灰度发布与 A/B 测试- cookie的值设置为always时,它将被路由到Canary入口
- cookie的值设置为 never时,请求不会被发送到Canary入口
- 对于任何其他值,将忽略 cookie 并将请求与其他金丝雀规则进行优先级的比较
规则的应用次序
- Canary规则会按特定的次序进行评估
- 次序:canary-by-header -> canary-by-cookie -> canary-weight
Canary规则策略
- 基于服务权重的流量切分:假如在生产上已经运行了A应用对外提供服务,此时开发修复了一些Bug,需要发布A1版本将其上线,但是我们又不希望直接的将所有流量接入到新的A1版本,而是希望将10%的流量进入到A1中,待A1稳定后,才会将所有流量接入进来,再下线原来的A版本。
- 基于用户请求头Header的流量切分:由于基于权重的发布场景比较粗糙,无法限制具体的用户访问行为。我们有时候会有这样的需求,比如我们有北京、上海、深圳这三个地区的用户,已经有A版本的应用为这三个地区提供服务,由于更新了需求,我们需要发布A1应用,但是我们不想所有地区都访问A1应用,而是希望只有深圳的用户可以访问,待深圳地区反馈没问题后,才开放其他地区。
3.4 Ingress和LoadBalancer的区别
特性 | Ingress | LoadBalancer |
---|---|---|
定义 | 集群内外部流量的路由入口,基于URL路径和主机名路由流量 | 云负载均衡器,自动为Service创建外部负载均衡器 |
使用场景 | 微服务架构、基于URL路由的流量管理、集群内多个服务共用入口 | 简单的将服务暴露到外部网络,提供公网访问 |
支持的协议 | HTTP/HTTPS | HTTP/HTTPS 或 TCP/UDP(依赖云提供商的负载均衡器) |
负载均衡 | 通过Ingress Controller(如NGINX)提供 | 云服务商自动创建的负载均衡器,流量转发到NodePort或TargetPort |
SSL/TLS 支持 | ✅ 支持SSL/TLS配置,提供HTTPS加密 | ✅ 需要配置SSL证书(通常由云提供商管理) |
是否依赖外部云平台 | ❌ 不依赖云平台,集群内部处理 | ✅ 依赖云平台的负载均衡器(如AWS ELB, GCP L4/L7等) |
成本 | ❌ 无额外成本(Kubernetes原生功能) | 💸 会根据云服务商收费 |
扩展性与灵活性 | ✅ 可以在Ingress规则中灵活配置路由、路径重写、重定向 | ✅ 基本上由云服务商管理,适合简单的服务暴露需求 |