目录

本节实战

实战名称
💘 实战:定义与创建Service-2023.2.11(测试成功)
💘 实战:多端口Service定义-2022.7.21(测试成功)
💘 实战:Service代理模式-kubeadm方式修改ipvs模式-2022.7.21(测试成功)
💘 实战:二进制方式修改ipvs模式-2023.2.11(测试成功)
💘 实战:NodePort测试-2023.2.11(测试成功)
💘 实战:ExternalName类型自定义Endpoints测试-2023.2.12(测试成功)
💘 实战:Endpoints测试-2023.2.12(测试成功)
💘 实战:EndpointSlice 使用-2023.2.12(测试成功)

💘 实战:多端口Service定义-2023.2.12(测试成功)

  • 实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
   k8s version:v1.25.4
   containerd://1.6.10
  • 实验软件(无)

1、Service

1.Service概念

我们前面的课程中学习了一些常用控制器的基本用法,我们也了解到 Pod 的生命是有限的,死亡过后不会复活了。然后我们知道可以用 ReplicaSet 和 Deployment 来动态的创建和销毁 Pod,每个 Pod 都有自己的 IP 地址,但是如果 Pod 重建了的话那么他的 IP 很有可能也就变化了(有的网络插件,支持重建pod后,其pod ip是不变的,但大多数网络插件,pod重建后,其pod ip都会被重新分配的。)。这就会带来一个问题:比如我们有一些后端的 Pod 集合为集群中的其他应用提供 API 服务,如果我们在前端应用中把所有的这些后端的 Pod 的地址都写死,然后以某种方式去(比如轮询方式)访问其中一个 Pod 的服务,这样看上去是可以工作的,对吧?但是如果这个 Pod 挂掉了,然后重新启动起来了,是不是 IP 地址非常有可能就变了,这个时候前端就极大可能访问不到后端的服务了。

遇到这样的问题该怎么解决呢?在没有使用 Kubernetes 之前,我相信可能很多同学都遇到过这样的问题,不一定是 IP 变化的问题,比如我们在部署一个 WEB 服务的时候,前端一般部署一个 Nginx 作为服务的入口,然后 Nginx 后面肯定就是挂载的这个服务的大量后端服务,很早以前我们可能是去手动更改Nginx 配置中的 upstream 选项,来动态改变提供服务的数量。到后面出现了一些服务发现的工具,比如 Consul、ZooKeeper 还有我们熟悉的 etcd 等工具,有了这些工具过后我们就可以只需要把我们的服务注册到这些服务发现中心去就可以,然后让这些工具动态的去更新 Nginx 的配置就可以了,我们完全不用去手工的操作了,是不是非常方便。

同样的,要解决我们上面遇到的问题是不是**实现一个服务发现的工具也可以解决?**没错的,当我们 Pod 被销毁或者新建过后,我们可以把这个 Pod 的地址注册到这个服务发现中心去就可以,但是这样的话我们的前端应用就不能直接去连接后台的 Pod 集合了,应该连接到一个能够做服务发现的中间件上面,对吧?

为解决这个问题 Kubernetes 就为我们提供了这样的一个对象 - Service,Service 是一种抽象的对象,它定义了一组 Pod 的逻辑集合和一个用于访问它们的策略,其实这个概念和微服务非常类似**。一个 Serivce 下面包含的 Pod 集合是由 Label Selector 来决定的。**

比如我们上面的例子,假如我们后端运行了3个副本,这些副本都是可以替代的,因为前端并不关心它们使用的是哪一个后端服务。尽管由于各种原因后端的 Pod 集合会发送变化,但是前端却不需要知道这些变化,也不需要自己用一个列表来记录这些后端的服务,Service 的这种抽象就可以帮我们达到这种解耦的目的。

画图说明如下:

image-20230211110946745

image-20230211111353848

image-20230211155340629

2.Service存在的意义

Service引入主要是解决Pod的动态变化,提供统一访问入口

  • 防止Pod失联,准备找到提供同一个服务的Pod(服务发现
  • 定义一组Pod的访问策略(负载均衡

  • 这边提出2个问题
第1个问题:pod的ip地址是不是固定的?
    =>pod不重建,ip是不会变化的;
        那么,什么时候pod会重建呢?=>`更新镜像版本时,pod会被重建`;
第2个问题:pod的ip是配置在哪个容器上的?
    =>ip是配置在`infra container上的`
  • 现在有个问题

问题:
1、如何让前端程序连接后端程序?--> LB
2、如何让多个前端程序为用户提供服务?-->LB

pod是短暂的,ip是会变的;
一般k8s里部署的pod都是多副本的,此时如果给用户/其它程序提供服务时,就需要有用到`负载均衡LB`来解决统一访问问题;

LB肯定是要关联服务器的具体IP的;
如果这一组pod版本更新后,ip也就发生了变化,如何能让LB的动态地感知这些ip发生的变化呢?(pod的动态发现)

负载均衡
Pod自动发现

=>此时,就需要用到service这个功能了。
  • service主要功能
service主要功能:
1、为pod提供负载均衡(iptables、ipvs)
2、动态感知pod的变化,会把pod ip变化更新到负载均衡
  • 注意:四层和七层的区别
OSI七层模型层

四层,传输层∶基于ip+port转发,不考虑其他的东西
七层,应用层∶会考虑协议层面东西,例如`域名、uri、cookie、 header`,典型的http协议.

3.Pod与Service的关系

  • Service通过标签关联一组Pod
  • Service使用iptables或者ipvs为一组Pod提供负载均衡能力

  • 注意:svc和项目是一一对应的
每个svc和每个业务项目都是一一对应的;
每部署一个deployment,就需要创建一个svc;
而deployment就是运行我们项目的地方;

  • 注意:service->deployment->pod
在k8s里,需要用到标签的,最常用的就是`deployment/service -> 多个pod`这些资源了,其他资源很少用到标签的;

2、三种IP

在继续往下学习 Service 之前,我们需要先弄明白 Kubernetes 系统中的三种IP,因为经常有同学混乱。

  • Node IP:Node 节点的 IP 地址
  • Pod IP: Pod 的 IP 地址
  • Cluster IP: Service 的 IP 地址

首先,Node IP 是 Kubernetes 集群中节点的物理网卡 IP 地址(一般为内网),所有属于这个网络的服务器之间都可以直接通信,所以 Kubernetes 集群外要想访问 Kubernetes 集群内部的某个节点或者服务,肯定得通过 Node IP 进行通信(这个时候一般是通过外网 IP 了)。

然后 Pod IP 是每个 Pod 的 IP 地址,它是网络插件进行分配的,前面我们已经讲解过。

最后 Cluster IP 是一个虚拟的 IP,仅仅作用于 Kubernetes Service 这个对象,由 Kubernetes 自己来进行管理和分配地址。

image-20230211155128918

3、定义 Service

定义 Service 的方式和我们前面定义的各种资源对象的方式类型,例如,假定我们有一组 Pod 服务,它们对外暴露了 8080 端口,同时都被打上了 app=myapp 这样的标签,那么我们就可以像下面这样来定义一个 Service 对象:

apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
    name: myapp-http

然后通过的使用 kubectl create -f myservice.yaml 就可以创建一个名为 myservice 的 Service 对象,**它会将请求代理到使用 TCP 端口为 8080,具有标签 **app=myapp的 Pod 上,**这个 Service 会被系统分配一个我们上面说的 Cluster IP,**该 Service 还会持续的监听 selector 下面的 Pod,会把这些 Pod 信息更新到一个名为 myservice 的Endpoints 对象上去,这个对象就类似于我们上面说的 Pod 集合了。

方法1:

#nginx-svc-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web #1
  template:
    metadata:
      labels:
        app: web #2
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - protocol: TCP
          containerPort: 80 #容器内暴露的端口,注意:这里的 containerPort: 80只是一个标识,真正起作用的还是容器里服务的端口号                        
          name: nginx-http

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  selector:
    app: web #3
  ports:
  - protocol: TCP
    port: 8080 #如果没有配置targetPort,则这里的port 要和容器内暴露的端口保持一致
    targetPort: 80 #—定要和容器内你的这个应用暴露的端口保持一致
    name: myapp-http

方法2:

需要注意的是,Service 能够将一个接收端口映射到任意的 targetPort默认情况下,targetPort 将被设置为与 port 字段相同的值可能更有趣的是,targetPort 可以是一个字符串,引用了 backend Pod 的一个端口的名称。因实际指派给该端口名称的端口号,在每个 backend Pod 中可能并不相同,所以对于部署和设计 Service,这种方式会提供更大的灵活性。

#nginx-svc-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web #1
  template:
    metadata:
      labels:
        app: web #2
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - protocol: TCP
          containerPort: 80 #容器内暴露的端口,注意:这里的 containerPort: 80只是一个标识,真正起作用的还是容器里服务的端口号                        
          name: nginx-http

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  selector:
    app: web #3
  ports:
  - protocol: TCP
    port: 8080 #如果没有配置targetPort,则这里的port 要和容器内暴露的端口保持一致
    targetPort: nginx-http  #80 #—定要和容器内你的这个应用暴露的端口保持一致,这里直接使用一个containerPort name也是可以的。
    name: myapp-http

另外 Service 能够支持 TCP 和 UDP 协议,默认是 TCP 协议。

image-20230211184311441

💘 实战:定义与创建Service-2023.2.11(测试成功)

image-20230211164553876

  • 实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
   k8s version:v1.25.4
   containerd://1.6.10
  • 实验软件(无)

  • 定义资源清单

#nginx-svc-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web #1
  template:
    metadata:
      labels:
        app: web #2
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - protocol: TCP
          containerPort: 80 #容器内暴露的端口,注意:这里的 containerPort: 80只是一个标识,真正起作用的还是容器里服务的端口号                        
          name: nginx-http

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  selector:
    app: web #3
  type: ClusterIP # 服务类型,默认就是ClusterIP
  ports:
  - protocol: TCP
    port: 8080 #如果没有配置targetPort,则这里的port 要和容器内暴露的端口保持一致
    targetPort: 80 #—定要和容器内你的这个应用暴露的端口保持一致
    name: myapp-http
  • 测试

下面我们来部署测试下效果,先部署下:

[root@master1 ~]#kubectl apply -f nginx-svc-demo.yaml
deployment.apps/nginx created
service/myservice created

[root@master1 ~]#kubectl get po -owide
NAME                    READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
nginx-f4f7749b5-8wqjs   1/1     Running   0          58s   10.244.1.60   node1   <none>           <none>
nginx-f4f7749b5-97dw5   1/1     Running   0          28s   10.244.2.56   node2   <none>           <none>
nginx-f4f7749b5-hrfnj   1/1     Running   0          28s   10.244.1.61   node1   <none>           <none>
[root@master1 ~]#kubectl get svc -owide
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE     SELECTOR
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    68d     <none>
myservice    ClusterIP   10.107.211.179   <none>        8080/TCP   4m49s   app=web
nginx        ClusterIP   None             <none>        80/TCP     54d     app=nginx
[root@master1 ~]#kubectl get ep -owide
NAME         ENDPOINTS                                      AGE
kubernetes   172.29.9.61:6443                               68d
myservice    10.244.1.60:80,10.244.1.61:80,10.244.2.56:80   4m52s
nginx        <none>                                         54d

我们来分别访问下podIP:80和svcIP:8080看下效果:

#访问podIP:80是可以的
[root@master1 ~]#curl 10.244.1.62:80
<!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>

#访问svcIP:80是可以的
[root@master1 ~]#curl 10.107.211.179:8080
<!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定义-2022.7.21(测试成功)

image-20230211184037022

多端口Service定义:对于某些服务,需要公开多个端口,Service也需要配置多个端口定义,通过端口名称区分。

  • 实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
   k8s version:v1.25.4
   containerd://1.6.10
  • 实验软件(无)

  • 编写service资源清单

# service-ports.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web
  name: web
spec:
  ports:
  - port: 80
    name: api1 #port:80 name
    protocol: TCP
    targetPort: 80 #port:80 
  - port: 81
    name: api2 #port:81 name
    protocol: TCP
    targetPort: 81 #port:81
  selector:
    app: web
  type: NodePort
  • 部署并查看
[root@k8s-master ~]#kubectl apply -f service3.yaml
[root@k8s-master ~]#kubectl get svc

一般这种情况,工作中很少用到。常规下,pod里只提供一个服务端口。

测试结束。😘

4、kube-proxy

前面我们讲到过,在 Kubernetes 集群中,每个 Node 会运行一个 kube-proxy 进程, 负责为 Service 实现一种 VIP(虚拟 IP,就是我们上面说的 clusterIP)的代理形式现在的 Kubernetes 中默认是使用的 iptables 这种模式来代理。

image-20230211190210557

我们来大概看下当前k8s集群kube-proxy组件的情况:

现在版本默认模式是ipvs;v1.22,已经默认使用ipvs了;

[root@master1 ~]#kubectl get po -A -owide

[root@master1 ~]#kubectl get ds -nkube-system
NAME              DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
kube-flannel-ds   3         3         3       3            3           <none>                   41d
kube-proxy        3         3         3       3            3           kubernetes.io/os=linux   41d

[root@master1 ~]#kubectl get ds kube-proxy -nkube-system -oyaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  annotations:
    deprecated.daemonset.template.generation: "1"
  creationTimestamp: "2021-10-31T00:00:26Z"
  generation: 1
  labels:
    k8s-app: kube-proxy
  name: kube-proxy
  namespace: kube-system
  resourceVersion: "808267"
  uid: ba1a6f0d-72c9-46a1-b295-594d860a7d70
spec:
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kube-proxy
  template:
    metadata:
      creationTimestamp: null
      labels:
        k8s-app: kube-proxy
    spec:
      containers:
      - command:
        - /usr/local/bin/kube-proxy
        - --config=/var/lib/kube-proxy/config.conf
        - --hostname-override=$(NODE_NAME)
        env:
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: spec.nodeName
        image: registry.aliyuncs.com/k8sxio/kube-proxy:v1.22.2
        imagePullPolicy: IfNotPresent
        name: kube-proxy
        resources: {}
        securityContext:
          privileged: true
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/lib/kube-proxy
          name: kube-proxy
        - mountPath: /run/xtables.lock
          name: xtables-lock
        - mountPath: /lib/modules
          name: lib-modules
          readOnly: true
      dnsPolicy: ClusterFirst
      hostNetwork: true
      nodeSelector:
        kubernetes.io/os: linux
      priorityClassName: system-node-critical
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: kube-proxy
      serviceAccountName: kube-proxy
      terminationGracePeriodSeconds: 30
      tolerations:
      - operator: Exists
      volumes:
      - configMap:
          defaultMode: 420
          name: kube-proxy
        name: kube-proxy
      - hostPath:
          path: /run/xtables.lock
          type: FileOrCreate
        name: xtables-lock
      - hostPath:
          path: /lib/modules
          type: ""
        name: lib-modules
  updateStrategy:
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1
    type: RollingUpdate
status:
  currentNumberScheduled: 3
  desiredNumberScheduled: 3
  numberAvailable: 3
  numberMisscheduled: 0
  numberReady: 3
  observedGeneration: 1
  updatedNumberScheduled: 3

1.iptables

这种模式,kube-proxy 会 watch apiserver (基本上所有的组件都是去watch api-server的)对 Service 对象和 Endpoints 对象的添加和移除。对每个 Service,它会添加上 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某一个 Pod 上面。我们还可以使用 Pod readiness 探针 验证后端 Pod 可以正常工作,以便 iptables 模式下的 kube-proxy 仅看到测试正常的后端,这样做意味着可以避免将流量通过 kube-proxy 发送到已知失败的 Pod 中,所以对于线上的应用来说一定要做 readiness 探针。

iptables 模式的 kube-proxy 默认的策略是,随机选择一个后端 Pod。

比如当创建 backend Service 时,Kubernetes 会给它指派一个虚拟 IP 地址,比如 10.0.0.1。假设 Service 的端口是 1234,该 Service 会被集群中所有的 kube-proxy 实例观察到。当 kube-proxy 看到一个新的 Service,它会安装一系列的 iptables 规则,从 VIP 重定向到 per-Service 规则。 该 per-Service 规则连接到 per-Endpoint 规则,该 per-Endpoint 规则会重定向(目标 NAT)到后端的 Pod。

注意:

假设没有kube-proxy,让大家去做这件事情,其实也可以,如果你对iptanles很熟悉的话,去定义一个虚拟ip,然后配置到我们的iptables规则上去,这样是可以达到预期效果的,是没问题的。

2.ipvs

除了 iptables 模式之外,kubernetes 也支持 ipvs 模式,在 ipvs 模式下,kube-proxy watch Kubernetes 服务和端点,调用 netlink 接口相应地创建 IPVS 规则, 并定期将 IPVS 规则与 Kubernetes 服务和端点同步。该控制循环可确保 IPVS 状态与所需状态匹配。访问服务时,IPVS将流量定向到后端 Pod 之一。

但是使用ipvs并不代表不使用iptables了,ipvs是依赖于iptables的。

IPVS 代理模式基于类似于 iptables 模式的 netfilter 钩子函数,但是使用**哈希表(我们知道哈希表在做查找的时候,它会比其他方式的效率要高很多)**作为基础数据结构,并且在内核空间中工作

所以与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy 重定向通信的延迟要短,并且在同步代理规则时具有更好的性能;

与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量;

所以对于较大规模的集群会使用 ipvs 模式的 kube-proxy,只需要满足节点上运行 ipvs 的条件,然后我们就可以直接将 kube-proxy 的模式修改为 ipvs,如果不满足运行条件会自动降级为 iptables 模式,现在都推荐使用 ipvs 模式,可以大幅度提高 Service 性能。

IPVS 提供了更多选项(负载策略)来平衡后端 Pod 的流量,默认是 rr,有如下一些策略:

  • rr: round-robin
  • lc: least connection (smallest number of open connections)
  • dh: destination hashing
  • sh: source hashing
  • sed: shortest expected delay
  • nq: never queue

不过现在只能整体修改策略,可以通过 kube-proxy 中配置 –ipvs-scheduler 参数来实现,暂时不支持特定的 Service 进行配置

我们也可以实现基于客户端 IP 的会话亲和性(session亲和性),可以将 service.spec.sessionAffinity 的值设置为 “ClientIP” (默认值为 “None”)即可,此外还可以通过适当设置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 来设置最大会话停留时间(默认值为 10800 秒,即 3 小时):

apiVersion: v1
kind: Service
spec:
  sessionAffinity: ClientIP
  ...

这个有些场景下还是很有用的:

[root@master1 ~]#kubectl explain svc.spec
……
   sessionAffinity	<string>
     Supports "ClientIP" and "None". Used to maintain session affinity. Enable
     client IP based session affinity. Must be ClientIP or None. Defaults to
     None. More info:
     https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies

   sessionAffinityConfig	<Object>
     sessionAffinityConfig contains the configurations of session affinity.
……    

[root@master1 ~]#kubectl explain svc.spec.sessionAffinityConfig.clientIP
KIND:     Service
VERSION:  v1

RESOURCE: clientIP <Object>:

DESCRIPTION:
     clientIP contains the configurations of Client IP based session affinity.

     ClientIPConfig represents the configurations of Client IP based session
     affinity.

FIELDS:
   timeoutSeconds	<integer>
     timeoutSeconds specifies the seconds of ClientIP type session sticky time.
     The value must be >0 && <=86400(for 1 day) if ServiceAffinity ==
     "ClientIP". Default value is 10800(for 3 hours).

比如你的后端服务是一个websocket或者是一些长链接服务的话,这个时候你可能最好配置下session亲和性。
如果你把这个sessionAffinity给它配置成ClientIP,比如说你现在有10个请求,那么10个请求来源于不同的ip地址的话,假设你第一个请求访问我们后端服务是到达了pod1,那么后面所有的9个请求流量都会到达pod1的,这样的话,就是基于ip的session亲和性

当然,更复杂的可能是基于header头来做session亲和性,但service不支持这种更复杂的配置。

所以,你要使用cookie的话,我们后面可以使用ingress,比如说有些ingress控制器可以帮我们做这些事情。

亲和性

Service 只支持两种形式的会话亲和性服务:None 和 ClientIP,不支持基于 cookie 的会话亲和性,这是因为 Service 不是在 HTTP 层面上工作的,处理的是 TCP 和 UDP 包,并不关心其中的载荷内容,因为 cookie 是 HTTP 协议的一部分,Service 并不知道它们,所以会话亲和性不能基于 Cookie。

image-20230211185810036

💘 实战:Service代理模式-kubeadm方式修改ipvs模式-2022.7.21(测试成功)

image.png

  • 实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
   k8s version:v1.25.4
   containerd://1.6.10
  • 实验软件(无)

1、查看当前services的代理模式是什么

查看由Daemonset控制器创建的kube-proxy

[root@k8s-master ~]#kubectl get pod -n kube-system #查看由Daemonset控制器创建的kube-proxy
NAME                                       READY   STATUS    RESTARTS   AGE
calico-kube-controllers-6949477b58-hlxp7   1/1     Running   13         18d
calico-node-c5sl6                          1/1     Running   31         18d
calico-node-l7q5q                          1/1     Running   9          18d
calico-node-sck2q                          1/1     Running   8          18d
etcd-k8s-master                            1/1     Running   9          18d
kube-apiserver-k8s-master                  1/1     Running   10         18d
kube-controller-manager-k8s-master         1/1     Running   10         18d
kube-proxy-9249w                           1/1     Running   12         18d
kube-proxy-mj7l5                           1/1     Running   8          18d
kube-proxy-p9bd4                           1/1     Running   9          18d
kube-scheduler-k8s-master                  1/1     Running   10         18d
[root@k8s-master ~]#

方法1:查看kube-system这个configmap

[root@master1 ~]#kubectl get cm kube-proxy -nkube-system -oyaml

方法2:看一个kube-proxypod的日志

[root@k8s-master ~]#kubectl logs kube-proxy-9249w -n kube-system

从这里可以看出kube-proxy维护的svc的代理模式类型是什么样的?:默认是ipatbles方式的。

kube-proxy是由DaemonSet控制器部署的
注意:但是这个并没有在/etc/kubernetes/manifests/目录下:

2、在线修改kube-proxy的configmap

一、kubeadm方式修改ipvs模式:
# kubectl edit configmap kube-proxy -n kube-system
...
mode: “ipvs“
...
# kubectl delete pod kube-proxy-btz4p -n kube-system

注:
1、kube-proxy配置文件以configmap方式存储
2、如果让所有节点生效,需要重建所有节点kube-proxy pod !!!这个需要注意!!!(那么问题来了:(1)直接修改这个会影响业务吗??--》应该会的吧。。。(2)这个也太繁琐了吧,如果有1000个节点,那么也要执行1000次删除pod操作吗?--》这个可以借助于label进行批量删除的;)
[root@k8s-master ~]#kubectl edit configmap kube-proxy -n kube-system #mode: ““
改成
mode: “ipvs“
[root@k8s-master ~]#

3、重启下kube-proxy

kube-proxy本身没有实现热更新的机制的,因此需要重启下kube-proxy:

实际测试过程:
这里先以一个kube-proxy为例:
先定位下这个kube-peoxy在哪个节点上?

在node1上:

在k8s-master上删除次kube-proxy pod:(一共有3个pod的,都需要进行删除重建操作的!!!

[root@k8s-master ~]#kubectl delete pod kube-proxy-9249w -n kube-system

4、all节点安装ipvsadm软件包font>

all节点都安装下ipvsadm软件包:

[root@k8s-node1 ~]#yum install -y ipvsadm
[root@k8s-node2 ~]#yum install -y ipvsadm
[root@k8s-master ~]#yum install -y ipvsadm

node1查看会有很多ipvs规则(node1上刚有更改kube-proxy的代理模式为ipvs):(k8s-node2效果一样)

virtual server和real server:

  • 我们现在在node1上访问这个cluster ip,它的流程是怎么样的呢?

5、查看ipvs代理模式包的传输流程

流程包流程:客户端 ->NodePort/ClusterIP(iptables/Ipvs负载均衡规则) -> 分布在各节点Pod

查看负载均衡规则:
• iptables模式
iptables-save |grep <SERVICE-NAME>

• ipvs模式
ipvsadm -L -n

cluster ip会绑定定到kube-ipvs0这个虚拟网卡上来的
这里和iptables不同,iptables是看不到任何实际网卡信息的,走的都iptables规则;

你要访问Nodeport也是走的这个规则:

实验结束.😘

💘 实战:二进制方式修改ipvs模式-2023.2.11(测试成功)

二进制方式修改ipvs模式:
# vi kube-proxy-config.yml 
mode: ipvs
ipvs:
scheduler: "rr“

# systemctl restart kube-proxy
注:配置文件路径根据实际安装目录为准

iptables vs ipvs

Iptables: 
• 灵活,功能强大
• 规则遍历匹配和更新,呈线性时延

IPVS: 
• 工作在内核态,有更好的性能
• 调度算法丰富:rr(默认是rr),wrr,lc,wlc,ip hash...


1、iptables代理模式
代理网关;
nat;
iptables功能强大;
iptables可以做到防火墙的功能,还可以做到负载均衡器的功能;
但,时延性比较大;


2、ipvs代理模式:
ipvsadm -L -n #-n代表不以名字显示(不加的话,可能会很慢,因为需要域名解析)  -L代表以列出来
从上往下匹配规则;
QA:ipvs的几种模式:NAT,DR,还有个叫什么来着的?
QA:换成ipvs后,k8s还是要用到iptables的,只是iptables里面的规则没那么多了;


总结:
小规模几十台可以使用iptables规则,但大规模(几百台以上)可以选择使用ipvs,效率会更好些;
目前ipvs已经很成熟了:国内大厂都在用;

所以说,ipvs的效率要比iptables高,特别是当你service服务达到一定数量级之后的话,ipvs的性能要比iptables高出很多个数量级的。
所以说,如果你的集群node数量非常大,你的service服务也非常大,我们就要考虑使用ipvs了。但是,如果你的k8s集群规模较小,基本上可能小于10个节点,还是使用iptables比较好一些,但是你要使用ipvs,还是没有任何问题的。

之前,看到网上有人对ipvs和iptables 2种代理模式做了性能测试的,iptables在node节点为个位数,或者10几个节点的话,那么使用iptables代理模式效率是更高的。但是,当你超过了这个阈值之后,达到50个节点,100个节点,1000个节点,那么在性能上,ipvs要超过iptables好几个数量级的。

5、Service常见类型

我们在定义 Service 的时候可以指定一个自己需要的类型的 Service,如果不指定的话默认是 ClusterIP类型。

我们可以使用的服务类型如下:

1.ClusterIP

集群内部使用

通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的服务类型。

2.NodePort

对外暴露应用

通过每个 Node 节点上的 IP 和**静态端口(NodePort)**暴露服务。**NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。**通过请求 NodeIp:NodePort(注意是k8s集群的每个节点都可以通过这种方式去访问的),可以从集群的外部访问一个 NodePort 服务。
访问地址:<任意NodeIP>:
每个NodePort都会随机分配一个端口号,端口范围:30000-32767

注意:service的nodePort的取值范围有默认值:30000-32767,你手动指定的话,也要在这个范围内才可以的,当然不在这个范围内,也是可以的,但就有可能和其它端口冲突。

  • 负载均衡器

NodePort:会在每台Node上监听端口接收用户流量,在实际情况下,对用户暴露的只会有一个IP和端口,那这么多台Node该使哪台让用户访问呢?这时就需要前面加一个公网负载均衡器为项目提供统一访问入口了

image-20230211214244531

  • kubernetes主服务

这里有个kubernetes主服务概念,也就是deafult命名空间下的kubernetesservice:

💘 实战:NodePort测试-2023.2.11(测试成功)

image-20230211220935953

  • 实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
   k8s version:v1.25.4
   containerd://1.6.10
  • 实验软件(无)
  • 接下来我们来给大家创建一个 NodePort 的服务来访问我们的 Nginx 服务:
# service-nodeport-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web #1
  template:
    metadata:
      labels:
        app: web #2
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - protocol: TCP
          containerPort: 80 #容器内暴露的端口,注意:这里的 containerPort: 80只是一个标识,真正起作用的还是容器里服务的端口号                        
          name: nginx-http

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  selector:
    app: web #3
  type: NodePort # 服务类型,默认是ClusterIP
  ports:
  - protocol: TCP
    port: 8080 #如果没有配置targetPort,则这里的port 要和容器内暴露的端口保持一致
    targetPort: nginx-http #—定要和容器内你的这个应用暴露的端口保持一致
    name: myapp-http
  • 部署并测试
[root@master1 ~]#kubectl apply -f service-nodeport-demo.yaml
deployment.apps/nginx created
service/myservice created

[root@master1 ~]#kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
nginx-757d547965-7rhbp   1/1     Running   0          55s
nginx-757d547965-t6h6m   1/1     Running   0          25s
nginx-757d547965-zkxs9   1/1     Running   0          25s
[root@master1 ~]#kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP          68d
myservice    NodePort    10.103.191.211   <none>        8080:32302/TCP   6m37s

#测试,符合预期
[root@master1 ~]#curl 172.29.9.62:32302
<!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>

我们可以看到 myservice 的 TYPE 类型已经变成了 NodePort,后面的 PORT(S) 部分也多了一个 32302的映射端口。

测试结束。😘

3.LoadBalancer

对外暴露应用,适用公有云

**使用云提供商的负载局衡器,可以向外部暴露服务。**外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务,这个需要结合具体的云厂商进行操作。(注意:LoadBalancer也会自动生成NodePort,而NodePort也会自动生成ClusterIp的。)

LoadBalancer:与NodePort类似,在每个节点上启用一个端口来暴露服务。除此之外,Kubernetes会请求底层云平台(例如阿里云、腾讯云、AWS等)上的负载均衡器,将每个Node([NodeIP]:[NodePort])作为后端添加进去。

image-20230211214458297

4.ExternalName

ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。

[root@master1 ~]# kubectl explain svc.spec
"ExternalName"
     aliases this service to the specified externalName. Several(各自的,几个的) other fields do
     not apply to ExternalName services. More info:
     https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
     
   externalName <string>
     externalName is the external reference that discovery mechanisms will
     return as an alias for this service (e.g. a DNS CNAME record). No proxying
     will be involved(涉及). Must be a lowercase RFC-1123 hostname
     (https://tools.ietf.org/html/rfc1123) and requires `type` to be
     "ExternalName".

ExternalName 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

当访问地址 my-service.prod.svc.cluster.local(后面服务发现的时候我们会再深入讲解)时,集群的 DNS 服务将返回一个值为 my.database.example.com 的 CNAME 记录。访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在 DNS 层而且不会进行代理或转发。如果后续决定要将数据库迁移到 Kubernetes 集群中,可以启动对应的 Pod,增加合适的 Selector 或 Endpoint,修改 Service 的 type,完全不需要修改调用的代码,这样就完全解耦了。

注意下如下讲解内容:

以后在访问我们的service的时候,其实就被重定向到了下面这个mysql.rds.beijing.aliyuncs.com这个域名了。那为什么要做这样一个事情呢?

这种类型更多的可能是用于我们服务迁移的这样一个场景,比如最开始我们的服务是部署在我们的rds上的,现在我们就把服务迁移到了k8s上来了,但是例如我们这个mysql服务不会立刻把它部署到我们的k8s上去,那么这样的话,如果我们的程序还是写上之前的mysql.rds.beijing.aliyuncs.com,那以后我把它迁移到了k8s过后,我还要去修改我的程序,如果能够把它统一当成k8s里面的一个服务的话,那是不是就比较好了,所以这个时候,我们就可以把这个mysql.rds.beijing.aliyuncs.com去做一个externalname,去做一个CNAME的映射。

所以,这种类型更多的是在我们服务迁移的时候使用的。

服务迁移(过渡期)

image-20230211212027354

💘 实战:ExternalName类型自定义Endpoints测试-2023.2.12(测试成功)

image-20230212091641950

  • 实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
   k8s version:v1.25.4
   containerd://1.6.10
  • 实验软件(无)

  • 除了可以直接通过 externalName 指定外部服务的域名之外,我们还可以通过自定义 Endpoints 来创建 Service,前提是 clusterIP=None,名称要和 Service 保持一致,如下所示:

apiVersion: v1
kind: Service
metadata:
  name: etcd-k8s
  namespace: kube-system
  labels:
    k8s-app: etcd
spec:
  type: ClusterIP
  clusterIP: None
  ports:
  - name: port
    port: 2379

---

apiVersion: v1
kind: Endpoints
metadata:
  name: etcd-k8s  # 名称必须和 Service 一致
  namespace: kube-system
  labels:
    k8s-app: etcd
subsets:
- addresses:
  - ip: 10.151.30.57  # Service 将连接重定向到 endpoint
  ports:
  - name: port
    port: 2379   # endpoint 的目标端口

上面这个服务就是将外部的 etcd 服务引入到 Kubernetes 集群中来。

  • 自定义Endpoints

我们可以看到,当前集群只有一个etcd.

假设我们再k8s集群外部有一个etcd集群(3个节点),这个时候,我想在k8s集群内部来使用这个集群,该怎么做呢?最好的方式就是把这个外部etcd集群给它引入到k8s集群里来,怎么引入呢?–>“自定义Endpoints引入进来”,因为我们刚才说过,如果创建service的话,默认就会创建一个同名的Endpoints.

这边来创建下资源清单文件:

#etcd-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: etcd-svc
spec:
  type: ClusterIP
  clusterIP: None #一定没有clusterIP,要设置成None  没有定义selector
  ports:
  - name: client
    port: 2379

---
apiVersion: v1
kind: Endpoints
metadata:
  name: etcd-svc
subsets: #配置subsets
- addresses: #Service 将连接重定向到Endpoints列表                        
  - ip: 10.151.30.11
  - ip: 10.151.30.20
  - ip: 10.151.30.23
  ports:
  - name: client
    port: 2379
  • 部署并查看
[root@master1 ~]#kubectl apply -f etcd.svc.yaml 
service/etcd-svc unchanged
endpoints/etcd-svc created

[root@master1 ~]#kubectl get svc etcd-svc
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
etcd-svc   ClusterIP   None         <none>        2379/TCP   60s

[root@master1 ~]#kubectl get ep etcd-svc
NAME       ENDPOINTS                                               AGE
etcd-svc   10.151.30.11:2379,10.151.30.20:2379,10.151.30.23:2379   46s

[root@master1 ~]#kubectl describe svc etcd-svc
Name:              etcd-svc
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP Family Policy:  RequireDualStack
IP Families:       IPv4,IPv6
IP:                None
IPs:               None
Port:              client  2379/TCP
TargetPort:        2379/TCP
Endpoints:         10.151.30.11:2379,10.151.30.20:2379,10.151.30.23:2379
Session Affinity:  None
Events:            <none>
[root@master1 ~]#

注意:这种方式和我们之前的ClusterIP类型类似,唯一不同的就是这种类型没有ClusterIp,那么这种情况下,我们该如何去访问我们的etcd-svc呢?

这个的话,就要做一下我们的service的自动发现了,下节课和大家讲解一下。

因为我们的ClusterIP也是不稳定的。

假如说我们的svc-demo被重建了,那么这个CLusterIP也会变化的。所以它也不是一个稳定的方式。

所以,通过ClusterIp去访问我们的服务也是不靠谱的,这个下节课我们来讲一下我们服务发现的知识。

实验结束。😘

5.externalIPs

Service 的属性中还有一个 externalIPs 的属性,从 Kubernetes 官网文档可以看到该属性的相关描述:

如果外部的 IP 路由到集群中一个或多个 Node 上,Kubernetes Service 会被暴露给这些 externalIPs。通过外部 IP(作为目的 IP 地址)进入到集群,传到 Service 的端口上的流量,将会被路由到 Service 的Endpoint 上,externalIPs 不会被 Kubernetes 管理,它属于集群管理员的职责范畴。

这里最重要的一点就是确保使用哪个 IP 来访问 Kubernetes 集群,使用外部 IP Service 类型,我们可以将Service 绑定到连接集群的 IP。

参考文档:https://www.fadhil-blog.dev/blog/kubernetes-external-ip/。

云环境上可能回用到这个,但一般情况下是用不到这个的。

  • 注意

[root@master1 ~]#kubectl explain svc.spec

   externalIPs  <[]string>
     externalIPs is a list of IP addresses for which nodes in the cluster will
     also accept traffic for this service. These IPs are not managed by
     Kubernetes. The user is responsible for ensuring that traffic arrives at a
     node with this IP. A common example is external load-balancers that are not
     part of the Kubernetes system.

6、Endpoints 与 Endpointslices

1.Endpoints

我们已经知道在 Service 创建时,Kubernetes 会根据 Service 关联一个 Endpoints 资源,若 Service 没有定义 selector 字段,将不会自动创建 Endpoints。Endpoints 是 Kubernetes 中的一个资源对象,存储在 etcd中,用来记录一个 Service 对应一组 Pod 的访问地址,一个 Service 只有一个 Endpoints 资源,Endpoints 资源会去观测 Pod 集合,只要服务中的某个 Pod 发生变更,Endpoints 就会进行同步更新。

💘 实战:Endpoints测试-2023.2.12(测试成功)

image-20230212111610532

  • 实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
   k8s version:v1.25.4
   containerd://1.6.10
  • 实验软件(无)

  • 比如现在我们部署如下所示 3 个副本的 httpbin 测试应用:

# httpbin-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 3
  selector:
    matchLabels:
      app: httpbin
  template:
    metadata:
      labels:
        app: httpbin
    spec:
      containers:
        - name: httpbin
          image: kennethreitz/httpbin:latest
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
 
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
spec:
  selector:
    app: httpbin
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 80
  type: ClusterIP
  • 在该应用中我们定义了 3 个副本,然后创建了一个 Service 对象来关联这些 Pod,直接应用该资源清单即可:
[root@master1 ~]#kubectl apply -f httpbin-deploy.yaml
deployment.apps/httpbin created
service/httpbin created

[root@master1 ~]# kubectl get pods -l app=httpbin -o wide
NAME                       READY   STATUS    RESTARTS   AGE     IP           NODE    NOMINATED NODE   READINESS GATES
httpbin-549c696b8d-2v67s   1/1     Running   0          8m53s   10.244.1.5   node1   <none>           <none>
httpbin-549c696b8d-jjr27   1/1     Running   0          8m53s   10.244.2.5   node2   <none>           <none>
httpbin-549c696b8d-lljkw   1/1     Running   0          8m53s   10.244.2.4   node2   <none>           <none>
[root@master1 ~]# kubectl get pods -l app=httpbin -o wide
NAME                       READY   STATUS    RESTARTS   AGE     IP           NODE    NOMINATED NODE   READINESS GATES
httpbin-549c696b8d-2v67s   1/1     Running   0          8m53s   10.244.1.5   node1   <none>           <none>
httpbin-549c696b8d-jjr27   1/1     Running   0          8m53s   10.244.2.5   node2   <none>           <none>
httpbin-549c696b8d-lljkw   1/1     Running   0          8m53s   10.244.2.4   node2   <none>           <none>
[root@master1 ~]# kubectl get svc httpbin
NAME      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
httpbin   ClusterIP   10.103.29.35   <none>        80/TCP    9m14s
[root@master1 ~]# kubectl get ep httpbin
NAME      ENDPOINTS                                   AGE
httpbin   10.244.1.5:80,10.244.2.4:80,10.244.2.5:80   9m24s
[root@master1 ~]# kubectl get ep httpbin -oyaml
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    endpoints.kubernetes.io/last-change-trigger-time: "2023-02-12T03:04:54Z"
  creationTimestamp: "2023-02-12T03:04:00Z"
  name: httpbin
  namespace: default
  resourceVersion: "8431"
  uid: ecce8bfc-e1dc-4a11-bd76-46ed766d1daf
subsets:
- addresses:
  - ip: 10.244.1.5
    nodeName: node1
    targetRef:
      kind: Pod
      name: httpbin-549c696b8d-2v67s
      namespace: default
      uid: 1fd41301-ecbe-4e12-ba73-80430e2842dd
  - ip: 10.244.2.4
    nodeName: node2
    targetRef:
      kind: Pod
      name: httpbin-549c696b8d-lljkw
      namespace: default
      uid: 47d28332-6763-42d8-a5c2-8729abfab7cb
  - ip: 10.244.2.5
    nodeName: node2
    targetRef:
      kind: Pod
      name: httpbin-549c696b8d-jjr27
      namespace: default
      uid: b80a0452-631d-49f3-a17c-acf4111a5b91
  ports:
  - name: http
    port: 80
    protocol: TCP
[root@master1 ~]#

由于我们这里创建了一个 Service 对象,所以也会自动创建一个对应的 Endpoints 对象,只有当 Pod 正常运行后才会被包含到 Endpoints 对象中去,所以往往如果是线上应用我们都是强烈推荐为 强烈推荐为 Pod 配置上 readiness probe 的,当应用还未就绪的时候就不会进入 Endpoints,也就不会对外提供服务了,当应用下线后就从该对象中摘掉。

从上述示例可以看到,Endpoints 中的所有网络端点,分别对应了每个 Pod 的 IP 地址,也就是上面对象中的subsets 里面的数据。

测试结束。😘

2.Endpoints 的不足之处

但实际上 Endpoints 也有它的一些不足之处,比如:

  • Kubernetes 限制单个 Endpoints 对象中可以容纳的端点数量。当一个服务有超过 1000 个后备端点时,Kubernetes 会截断 Endpoints 对象中的数据,这种情况下,Kubernetes 选择最多1000 个可能的后端端点来存储到 Endpoints 对象中,并在 Endpoints 中配置上endpoints.kubernetes.io/over-capacity:truncated 注解。如果后端 Pod 的数量低于 1000,控制平面会移除该注解。

  • 一个 Service 只有一个 Endpoints 资源,这意味着它需要为支持相应服务的每个 Pod 存储 IP 等网络信息。这导致 Endpoints 资源变的十分巨大,其中一个端点发生了变更,将会导致整个 Endpoints 资源更新。当业务需要进行频繁端点更新时,一个巨大的 API 资源被相互传递,而这会影响到 Kubernetes 组件的性能,并且会产生大量的网络流量和额外的处理。

所以当你的应用规模达到了上千规模的话,我们就不能使用原有的这种方案了,一个新的对象 Endpointslices 就出现了,Endpointslices 为 Endpoints 提供了一种可扩缩和可拓展的替代方案,缓解处理大量网络端点带来的性能问题,还能为一些诸如拓扑路由的额外功能提供一个可扩展的平台,该特性在 Kubernetes v1.21+ 的版本中已提供支持。

image-20230212135850966

默认情况下,控制面创建和管理的 EndpointSlice 将包含不超过 100 个端点,但是我们可以使用 kubecontroller-manager 的 --max-endpoints-per-slice 标志设置此值,其最大值为 1000。

3.为什么需要 Endpointslices

为了说明这些问题的严重程度,这里举一个简单的例子。

假设我们有一个 2000 个 Pod 副本的服务,它最终生成的Endpoints 资源对象会很大(比如 1.5MB (etcd 具有最大请求大小限制1.5MB),还会截断成 1000),在生产环境中,如果该服务发生滚动更新或节点迁移,那么 Endpoints 资源将会频繁变更,当该列表中的某个网络端点发生了变化,那么就要将完整的 Endpoint 资源分发给集群中的每个节点。如果我们在一个具有 3000 个节点的大型集群中,这会是个很大的问题,每次更新将跨集群发送 4.5GB 的数据(1.5MB*3000 ,即 Endpoint 大小*节点个数),并且每次端点更新都要发送这么多数据。想象一下,如果进行一次滚动更新,共有 2000 个 Pod 全部被替换,那么传输的数据量将会是TB 级数据。这不进对集群内的网络带宽浪费巨大,而对 Master 的冲击非常大,会影响 Kubernetes 整体的性能。

image-20230212140157292

image-20230212095352273

如果使用了 Endpointslices,假设一个服务后端有 2000 个 Pod,我们可以让每个 Endpointslices 存储 100 个端点,最终将获得 20 个 Endpointslices。添加或删除 Pod 时,只需要更新其中 1 个 Endpointslice 资源即可,这样操作后,可扩展性和网络可伸缩有了很大的提升。

比起在流量高峰时,服务为了承载流量,扩容出大量的 Pod,Endpoints 资源会被频繁更新,两个使用场景的差异就变得非常明显。更重要的是,既然服务的所有 Pod IP 都不需要存储在单个资源中,那么我们也就不必担心 etcd 中存储的对象的大小限制。

image-20230212095711856

所以我们可以了解到 Endpoints 和 Endpointslice 两种资源的不同之处了。

  • Endpoints 适用场景

    • 有弹性伸缩需求,Pod 数量较少,传递资源不会造成大量网络流量和额外处理。

    • 无弹性伸缩需求,Pod 数量不会太多。哪怕 Pod 数量是固定,但是总是要滚动更新或者出现故障的。

  • Endpointslice 适用场景:

    • 有弹性需求,且 Pod 数量较多(几百上千)。

    • Pod 数量很多(几百上千),因为 Endpoints 网络端点最大数量限制为 1000,所以超过 1000 的 Pod 必须得用 Endpointslice。

4.EndpointSlice 使用

💘 实战:EndpointSlice 使用-2023.2.12(测试成功)

image-20230212142709737

  • 实验环境
实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点
   k8s version:v1.25.4
   containerd://1.6.10
  • 实验软件(无)

EndpointSlice 控制器会 Watch Service 和 Pod 来自动创建和更新 EndpointSlices 对象,然后 kube-proxy同样会 Watch Service 和 EndpointSlices 对象来更新 iptables 或 ipvs proxy 规则。

  • 由于默认情况下,控制面创建和管理的 EndpointSlice 将包含不超过 100 个端点,为了测试方便,我们这里将其修改为5,将 kube-controller-manager 的 --max-endpoints-per-slice 标志设置为 5,最大值为 1000。
[root@master1 ~]#vim  /etc/kubernetes/manifests/kube-controller-manager.yaml
--max-endpoints-per-slice=5

image-20230212141431782

  • 比如我们现在创建如下的资源对象:
# httpbin-deploy-slice.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 17
  selector:
    matchLabels:
      app: httpbin
  template:
    metadata:
      labels:
        app: httpbin
    spec:
      containers:
        - name: httpbin
          image: kennethreitz/httpbin:latest
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
 
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
spec:
  selector:
    app: httpbin
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 80
  type: ClusterIP
  • 直接应用即可
[root@master1 ~]# kubectl apply -f httpbin-deploy-slice.yaml
deployment.apps/httpbin created
service/httpbin created
  • 当 Pod 和 Service 创建后便会自动创建 EndpointSlices 对象了:
[root@master1 ~]#kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
httpbin      ClusterIP   10.111.73.187    <none>        80/TCP           13s
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP          69d
myservice    NodePort    10.103.191.211   <none>        8080:32302/TCP   16h
[root@master1 ~]#kubectl get endpointslice
NAME              ADDRESSTYPE   PORTS     ENDPOINTS                                         AGE
httpbin-k9hv7     IPv4          80        10.244.2.20,10.244.2.22                           23s
httpbin-ns5hx     IPv4          80        10.244.2.16,10.244.1.19,10.244.2.14 + 2 more...   25s
httpbin-t5r2s     IPv4          80        10.244.1.20,10.244.2.17,10.244.1.22 + 2 more...   24s
httpbin-wqklk     IPv4          80        10.244.1.16,10.244.1.17,10.244.1.15 + 2 more...   30s
kubernetes        IPv4          6443      172.29.9.61                                       69d
myservice-jkhgx   IPv4          <unset>   <unset>                                           16h

#注意:这里也是有ep对象的。
[root@master1 ~]#kubectl get ep
NAME         ENDPOINTS                                                   AGE
httpbin      10.244.1.15:80,10.244.1.16:80,10.244.1.17:80 + 14 more...   9m19s
kubernetes   172.29.9.61:6443                                            69d
myservice    <none>                                                      16h

由于我们这里配置的每个 EndpointSlice 最多有 5 个 Endpoint,一共 17 个副本,所以这里自动创建了 4 个EndpointSlices 对象,当然现在 kube-proxy 组件会 Watch 所有的这个 4 个对象。

image-20230212141708427

但是当我们更新一个 Pod 的时候只有包含该 Pod 的 EndpointSlice 对象变更,该对象中包含的 Endpoints 列表非常少,这就大大提升了性能。

  • 我们可以查看任意一个 EndpointSlice 对象,如下所示:
[root@master1 ~]#kubectl get endpointslice httpbin-k9hv7 -oyaml
addressType: IPv4
apiVersion: discovery.k8s.io/v1
endpoints:
- addresses:
  - 10.244.2.20
  conditions:
    ready: true
    serving: true
    terminating: false
  nodeName: node2
  targetRef:
    kind: Pod
    name: httpbin-549c696b8d-vrmlr
    namespace: default
    uid: 808bff2e-011b-4a81-8ada-7a4ba2821bdb
- addresses:
  - 10.244.2.22
  conditions:
    ready: true
    serving: true
    terminating: false
  nodeName: node2
  targetRef:
    kind: Pod
    name: httpbin-549c696b8d-vfsbv
    namespace: default
    uid: cace1a79-2d0b-44d2-940e-673bfe0a8057
kind: EndpointSlice
metadata:
  annotations:
    endpoints.kubernetes.io/last-change-trigger-time: "2023-02-12T06:15:32Z"
  creationTimestamp: "2023-02-12T06:15:35Z"
  generateName: httpbin-
  generation: 2
  labels:
    endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io
    kubernetes.io/service-name: httpbin
  name: httpbin-k9hv7
  namespace: default
  ownerReferences:
  - apiVersion: v1
    blockOwnerDeletion: true
    controller: true
    kind: Service
    name: httpbin
    uid: fcd0bbc9-215d-4d3b-bcb0-686ee4d6388a
  resourceVersion: "25120"
  uid: e1ad3c60-7839-487e-8c5f-f7c7cddbaecb
ports:
- name: http
  port: 80
  protocol: TCP
[root@master1 ~]#

从上面可以看到 EndpointSlice 对象中通过 endpoints 属性来保存 Endpoint 数据,每个 Endpoint 中 包含Pod IP、节点名以及关联的 Pod 对象信息,其中还包括一个 conditions 属性。

- addresses:
  - 10.244.2.20
  conditions: #端点的状态
    ready: true
    serving: true
    terminating: false
  nodeName: node2
  targetRef:
    kind: Pod
    name: httpbin-549c696b8d-vrmlr
    namespace: default
    uid: 808bff2e-011b-4a81-8ada-7a4ba2821bdb

EndpointSlice 存储了可能对使用者有用的、有关端点的状态,分别是 ready、 serving 和 terminating 。

  • Ready(就绪):ready 是映射 Pod 的 Ready 状况的。对于处于运行中的 Pod,它的 Ready 状况被设置为True,应该将此 EndpointSlice 状况也设置为 true。出于兼容性原因,当 Pod 处于终止过程中,ready 永远不会为 true。

  • Serving(服务中):serving 状况与 ready 状况相同,不同之处在于它不考虑终止状态。如果 EndpointSliceAPI 的使用者关心 Pod 终止时的就绪情况,就应检查此状况。

  • Terminating(终止中):terminating 是表示端点是否处于终止中的状况,对于 Pod 来说,这是设置了删除时间戳的 Pod。

此外 EndpointSlice 中的每个端点都可以包含一定的拓扑信息,拓扑信息包括端点的位置,对应节点、可用区的信息,这些信息体现为 EndpointSlices 的如下端点字段:

  • nodeName - 端点所在的 Node 名称
  • zone - 端点所处的可用区。

为了确保多个实体可以管理 EndpointSlice 而且不会相互产生干扰, Kubernetes 定义了标签endpointslice.kubernetes.io/managed-by,用来标明哪个实体在管理某个 EndpointSlice。 端点切片控制器会在自己所管理的所有 EndpointSlice 上将该标签值设置为 endpointslice-controller.k8s.io,管理EndpointSlice 的其他对象也应该为此标签设置一个唯一值。

此外还有一个地址类型 AddressType:IPv4、IPv6、FQDN(全限定域名)。

关于 Service 的使用还有很多其他特性,比如 LoadBalancer,我们将在后面的课程中慢慢接触到。

测试结束。😘

FAQ

label和annotations的区别

# volume-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: volume-pod
  namespace: kube-system
  labels:
    k8s-app: test-volume
    node-env: test
  annotations:
    own: youdianzhishi
    build: test

1.label更多用于开发 人员和我们运维人员,对使用k8s人员来说的,用于来标记的;用于资源对象匹配和用户筛选用的;
2.annotations更多是用于其它应用程序来读取资源对象时来使用的;annotations就是注解;

注意

service,一般是没有什么情况,要去删除它的,因此这个ip是可以长期占有的;
service只认标签,其他一概不认;
service就是一个声明,没有什么重启的概念
cluster IP是对应svc的,一般不会变啊,除非你删除了svc然后重建;

通过监听端口来引流的;

注意:公有云的话,一般推荐使用`LoadBalancer`;
自建k8s的话,推荐使用`NodePort`;

Pod对外暴露:集群之内(pod、node)的其他应用,集群之外的其他应用或者用户;

#注意:CLUSTER-IP
CLUSTER-IP可以用`kubectl get svc`输出的;
Pod IP可以用`kubectl get pod -o wide`输出;(kubectl get ep也可以输出)

Cluster Ip是k8s集群之内的网络;
Service三种常用类型都是拥有这个`cluster ip`的:service默认都会创建一个cluster ip;
VIP:集群ip,虚拟ip;
=>vip,  cluster ip是一个虚拟地址存在的;
cluster ip可以手动指定,但是没必要;

这个Cluster ip在集群内部,任何POd,任何node都是可以访问到的;
那么问题来了:
node应该都是可以直接访问任何ns下的svc的; 但pod里可以访问任何ns下的svc吗?;--是可以的;

关于我

我的博客主旨:

  • 排版美观,语言精炼;
  • 文档即手册,步骤明细,拒绝埋坑,提供源码;
  • 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!

🍀 微信二维码
x2675263825 (舍得), qq:2675263825。
image.png

🍀 微信公众号
《云原生架构师实战》
image.png

🍀 语雀
https://www.yuque.com/xyy-onlyone
https://www.yuque.com/xyy-onlyone/exkgza?# 《语雀博客》
image.png

🍀 博客
www.onlyyou520.com
image.png
image.png

🍀 csdn
https://blog.csdn.net/weixin_39246554?spm=1010.2135.3001.5421
image.png

🍀 知乎
https://www.zhihu.com/people/foryouone
image.png

最后

好了,关于本次就到这里了,感谢大家阅读,最后祝大家生活快乐,每天都过的有意义哦,我们下期见!

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐