Service东西流量管理

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   #把当前命名空间中所有的deploy添加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类型

  1. ClusterIP:在集群内部使用,默认值,只能从集群中访问。

​ 使用场景:

  • 微服务之间的通信。
  • 内部 API 或数据库的访问。
  • 不需要暴露给外部的服务。
  1. NodePort:在所有安装了 Kube-Proxy 的节点上打开一个端口,此端口可以代理至后端Pod,可以通过 NodePort 从集群外部访问集群内的服务,格式为 NodeIP:NodePort。但是在新版的k8s中宿主机已经不监听该端口了,依然不影响访问。

​ 使用场景:

  • 简单的测试环境或开发阶段。
  • 外部临时访问服务。
  1. LoadBalancer:使用云提供商的负载均衡器公开服务,成本较高。

​ 使用场景:

  • 生产环境中需要高可用性和负载均衡的服务。
  • Web 应用或 API 服务的对外访问。
  1. ExternalName:通过返回定义的 CNAME 别名,没有设置任何类型的代理,需要 1.7 或更高版本 kube-dns 支持。

​ 使用场景:

  • 需要在集群内部访问外部数据库或 API 服务。
  • 服务重定向到外部域名。
  1. 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]# cat service-external-ip.yaml
apiVersion: v1
kind: Service #定义资源的类型
metadata: #定义对象的元数据,例如:名称、标签、注释等
name: service-external-ip #定义资源的名称,要和Endpoints中metadata.name保持一致
labels: #定义资源的标签,键值对的形式
app: external-ip #定义的标签内容
spec: #定义资源对象的期望状态
ports: #定义service服务暴露的端口
- port: 80 #service服务暴露的端口是80,客户端通过此端口访问后端服务
name: http #端口的名称,要和Endpoints中subsets.ports.name一致
protocol: TCP #定义使用的协议,要和Endpoints中subsets.ports.protocol一致
targetPort: 10000 #定义后端服务的端口,必须匹配pod中容器的端口,这里要和Endpoints中subsets.ports.port一致
type: ClusterIP #service的类型,集群内部访问

--- #一个yaml文件写两种资源类型需要用---分割
apiVersion: v1
kind: Endpoints #资源类型Endpoints
metadata: #定义资源对象的元数据
name: service-external-ip #定义Endpoints资源的名称,Service 与 Endpoints 是通过 metadata.name 绑定关系的。只有名称相同,它们才会被关联。要与service中metadata.name保持一致。
labels: #标签
app: external-ip #定义资源的标签
subsets: #定义端点的子集,包括 IP 地址和端口信息。确保子集配置正确且对应实际的外部服务。
- addresses: #包含一个或多个 IP 地址。确保 IP 地址是外部服务的实际地址
- ip: 123.249.7.183 #外部服务的 IP 地址。
ports: #定义服务暴露的端口。配置要与外部服务的实际端口一致。
- name: http #端口的名称,要与service中spec.ports.name保持一致
port: 10000 #外部服务的端口。要与service中spec.ports.targetPort的端口保持一致
protocol: TCP #定义使用的协议。要与service中spec.ports.protocol的协议保持一致

总结:

  1. Service 的 port 可以与 Endpoints 的 port 不同,port 是集群内的 Pod 访问 Service 的端口。
  2. 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]# cat rabbitmq-service.yaml
kind: Service
apiVersion: v1
metadata:
name: rabbitmq-service
namespace: default
spec:
type: NodePort #这里为了在浏览器测试,直接用的NodePort
selector:
app: rabbitmq #这里的标签要和上述创建deployment中pod的标签一致。可以使用kubectl get deployments.apps rabbitmq -oyaml查看pod的标签。
ports:
- protocol: TCP
name: rabbitmq-port #如果一个service中配置了多个端口,必须为每个端口配置一个name
port: 5672 #service暴露的5672指向targetPort的5672
targetPort: 5672
- protocol: TCP
name: rabbitmq-management-port
port: 15672 #service暴露的15672指向targetPort的15672
targetPort: 15672
#nodePort: 30384 通过nodePort参数指定外部访问的端口号,这里注释该参数,svc会随机分配

创建上述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
#开启Service的会话保持
[root@k8s-master01 ~]# kubectl get svc nginx -oyaml
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: nginx
spec:
ports:
- nodePort: 32339
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
#sessionAffinity: None #会话保持默认不开启
sessionAffinity: ClientIP #开启基于客户端ip的会话保持,目前支持这一种会话保持
sessionAffinityConfig: #会话保持设置
clientIP:
timeoutSeconds: 10800 #会话保持时间,默认3小时
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 会执行以下操作:

  1. 创建 DNS 记录:为每个 Pod 创建一个 DNS 记录,该记录的名称基于 Service 名称、Pod名称和命名空间定义,格式为<pod-name>.<service-name>.<namespace>.svc.cluster.local
  2. 暴露 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.serviceName一致
spec:
clusterIP: None #通过设置clusterIP: None参数让该Service成为Headless Service,k8s不会为它分配ip地址
ports:
- name: http
port: 80
targetPort: 80
selector:
app: nginx
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: nginx
spec:
serviceName: "nginx-headless-service" #这里指定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 规则,以实现请求的转发。

工作流程:

  1. 当 Service 被创建或更新时,kube-proxy 会读取 Service 和 Endpoint 对象的信息,并生成相应的 iptables 规则

  2. 这些 iptables 规则被添加到内核的 netfilter 处理链中,以拦截和转发目标为 Service IP 地址的流量

  3. 当客户端访问 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 的情况下也能保持高性能。

工作流程

  1. 当 Service 被创建或更新时,kube-proxy 会读取 Service 和 Endpoint 对象的信息,并配置IPVS 负载均衡策略

  2. IPVS 负载均衡器会根据配置的调度算法(如轮询、最少连接等)将请求转发到后端的一个或多个 Pod 上

  3. 当客户端访问 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  #如果没有这个命令执行 yum -y install ipvsadm进行安装

[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