Service东西流量管理 十八岁 2026-08-01 2026-08-01
1.Service服务发布 1.1传统架构和k8s架构
1.2Label和Selector
一般不推荐更改pod 的标签:
案例:
查看资源的标签--show-labels:
1 [root@k8s-master01 pra]# kubectl get node --show-labels
使用-l参数过滤想要的标签:
1 2 3 4 5 6 # 查看具有disktype=ssd标签的node节点,使用-l参数 [root@k8s-master01 pra]# kubectl get nodes -l disktype=ssd NAME STATUS ROLES AGE VERSION k8s-master03 Ready <none> 26d v1.28.11 k8s-node01 Ready <none> 26d v1.28.11 k8s-node02 Ready <none> 26d v1.28.11
1.2.1添加lable 命令格式:
1 kubectl label 资源类型 [资源名称] key=value
指定单个资源添加标签:
1 2 # kubectl label deploy nginx version=v1 deployment.apps/nginx labeled
查看标签:
1 2 3 # kubectl get deploy nginx --show-labels NAME READY UP-TO-DATE AVAILABLE AGE LABELS nginx 1/1 1 1 101s app=nginx,version=v1
指定多个资源添加标签:
1 2 # kubectl label deploy --all svc=true deployment.apps/nginx labeled
根据已有标签过滤之后再添加标签:
1 2 # kubectl label deploy -l app=nginx svc2=true deployment.apps/nginx labeled
同时添加多个标签:
1 2 # kubectl label deploy -l app=nginx a=b c=d deployment.apps/nginx labeled
练习给node节点打标签:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 [root@k8s-master01 ~]# kubectl get node NAME STATUS ROLES AGE VERSION k8s-master01 Ready <none> 26d v1.28.11 k8s-master02 Ready <none> 26d v1.28.11 k8s-master03 Ready <none> 26d v1.28.11 k8s-node01 Ready <none> 26d v1.28.11 k8s-node02 Ready <none> 26d v1.28.11 # 给node01和node02打上role=node的标签, [root@k8s-master01 ~]# kubectl label nodes k8s-node01 k8s-node02 role=node # 给master01 和master02打上role=master的标签 [root@k8s-master01 ~]# kubectl label nodes k8s-master01 k8s-master02 role=master # 给master02节点打上gpu=true 的标签 [root@k8s-master01 ~]# kubectl label nodes k8s-master02 gpu=true node/k8s-master02 labeled # 查看node的标签 [root@k8s-master01 ~]# kubectl get nodes --show-labels NAME STATUS ROLES AGE VERSION LABELS k8s-master01 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master01,kubernetes.io/os=linux,node.kubernetes.io/node=,role=master k8s-master02 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,gpu=true,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master02,kubernetes.io/os=linux,node.kubernetes.io/node=,role=master k8s-master03 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master03,kubernetes.io/os=linux,node.kubernetes.io/node= k8s-node01 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node01,kubernetes.io/os=linux,node.kubernetes.io/node=,region=subnet7,role=node k8s-node02 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node02,kubernetes.io/os=linux,node.kubernetes.io/node=,region=subnet7,role=node # 查看所有具有role=node标签的节点 # node是资源类型,这里也可以是pod、deployment、sts、dm等 # -l 是 --selector 的缩写,表示根据标签过滤资源。根据role=node标签过滤资源 [root@k8s-master01 ~]# kubectl get nodes -l role=node NAME STATUS ROLES AGE VERSION k8s-node01 Ready <none> 27d v1.28.11 k8s-node02 Ready <none> 27d v1.28.11
1.2.3修改标签(label) #将标签region=subnet7改为region=subnet120
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 查看当前node01和node02节点中有region=subnet7标签 [root@k8s-master01 ~]# kubectl get nodes --show-labels -l region NAME STATUS ROLES AGE VERSION LABELS k8s-node01 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node01,kubernetes.io/os=linux,node.kubernetes.io/node=,region=subnet7,role=node k8s-node02 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node02,kubernetes.io/os=linux,node.kubernetes.io/node=,region=subnet7,role=node # 将node01节点中region=subnet7标签改为region=subnet120,使用--overwrite # 使用--overwrite会覆盖已经存在标签的值,这里表示会覆盖region的值 [root@k8s-master01 ~]# kubectl label nodes k8s-node01 region=subnet120 --overwrite node/k8s-node01 labeled # 验证k8s-node01节点有region=subnet120标签 [root@k8s-master01 ~]# kubectl get nodes -l region=subnet120 NAME STATUS ROLES AGE VERSION k8s-node01 Ready <none> 27d v1.28.11
1.2.4删除标签(label) 删除k8s-node01节点 key 名为region 的标签:
1 2 [root@k8s-master01 ~]# kubectl label nodes k8s-node01 region- node/k8s-node01 unlabeled
批量删除region标签:
1 2 3 4 5 6 7 8 9 10 11 12 13 # 可以看到只有node02有 region 作为key的标签 [root@k8s-master01 ~]# kubectl get nodes -l region NAME STATUS ROLES AGE VERSION k8s-node02 Ready <none> 27d v1.28.11 # 删除node节点上具有region标签 # -l region先对具有redion的节点进行筛选,在 region- 对key进行删除。 [root@k8s-master01 ~]# kubectl label nodes -l region region- node/k8s-node02 unlabeled # 删除后已经没有节点具有region为key的标签 [root@k8s-master01 ~]# kubectl get nodes -l region No resources found
1.2.5selector选择器 首先使用--show-labels 查看指定资源目前已有的 Label:
1 2 3 4 5 6 7 [root@k8s-master01 ~]# kubectl get nodes --show-labels NAME STATUS ROLES AGE VERSION LABELS k8s-master01 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master01,kubernetes.io/os=linux,node.kubernetes.io/node=,role=master k8s-master02 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,gpu=true,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master02,kubernetes.io/os=linux,node.kubernetes.io/node=,role=master k8s-master03 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master03,kubernetes.io/os=linux,node.kubernetes.io/node= k8s-node01 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node01,kubernetes.io/os=linux,node.kubernetes.io/node=,region=subnet7,role=node k8s-node02 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node02,kubernetes.io/os=linux,node.kubernetes.io/node=,region=subnet7,role=node
选择匹配 role为 master 或者 node 的 节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 如果只想看节点,不带节点的标签可以不加--show-labels # 获取标签 role 的值在 node 或 master 中的节点。 [root@k8s-master01 ~]# kubectl get nodes -l 'role in (node,master)' --show-labels NAME STATUS ROLES AGE VERSION LABELS k8s-master01 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master01,kubernetes.io/os=linux,node.kubernetes.io/node=,role=master k8s-master02 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,gpu=true,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master02,kubernetes.io/os=linux,node.kubernetes.io/node=,role=master k8s-node01 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node01,kubernetes.io/os=linux,node.kubernetes.io/node=,region=subnet7,role=node k8s-node02 Ready <none> 27d v1.28.11 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node02,kubernetes.io/os=linux,node.kubernetes.io/node=,region=subnet7,role=node # 筛选gpu不等于true ,role=node或者master的节点 [root@k8s-master01 ~]# kubectl get nodes -l 'gpu!=true ,role in (node,master)' NAME STATUS ROLES AGE VERSION k8s-master01 Ready <none> 27d v1.28.11 k8s-node01 Ready <none> 27d v1.28.11 k8s-node02 Ready <none> 27d v1.28.11 # 筛选具有某个key的 [root@k8s-master01 ~]# kubectl get nodes -l gpu NAME STATUS ROLES AGE VERSION k8s-master02 Ready <none> 27d v1.28.11
标签筛选的方式总结:
等于匹配
1 2 # 获取带有 app=nginx 标签的所有 Pods。 kubectl get pods -l app=nginx
不等于匹配
1 2 # 获取不带 app=nginx 标签的所有 Pods。 kubectl get pods -l app!=nginx
存在匹配
1 2 # 获取带有 env 标签的所有 Pods,无论其值是什么。 kubectl get pods -l 'env'
不存在匹配
1 2 # 获取不带 env 标签的所有 Pods。 kubectl get pods -l '!env'
集合匹配
in
1 2 # 获取标签 role 的值在 node 或 master 中的节点。 kubectl get nodes -l 'role in (node,master)'
notin
1 2 # 获取标签 role 的值不在 node 或 master 中的节点。 kubectl get nodes -l 'role notin (node,master)'
多条件组合
1 2 # 获取同时带有 env =production 和 app=nginx 标签的 Pods。 kubectl get pods -l 'env=production,app=nginx'
1.2.6应用案例: 公司与 xx 银行有一条专属的高速光纤通道,此通道只能与 192.168.7.0 网段进行通信,因此只能将与 xx 银行通信的应用部署到 192.168.7.0 网段所在的节点上,此时可以对节点添加 Label:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 # 例如k8s-node02是跟银行业务对接的,应用需要部署到k8s-node02节点上 [root@k8s-master01 pra]# kubectl get node NAME STATUS ROLES AGE VERSION k8s-master01 Ready <none> 26d v1.28.11 k8s-master02 Ready <none> 26d v1.28.11 k8s-master03 Ready <none> 26d v1.28.11 k8s-node01 Ready <none> 26d v1.28.11 k8s-node02 Ready <none> 26d v1.28.11 # 给k8s-node02 添加region=subnet7标签(该标签表示node02服务器网段是192.168.7.0) # 这将为 k8s-node02 节点添加 region=subnet7 标签。 [root@k8s-master01 pra]# kubectl label nodes k8s-node02 region=subnet7 node/k8s-node02 labeled # 验证node02节点上是否有该标签,-l 跟标签 可以对条件标签进行筛选 [root@k8s-master01 pra]# kubectl get nodes -l region=subnet7 NAME STATUS ROLES AGE VERSION k8s-node02 Ready <none> 26d v1.28.11 # 在deployment或者其他控制器中指定将pod部署到该节点 # spec.template.spec.nodeSelector配置该区域 [root@k8s-master01 pra]# cat nginx-deployment.yaml apiVersion: apps/v1 #指定这个资源使用的 Kubernetes API 版本,固定写法 kind: Deployment #指定资源类型是 Deployment。 metadata: # deployment的元数据 name: nginx-deployment # 创建的deployment的名字 labels: #键值对标签,用于资源的分类和选择 app: nginx #给这个deployment资源打上'app: nginx'标签 spec: #描述 Deployment 的期望状态,定义 Deployment 的具体配置和行为。 replicas: 2 # 指定需要运行的 Pod 副本数量。 selector: #选择器,用于选择哪些pod属于这个deployment matchLabels: #匹配标签,选择带有特定标签的pod app: nginx #指定带有' app: nginx '标签的pod进行管理 template: # pod的模板,用于定义pod metadata: #pod的元数据 labels: #标签,配置pod的标签 app: nginx #确保创建的 Pod 具有'app: nginx' 标签。 spec: #描述pod的期望状态,定义pod的具体配置和行为 nodeSelector: #节点选择器 region: subnet7 #匹配具有region=subnet7标签的节点部署pod containers: #容器列表,复数,可配置多个容器 - name: nginx #容器的名称 image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.27-alpine #定义容器使用的镜像 ports: #端口列表,复数,可以暴露多个端口 - containerPort: 80 #指定容器暴露的端口号 env: - name: test_env value: env # 创建deployment [root@k8s-master01 pra]# kubectl create -f nginx-deployment.yaml deployment.apps/nginx-deployment created # 验证deployment管理的pod是否部署在k8s-node02节点 [root@k8s-master01 pra]# kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-deployment-69b546f6ff-5wc6b 1/1 Running 0 28s 172.16.58.215 k8s-node02 <none> <none> nginx-deployment-69b546f6ff-wrq5v 1/1 Running 0 28s 172.16.58.214 k8s-node02 <none> <none>
1.3Service Service是k8s开箱即用的一个用于提供负载均衡、服务发现等能力的资源。
Service 为pod提供了一个抽象层,将一组具有相同功能的pod抽象为一个逻辑上的服务。无论匹配的pod如何变法,比如重启、迁移、扩容或者缩容等,Service都能保持一个稳定的访问接口,从而让我们无需关心服务所在的具体位置、ip等细节。
Service主要功能:
服务之间的服务发现
代理一个或一组pod
代理ip或域名
什么是Endpoints:
1.3.1Service类型
ClusterIP: 在集群内部使用,默认值,只能从集群中访问。
使用场景:
微服务之间的通信。
内部 API 或数据库的访问。
不需要暴露给外部的服务。
NodePort: 在所有安装了 Kube-Proxy 的节点上打开一个端口,此端口可以代理至后端Pod,可以通过 NodePort 从集群外部访问集群内的服务,格式为 NodeIP:NodePort。但是在新版的k8s中宿主机已经不监听该端口了,依然不影响访问。
使用场景:
LoadBalancer: 使用云提供商的负载均衡器公开服务,成本较高。
使用场景:
生产环境中需要高可用性和负载均衡的服务。
Web 应用或 API 服务的对外访问。
ExternalName: 通过返回定义的 CNAME 别名,没有设置任何类型的代理,需要 1.7 或更高版本 kube-dns 支持。
使用场景:
需要在集群内部访问外部数据库或 API 服务。
服务重定向到外部域名。
Headless Service (通过设置 ClusterIP: None):不分配 Cluster IP,直接通过 DNS 名称解析到后端 Pods。适用于需要直接访问 Pod IP 的状态服务
使用场景:
数据库、状态服务等需要直接访问 Pod 的应用。
StatefulSet 中的服务发现。
1.3.2创建一个service 创建Service可以使用expose命令和通过yaml文件定义。
expose基本语法:
1 kubectl expose <资源类型> <资源名称> --port=<端口> [选项]
常见案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 基本用法 kubectl expose deployment my-deployment --port=80 --target-port=8080 # 指定 Service 类型为 NodePort kubectl expose deployment my-deployment \ --port=80 \ #指定Service的端口是80 --target-port=8080 \ #指定目标pod的端口 --type=NodePort \ #指定Service的类型 --name=my-service #指定Service的名字 # 指定 NodePort 端口号 kubectl expose deployment my-deployment \ --port=80 \ --target-port=8080 \ --type=NodePort \ --node-port=30080 #指定NodePort端口号是30080,外部通过节点ip:30080访问 # LoadBalancer 类型 kubectl expose deployment my-deployment \ --port=80 \ --target-port=8080 \ --type=LoadBalancer
定义 Service 的 yaml 文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: v1 #指定这个资源的api版本 kind: Service #指定资源的类型,这里是Service,区分大小写 metadata: # 定义资源的元数据,name字段是必须的,还以包含namespace、labels、annotations等 labels: #定义service的标签 app: nginx-app name: service-for-nginx-deployment #为service定义一个名称,该名称在命名空间内必须是唯一的 spec: #定义service的具体配置,也就是期望的状态 selector: #选择器,用于选择pod标签的选择器,service会将流量路由到相应标签的pod上 app: nginx #pod的标签,表示选择标签为app=nginx的pod sessionAffinity: None # 会话保持配置,None表示不设置会话保持,另一个参数ClientIP开启基于ip的会话保持 ports: #定义service暴露的端口,可以有多个 - protocol: TCP #Service 与其后端 Pods 之间通信使用的网络协议。这是由你的应用程序所使用的网络协议决定的 name: web #定义service端口的名字 port: 80 #Service 暴露的端口。集群内其他服务通过这个端口访问 Service targetPort: 80 #后端目标 Pods 上的端口,必须匹配容器在 Pod 中暴露的端口,Service 会将流量转发到这个端口 - name: https port: 443 targetPort: 443 protocol: https
该示例为 service-for-nginx-deployment:80 即可访问到具有 app=nginx 标签的 Pod 的 80 端口上。
需要注意的是,Service 能够将一个接收端口映射到任意的 targetPort,如果 targetPort 为空,targetPort 将被设置为与 Port 字段相同的值。targetPort 可以设置为一个字符串,引用 backend 的Pod 的一个端口的名称,这样的话即使更改了 Pod 的端口,也不会对 Service 的访问造成影响。
Kubernetes Service 能够支持 TCP、UDP、SCTP 等协议,默认为 TCP 协议。
创建一个deployment管理5个副本的pod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 [root@k8s-master01 pra]# cat nginx-deployment.yaml apiVersion: apps/v1 #指定这个资源使用的 Kubernetes API 版本,固定写法 kind: Deployment #指定资源类型是 Deployment。 metadata: # deployment的元数据 name: nginx-deployment # 创建的deployment的名字 labels: #键值对标签,用于资源的分类和选择 app: nginx #给这个deployment资源打上'app: nginx'标签 spec: #描述 Deployment 的期望状态,定义 Deployment 的具体配置和行为。 replicas: 5 # 指定需要运行的 Pod 副本数量。 selector: #选择器,用于选择哪些pod属于这个deployment matchLabels: #匹配标签,选择带有特定标签的pod app: nginx #指定带有' app: nginx '标签的pod进行管理 template: # pod的模板,用于定义pod metadata: #pod的元数据 labels: #标签,配置pod的标签 app: nginx #确保创建的 Pod 具有'app: nginx' 标签。 spec: #描述pod的期望状态,定义pod的具体配置和行为 containers: #容器列表,复数,可配置多个容器 - name: nginx #容器的名称 image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.27-alpine #定义容器使用的镜像 ports: #端口列表,复数,可以暴露多个端口 - containerPort: 80 #指定容器暴露的端口号 # 创建这个deployment [root@k8s-master01 pra]# kubectl create -f nginx-deployment.yaml deployment.apps/nginx-deployment created # 检查创建的deployment和pod [root@k8s-master01 pra]# kubectl get deploy NAME READY UP-TO-DATE AVAILABLE AGE nginx-deployment 5/5 5 5 15s # 5个副本的pod [root@k8s-master01 pra]# kubectl get po NAME READY STATUS RESTARTS AGE nginx-deployment-66bb646879-4vm88 1/1 Running 0 11s nginx-deployment-66bb646879-hnwf6 1/1 Running 0 11s nginx-deployment-66bb646879-m9s9b 1/1 Running 0 11s nginx-deployment-66bb646879-rpb96 1/1 Running 0 11s nginx-deployment-66bb646879-vplkh 1/1 Running 0 11s
利用上述service的yaml文件创建service
1 2 [root@k8s-master01 pra]# kubectl create -f service-nginx-deploy.yaml service/service-for-nginx-deployment created
验证service是否创建:
1 2 3 4 5 # 名字是service-for-nginx-deployment的service已经存在 [root@k8s-master01 pra]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 27d service-for-nginx-deployment ClusterIP 10.96.148.11 <none> 80/TCP 18s
通过service的ip就可以访问后端的pod:
pod需要和service在同一个命名空间,否则无法代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [root@k8s-master01 pra]# curl 10.96.148.11 <!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>
pod重建或者更新,pod的ip地址是被变动的,但是我们通过service的ip还是可以正常访问pod的:
删除pod:
1 2 3 4 5 6 [root@k8s-master01 pra]# kubectl delete pods -l app=nginx pod "nginx-deployment-66bb646879-4vm88" deleted pod "nginx-deployment-66bb646879-hnwf6" deleted pod "nginx-deployment-66bb646879-m9s9b" deleted pod "nginx-deployment-66bb646879-rpb96" deleted pod "nginx-deployment-66bb646879-vplkh" deleted
pod会被重新创建,此时pod的ip地址跟上述pod的ip完全不同,但是使用service还是正常访问pod
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 [root@k8s-master01 pra]# kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-deployment-66bb646879-4dv7z 1/1 Running 0 28s 172.16.58.217 k8s-node02 <none> <none> nginx-deployment-66bb646879-bg6lm 1/1 Running 0 28s 172.16.32.153 k8s-master01 <none> <none> nginx-deployment-66bb646879-fc68l 1/1 Running 0 28s 172.16.122.153 k8s-master02 <none> <none> nginx-deployment-66bb646879-mlt79 1/1 Running 0 28s 172.16.195.22 k8s-master03 <none> <none> nginx-deployment-66bb646879-rrzt2 1/1 Running 0 28s 172.16.85.248 k8s-node01 <none> <none> 使用service的ip进行访问 [root@k8s-master01 pra]# curl 10.96.148.11 <!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>
跨命名空间访问:service.namespace.svc.cluster.local(service名字.命名空间名字,svc.cluster.local固定写法)
1.3.3NodePort 类型 如果将 Service 的 type 字段设置为 NodePort,则 Kubernetes 将从–service-node-port-range 参数指定的范围(默认为 30000-32767)中自动分配端口,也可以手动指定 NodePort,创建该 Service后,集群每个节点都将暴露一个端口,通过某个宿主机的 IP+端口即可访问到后端的应用。
1 2 3 # 默认为 30000-32767根据kube-apiserver.service文件中的范围随机生成 cat /usr/lib/systemd/system/kube-apiserver.service |grep 'service-node-port' --service-node-port-range=30000-32767 \ #建议使用3000-32767这个范围
定义一个 NodePort 类型的 Service 格式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@k8s-master01 pra]# cat service-nginx-deploy.yaml apiVersion: v1 #指定这个资源的api版本 kind: Service #指定资源的类型,这里是Service,区分大小写 metadata: # 定义资源的元数据,name字段是必须的,还以包含namespace、labels、annotations等 name: service-for-nginx-deployment #为service定义一个名称,该名称在命名空间内必须是唯一的 spec: #定义service的具体配置,也就是期望的状态 type: NodePort #将service配置为NodePort类型 selector: #选择器,用于选择pod标签的选择器,service会将流量路由到相应标签的pod上 app: nginx #pod的标签,表示选择标签为app=nginx的pod ports: #定义service暴露的端口,可以有多个 - protocol: TCP #Service 与其后端 Pods 之间通信使用的网络协议。这是由你的应用程序所使用的网络协议决定的 port: 80 #Service 暴露的端口。集群内其他服务通过这个端口访问 Service targetPort: 80 #后端目标 Pods 上的端口,必须匹配容器在 Pod 中暴露的端口,Service 会将流量转发到这个端口
创建这个service:
1 [root@k8s-master01 pra]# kubectl replace -f service-nginx-deploy.yaml
查看创建的service:名字service-for-nginx-deployment 的service的类型是NodePort,这样就可以通过任意安装Kube-Proxy 节点的ip加32222端口访问到后端的pod
1 2 3 4 5 #可以看到 [root@k8s-master01 pra]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 28d service-for-nginx-deployment NodePort 10.96.148.11 <none> 80:32222/TCP 4h12m
32222端口也可以手动指定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 # 手动指定service暴露的端口,通过修改nodePort: 32333手动指定端口,手动指定的端口不能和节点上的端口冲突。 [root@k8s-master01 pra]# kubectl edit service service-for-nginx-deployment # Please edit the object below. Lines beginning with a '#' will be ignored, # and an empty file will abort the edit. If an error occurs while saving this file will be # reopened with the relevant failures. # apiVersion: v1 kind: Service metadata: creationTimestamp: "2024-07-20T10:08:36Z" name: service-for-nginx-deployment namespace: default resourceVersion: "2943581" uid: 29d10e3c-0f56-494d-a148-669bf7522ed9 spec: clusterIP: 10.96.148.11 clusterIPs: - 10.96.148.11 externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster ipFamilies: - IPv4 ipFamilyPolicy: SingleStack ports: - nodePort: 32333 #手动指定nodePort的端口 port: 80 protocol: TCP targetPort: 80 selector: app: nginx sessionAffinity: None #None表示不开启会话保持 type: NodePort status: loadBalancer: {}
1.3.4.ExternalName代理域名 1.3.4.1基本使用 ExternalName Service 是 Service 的特例,它没有 Selector,也没有定义任何端口和 Endpoint,它通过返回该外部服务的别名来提供服务。
比如可以定义一个 Service,后端设置为一个外部域名,这样通过 Service 的名称即可访问到该域名。使用 nslookup 解析以下文件定义的 Service,集群的 DNS 服务将返回一个值为my.database.example.com 的 CNAME 记录:
1 2 3 4 5 6 7 8 [root@k8s-master01 pra]# cat externalname-service.yaml apiVersion: v1 kind: Service #定义资源的类型,这里是service metadata: #定义资源的元数据 name: external-name #定义这个资源的名称 spec: #定义资源的期望状态 type: ExternalName #资源的类型是externalname externalName: www.jiugen.net #指定代理的后端外部域名,集群内部pod可以通过这个service的名称访问到www.jiugen.net的内容
验证pod通过上述创建的srvice名称访问www.jiugen.net
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # 创建service [root@k8s-master01 pra]# kubectl create -f externalname-service.yaml # 查看创建的svc [root@k8s-master01 pra]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE external-name ExternalName <none> www.jiugen.net <none> 7m3s kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 28d service-external-ip ClusterIP 10.96.20.32 <none> 80/TCP 102m service-for-nginx-deployment NodePort 10.96.148.11 <none> 80:32333/TCP 20h [root@k8s-master01 pra]# # 进入pod中,访问名称是external-name 的service,验证是否代理到了 www.jiugen.net [root@k8s-master01 pra]# kubectl exec -ti nginx-deployment-66bb646879-4kjwj -- sh / # ping external-name #可以看到这里和ping www.jiugen.net返回的ip相同,代理成功 PING external-name (121.36.48.57): 56 data bytes 64 bytes from 121.36.48.57: seq=0 ttl=44 time=8.858 ms 64 bytes from 121.36.48.57: seq=1 ttl=44 time=9.396 ms 64 bytes from 121.36.48.57: seq=2 ttl=44 time=9.854 ms ^C --- external-name ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max = 8.858/9.369/9.854 ms / # ping www.jiugen.net #可以看到这里和ping external-name返回的ip相同,代理成功 PING www.jiugen.net (121.36.48.57): 56 data bytes 64 bytes from 121.36.48.57: seq=0 ttl=44 time=9.215 ms 64 bytes from 121.36.48.57: seq=1 ttl=44 time=9.739 ms 64 bytes from 121.36.48.57: seq=2 ttl=44 time=28.221 ms ^C --- www.jiugen.net ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max = 9.215/15.725/28.221 ms
1.3.4.2其他案例 ExternalName类型的Service用来代理外部的域名,那么为什么不直接访问域名呢?还要多此一举使用Service进行代理呢?
假设某个项目具备 DEV/UAT 两个环境,每个环境需要链接指定的数据库等基础组件。基础组件同样也是在 K8s 中按照不同的环境进行划分和部署,比如 DEV 环境所用的基础组件均在basic-component-dev 命名空间下,以此类推。
为了降低配置文件的维护复杂度,准备使用 ExternalName 类型的 Service 对基础组件的连接地址进行映射,这样就可以用同名的 Service 区分不同的环境,从而降低配置文件维护的复杂度。比如配置了在同一个项目的不同环境里面都配置一个同名的 Redis Service,类型为ExternalName,并且按照不同环境指向不同的基础组件地址,这样每个项目的不同环境,都可以用 Redis 这一个地址就可以访问到不同基础组件。
环境准备:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 创建两个命名空间 [root@k8s-master01 ~]# kubectl create ns basic-component-dev namespace/basic-component-dev created [root@k8s-master01 ~]# kubectl create ns basic-component-uat namespace/basic-component-uat created # 创建服务,分别在两个命名空间中部署redis服务 [root@k8s-master01 ~]# kubectl create deploy redis -n basic-component-dev --image=registry.cn-beijing.aliyuncs.com/dotbalo/redis:7.2.5 deployment.apps/redis created [root@k8s-master01 ~]# kubectl create deploy redis -n basic-component-uat --image=registry.cn-beijing.aliyuncs.com/dotbalo/redis:7.2.5 deployment.apps/redis created # 为两个命名空间的redis创建Service用来暴露服务 [root@k8s-master01 ~]# kubectl expose deploy redis --port 6379 -n basic-component-dev service/redis exposed [root@k8s-master01 ~]# kubectl expose deploy redis --port 6379 -n basic-component-uat service/redis exposed
访问测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 # 创建一个专门用于测试redis的客户端 [root@k8s-master01 ~]# kubectl create deploy redis-cli --image=registry.cn-beijing.aliyuncs.com/dotbalo/redis:7.2.5 [root@k8s-master01 ~]# kubectl get deployments.apps NAME READY UP-TO-DATE AVAILABLE AGE redis-cli 1/1 1 1 19h [root@k8s-master01 ~]# kubectl get pod NAME READY STATUS RESTARTS AGE redis-cli-57cc5fd584-6ndnb 1/1 Running 0 19h # 进入该redis客户端的pod中,分别连接两个环境的redis [root@k8s-master01 ~]# kubectl exec -ti redis-cli-57cc5fd584-6ndnb -- bash root@redis-cli-57cc5fd584-6ndnb:/data# # 连接dev环境的redis,#redis是service名字,basic-component-dev是所在的命名空间 root@redis-cli-57cc5fd584-6ndnb:/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" # 连接uat环境的redis root@redis-cli-57cc5fd584-6ndnb:/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" # 此时dev环境的程序连接redis的地址是:redis.basic-component-dev;此时uat环境的程序连接redis的地址是:redis.basic-component-uat。为了方便维护,就可以使用ExternalName类型的Service对不同环境的redis统一管理,分别在两个命名空间中创建同名的Service服务指向他们自己的redis服务,程序中使用这个同名的redis即可,便于维护和管理。 # 创建项目的命名空间: [root@k8s-master01 ~]# kubectl create ns project-dev namespace/project-dev created [root@k8s-master01 ~]# kubectl create ns project-uat namespace/project-uat created # 在每个项目的环境下,创建一个 externalName 类型的 Service,用于连接到不同环境的redis基础组件: # yaml文件内容如下: [root@k8s-master01 13-service]# cat redis-service-dev.yaml kind: Service apiVersion: v1 metadata: name: redis-service #在project-dev命名空间中创建了名字是redis-service的Service namespace: project-dev spec: type: ExternalName externalName: redis.basic-component-dev.svc.cluster.local #这里配置代理basic-component-dev命名空间的redis服务 [root@k8s-master01 13-service]# cat redis-service-uat.yaml kind: Service apiVersion: v1 metadata: name: redis-service #在project-uat命名空间中创建了名字是redis-service的Service namespace: project-uat spec: type: ExternalName externalName: redis.basic-component-uat.svc.cluster.local #这里配置代理basic-component-uat命名空间的redis服务 # 创建上述service [root@k8s-master01 13-service]# kubectl create -f . service/redis-service created service/redis-service created # 接下来在每个项目的环境下,创建两个 Redis 客户端,用于模拟需要链接 Redis 的应用程序: [root@k8s-master01 13-service]# kubectl create deploy usercenter --image=registry.cn-beijing.aliyuncs.com/dotbalo/redis:7.2.5 -n project-dev deployment.apps/usercenter created [root@k8s-master01 13-service]# kubectl create deploy usercenter --image=registry.cn-beijing.aliyuncs.com/dotbalo/redis:7.2.5 -n project-uat deployment.apps/usercenter created # 检查pod [root@k8s-master01 13-service]# kubectl get po -n project-dev NAME READY STATUS RESTARTS AGE usercenter-6685654cc4-g2p54 1/1 Running 0 22s [root@k8s-master01 13-service]# kubectl get po -n project-uat NAME READY STATUS RESTARTS AGE usercenter-6685654cc4-gbqht 1/1 Running 0 19s # 分别进入不同环境的redis 的pod中使用同一个地址 redis-service,连接的是不同的redis,这样在程序中维护一个redis地址即可。方便管理 [root@k8s-master01 13-service]# kubectl exec -ti -n project-dev usercenter-6685654cc4-g2p54 -- bash root@usercenter-6685654cc4-g2p54:/data# redis-cli -h redis-service redis-service:6379> get a "dev" redis-service:6379> root@usercenter-6685654cc4-g2p54:/data# exit [root@k8s-master01 13-service]# kubectl exec -ti -n project-uat usercenter-6685654cc4-gbqht -- bash root@usercenter-6685654cc4-gbqht:/data# redis-cli -h redis-service redis-service:6379> get a "uat"
1.3.5使用 Service代理K8s外部服务 使用场景:
➢ 希望在生产环境中使用某个固定的名称而非 IP 地址访问外部的中间件服务;
➢ 希望 Service 指向另一个 Namespace 中或其他集群中的服务;
➢ 正在将工作负载转移到 Kubernetes 集群,但是一部分服务仍运行在 Kubernetes 集群之外的 backend。
使用kubernetes代理外部服务,service的yaml文件就不要指定匹配pod的标签了(spec.selector) ,创建一个同名的Endpoints就可以被service代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [root@k8s-master01 pra ] apiVersion: v1 kind: Service metadata: name: service-external-ip labels: app: external-ip spec: ports: - port: 80 name: http protocol: TCP targetPort: 10000 type: ClusterIP --- apiVersion: v1 kind: Endpoints metadata: name: service-external-ip labels: app: external-ip subsets: - addresses: - ip: 123.249 .7 .183 ports: - name: http port: 10000 protocol: TCP
总结:
Service 的 port 可以与 Endpoints 的 port 不同,port 是集群内的 Pod 访问 Service 的端口。
Service 的 targetPort 必须与 Endpoints 的 port 相同,确保请求能够正确转发到实际的外部服务端口。
通过上述配置,集群内的 Pod 可以通过访问 service-external-ip:80 来连接到外部服务 123.249.7.183:10000。
Service 的 port 是客户端访问 Service 的入口端口。
Service 的 targetPort 必须与 Endpoints 的 port 一致,以便正确转发请求到后端实际服务。
Endpoint IP 地址不能是 loopback(127.0.0.0/8)、link-local(169.254.0.0/16)或者 linklocal 多播地址(224.0.0.0/24)。
访问没有 Selector 的 Service 与有 Selector 的 Service 的原理相同,通过 Service 名称即可访问,请求将被路由到用户定义的 Endpoint。
验证k8s代理外部服务是否成功:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 [root@k8s-master01 pra]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 28d service-external-ip ClusterIP 10.96.20.32 <none> 80/TCP 37m service-for-nginx-deployment NodePort 10.96.148.11 <none> 80:32333/TCP 19h # 测试通过Service(service-external-ip)的ip访问10.96.20.32 [root@k8s-master01 pra]# wget 10.96.20.32 --2024-07-21 13:50:20-- http://10.96.20.32/ Connecting to 10.96.20.32:80... connected. HTTP request sent, awaiting response... 200 OK Length: 612 [text/html] Saving to: ‘index.html’ 100% [==========================================================>] 612 --.-K/s in 0s 2024-07-21 13:50:20 (167 MB/s) - ‘index.html’ saved [612/612] # ep是Endpoints的缩写 [root@k8s-master01 pra]# kubectl get ep NAME ENDPOINTS AGE kubernetes 192.168.0.200:6443,192.168.0.201:6443,192.168.0.202:6443 28d service-external-ip 123.249.7.183:8080 38m service-for-nginx-deployment 172.16.122.154:80,172.16.195.22:80,172.16.32.154:80 + 2 more... 19h # 可以看到直接访问123.249.7.183:10000和访问wget 10.96.20.32的效果是一样的,表示代理外部服务成功 [root@k8s-master01 pra]# wget 123.249.7.183:10000 --2024-07-21 13:51:02-- http://123.249.7.183:10000/ Connecting to 123.249.7.183:80... connected. HTTP request sent, awaiting response... 200 OK Length: 612 [text/html] Saving to: ‘index.html’ 100% [============================================================>] 612 --.-K/s in 0s 2024-07-21 13:51:02 (179 MB/s) - ‘index.html’ saved [612/612]
1.3.6多端口的service 在 Kubernetes 中,有的程序可能会监听多个端口,Service 也支持同时代理多个端口。比如在k8s中部署一个RabbitMQ服务,它具有两个端口,5672是程序连接用于数据交互的端口,15672是RabbitMQ管理页面的端口。
首先在k8s集群中部署RabbitMQ,镜像rabbitmq:4.0.2-management:
这个镜像包含了:
✅ 完整的 RabbitMQ 服务器 - 消息队列核心功能
✅ AMQP 协议支持 - 端口 5672(应用连接用)
✅ Web 管理控制台 - 端口 15672(可视化管理)
✅ 管理插件 - 已预装并启用
1 2 [root@k8s-master01 13-service]# kubectl create deployment rabbitmq --image=registry.cn-beijing.aliyuncs.com/k8s-liujunwei/rabbitmq:4.0.2-management deployment.apps/rabbitmq created
接下来可以创建一个 Service,把 5672 指向 Pod 的 5672,15672 指向pod 15672:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [root@k8s-master01 13 -service ] kind: Service apiVersion: v1 metadata: name: rabbitmq-service namespace: default spec: type: NodePort selector: app: rabbitmq ports: - protocol: TCP name: rabbitmq-port port: 5672 targetPort: 5672 - protocol: TCP name: rabbitmq-management-port port: 15672 targetPort: 15672
创建上述Service:
1 2 [root@k8s-master01 13-service]# kubectl create -f rabbitmq-service.yaml service/rabbitmq-service created
查看创建的service:
1 2 3 [root@k8s-master01 13-service]# kubectl get svc rabbitmq-service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE rabbitmq-service NodePort 10.96.133.159 <none> 5672:31828/TCP,15672:30384/TCP 2m53s
测试,通过浏览器输入节点ip:30384,可以看到已经成功代理。
1.3.7Service会话保持功能 Kubernetes 的 Service 支持基于客户端 IP 的会话保持,确保同一个客户端的请求始终被转发到同一个 Pod。
默认创建的Service是没有配置会话保持的,如果需要开启Service基于客户端的会话保持需要设置sessionAffinity字段(该字段默认为None)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [root@k8s-master01 ~ ] apiVersion: v1 kind: Service metadata: labels: app: nginx name: nginx spec: ports: - nodePort: 32339 port: 80 protocol: TCP targetPort: 80 selector: app: nginx sessionAffinity: ClientIP sessionAffinityConfig: clientIP: timeoutSeconds: 10800 type: NodePort
设置会话保持后(sessionAffinity),在timeoutSeconds时间段service都会将流量路由到同一个pod上。在超过timeoutSeconds时间后重新轮询。
在实际的生产环境中不建议使用基于Service的会话保持功能,原因如下:
pod是临时资源,在某些原因下导致删除或者重启,此时如果用户会话绑定到某个 Pod,而该 Pod 被删除,会话状态(如 Session、Token)将丢失 ,用户需要重新登录。
通过NodePort类型的Service访问Nginx服务时,请求并不是直接从您的电脑到达Nginx Pod,而是经过了Kubernetes的网络层处理。这导致Nginx日志中记录的客户端IP不是您真实电脑的IP地址,而是Kubernetes网络中的某个内部IP地址(例如172.16.32.128)。
负载不均衡,导致资源利用率低。假如用户 A、B、C 同时访问服务,Session Affinity 将他们的请求分别绑定到 Pod1、Pod2、Pod3。如果用户 A 的请求量远高于其他用户,Pod1 可能过载,而 Pod2、Pod3 空闲。
现代微服务架构倾向于无状态设计 ,即服务实例之间不依赖本地存储的状态。Session Affinity 强制将请求绑定到特定 Pod,违背了无状态原则,导致:扩缩容复杂 :新增 Pod 无法立即分担流量。故障恢复困难 :Pod 故障时,会话状态无法自动迁移。
总结:生产环境不建议使用 Session Affinity 的原因:
问题
影响
Pod 生命周期不可控
会话状态易丢失,用户需频繁重新登录
基于 IP 的会话保持不可靠
多个用户共享 IP,导致负载不均
负载不均衡
某些 Pod 过载,资源利用率低
违背无状态原则
扩缩容和故障恢复复杂
不支持高级流量管理
无法实现金丝雀发布、A/B 测试等
最佳实践建议:
场景
推荐方案
小型测试环境
使用 sessionAffinity: ClientIP 快速验证
生产环境
使用 共享会话存储(Redis) 或 JWT 无状态认证
需要会话粘性
使用 Ingress 的 Cookie 粘性 (如 Nginx Ingress)
1.3.8 Headless Service 1.3.8.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.3.8.2使用场景
有状态应用的服务发现和负载均衡: 有状态应用(如数据库、消息队列,eureka集群等)通常需要为每个 Pod 分配一个唯一的标识符(如 Pod 名称或 IP 地址),以便其他服务或其他节点可以连接到某个实例。Headless Service可以满足这一需求,通过直接暴露 Pod 的 IP 地址和 DNS 记录,实现服务发现和负载均衡。
需要直接访问Pod IP 的应用: 在某些情况下,客户端可能需要直接访问 Pod 的 IP 地址,而不需要通过 Service 的负载均衡机制,此时也可以通过 Headless Service 实现。
分布式系统: 在分布式系统中,各个节点之间需要直接通信,并且每个节点都有自己的身份和状态。Headless Service 可以为每个节点分配一个唯一的 DNS 实体名称,支持节点之间的直接交互和负载均衡
3.3.8.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=my-app 标签的 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
3.3.8.4Headless Service使用 创建一个 StatefulSet 和 Headless Service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 kind: Service apiVersion: v1 metadata: name: nginx-headless-service spec: clusterIP: None ports: - name: http port: 80 targetPort: 80 selector: app: nginx --- kind: StatefulSet apiVersion: apps/v1 metadata: name: nginx spec: serviceName: "nginx-headless-service" replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx imagePullPolicy: IfNotPresent
1.3.9Service代理模式 1.3.9.1Iptables 代理模式 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 数量的增加(超过4000),iptables 规则的数量也会急剧增加,进而导致性能下降。iptables的更新操作可能会暂时锁定整个 iptables 规则表,影响网络性能。
iptables负载均衡算法: 仅支持随机选择算法
1.3.9.2IPVS 代理模式 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 来接收新请求。
1.3.9.3将Service的代理模式改为ipvs 查看当前的代理模式:
1 2 3 # 10249是kube-proxy的端口 [root@k8s-master01 ~]# curl 127.0.0.1:10249/proxyMode iptables
更改 proxy 的代理模式为 ipvs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # kubeadmin安装方式通过修改configmap实现 kubectl edit cm kube-proxy -n kube-system metricsBindAddress: 127.0.0.1:10249 mode: "ipvs" #将mode的值修改为ipvs,如果是空默认iptables nodePortAddresses: null # 二进制安装方式通过修改kube-proxy的配置文件实现(注意所有节点都需要修改) [root@k8s-master01 ~]# find / -name kube-proxy.yaml #找到配置文件位置 /etc/kubernetes/kube-proxy.yaml [root@k8s-master01 ~]# vim /etc/kubernetes/kube-proxy.yaml #修改 metricsBindAddress: 127.0.0.1:10249 mode: "ipvs" #同理将mode的值改为ipvs即可 nodePortAddresses: null
修改后重启kube-proxy:
1 2 3 # kubeadmin安装方式通过kube-proxy资源的spec字段触发更新,或者删除pod会自动创建 # 二进制安装方式通过systemctl restart kube-proxy.service重启(注意所有节点都需要修改)
再次查看代理模式:
1 2 [root@k8s-master01 ~]# curl 127.0.0.1:10249/proxyMode ipvs
在节点上查看ipvs规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # ipvsadm -ln [root@k8s-master01 ~]# ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 172.16.32.128:30285 rr TCP 172.16.32.128:30384 rr -> 172.16.135.187:15672 Masq 1 0 0 TCP 172.16.32.128:30410 rr -> 172.16.32.165:8761 Masq 1 0 0 TCP 172.16.32.128:30716 rr -> 172.16.85.240:20001 Masq 1 0 0 TCP 172.16.32.128:30784 rr -> 172.16.122.191:6379 Masq 1 0 0 TCP 172.16.32.128:31740 rr -> 172.16.195.44:80 Masq 1 0 0 TCP 172.16.32.128:31828 rr -> 172.16.135.187:5672 Masq 1 0 0 TCP 172.16.32.128:31846 rr -> 172.16.85.240:9090 Masq 1 0 0 TCP 172.16.32.128:32148 rr -> 172.16.85.237:80 Masq 1 0 0
1.3.9.4更改ipvs负载均衡算法 生产环境中建议是用最少连接数(lr)算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # kubeadmin安装方式通过修改configmap实现 kubectl edit cm kube-proxy -n kube-system iptables: masqueradeAll: false masqueradeBit: 14 minSyncPeriod: 0s syncPeriod: 30s ipvs: masqueradeAll: true minSyncPeriod: 5s scheduler: "lc" #找到ipvs块,将scheduler算法改为lc,如果是""默认为rr轮询 syncPeriod: 30s # 二进制安装方式通过修改kube-proxy的配置文件实现(注意所有节点都需要修改) [root@k8s-master01 ~]# find / -name kube-proxy.yaml #找到配置文件位置 /etc/kubernetes/kube-proxy.yaml [root@k8s-master01 ~]# vim /etc/kubernetes/kube-proxy.yaml #修改 iptables: masqueradeAll: false masqueradeBit: 14 minSyncPeriod: 0s syncPeriod: 30s ipvs: # 找到ipvs块,修改轮询算法找该算法下的scheduler字段修改 masqueradeAll: true minSyncPeriod: 5s scheduler: "lc" #找到ipvs块,将scheduler算法改为lc,如果是""默认为rr轮询 syncPeriod: 30s kind: KubeProxyConfiguration metricsBindAddress: 127.0.0.1:10249 mode: "ipvs" nodePortAddresses: null
重启kube-proxy:
1 2 3 # kubeadmin安装方式通过kube-proxy资源的spec字段触发更新,或者删除pod会自动创建 # 二进制安装方式通过systemctl restart kube-proxy.service重启(注意所有节点都需要修改)
查看ipvs算法是否修改为最少连接数(lc)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Scheduler字段表示负载调度算法,lc表示最少连接数 [root@k8s-master01 ~]# ipvsadm -ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 172.16.32.128:30285 lc TCP 172.16.32.128:30384 lc -> 172.16.135.187:15672 Masq 1 0 0 TCP 172.16.32.128:30410 lc -> 172.16.32.165:8761 Masq 1 0 0 TCP 172.16.32.128:30716 lc -> 172.16.85.240:20001 Masq 1 0 0 TCP 172.16.32.128:30784 lc -> 172.16.122.191:6379 Masq 1 0 0 TCP 172.16.32.128:31740 lc