kubernetes-服务发现、endpoint、headless、

背景: 一些组织将生产环境 前到k8s 要经历的步骤

第一阶段: 先迁移无状态服务 无状态应用, 如果要请求有状态服务的话 只能请求外部

  • 方式1 写死请求地址IP PORT
  • 方式2 将有状态服务的 dns名称做一个别名(ExternalName) 放到k8s中
  • 方式3 headless service

第二阶段: 逐渐迁移有状态应用

可能保留一部分 ExternalName

第三阶段: 应用重构

面向k8s环境 重复利用k8s 去重写整个应用

第四阶段: 微服务化

Service 介绍

Service是Kubernetes标准的API资源类型之一

功能: 负责服务发现功能

过标签选择器筛选同一名称空间下的Pod资源的标签,完成Pod筛选 - 实际上是由与Service同名的Endpoint或EndpointSlice资源及控制器完成 - 自动创建Endpoint资源

功能: 负责流量调度、流量分发

由运行各工作节点的kube-proxy根据配置的模式生成相应的流量调度规则

  • iptables
  • ipvs

模型的配置策略

访问路径

client -->service对象 service_ip:service_port---> pod对象 pod_ip:pod_port

功能1 为动态的Pod资源提供近似静态的流量入口

功能2 持续监视着相关Pod资源的变动,并实时反映至相应的流量调度规则之上

  • 服务发现

Service服务发现

  • 以pod形式运行的服务
  • pod添加lable 后面通过 lable selector
  • 注解

动态可配置负载均衡

从Service的视角来看,Kubernetes集群的每个工作节点都是动态可配置的负载均衡器

  • 对于隶属某Service的一组Pod资源,该Service资源能够将集群中的每个工作节点配置为该组Pod的负载均衡器
  • 客户端可以是来自集群之上的Pod,也可以是集群外部的其它端点
  • 对于一个特定的工作节点上的某Service来说,其客户端通常有两类
    • 集群内部的client(该节点之上的进程) 可通过该Service的Service_IP:Service_Port 访问进入
    • 集群外部的client(该节点之外的端点) 可经由Node_IP:Node_Port 访问进入
      • iptable中存在 NAT规则

问题

显然,若客户端进入的节点,并非目标Pod所在的节点时,报文转发的路径中必然存在跃点 我们期望访问node的时候 直接将流量给当前node上的pod 不要再经过其他的路由

解决方案:

client-->外部的负载均衡器(决定调用哪个NodeIP)-->Node_IP:Node_Port

外部的负载均衡器 请求apiserver 获取当前service关联的pod 以及 pod所在的节点, 当外部的负载均衡器接收到请求时 直接将流量调度给运行了该pod的node节点

Service类型

可以分为4种类型

ClusterIP

集群内的IP, 仅支持集群内的客户端请求 因为需要通过节点的内核进行转发 支持Service_IP:Service_Port接入

NodePort

全方位的地址转换, 源地址转换 目标地址转换

核心作用: 接入集群外部的客户端 对集群内的service请求

请求方式: NodeIP + Nodeport是 接入外部流量的接口

  • Nodeport是指 30000-32768端口 是动态分配的

只适用于内部服务之间 需要告诉客户端访问的端口

支持Node_IP:Node_Port接入,同时支持ClusterIP 增强版的ClusterIP

NodePort访问模型

LoadBalancer(需要自己开发)

核心作用: 将Node_Port暴露的非标端口、动态端口 以一个标准的方式暴露出去

访问路径: client -> LB IP:LB PORT -> Node_IP:Node_Port -> POD IP:PORT

  • LB IP(附着在某个NODE上 或者云厂商提供的IP)
  • LB的负载均衡算法 挑选出NODE
  • NodePort的负载均衡算法 挑出Pod

问题: NODE到POD 可能还会经过一次转发 可能出现二次转发,Node_IP和POD IP不是一台机器 需要再经过一次转发, 所以尽可能选出的Node和Pod在一起 避免流量大的问题

externalTrafficPolicy参数(流量策略)

  • local 当一个节点收到请求后 只能调用当前Node运行的Pod
  • cluster 默认的值 接收到请求后可以调度给任意一个pod

支持通过外部的LoadBalancer的LB_IP:LB_Port接入,同时支持NodePort和ClusterIP;

  • Kubernetes集群外部的LoadBalancer负责将接入的流量转发至工作节点上的NodePort
  • LoadBalancer需与相关的NodePort的Service生命周期联动
  • LoadBalancer 需要和apiserver联动 获取当前service关联的pod 以及 pod所在的节点
    • LoadBalancer应该是由软件定义
    • Kubernetes需要同LoadBalancer所属的管理API联动

LoadBalancer访问模型

ExternalName 外部名称

应用部署在k8s内部 数据库在集群外部 两者通信怎么办?

请求路径: client -> servicename:service_port -> externalservice_dnsname:port -> externalip:port

  • 让用户把运行于外部的流量 接入 到集群内部
  • 负责将集群外部的服务引入到集群中
  • 需要借助于ClusterDNS上的CNAME资源记录完成
  • 特殊类型,无需ClusterIP和NodePort
  • 无须定义标签选择器发现Pod对象

k8s内部的服务 访问外部的mysql怎么办?

  • 直接访问外网IP
  • 手工定义成集群内的一个service
    • 手工定义endpoint 写上外部的ip
    • externalname 指向一个 dns dns然后解析到外部的mysql

三种网络 service network node network pod network

创建Service资源

由Service表示的负载均衡器,主要定义如下内容

  • 负载均衡器入口:ClusterIP及相关的Service Port、NodePort(每个节点的Node IP都可用)
    • 根据通信需求,确定选择的类型
  • 标签选择器:用于筛选Pod,并基于筛选出的Pod的IP生成后端端点列表(被调度的上游端点)
  • Service类型的专有配置

标签和标签选择器

标签:附加在资源对象上的键值型元数据

  • 键标识:由"键前缀(可选)"和"键名"组成,格式为"key_prefix/key_name"
    • 键前缀必须使用DNS域名格式
    • 键名的命名格式:支持字母、数字、连接号、下划线和点号,且只能以字母或数字开头;最长63个字符;
  • "kubectl label"命令可管理对象的标签

标签选择器:基于标签筛选对象的过滤条件,支持两种类型

  • 基于等值关系的选择器
    • 操作符:=或==、!=
  • 基于集合关系的选择器
    • 操作符:in、notin和exists
    • 使用格式:KEY in (VALUE1, VALUE2, ...)、 KEY notin (VALUE1, VALUE2, ...)、KEY 和 !KEY

Service资源规范

规范

yaml 复制代码
apiVersion: v1 
kind: Service 
metadata: 
    name: ... 
    namespace: ... 
spec: 
    type # Service类型,默认为ClusterIP 
    selector # 等值类型的标签选择器,内含"与"逻辑 
    ports: # Service的端口对象列表 
    - name # 端口名称 
      protocol # 协议,目前仅支持TCP、UDP和SCTP,默认为TCP 
      port # Service的端口号 
      targetPort # 后端目标进程的端口号或名称,名称需由Pod规范定义 
      nodePort # 节点端口号,仅适用于NodePort和LoadBalancer类型 
    clusterIP # Service的集群IP,建议由系统自动分配 
    externalTrafficPolicy # 外部流量策略处理方式,Local表示由当前节点处理,Cluster表示向集群范围调度 
    loadBalancerIP # 外部负载均衡器使用的IP地址,仅适用于LoadBlancer 
    externalName # 外部服务名称,该名称将作为Service的DNS CNAME值

创建pod

sql 复制代码
kubectl create deployment demoapp --image=ikubernetes/demoapp:v1.0 --replicas=3


root@k8s-master01:~# kubectl get pods -o wide --show-labels
NAME                      READY   STATUS    RESTARTS      AGE     IP            NODE           NOMINATED NODE   READINESS GATES   LABELS
demoapp-75f59c894-7kmns   1/1     Running   0             4m30s   10.244.1.10   k8s-master02   <none>           <none>            app=demoapp,pod-template-hash=75f59c894
demoapp-75f59c894-dqzvp   1/1     Running   0             4m30s   10.244.2.10   k8s-master03   <none>           <none>            app=demoapp,pod-template-hash=75f59c894
demoapp-75f59c894-rwfkl   1/1     Running   0             4m30s   10.244.0.19   k8s-master01   <none>           <none>            app=demoapp,pod-template-hash=75f59c894
redis-pv-pod              1/1     Running   3 (66m ago)   5d22h   10.244.2.8    k8s-master03   <none>           <none>            app=redis
volumes-hostpath-demo     1/1     Running   8 (66m ago)   5d23h   10.244.1.8    k8s-master02   <none>           <none>            app=redis

创建一个ClusterIP类型的service 生成一个定义文件 service_demoapp_cluster_ip.yml

scss 复制代码
root@k8s-master01:~# kubectl create service clusterip demoapp_cluster_ip --tcp=80:80 --dry-run=client -o yaml  

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: demoapp
  name: demoapp
spec:
  ports:
  - name: 80-80
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: demoapp_cluster_ip
  type: ClusterIP
status:
  loadBalancer: {}

测试连通性

scss 复制代码
root@k8s-master01:~# kubectl apply -f service_demoapp_cluster_ip.yml                                         
root@k8s-master01:~# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
demoapp      ClusterIP   10.106.219.38   <none>        80/TCP    5s
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   9d
root@k8s-master01:~# ping 10.106.219.38 
PING 10.106.219.38 (10.106.219.38) 56(84) bytes of data.
^C
--- 10.106.219.38 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

root@k8s-master01:~# curl 10.106.219.38 
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.1, ServerName: demoapp-75f59c894-rwfkl, ServerIP: 10.244.0.19!
root@k8s-master01:~# curl 10.106.219.38 
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-75f59c894-7kmns, ServerIP: 10.244.1.10!
root@k8s-master01:~# curl 10.106.219.38 
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-75f59c894-dqzvp, ServerIP: 10.244.2.10!

创建一个nodeport类型的service

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: demoapp-nodeport
  name: demoapp-nodeport
spec:
  ports:
  - name: 80-80
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: demoapp
  type: NodePort
status:
  loadBalancer: {}

查看创建的资源 注意生成了一个31671的端口 这个可以从集群外部进行访问

sql 复制代码
root@k8s-master01:~# kubectl apply -f service_node_port.yml 
service/demoapp-nodeport created
root@k8s-master01:~# kubectl get svc -o wide
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE    SELECTOR
demoapp            ClusterIP   10.106.219.38   <none>        80/TCP         6m9s   app=demoapp
demoapp-nodeport   NodePort    10.111.40.126   <none>        80:31671/TCP   6s     app=demoapp
kubernetes          ClusterIP   10.96.0.1       <none>        443/TCP        9d     <none>

集群外测试联通

跃点问题

externalTrafficPolicy: local 哪个节点接入 就把流量调度给这个节点上的pod

如果你使用一个外部负载均衡器来检查它端点的运行状况(就像 AWS ELB 所做的那样),它就会只将流量发送到应该接收流量的节点上,这样就能改善延迟、减少计算开销、降低出口成本并提升健全性。

NodePort Service流量策略

流量策略一:externalTrafficPolicy: Cluster

表示在整个Kubernetes集群范围内调度;

  • 该流量策略下,请求报文从某个节点上的NodePort进入,该节点 上的Service会将其调度至任何一个可用后端Pod之上,而不关心 Pod运行于哪个节点;

流量策略二:externalTrafficPolicy: Local

表示仅将请求调度至当前节点上运行的 可用后端端点;

  • 该流量策略下,请求报文从某节点NodePort进入后,该节点上的 Service仅会将请求调度至当前节点上适配到该Service的后端端点
  • 仅应该从运行有目标Service对象后端Pod对象的节点的NodePort 发起访问
  • 需要在上层做一个loadbalancer

Endpoint资源

对于每个service 都存在一个与之同名的Endpoint或EndpointSlice对象

Endpoint的作用

负责帮助完成 发现基于label selecter 去找到的pod IP地址 作为后端端点去使用

创建的方式

  • 在本身不存在的情况下 会自动创建Endpoint资源(跟service同名)
  • 在本身存在的情况下 service会直接引用该Endpoint资源(可以利用这个机制 手工去创建endpoint资源)

特殊的service类型总结

对比常规的路径Service解析的结果:

client ---> ServiceName --->ServiceIP(label selecter负载均衡转发)--->PodIP

一、ExternalName 外部名称(引入集群外部流量)

ServiceName解析的结果 是外部的DNS地址 简称ExternaleServiceName

访问路径:

client -->访问 servicename:serviceport -->解析成externalServiceDNSName:serviceport -->externalDNS(外部的DNS) -->解析成外部的 ExternalhostIP 即serviceip:port(外部的服务)

使用场景

内部服务 访问 外部服务

二、手动创建Endpoint资源(引入集群外部流量)

ServiceName解析的结果 依然是ServiceIp(但ServiceIp调度到的上游端点 是外部的IP地址 简称ExternalIp)

手动创建Endpoint资源 去关联的上游端点的IP, 可以不是自动发现的, 由用户静态指定的(外部断点的服务地址)

这时 service_name通过 不再触发label selecter过滤机制去自动发现pod IP, 而是指向静态一个外部的IP

访问路径

pod client --> servicename:serviceport --->解析成 serviceip:serviceport---> 直接到达externalHostIP:port

使用场景

内部服务 访问 外部服务

三、Headless Service 无头服务 (内部流量直达)

创建service的时候 明确指定不配置clusterIP, 但依然会使用label selector的过滤机制

访问路径:

client --->service_name:service_port --> 解析成PodIP

流量不会到service ip - ServiceName解析的结果 直接到PodIP

使用场景

集群内部 访问 集群内部, 不期望经由service_name的负载均衡转发 期望直接到POD上

相关推荐
Asthenia04121 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom2 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide2 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9652 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04122 小时前
Spring 启动流程:比喻表达
后端
Asthenia04123 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua3 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫