背景: 一些组织将生产环境 前到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上