五、K8s服务发布
文章目录
- 五、K8s服务发布
-
-
- 1、服务发布场景
- 2、K8s服务发布:东西流量管理Service
- 3、K8s服务发布:南北流量管理Ingress
-
1、服务发布场景
1.1 服务发布分类总结
- 用户访问
- 服务间访问
- 基础组件访问
1.2 K8s服务发布架构-无注册中心
1.3 K8s服务发布架构-有注册中心
1.4 Service是如何代理Pod的
Label:K8s任何资源都有标签的概念,用于给同类的资源进行分组。比如一个集群有很多个节点,可以根据不同的地域、网段、节点类型进行分组,方便管理。
Selector:(标签选择器)可以通过同一类资源的不同标签进行精确的查询数据。比如想要查询某个命名空间下所有具有app=payment标签的Pod,可以使用Selector进行过滤。
1.5 什么是Service
Service是K8s开箱即用的一个用于提供负载均衡、服务发现等能力的资源。
Service为Pod提供了一个抽象层,将一组具有相同功能的Pod抽象为一个逻辑上的服务。无论匹配的Pod如何变化,比如重启、迁扩缩容等,Service都能保持一个稳定的访问接口,从而让我们无需关心服务所在的具体位置、IP等细节。
- 主要功能:
- 服务之间的服务发现
- 代理一个或一组Pod
- 代理IP或域名
1.6 什么是Endpoints
Endpoints可以理解为Service的一部分,主要用于记录Service对应的所有Pod的IP地址和端口信息。Service通过Endpoints来找到并访问后端的Pod。
Endpoints资源记录了Pod的IP地址和端口列表,当后端Pod产生变化时,K8s的控制器会更新Endpoints里面的配置信息,从而保证Service能够正确的路由到关联且正常运行的Pod中。
只有当Service名称和端口信息与Endpoints一样时,Service和Endpoints才会自动建立关联。
2、K8s服务发布:东西流量管理Service
# 创建一个测试资源:
[root@k8s-master01 ~]# kubectl create deploy counter --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/counter:v1
[root@k8s-master01 ~]# kubectl create deploy nginx --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
2.1 Label 和 Selector
2.1.1 添加标签(Label)
# 指定单个资源添加标签:
[root@k8s-master01 ~]# kubectl label deploy nginx version=1
# 查看标签
[root@k8s-master01 ~]# kubectl get deploy nginx --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
nginx 1/1 1 1 58s app=nginx,version=1
# 指定多个资源添加标签:
[root@k8s-master01 ~]# kubectl label deploy --all test=true
[root@k8s-master01 ~]# kubectl get deploy --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
counter 1/1 1 1 2m9s app=counter,test=true
nginx 1/1 1 1 2m3s app=nginx,test=true,version=1
# 根据已有标签过滤之后再添加标签:
[root@k8s-master01 ~]# kubectl label deploy -l app=counter svc=true
[root@k8s-master01 ~]# kubectl get deploy --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
counter 1/1 1 1 3m56s app=counter,svc=true,test=true
nginx 1/1 1 1 3m50s app=nginx,test=true,version=1
# 同时添加多个标签:
[root@k8s-master01 ~]# kubectl label deploy -l app=nginx a=b c=d
[root@k8s-master01 ~]# kubectl get deploy --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
counter 1/1 1 1 5m35s app=counter,svc=true,test=true
nginx 1/1 1 1 5m29s a=b,app=nginx,c=d,test=true,version=1
2.1.2 修改标签(Label)
# 已经存在的标签名,不允许直接进行修改:
# 如需修改,可以使用--overwrite 参数:
[root@k8s-master01 ~]# kubectl label deploy -l app=nginx version=2 --overwrit
[root@k8s-master01 ~]# kubectl get deploy --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
counter 1/1 1 1 8m6s app=counter,svc=true,test=true
nginx 1/1 1 1 8m a=b,app=nginx,c=d,test=true,version=2
2.1.3 删除标签(Label)
# 删除 Key 为 version 的标签:
[root@k8s-master01 ~]# kubectl label deploy -l app=nginx a- c-
[root@k8s-master01 ~]# kubectl get deploy --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
counter 1/1 1 1 12m app=counter,svc=true,test=true
nginx 1/1 1 1 12m app=nginx,test=true,version=2
2.1.4 Selector 选择器
# 首先使用--show-labels 查看指定资源目前已有的 Label
[root@k8s-master01 ~]# kubectl get deploy --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
counter 1/1 1 1 24m app=counter,svc=true,test=true
nginx 1/1 1 1 23m app=nginx,test=true,version=2
# 查询 app 为 nginx 的 Deployment
[root@k8s-master01 ~]# kubectl get deploy -l app=nginx --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
nginx 1/1 1 1 24m app=nginx,test=true,version=2
# 查询 app 为 nginx 或 counter 的 Deployment
[root@k8s-master01 ~]# kubectl get deploy -l 'app in (nginx,counter)' --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
counter 1/1 1 1 26m app=counter,svc=true,test=true
nginx 1/1 1 1 26m app=nginx,test=true,version=2
# 查询 app 为 nginx 或 counter 但不包括 version=v2 的 Deployment
[root@k8s-master01 ~]# kubectl get deploy -l 'app in (nginx,counter)' -l version!=2 --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
counter 1/1 1 1 27m app=counter,svc=true,test=true
2.1.5 使用案例
公司与 xx 银行有一条专属的高速光纤通道,此通道只能与 192.168.7.0 网段进行通信,因此只能将与 xx 银行通信的应用部署到 192.168.7.0 网段所在的节点上,此时可以对节点添加 Label:
# 查看pod部署情况(都在node01节点上面)
[root@k8s-master01 ~]# kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
....
nginx-5f58c9c676-kvhb7 1/1 Running 0 32m 172.16.85.224 k8s-node01 <none> <none>
# 为node02节点打上标签
[root@k8s-master01 ~]# kubectl label node k8s-node02 region=subnet7
[root@k8s-master01 ~]# kubectl get node -l region=subnet7
NAME STATUS ROLES AGE VERSION
k8s-node02 Ready <none> 9d v1.32.5
# 最后在 Deployment 或其他控制器中指定将 Pod 部署到该节点
[root@k8s-master01 ~]# kubectl edit deploy nginx
...
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
nodeSelector:
region: subnet7 # 只会部署属于这个标签的节点上
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
imagePullPolicy: IfNotPresent
...
# 查看部署节点
[root@k8s-master01 ~]# kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
....
nginx-654b5496df-cv47v 1/1 Running 0 15s 172.16.58.241 k8s-node02 <none> <none>
2.2 定义 Service
# 创建 Service 可以使用 expose 命令
[root@k8s-master01 ~]# kubectl expose deploy nginx --port 80
[root@k8s-master01 ~]# kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.98.222.5 <none> 80/TCP 7s
# 查看其yaml配置
[root@k8s-master01 ~]# kubectl get svc nginx -oyaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2025-06-12T13:01:11Z"
labels:
app: nginx
test: "true"
version: "2"
name: nginx
namespace: default
resourceVersion: "205211"
uid: ea457597-c462-4206-87e0-554920fd7a7d
spec:
clusterIP: 10.98.222.5 # Service 的 IP,不需要手动指定
clusterIPs:
- 10.98.222.5
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports: # Service 端口配置
- port: 80 # Service 端口
protocol: TCP # 代理协议(能够支持 TCP、UDP、SCTP 等协议,默认为 TCP 协议)
targetPort: 80 # 目标端口,程序端口
selector: # 代理到哪些 Pod
app: nginx
sessionAffinity: None # 会话保持配置
type: ClusterIP # Service 类型
status:
loadBalancer: {}
Service 支持将一个接收端口映射到任意的 targetPort,如果 targetPort 为空,targetPort 将被设置为与 Port 字段相同的值。targetPort 可以设置为一个字符串,引用 Pod 的一个端口的名称,这样的话即使更改了 Pod 的端口,也不会对 Service 的访问造成影响。
# 创建服务
[root@k8s-master01 ~]# kubectl create deploy nginx --replicas=3 --port=80 --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:stable --dry-run=client -oyaml > nginx.yaml
[root@k8s-master01 ~]# cat nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:stable
name: nginx
ports:
- containerPort: 80
resources: {}
status: {}
[root@k8s-master01 ~]# kubectl create -f nginx.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-b5d5f997c-6v2w7 1/1 Running 0 5s
nginx-b5d5f997c-v9scx 1/1 Running 0 5s
nginx-b5d5f997c-xpbf9 1/1 Running 0 5s
# Service 名访问测试:
[root@k8s-master01 ~]# kubectl create deploy cluster-test --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/debug-tools:latest -- sleep 3600
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
cluster-test-78df88bdc8-b55mb 1/1 Running 0 5m37s
[root@k8s-master01 ~]# kubectl exec -ti cluster-test-78df88bdc8-b55mb -- bash
(13:57 cluster-test-78df88bdc8-b55mb:/) nslookup nginx
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx.default.svc.cluster.local
Address: 10.98.222.5
(13:57 cluster-test-78df88bdc8-b55mb:/) curl nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
2.3 Service 类型
- Kubernetes Service 主要包括以下几种类型:
- ClusterIP:在集群内部使用,默认值,只能从集群中访问。
- NodePort:在所有安装了 Kube-Proxy 的节点上打开一个端口,此端口可以代理至后端Pod,可以通过 NodePort 从集群外部访问集群内的服务,格式为 NodeIP:NodePort。
- ExternalName:通过返回定义的 CNAME 别名,没有设置任何类型的代理,需要 1.7 或更高版本 kube-dns 支持。
- LoadBalancer:使用云提供商的负载均衡器公开服务,成本较高。
2.4 NodePort 类型
如果将 Service 的 type 字段设置为 NodePort,则 Kubernetes 将从 --service-node-port-range
参数指定的范围(默认为 30000-32767)中自动分配端口,也可以手动指定 NodePort,创建该 Service 后,集群每个节点都将暴露一个端口,通过某个宿主机的 IP+端口即可访问到后端的应用。
# 定义一个 NodePort 类型的 Service 格式如下:
[root@k8s-master01 ~]# kubectl get svc nginx -oyaml > nginx-nodeport.yaml
[root@k8s-master01 ~]# vim nginx-nodeport.yaml
[root@k8s-master01 ~]# cat nginx-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: NodePor
# 创建Service
[root@k8s-master01 ~]# kubectl create -f nginx-nodeport.yaml
[root@k8s-master01 ~]# kubectl get svc nginx-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-nodeport NodePort 10.107.186.200 <none> 80:31538/TCP 11s
# 检查对外访问
[root@k8s-master01 ~]# curl 192.168.200.50:31538
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
浏览器访问
2.5 ExternalName 代理域名
2.5.1 基本使用
ExternalName Service 是 Service 的特例,它没有 Selector,也没有定义任何端口和 Endpoint,它通过返回该外部服务的别名来提供服务,和域名解析的 CNAME 类似。
比如可以定义一个 Service,后端设置为一个外部域名,这样通过 Service 的名称即可访问到该域名。使用 nslookup 解析以下文件定义的 Service,集群的 DNS 服务将返回一个值为www.taobao.com 的 CNAME 记录:
apiVersion: v1
kind: Service
metadata:
name: my-externalname
spec:
type: ExternalName
externalName: www.taobao.com
2.5.2 使用案例
使用案例:假设某个项目具备 DEV/UAT 两个环境,每个环境需要链接指定的数据库等基础组件。基础组件同样也是在 K8s 中按照不同的环境进行划分和部署,比如 DEV 环境所用的基础组件均在 basic-component-dev 命名空间下,以此类推。
为了降低配置文件的维护复杂度,准备使用 ExternalName 类型的 Service 对基础组件的连接地址进行映射,这样就可以用同名的 Service 区分不同的环境,从而降低配置文件维护的复杂度。比如配置了在同一个项目的不同环境里面都配置一个同名的 Redis Service,类型为ExternalName,并且按照不同环境指向不同的基础组件地址,这样每个项的不同环境,都可以用 Redis 这一个地址就可以访问到不同基础组件。
1、部署两个redis服务,分别在不同的命名空间(我们管这些redis叫目标服务)
# 环境准备:
# 创建 Namespace
[root@k8s-master01 ~]# kubectl create ns basic-component-dev
[root@k8s-master01 ~]# kubectl create ns basic-component-uat
[root@k8s-master01 ~]# kubectl get ns
NAME STATUS AGE
basic-component-dev Active 9s
basic-component-uat Active 4s
# 创建服务
[root@k8s-master01 ~]# kubectl create deploy redis -n basic-component-dev --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/redis:7.2.5
[root@k8s-master01 ~]# kubectl create deploy redis -n basic-component-uat --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/redis:7.2.5
[root@k8s-master01 ~]# kubectl get po -n basic-component-dev
NAME READY STATUS RESTARTS AGE
redis-564b7bcf74-qnm89 1/1 Running 0 71s
[root@k8s-master01 ~]# kubectl get po -n basic-component-uat
NAME READY STATUS RESTARTS AGE
redis-564b7bcf74-tzm99 1/1 Running 0 66s
# 创建 Service
[root@k8s-master01 ~]# kubectl expose deploy redis --port 6379 -n basic-component-dev
[root@k8s-master01 ~]# kubectl expose deploy redis --port 6379 -n basic-component-uat
[root@k8s-master01 ~]# kubectl get svc -n basic-component-dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis ClusterIP 10.104.193.4 <none> 6379/TCP 76s
[root@k8s-master01 ~]# kubectl get svc -n basic-component-uat
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis ClusterIP 10.100.159.225 <none> 6379/TCP 75s
2、随意创建一个redis服务(任意空间),并修改key值后续做测试使用(仅作连接测试使用)
# 访问测试:
# 创建一个专门用于测试的 Redis 客户端
[root@k8s-master01 ~]# kubectl create deploy redis-cli --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/redis:7.2.5
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
....
redis-cli-7d5cc47ff9-7kk7t 1/1 Running 0 4s
# 测试每个环境的 Redis 基础组件
[root@k8s-master01 ~]# kubectl exec -it redis-cli-7d5cc47ff9-7kk7t -- bash
root@redis-cli-7d5cc47ff9-7kk7t:/data# redis-cli -h redis.basic-component-dev
redis.basic-component-dev:6379> set a dev
OK
redis.basic-component-dev:6379> get a
"dev"
root@redis-cli-7d5cc47ff9-7kk7t:/data# redis-cli -h redis.basic-component-uat
redis.basic-component-uat:6379> set a uat
OK
redis.basic-component-uat:6379> get a
"uat"
3、创建两个新命名空间,并为目标服务的redis做代理(此空间下的服务叫源服务)
创建项目的两个环境:
[root@k8s-master01 ~]# kubectl create ns projecta-dev
[root@k8s-master01 ~]# kubectl create ns projecta-uat
[root@k8s-master01 ~]# kubectl get ns
NAME STATUS AGE
....
projecta-dev Active 11s
projecta-uat Active 4s
# 在每个项目的环境下,创建一个 externalName 类型的Service,用于连接到不同环境的基础组件:
[root@k8s-master01 ~]# vim externalname.yaml
[root@k8s-master01 ~]# cat externalname.yaml
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: projecta-dev
spec:
type: ExternalName
externalName: redis.basic-component-dev.svc.cluster.local
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: projecta-uat
spec:
type: ExternalName
externalName: redis.basic-component-uat.svc.cluster.local
[root@k8s-master01 ~]# kubectl create -f externalname.yaml
4、在两个源服务命名空间下分别创建一个redis服务
# 接下来在每个项目的环境下,创建两个 Redis 客户端,用于模拟需要链接 Redis 的应用程序:
[root@k8s-master01 ~]# kubectl create deploy usercenter --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/redis:7.2.5 -n projecta-dev
[root@k8s-master01 ~]# kubectl create deploy usercenter --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/redis:7.2.5 -n projecta-uat
[root@k8s-master01 ~]# kubectl get po -n projecta-dev
NAME READY STATUS RESTARTS AGE
usercenter-f5f49fcfc-8zdsb 1/1 Running 0 36s
[root@k8s-master01 ~]# kubectl get po -n projecta-uat
NAME READY STATUS RESTARTS AGE
usercenter-f5f49fcfc-j9gjz 1/1 Running 0 31s
5、分别让源空间下的redis服务(开发/测试)去访问目标服务下的redis
# 测试每个环境下的 externalName:
# 开发环境
[root@k8s-master01 ~]# kubectl exec -it usercenter-f5f49fcfc-8zdsb -n projecta-dev -- bash
root@usercenter-f5f49fcfc-8zdsb:/data# redis-cli -h redis
redis:6379> get a
"dev"
# UAT 环境
root@usercenter-f5f49fcfc-8zdsb:/data#
root@usercenter-f5f49fcfc-8zdsb:/data# exit
exit
[root@k8s-master01 ~]# kubectl exec -it usercenter-f5f49fcfc-j9gjz -n projecta-uat -- bash
root@usercenter-f5f49fcfc-j9gjz:/data# redis-cli -h redis
redis:6379> get a
"uat"
2.6 使用 Service 代理 K8s 外部服务
使用场景:
- 希望在生产环境中使用某个固定的名称而非 IP 地址访问外部的中间件服务;
- 希望 Service 指向另一个 Namespace 中或其他集群中的服务;
- 正在将工作负载转移到 Kubernetes 集群,但是一部分服务仍运行在 Kubernetes 集群之外的 backend。
[root@k8s-master01 ~]# vim baidu.yaml
[root@k8s-master01 ~]# cat baidu.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: baidu
name: baidu
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
sessionAffinity: None
type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
labels:
app: baidu
name: baidu
subsets:
- addresses:
- ip: 182.61.201.211 # 此为baidu.com真实IP
ports:
- name: http
port: 80
protocol: TCP
[root@k8s-master01 ~]# kubectl create -f baidu.yaml
Endpoint IP 地址不能是loopback(127.0.0.0/8)、link-local(169.254.0.0/16)或者 linklocal多播地址(224.0.0.0/24)。
[root@k8s-master01 ~]# kubectl get svc baidu
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
baidu ClusterIP 10.101.43.61 <none> 80/TCP 6m48s
[root@k8s-master01 ~]# kubectl get ep baidu
NAME ENDPOINTS AGE
baidu 182.61.201.211:80 6m54s
[root@k8s-master01 ~]# curl 10.101.43.61:80
<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>
访问没有 Selector 的 Service 与有 Selector 的 Service 的原理相同,通过 Service 名称即可访问,请求将被路由到用户定义的 Endpoints
2.7 多端口 Service
有的程序可能会监听多个端口,Service 也支持同时代理多个端口。比如在 K8s 中部署一个RabbitMQ,它具有两个端口,5672 是程序连接用于数据交互的接口,15672 是 RabbitMQ 管理页面的端口。
首先在 K8s 上部署一个 RabbitMQ:
[root@k8s-master01 ~]# kubectl create deploy rabbitmq --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/rabbitmq:3-management
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
rabbitmq-58479c594b-pqlpb 1/1 Running 0 2m17s
# 接下来可以创建一个 Service,把 5672 指向 Pod 的 5672,15672 指向 15672:
[root@k8s-master01 ~]# vim rabbitmq.yaml
[root@k8s-master01 ~]# cat rabbitmq.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: rabbitmq
name: rabbitmq
spec:
ports:
- name: amqp
port: 5672
protocol: TCP
targetPort: 5672
- name: http
port: 15672
protocol: TCP
targetPort: 15672
selector:
app: rabbitmq
type: NodePort
# 创建service
[root@k8s-master01 ~]# kubectl create -f rabbitmq.yaml
[root@k8s-master01 ~]# kubectl get svc rabbitmq
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
rabbitmq NodePort 10.97.66.74 <none> 5672:31856/TCP,15672:31239/TCP 7m51s
浏览器登录
2.8 会话保持
# K8s 的 Service 支持会话保持,但是目前仅支持基于客户端 IP 的会话保持:
[root@k8s-master01 ~]# kubectl edit svc nginx
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
sessionAffinity: ClientIP # ClientIP:配置基于 IP 的会话保持,None:不开启会话保持
sessionAffinityConfig: # 会话保持配置
clientIP:
timeoutSeconds: 10800 # 会话保持配置时间
type: ClusterIP
2.9 Headless Service
2.9.1 定义
- Headless Service 是 Kubernetes 中一种特殊类型的 Service,它会直接暴露 Pod 的 IP 地址和DNS 记录给客户端,适用于有状态应用的服务发现和负载均衡以及需要直接访问 Pod IP 的应用场景。
- Headless Service 不需要分配 ClusterIP,而是通过 DNS 记录直接返回 Pod 的 IP 地址,所以和普通 Service 最大的区别就是使用 nslookup 解析一个 Headless Service 返回的是 Pod IP, 而普通 Service 返回的是 Service 的 IP。
2.9.2 使用场景
- 有状态应用的服务发现和负载均衡:有状态应用(如数据库、消息队列等)通常需要为每个 Pod 分配一个唯一的标识符(如 Pod 名称或 IP 地址),以便其他服务或其他节点可以连接到某个实例。Headless Service 可以满足这一需求,通过直接暴露 Pod 的 IP 地址和 DNS 记录,实现服务发现和负载均衡。
- 需要直接访问 Pod IP 的应用:在某些情况下,客户端可能需要直接访问 Pod 的 IP 地址,而不需要通过 Service 的负载均衡机制,此时也可以通过 Headless Service 实现。
- 分布式系统:在分布式系统中,各个节点之间需要直接通信,并且每个节点都有自己的身份和状态。Headless Service 可以为每个节点分配一个唯一的 DNS 实体名称,支持节点之间的直接交互和负载均衡
2.9.3 工作原理
当创建一个 Headless Service 时,Kubernetes 会执行以下操作:
- 创建 DNS 记录:为每个 Pod 创建一个 DNS 记录,该记录的名称基于 Service 名称、Pod名称和命名空间定义,格式为
<pod-name>.<service-name>.<namespace>.svc.cluster.local
。- 暴露 Pod IP:客户端可以通过查询 DNS 记录获取 Pod 的 IP 地址,并直接访问某个 Pod。比如创建一个名为 my-headless-service 的 Headless Service,这个 Service 匹配了 app=myapp 标签的 Pod,该服务具有三个副本,每个副本的名字是 pod-0、pod-1 和pod-2。此时可以通过如下 DNS 名字进行访问:
- pod-0.my-headless-service.default.svc.cluster.local
- pod-1.my-headless-service.default.svc.cluster.local
- pod-2.my-headless-service.default.svc.cluster.local
2.9.4 Headless Service 使用
# 创建一个 StatefulSet 和 Headless Service:
[root@k8s-master01 ~]# vim headless.yaml
[root@k8s-master01 ~]# cat headless.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: headless
name: headless
spec:
ports:
- port: 80
clusterIP: None
selector:
app: headless
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: headless
spec:
serviceName: "headless"
replicas: 3
selector:
matchLabels:
app: headless
template:
metadata:
labels:
app: headless
spec:
containers:
- image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:stable
name: headless
ports:
- containerPort: 80
# 创建
[root@k8s-master01 ~]# kubectl create -f headless.yaml
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
cluster-test-78df88bdc8-b55mb 1/1 Running 5 (4m7s ago) 5h8m
headless-0 1/1 Running 0 13s
headless-1 1/1 Running 0 11s
headless-2 1/1 Running 0 9s
# 发现没有分配IP
[root@k8s-master01 ~]# kubectl get svc headless
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
headless ClusterIP None <none> 80/TCP 23s
# 首先查看下三个pod分别分配的地址
[root@k8s-master01 ~]# kubectl get po -l app=headless -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
headless-0 1/1 Running 0 3m23s 172.16.85.235 k8s-node01 <none> <none>
headless-1 1/1 Running 0 3m21s 172.16.58.243 k8s-node02 <none> <none>
headless-2 1/1 Running 0 3m19s 172.16.85.236 k8s-node01 <none> <none>
# 随意进入一个pod下
[root@k8s-master01 ~]# kubectl exec -it cluster-test-78df88bdc8-b55mb -- bash
# 可以直连IP地址
(19:04 cluster-test-78df88bdc8-b55mb:/) nslookup headless-0.headless
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: headless-0.headless.default.svc.cluster.local
Address: 172.16.85.235
(19:05 cluster-test-78df88bdc8-b55mb:/) nslookup headless-1.headless
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: headless-1.headless.default.svc.cluster.local
Address: 172.16.58.243
(19:06 cluster-test-78df88bdc8-b55mb:/) nslookup headless-2.headless
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: headless-2.headless.default.svc.cluster.local
Address: 172.16.85.236
# 负载均衡
(19:06 cluster-test-78df88bdc8-b55mb:/) nslookup headless
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: headless.default.svc.cluster.local
Address: 172.16.85.235
Name: headless.default.svc.cluster.local
Address: 172.16.58.243
Name: headless.default.svc.cluster.local
Address: 172.16.85.236
2.10 Service 代理模式
2.10.1 Iptables 代理模式
Iptables 是 Linux 原生提供的一个功能强大的防火墙工具,可以用来设置、维护和检查 IPv4数据包,并且支持源目地址转换等规则。在 iptables 代理模式下,kube-proxy 通过监听 Kubernetes API Server 中 Service 和 Endpoint 对象的变化,动态地更新节点上的 iptables 规则,以实现请求的转发。
工作流程:
- 当 Service 被创建或更新时,kube-proxy 会读取 Service 和 Endpoint 对象的信息,并生成相应的 iptables 规则
- 这些 iptables 规则被添加到内核的 netfilter 处理链中,以拦截和转发目标为 Service IP 地址的流量
- 当客户端访问 Service 的 IP 地址时,iptables 规则会将流量随机重定向到后端的一个或多个Pod
优点与缺点:
- 优点:iptables 是 Linux 内核的一部分,性能稳定、可靠,iptables 规则易于理解和维护,功能多。
- 缺点:随着 Service 数量的增加,iptables 规则的数量也会急剧增加,进而导致性能下降。iptables的更新操作可能会暂时锁定整个 iptables 规则表,影响网络性能。
2.10.2 IPVS 代理模式
IPVS(IP Virtual Server)是一种基于内核的负载均衡器,提供了比 iptables 更高的转发性能。在 IPVS 代理模式下,kube-proxy 通过配置 IPVS 负载均衡器规则来代替使用 iptables。IPVS 使用更高效的数据结构(如 Hash 表)来存储和查找规则,可以在大量 Service 的情况下也能保持高性能。
工作流程:
- 当 Service 被创建或更新时,kube-proxy 会读取 Service 和 Endpoint 对象的信息,并配置IPVS 负载均衡策略
- IPVS 负载均衡器会根据配置的调度算法(如轮询、最少连接等)将请求转发到后端的一个或多个 Pod 上
- 当客户端访问 Service 的 IP 地址时,请求会直接被 IPVS 处理并转发到后端 Pod
优点与缺点:
- 优点:IPVS 专为负载均衡设计,性能优于 iptables。并且支持多种调度算法,可以根据实际需求选择合适的算法,同时 IPVS 的更新操作对性能的影响较小
- 缺点:在某些情况下,IPVS 可能需要依赖 iptables 来实现一些额外的功能(如源地址 NAT)
IPVS 负载均衡算法:
- 轮询:rr,按顺序轮流将请求转发到后端的各个 Pod 上,实现请求的均匀分配
- 最少链接:lc,将新的请求转发到当前连接数最少的 Pod 上,以平衡各 Pod 的负载
- 源地址哈希:sh,根据请求的源 IP 地址进行哈希计算,将相同源地址的请求转发到同一个 Pod 上,实现会话保持
- 目的地址哈希:dh,根据请求的目的 IP 地址(即 Service 的 Cluster IP)和端口进行哈希计算,选择后端 Pod
- 无需队列等待:nq,如果后端 Pod 的队列为空,则直接选择该 Pod;如果所有 Pod 的队列都非空,则采用其他策略(如轮询或最少连接)来选择 Pod
- 最短期望延迟:sed,考虑 Pod 的当前连接数和连接请求的平均处理时间,选择预计处理时间最短的 Pod 来接收新请求
3、K8s服务发布:南北流量管理Ingress
3.1 什么是Ingress
Ingress为K8s集群中的服务提供了一个统一的入口,可以提供负载均衡、SSL终止和基于名称(域名)的虚拟主机、应用的灰度发布等功能,在生产环境中常用的Ingress控制器有Treafik、Nginx、HAProxy、Istio等。
相对于Service,Ingress工作在七层(部分Ingress控制支持2和6层),所以可以支持HTTP协议的代理,也就是基于域名的匹配规则。
K8s使用域名发布服务的流程
Ingress Controller生产级高可用架构
3.2 Ingress Controller 生产级高可用推荐安装方式
官方 安装文档:
# 部分配置
kind: DaemonSet
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
nodeSelector:
kubernetes.io/os: linux
ingress: "true"
# 修改节点标签,配置为 ingress 专用节点
[root@k8s-master01 ~]# kubectl label node k8s-node02 ingress=true
# 创建 Ingress
[root@k8s-master01 ~]# kubectl create -f ingress-nginx-daemonset.yaml
# 检查pod
[root@k8s-master01 ~]# kubectl get po -n ingress-nginx
NAME READY STATUS RESTARTS AGE
....
ingress-nginx-controller-rv2xj 1/1 Running 0 3m54s
# 部署后可以在 node02 节点上看到 nginx 进程
[root@k8s-node02 ~]# netstat -lntp | grep nginx
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 1159164/ngin: mast
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1159164/ngin: mast
tcp 0 0 127.0.0.1:10245 0.0.0.0:* LISTEN 1159101/ngin-ingre
tcp 0 0 127.0.0.1:10247 0.0.0.0:* LISTEN 1159164/ngin: mast
tcp 0 0 127.0.0.1:10246 0.0.0.0:* LISTEN 1159164/ngin: mast
tcp 0 0 0.0.0.0:8181 0.0.0.0:* LISTEN 1159164/ngin: mast
tcp6 0 0 :::443 :::* LISTEN 1159164/ngin: mast
tcp6 0 0 :::10254 :::* LISTEN 1159101/ngin-ingre
tcp6 0 0 :::80 :::* LISTEN 1159164/ngin: mast
tcp6 0 0 :::8443 :::* LISTEN 1159101/ngin-ingre
tcp6 0 0 :::8181 :::* LISTEN 1159164/ngin: mast
3.3 Ingress 资源定义
apiVersion: networking.k8s.io/v1 # k8s >= 1.22 必须 v1
kind: Ingress
metadata:
name: nginx-ingress
spec:
ingressClassName: nginx # 指定该 Ingress 被哪个 Controller 解析
rules: # 定义路由匹配规则,可以配置多个
- host: nginx.test.com # 定义域名
http:
paths: # 详细的路由配置,可以配置多个
- backend: # 指定该路由的后端
service:
name: nginx
port:
number: 80
path: / # 指定 PATH
pathType: ImplementationSpecific # 指定匹配规则
- pathType:路径的匹配方式,目前有 ImplementationSpecific、Exact 和 Prefix 方式
- Exact:精确匹配,比如配置的 path 为/bar,那么/bar/将不能被路由;
- Prefix:前缀匹配,基于以 / 分隔的 URL 路径。比如 path 为/abc,可以匹配到/abc/bbb等,比较常用的配置;
- ImplementationSpecific:这种类型的路由匹配根据 Ingress Controller 来实现,可以当做一个单独的类型,也可以当做 Prefix 和 Exact。ImplementationSpecific 是 1.18 版本引入 Prefix 和 Exact 的默认配置
3.4 Ingress 入门
3.4.1 使用域名发布 K8s 的服务
# 使用域名发布 K8s 的服务
# 创建一个 web 服务:
[root@k8s-master01 ~]# kubectl create deploy nginx --replicas=3 --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-5f58c9c676-ctgpp 1/1 Running 0 4s
nginx-5f58c9c676-ffc26 1/1 Running 0 4s
nginx-5f58c9c676-ns9q5 1/1 Running 0 4s
# 暴露服务:
[root@k8s-master01 ~]# kubectl expose deploy nginx --port 80
[root@k8s-master01 ~]# kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.102.199.81 <none> 80/TCP 13s
# 创建 Ingress:
[root@k8s-master01 ~]# vim web-ingress.yaml
[root@k8s-master01 ~]# cat web-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
spec:
ingressClassName: nginx
rules:
- host: nginx.test.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /
pathType: ImplementationSpecific
[root@k8s-master01 ~]# kubectl create -f web-ingress.yaml
[root@k8s-master01 ~]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx-ingress nginx nginx.test.com 192.168.200.52 80 39s
# 将域名IP解析到hosts文件当中
[root@k8s-master01 ~]# echo "192.168.200.52 nginx.test.com" >> /etc/hosts
# 访问测试
[root@k8s-master01 ~]# curl nginx.test.com
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
3.4.2 Ingress 特例:不配置域名发布服务(不推荐)
# 创建 Ingress:
[root@k8s-master01 ~]# vim ingress-no-host.yaml
[root@k8s-master01 ~]# cat ingress-no-host.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress-no-host
spec:
ingressClassName: nginx
rules: # 这里就把域名去掉了,其他不变
- http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /no-host # 测试路径
pathType: ImplementationSpecific
[root@k8s-master01 ~]# kubectl create -f ingress-no-host.yaml
[root@k8s-master01 ~]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx-ingress nginx nginx.test.com 192.168.200.52 80 16m
nginx-ingress-no-host nginx * 80 9s
# 直接访问不通
[root@k8s-master01 ~]# curl 192.168.200.52
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
# 这个访问不同是因为路径是瞎写的
[root@k8s-master01 ~]# curl 192.168.200.52/no-host
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.15.12</center> # 出现了版本号,说明是可以被Controller路由的
</body>
</html>
3.5 Ingress 实战
3.5.1 配置练习环境
# 创建一个用于学习 Ingress 的 Namespace,之后所有的操作都在此 Namespace 进行:
[root@k8s-master01 ~]# kubectl create ns study-ingress
[root@k8s-master01 ~]# kubectl get ns study-ingress
NAME STATUS AGE
study-ingress Active 9s
# 创建一个 Nginx 模拟 Web 服务:
[root@k8s-master01 ~]# kubectl create deploy nginx --replicas=3 --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15 -n study-ingress
[root@k8s-master01 ~]# kubectl get po -n study-ingress
NAME READY STATUS RESTARTS AGE
nginx-5f58c9c676-fkwrk 1/1 Running 0 64s
nginx-5f58c9c676-kkb75 1/1 Running 0 64s
nginx-5f58c9c676-wj9r6 1/1 Running 0 64s
# 创建 Service:
[root@k8s-master01 ~]# kubectl expose deploy nginx --port 80 -n study-ingress
[root@k8s-master01 ~]# kubectl get svc -n study-ingress
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.96.210.213 <none> 80/TCP 6s
3.5.2 使用 HTTPS 发布服务
生产环境对外的服务,一般需要配置 https 协议,使用 Ingress 也可以非常方便的添加 https的证书。
由于我们是学习环境,并没有权威证书,所以需要使用 OpenSSL 生成一个测试证书。如果
是生产环境,证书为在第三方公司购买的证书,无需自行生成:
# 创建证书
[root@k8s-master01 ~]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginx.test.com"
[root@k8s-master01 ~]# kubectl create secret tls ca-secret --cert=tls.crt --key=tls.key -n study-ingress
[root@k8s-master01 ~]# kubectl get secret -n study-ingress
NAME TYPE DATA AGE
ca-secret kubernetes.io/tls 2 13s
# 配置 Ingress 添加 TLS 配置:
[root@k8s-master01 ~]# vim ingress-ssl.yaml
[root@k8s-master01 ~]# cat ingress-ssl.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: https-ingress
namespace: study-ingress
spec:
ingressClassName: nginx # ingress class 的名字
rules:
- host: nginx.test.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /
pathType: ImplementationSpecific
tls:
- hosts: # 证书所授权的域名列表
- nginx.test.com
secretName: ca-secret # 证书的 Secret 名字
[root@k8s-master01 ~]# kubectl create -f ingress-ssl.yaml
[root@k8s-master01 ~]# kubectl get ingress -n study-ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
https-ingress nginx nginx.test.com 192.168.200.52 80, 443 32s
# 访问测试
[root@k8s-master01 ~]# curl http://nginx.test.com -I
HTTP/1.1 308 Permanent Redirect
Date: Thu, 12 Jun 2025 23:48:24 GMT
Content-Type: text/html
Content-Length: 164
Connection: keep-alive
Location: https://nginx.test.com
浏览器访问(需要在电脑hosts文件做映射)
3.5.3 域名添加用户名密码认证
有些开源工具本身不提供密码认证,如果暴露出去会有很大风险,对于这类工具可以使用 Nginx 的 basic-auth 设置密码访问,具体方法如下,由于需要使用 htpasswd 工具,所以需要安装httpd:
# 安装httpd
[root@k8s-master01 ~]# yum install httpd -y
# 使用 htpasswd 创建 test 用户的密码
[root@k8s-master01 ~]# htpasswd -c auth test
New password:
Re-type new password:
Adding password for user test
# 基于之前创建的密码文件创建 Secret
[root@k8s-master01 ~]# kubectl create secret generic basic-auth --from-file=auth -n study-ingress
[root@k8s-master01 ~]# kubectl get secret -n study-ingress
NAME TYPE DATA AGE
basic-auth Opaque 1 12s
ca-secret kubernetes.io/tls 2 60m
# 创建包含密码认证的 Ingress
[root@k8s-master01 ~]# vim ingress-auth.yaml
[root@k8s-master01 ~]# cat ingress-auth.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auth-ingress
namespace: study-ingress
annotations:
# 需要密码认证的消息提醒
nginx.ingress.kubernetes.io/auth-realm: Please Input Your Username and Password
# 密码文件的 Secret 名称
nginx.ingress.kubernetes.io/auth-secret: basic-auth
# 认证类型,可以是 basic 和 digest
nginx.ingress.kubernetes.io/auth-type: basic
spec:
ingressClassName: nginx
rules:
- host: auth.test.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- auth.test.com
secretName: ca-secret
[root@k8s-master01 ~]# kubectl create -f ingress-auth.yaml
浏览器访问(需要在电脑hosts文件做映射)
3.5.4 开启会话保持
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
namespace: study-ingress
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 16m
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "route"
# 过期时间,expires 兼容旧版浏览器
nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
# 后端负载扩容后,是否需要重新分配流量,balanced: 重新分配 persistent: 保持粘
性
nginx.ingress.kubernetes.io/affinity-mode: persistent
spec:
ingressClassName: nginx
rules:
- host: nginx.test.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- nginx.test.com
secretName: ca-secret
3.5.5 配置流式返回 SSE(代理大模型服务)
# 如果后端服务需要持续的输出数据,或者需要长连接,此时需要更改请求头升级链接为长连接:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: https-ingress
namespace: study-ingress
annotations:
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
# snippet 通常用于配置一些不支持或者复杂的参数,比如配置请求头,或者逻辑控制
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header Updrade $http_updrade;
proxy_set_header Connection 'upgrade';
spec:
ingressClassName: nginx
rules:
- host: nginx.test.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- nginx.test.com
secretName: ca-secret
3.5.6 域名重定向 Redirect
在使用 Nginx 作为代理服务器时,Redirect 可用于域名的重定向,比如访问 old.com 被重定向到 new.com。Ingress 也可以实现 Redirect 功能,接下来用 nginx.redirect.com 作为旧域名,baidu.com 作为新域名进行演示:
[root@k8s-master01 ~]# vim ingress-redirect.yaml
[root@k8s-master01 ~]# cat ingress-redirect.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: redirect-ingress
namespace: study-ingress
annotations:
# 重定向到 https://www.baidu.com
nginx.ingress.kubernetes.io/permanent-redirect: https://www.baidu.com
nginx.ingress.kubernetes.io/permanent-redirect-code: "308"
spec:
ingressClassName: nginx
rules:
- host: nginx.redirect.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /
pathType: ImplementationSpecific
[root@k8s-master01 ~]# kubectl create -f ingress-redirect.yaml
[root@k8s-master01 ~]# kubectl get ingress -n study-ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
redirect-ingress nginx nginx.redirect.com 192.168.200.52 80 2m
# 使用 curl 访问域名 nginx.redirect.com,可以看到308(使用浏览器会自动跳转到百度的首页):
[root@k8s-master01 ~]# curl -I nginx.redirect.com
HTTP/1.1 308 Permanent Redirect
Date: Fri, 13 Jun 2025 02:01:56 GMT
Content-Type: text/html
Content-Length: 164
Connection: keep-alive
Location: https://www.baidu.com
3.5.7 访问地址重写 Rewrite
# 创建一个应用模拟后端服务:
[root@k8s-master01 ~]# kubectl create deploy backend-api --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:backend-api -n study-ingress
[root@k8s-master01 ~]# kubectl get po -n study-ingress
NAME READY STATUS RESTARTS AGE
backend-api-b98656967-4wsfn 1/1 Running 0 12s
# 创建 Service 暴露该应用
[root@k8s-master01 ~]# kubectl expose deploy backend-api --port 80 -n study-ingress
# 查看该 Service 的地址,并且通过/api-a 访问测试:
[root@k8s-master01 ~]# kubectl get svc -n study-ingress
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
backend-api ClusterIP 10.105.239.55 <none> 80/TCP 15s
# 配置 Ingress,假设需要配置一个/api-a 代理到该服务:
[root@k8s-master01 ~]# vim ingress-backend.yaml
[root@k8s-master01 ~]# cat ingress-backend.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: backend-api
namespace: study-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: nginx.rewrite.com
http:
paths:
- backend:
service:
name: backend-api
port:
number: 80
path: /api-a(/|$)(.*)
pathType: ImplementationSpecific
[root@k8s-master01 ~]# kubectl create -f ingress-backend.yaml
[root@k8s-master01 ~]# kubectl get ingress -n study-ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
backend-api nginx nginx.rewrite.com 192.168.200.52 80 7s
浏览器访问(需要在电脑hosts文件做映射)
3.5.8 访问速率限制
有时候可能需要限制速率以降低后端压力,或者限制单个 IP 每秒的访问速率防止攻击。此时可以使用 Nginx 的 rate limit 进行配置。
# 首先没有加速率限制,使用 ab 进行访问,Failed 为 0:
[root@k8s-master01 ~]# ab -c 10 -n 100 https://auth.test.com/ | grep requests
Complete requests: 100
Failed requests: 0
[root@k8s-master01 ~]# vim ingress-auth.yaml
[root@k8s-master01 ~]# cat ingress-auth.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auth-ingress
namespace: study-ingress
annotations:
nginx.ingress.kubernetes.io/auth-realm: Please Input Your Username and Password
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/limit-connections: "1" # 添加速率限制,限制只能有一个连接
spec:
ingressClassName: nginx
rules:
- host: auth.test.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /
pathType: ImplementationSpecific
[root@k8s-master01 ~]# kubectl replace -f ingress-auth.yaml
# 再次使用 ab 测试,Failed 为 5:
[root@k8s-master01 ~]# ab -c 10 -n 100 https://auth.test.com/ | grep requests
Complete requests: 100
Failed requests: 5
# 还有很多其它方面的限制,常用的配置如下:
#限制每秒的连接,单个 IP:
nginx.ingress.kubernetes.io/limit-rps
#限制每分钟的连接,单个 IP:
nginx.ingress.kubernetes.io/limit-rpm
#限制客户端每秒传输的字节数,单位为 K,需要开启 proxy-buffering:
nginx.ingress.kubernetes.io/limit-rate
# 速率限制白名单
nginx.ingress.kubernetes.io/limit-whitelist
3.5.9 Ingress Nginx 黑/白名单
1、配置黑名单
# 使用 nginx.ingress.kubernetes.io/denylist-source-range 添加黑名单,支持 IP、网段,多个黑名单逗号分隔:
[root@k8s-master01 ~]# cat ingress-ssl.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: https-ingress
namespace: study-ingress
annotations:
nginx.ingress.kubernetes.io/denylist-source-range: 192.168.200.50 # 添加黑名单
spec:
ingressClassName: nginx
rules:
- host: nginx.test.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- nginx.test.com
secretName: ca-secret
# 更新该 Ingress
[root@k8s-master01 ~]# kubectl replace -f ingress-ssl.yaml
# 访问测试
[root@k8s-master01 ~]# curl -I https://nginx.test.com -k
HTTP/2 403
date: Fri, 13 Jun 2025 03:17:14 GMT
content-type: text/html
content-length: 146
strict-transport-security: max-age=15724800; includeSubDomains
2、配置白名单
# 白名单表示只允许某个 IP 可以访问,直接在 yaml 文件中配置即可,比如只允许
192.168.200.50 访问,只需要添加一个 nginx.ingress.kubernetes.io/whitelist-source-range 注释即可:
[root@k8s-master01 ~]# vim ingress-ssl.yaml
[root@k8s-master01 ~]# cat ingress-ssl.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: https-ingress
namespace: study-ingress
annotations:
nginx.ingress.kubernetes.io/whitelist-source-range: 192.168.200.51 # 添加白名单
spec:
ingressClassName: nginx
rules:
- host: nginx.test.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- nginx.test.com
secretName: ca-secret
# 更新该 Ingress
[root@k8s-master01 ~]# kubectl replace -f ingress-ssl.yaml
# 白名单IP访问:
[root@k8s-node01 ~]# curl -I https://nginx.test.com -k
HTTP/2 200
date: Fri, 13 Jun 2025 03:28:54 GMT
content-type: text/html
content-length: 612
last-modified: Tue, 16 Apr 2019 13:08:19 GMT
etag: "5cb5d3c3-264"
accept-ranges: bytes
strict-transport-security: max-age=15724800; includeSubDomains
# 其他IP访问:
[root@k8s-master01 ~]# curl -I https://nginx.test.com -k
HTTP/2 403
date: Fri, 13 Jun 2025 03:28:51 GMT
content-type: text/html
content-length: 146
strict-transport-security: max-age=15724800; includeSubDomains
3、全局黑白名单
# Ingress-nginx 支持全局的黑白名单(白名单慎用),只需要在 ingress nginx 的配置文件中添加即可,添加后无需重启 Controller,加一个全局黑名单:
[root@k8s-master01 ~]# kubectl edit cm ingress-nginx-controller -n ingress-nginx
apiVersion: v1
data:
allow-snippet-annotations: "true"
denylist-source-range: 192.168.200.52
....
# 配置全局黑名单后,访问任何域名都会被拒绝:
[root@k8s-node02 ~]# curl -I https://nginx.test.com -k
HTTP/2 403
date: Fri, 13 Jun 2025 03:36:05 GMT
content-type: text/html
content-length: 146
strict-transport-security: max-age=15724800; includeSubDomains
# 添加一个全局白名单:
[root@k8s-master01 ~]# kubectl edit cm ingress-nginx-controller -n ingress-nginx
apiVersion: v1
data:
allow-snippet-annotations: "true"
whitelist-source-range: 192.168.200.50
....
# 只有白名单里面的IP是通的
[root@k8s-master01 ~]# curl -I https://nginx.test.com -k
HTTP/2 200
date: Fri, 13 Jun 2025 03:38:22 GMT
content-type: text/html
content-length: 612
last-modified: Tue, 16 Apr 2019 13:08:19 GMT
etag: "5cb5d3c3-264"
accept-ranges: bytes
strict-transport-security: max-age=15724800; includeSubDomains
# 其他非白名单在内的IP均不可访问
[root@k8s-node01 ~]# curl -I https://nginx.test.com -k
HTTP/2 403
date: Fri, 13 Jun 2025 03:38:19 GMT
content-type: text/html
content-length: 146
strict-transport-security: max-age=15724800; includeSubDomains
[root@k8s-node02 ~]# curl -I https://nginx.test.com -k
HTTP/2 403
date: Fri, 13 Jun 2025 03:38:13 GMT
content-type: text/html
content-length: 146
strict-transport-security: max-age=15724800; includeSubDomains
3.5.10 自定义错误页
每个项目在对外发布时,难免不了会有一些未知的错误,比如 404/502/503 等,为了给客户更加友好的提示,可以使用 default backend 自定义错误页
[root@k8s-master01 ~]# cat custom-default-backend.yaml
---
apiVersion: v1
kind: Service
metadata:
name: nginx-errors
labels:
app.kubernetes.io/name: nginx-errors
app.kubernetes.io/part-of: ingress-nginx
spec:
selector:
app.kubernetes.io/name: nginx-errors
app.kubernetes.io/part-of: ingress-nginx
ports:
- port: 80
targetPort: 8080
name: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-errors
labels:
app.kubernetes.io/name: nginx-errors
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: nginx-errors
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: nginx-errors
app.kubernetes.io/part-of: ingress-nginx
spec:
containers:
- name: nginx-error-server
image: crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/custom-error-pages:v1.0.1
ports:
- containerPort: 8080
# Setting the environment variable DEBUG we can see the headers sent
# by the ingress controller to the backend in the client response.
# env:
# - name: DEBUG
# value: "true"
# Mounting custom error page from configMap
# volumeMounts:
# - name: custom_error_pages
# mountPath: /www
# Mounting custom error page from configMap
# volumes:
# - name: custom_error_pages
# configMap:
# name: custom_error_pages
# items:
# - key: "404"
# path: "404.html"
# - key: "503"
# path: "503.html"
# 首先需要安装 default backend 服务:
[root@k8s-master01 ~]# kubectl create -f custom-default-backend.yaml -n ingress-nginx
[root@k8s-master01 ~]# kubectl get po -n ingress-nginx
NAME READY STATUS RESTARTS AGE
nginx-errors-5c658df8bf-f9xcz 1/1 Running 0 21s
# 更改 ingress-nginx 的启动参数,支持自定义错误页:
[root@k8s-master01 ~]# kubectl edit ds -n ingress-nginx
....
spec:
containers:
- args:
- --default-backend-service=ingress-nginx/nginx-errors
....
# 配置 ConfigMap,定义哪些错误码被重定向到自定义错误页
[root@k8s-master01 ~]# kubectl edit cm ingress-nginx-controller -n ingress-nginx
apiVersion: v1
data:
custom-http-errors: "404,502,503"
....
更新完成以后访问一个不存在的页面,比如之前定义的 nginx.test.com。访问一个不存在的页面 123,就会返回 Error Server 中的页面:
3.5.11 匹配请求头区分不同客户端
# 首先部署移动端应用:
[root@k8s-master01 ~]# kubectl create deploy phone --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:phone -n study-ingress
[root@k8s-master01 ~]# kubectl get po -n study-ingress
NAME READY STATUS RESTARTS AGE
phone-6b5ddd45c9-zm96z 1/1 Running 0 10s
# 创建手机端 Ingress:
[root@k8s-master01 ~]# vim ingress-phone.yaml
[root@k8s-master01 ~]# cat ingress-phone.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: phone
namespace: study-ingress
spec:
ingressClassName: nginx
rules:
- host: m.test.com
http:
paths:
- backend:
service:
name: phone
port:
number: 80
path: /
pathType: ImplementationSpecific
[root@k8s-master01 ~]# kubectl create -f ingress-phone.yaml
[root@k8s-master01 ~]# kubectl get ingress -n study-ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
phone nginx m.test.com 192.168.200.52 80 49s
# 部署电脑端应用:
[root@k8s-master01 ~]# kubectl create deploy laptop --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:laptop -n study-ingress
[root@k8s-master01 ~]# kubectl get po -n study-ingress
NAME READY STATUS RESTARTS AGE
laptop-7566dbdb8c-dc5w8 1/1 Running 0 17s
# 创建 Service
[root@k8s-master01 ~]# kubectl expose deploy laptop --port 80 -n study-ingress
service/laptop exposed
[root@k8s-master01 ~]# kubectl get svc -n study-ingress
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
laptop ClusterIP 10.99.239.61 <none> 80/TCP 26s
# 之后创建电脑端的 Ingress,注意 Ingress annotations 的 nginx.ingress.kubernetes.io/serversnippet 配置。Snippet 配置是专门用于一些复杂的 Nginx 配置,和 Nginx 配置通用。匹配移动端实例如下:
[root@k8s-master01 ~]# vim ingress-laptop.yaml
[root@k8s-master01 ~]# cat ingress-laptop.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: laptop
namespace: study-ingress
annotations:
nginx.ingress.kubernetes.io/server-snippet: |
set $agentflag 0;
if ($http_user_agent ~* "(Android|iPhone|WindowsPhone|UC|Kindle)" ){
set $agentflag 1;
}
if ( $agentflag = 1 ) {
return 301 http://m.test.com;
}
spec:
ingressClassName: nginx
rules:
- host: test.com
http:
paths:
- backend:
service:
name: laptop
port:
number: 80
path: /
pathType: ImplementationSpecific
[root@k8s-master01 ~]# kubectl create -f ingress-laptop.yaml
[root@k8s-master01 ~]# kubectl get ingress -n study-ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
laptop nginx test.com 192.168.200.52 80 28s
首先通过浏览器访问 test.com,可以看到页面是 Laptop:
接下来使用浏览器的开发者工具将终端类型改为 iPhone,或者直接用 iPhone 手机访问(线上业务一般配置的都有 DNS,可以直接解析域名,测试环境可能需要自己单独配置),如下图所示:
3.5.12 灰度/金丝雀/蓝绿发布
1、创建 v1 版本
# 首先创建模拟 Production(生产)环境的 Namespace 和服务:
[root@k8s-master01 ~]# kubectl create ns production
# 创建 v1 版本的应用
[root@k8s-master01 ~]# kubectl create deploy canary-v1 --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/canary:v1 -n production
[root@k8s-master01 ~]# kubectl get po -n production
NAME READY STATUS RESTARTS AGE
canary-v1-5dd7b4fbf6-b9gtk 1/1 Running 0 17s
# 建立service
[root@k8s-master01 ~]# kubectl expose deploy canary-v1 --port 8080 -n production
[root@k8s-master01 ~]# kubectl get svc -n production
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
canary-v1 ClusterIP 10.98.156.116 <none> 8080/TCP 10s
# 创建 V1 的 Ingress:
[root@k8s-master01 ~]# vim ingress-canary-v1.yaml
[root@k8s-master01 ~]# cat ingress-canary-v1.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: canary-v1
namespace: production
spec:
ingressClassName: nginx
rules:
- host: canary.com
http:
paths:
- backend:
service:
name: canary-v1
port:
number: 8080
path: /
pathType: Prefix
[root@k8s-master01 ~]# kubectl create -f ingress-canary-v1.yaml
[root@k8s-master01 ~]# kubectl get ingress -n production
NAME CLASS HOSTS ADDRESS PORTS AGE
canary-v1 nginx canary.com 192.168.200.52 80 51s
使用浏览器访问该服务,可以看到 Canary v1 的页面
2、创建 v3 版本
# 首先创建模拟 canary(生产)环境的 Namespace 和服务:
[root@k8s-master01 ~]# kubectl create ns canary
# 创建 v2 版本的应用
[root@k8s-master01 ~]# kubectl create deploy canary-v2 --image=crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/canary:v2 -n canary
[root@k8s-master01 ~]# kubectl get po -n canary
NAME READY STATUS RESTARTS AGE
canary-v2-c4f4ffddb-jdjlj 1/1 Running 0 7s
# 建立service
[root@k8s-master01 ~]# kubectl expose deploy canary-v2 --port 8080 -n canary
[root@k8s-master01 ~]# kubectl get svc -n canary
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
canary-v2 ClusterIP 10.98.59.177 <none> 8080/TCP 17s
3、Canary 版本切入部分流量
创建 v2 版本的 Ingress 时,需要添加两个注释,一个是 nginx.ingress.kubernetes.io/canary
,表明是灰度环境,nginx.ingress.kubernetes.io/canary-weight
表明切多少流量到该环境,本示例为50%:
[root@k8s-master01 ~]# vim ingress-canary-v2.yaml
[root@k8s-master01 ~]# cat ingress-canary-v2.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: canary-v2
namespace: canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "50"
spec:
ingressClassName: nginx
rules:
- host: canary.com
http:
paths:
- backend:
service:
name: canary-v2
port:
number: 8080
path: /
pathType: ImplementationSpecific
此时通过 nginx.ingress.kubernetes.io/canary-weight: "50"设置的权重是 50,即 v1:v2 为 5:5。再次访问可以看到页面会来回显示 v1 和 v2:
4、测试灰度发布
接下来使用 Ruby 脚本进行测试,此脚本会输出 v1 和 v2 的访问次数比值:
[root@k8s-master01 ~]# vim test-canary.rb
[root@k8s-master01 ~]# cat test-canary.rb
counts = Hash.new(0)
100.times do
output = `curl -s canary.com | grep 'Canary' | awk '{print $2}' | awk -F"<" '{print $1}'`
counts[output.strip.split.last] += 1
end
puts counts
# 安装 ruby 并测试,可以看到大致的比例是 5:5
[root@k8s-master01 ~]# yum install ruby -y
[root@k8s-master01 ~]# ruby test-canary.rb
{"v2"=>49, "v1"=>51}
[root@k8s-master01 ~]# ruby test-canary.rb
{"v1"=>38, "v2"=>62}
[root@k8s-master01 ~]# ruby test-canary.rb
{"v1"=>54, "v2"=>46}
3.6 Ingress 常见报错排查
404 表示访问的路由不存在,通常问题如下:
- Ingress 路径配置的不正确
- Ingress 的配置未被 Controller 解析
- 未使用正确的域名和路径访问
- 代理的服务没有该路径
- 请求的方法不同
413(Request Entity Too Large)报错
有时候需要上传一些大文件给程序,但是 nginx 默认允许的最大文件大小只有 8M,不足以满足生产最大上传需求,此时可以通过 nginx.ingress.kubernetes.io/proxy-body-size 参数进行更改(也可以在 ConfigMap 中全局添加):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
namespace: study-ingress
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 32m
spec:
....
503 一般是代理的服务不可用导致的,通常问题如下:
- Ingress 代理配置错误,比如 Service 名字或端口写错
- Ingress 代理的 Service 不存在
- Ingress 代理的 Service 后端 Pod 不正常
504 一般是代理的服务处理请求的时间过长,导致 Nginx 等待超时,此时需要确认服务的处
理时长,或者查看服务是否有问题
CORS 跨域报错:CORS错误。说明被跨域给拦截了,可以添加跨域配置
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: longtime
namespace: study-ingress
annotations:
# 允许跨域的请求方法
nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST,OPTIONS, DELETE"
# 允许携带的请求头
nginx.ingress.kubernetes.io/cors-allow-headers: "X-Forwarded-For, Xapp123-XPTO"
# 允许跨域的域名
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
spec:
....
此博客来源于:https://edu.51cto.com/lecturer/11062970.html