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上

相关推荐
啦啦右一1 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien1 小时前
Spring Boot常用注解
java·spring boot·后端
盛派网络小助手3 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
∝请叫*我简单先生3 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl
zquwei4 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
dessler4 小时前
Docker-run命令详细讲解
linux·运维·后端·docker
Q_19284999065 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
ZSYP-S5 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
Yuan_o_6 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端