k8s 中的 service 如何找到绑定的 Pod 以及如何实现 Pod 负载均衡
Service 资源主要用于为 Pod 对象提供一个固定、统一的访问接口及负载均衡的能力。
service 是一组具有相同 label pod 集合的抽象,集群内外的各个服务可以通过 service 进行互相通信。
当创建一个 service 对象时也会对应创建一个 endpoint 对象,endpoint 是用来做容器发现的,service 只是将多个 pod 进行关联,实际的路由转发都是由 kubernetes 中的 kube-proxy 组件来实现,因此,service 必须结合 kube-proxy 使用,kube-proxy 组件可以运行在 kubernetes 集群中的每一个节点上也可以只运行在单独的几个节点上,其会根据 service 和 endpoints 的变动来改变节点上 iptables 或者 ipvs 中保存的路由规则。
endpoint 是 k8s 集群中的一个资源对象,存储在 etcd 中,用来记录一个 service 对应的所有 pod 的访问地址。
如果 service 没有 selector 字段,当一个 service 被创建的时候, endpoint controller
不会自动创建 endpoint。
$ kubectl get svc -n study-k8s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
go-web-svc ClusterIP 10.233.55.112 <none> 8000/TCP 9d
$ kubectl get endpoints -n study-k8s
NAME ENDPOINTS AGE
go-web-svc 10.233.111.171:8000,10.233.111.172:8000,10.233.72.153:8000 + 2 more... 9d
</none>
上面的 service go-web-svc
,就有一个对应的 endpoint,ENDPOINTS 里面展示的就是 service 关联的 pod 的 ip 地址和端口。
其中 endpoint controller
负载维护 endpoint 对象,主要的功能有下面几种
1、负责生成和维护所有endpoint对象的控制器;
2、负责监听 service 和对应 pod 的变化;
3、监听到 service 被删除,则删除和该 service 同名的 endpoint 对象;
4、监听到新的 service 被创建,则根据新建 service 信息获取相关 pod 列表,然后创建对应 endpoint 对象;
5、监听到 service 被更新,则根据更新后的 service 信息获取相关 pod 列表,然后更新对应 endpoint 对象;
6、监听到 pod 事件,则更新对应的 service 的 endpoint 对象,将 podIp 记录到 endpoint中;
kube-proxy 是 Kubernetes 的核心组件,部署在每个 Node 节点上,它是实现 Kubernetes Service
的通信与负载均衡机制的重要组件; kube-proxy 负责为 Pod 创建代理服务,从 apiserver 获取所有 server 信息,并根据 server 信息创建代理服务,实现server到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络。
在 k8s 中提供相同服务的一组 pod 可以抽象成一个 service,通过 service 提供统一的服务对外提供服务,kube-proxy 存在于各个 node 节点上,负责为 service 提供 cluster 内部的服务发现和负载均衡,负责 Pod 的网络代理,它会定时从 etcd 中获取 service 信息来做相应的策略,维护网络规则和四层负载均衡工作。k8s 中集群内部的负载均衡就是由 kube-proxy 实现的,它是 k8s 中内部的负载均衡器,也是一个分布式代理服务器,可以在每个节点中部署一个,部署的节点越多,提供负载均衡能力的 Kube-proxy 就越多,高可用节点就越多。
简单点讲就是 k8s 内部的 pod 要访问 service ,kube-proxy 会将请求转发到 service 所代表的一个具体 pod,也就是 service 关联的 Pod。
同理对于外部访问 service 的请求,不论是 Cluster IP+TargetPort
的方式;还是用 Node 节点 IP+NodePort
的方式,都被 Node 节点的 Iptables 规则重定向到 Kube-proxy 监听 Service 服务代理端口。kube-proxy 接收到 Service 的访问请求后,根据负载策略,转发到后端的 Pod。
kube-proxy 的路由转发规则是通过其后端的代理模块实现的,其中 kube-proxy 的代理模块目前有四种实现方案,userspace、iptables、ipvs、kernelspace 。
userspace 模式在 k8s v1.2
后就已经被淘汰了,userspace 的作用就是在 proxy 的用户空间监听一个端口,所有的 svc 都转到这个端口,然后 proxy 内部应用层对其进行转发。proxy 会为每一个 svc 随机监听一个端口,并增加一个 iptables 规则。
从客户端到 ClusterIP:Port
的报文都会通过 iptables 规则被重定向到 Proxy Port
,Kube-Proxy 收到报文后,然后分发给对应的 Pod。
userspace 模式下,流量的转发主要是在用户空间下完成的,上面提到了客户端的请求需要借助于 iptables 规则找到对应的 Proxy Port
,因为 iptables 是在内核空间,这里就会请求就会有一次从用户态到内核态再返回到用户态的传递过程, 一定程度降低了服务性能。所以就会认为这种方式会有一定的性能损耗。
默认情况下,用户空间模式下的 kube-proxy 通过轮转算法选择后端。
首先来简单了解下 iptables:
iptables 是 Linux 中最常用的一种防火墙工具,除了防火墙它还可以用作 IP 转发和简单的负载均衡功能。基于 Linux 中的 netfilter 内核模块实现。 Netfilter 在协议中添加了一些钩子,它允许内核模块通过这些钩子注册回调函数,这样经过钩子的所有数据都会被注册在响应钩子上的函数处理,包括修改数据包内容、给数据包打标记或者丢掉数据包等。iptables 是运行在用户态的一个程序,通过 netlink 和内核的 netfilter 框架打交道,具有足够的灵活性来处理各种常见的数据包操作和过滤需求。它允许将灵活的规则序列附加到内核的数据包处理管道中的各种钩子上。
Netfilter 是 Linux 2.4.x
引入的一个子系统,它作为一个通用的、抽象的框架,提供一整套的 hook 函数的管理机制,使得诸如数据包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能。
在 kubernetes v1.2
之后 iptables 成为默认代理模式,这种模式下,kube-proxy 会监视 Kubernetes master
对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会安装 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面。因为流量转发都是在内核进行的,所以性能更高更加可靠。
可以看到该模式下 iptables 来做用户态的入口,kube-proxy 只是持续监听 Service 以及 Endpoints 对象的变化, iptables 通过设置的转发策略,直接将对 VIP 的请求转发给后端 Pod,iptables 使用 DNAT 来完成转发,其采用了随机数实现负载均衡。
如果 kube-proxy 在 iptables 模式下运行,并且所选的第一个 Pod 没有响应,则连接失败。 这与用户空间模式不同:在这种情况下,kube-proxy 将检测到与第一个 Pod 的连接已失败, 并会自动使用其他后端 Pod 重试。
该模式相比 userspace 模式,克服了请求在用户态-内核态反复传递的问题,性能上有所提升,但使用 iptables NAT 来完成转发,存在不可忽视的性能损耗,iptables 模式最主要的问题是在 service 数量大的时候会产生太多的 iptables 规则,使用非增量式更新会引入一定的时延,大规模情况下有明显的性能问题。
当集群的规模比较大时,iptables 规则刷新就会很慢,难以支撑大规模的集群。因为 iptables 的底层实现是链表,对路由规则的增删查改都需要遍历一次链表。
在 kubernetes v1.2
之后 ipvs 成为kube-proxy的默认代理模式。ipvs 正是解决这一问题的,ipvs 是 LVS 的负载均衡模块,与 iptables 比较像的是,ipvs 的实现虽然也基于 netfilter 的钩子函数,但是它却使用哈希表作为底层的数据结构并且工作在内核态,也就是说 ipvs 在重定向流量和同步代理规则有着更好的性能,几乎允许无限的规模扩张。
ipvs 支持三种负载均衡模式:
1、DR模式(Direct Routing);
2、NAT 模式(Network Address Translation);
3、Tunneling(也称 ipip 模式)。
三种模式中只有 NAT 支持端口映射,所以 ipvs 使用 NAT 模式。linux 内核原生的 ipvs 只支持 DNAT,当在数据包过滤,SNAT 和支持 NodePort 类型的服务这几个场景中ipvs 还是会使用 iptables。
ipvs 也支持更多的负载均衡算法:
- rr:round-robin/轮询;
- lc:least connection/最少连接;
- dh:destination hashing/目标哈希;
- sh:source hashing/源哈希;
- sed:shortest expected delay/预计延迟时间最短;
- nq:never queue/从不排队
kernelspace 模式是 windows 上的代理模式,这里不展开讨论了
service 的 endpoints 解决了容器发现问题,但是不提前知道 service 的 Cluster IP
,就无法知道 service 服务了。Kubernetes 支持两种基本的服务发现模式 —— 环境变量和 DNS。
当一个 pod 创建完成之后,kubelet 会在该 pod 中注册该集群已经创建的所有 service 相关的环境变量,但是需要注意的是,在 service 创建之前的所有 pod 是不会注册该环境变量的,所以在平时使用时,建议通过 DNS 的方式进行 service 之间的服务发现。
举个例子,一个名称为 redis-primary 的 Service 暴露了 TCP 端口 6379, 同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:
REDIS_PRIMARY_SERVICE_HOST=10.0.0.11
REDIS_PRIMARY_SERVICE_PORT=6379
REDIS_PRIMARY_PORT=tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP_PROTO=tcp
REDIS_PRIMARY_PORT_6379_TCP_PORT=6379
REDIS_PRIMARY_PORT_6379_TCP_ADDR=10.0.0.11
可以在集群中部署 CoreDNS 服务(旧版本的 kubernetes 群使用的是 kubeDNS), 来达到集群内部的 pod 通过DNS 的方式进行集群内部各个服务之间的通讯。
当前 kubernetes 集群默认使用 CoreDNS 作为默认的 DNS 服务,主要原因是 CoreDNS 是基于 Plugin 的方式进行扩展的,简单,灵活,并且不完全被Kubernetes所捆绑。
同时 k8s 中也建议使用 DNS 来做服务发现。
Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName 类型的 Service 的方式。
k8s 中一般使用 Service 为 Pod 对象提供一个固定、统一的访问接口及负载均衡的能力;
k8s 中的负载均衡主要借助于 endpoint 和 kube-proxy 来实现;
endpoint 是 k8s 集群中的一个资源对象,存储在 etcd 中,用来记录一个 service 对应的所有 pod 的访问地址,当一个 service 关联的 pod 被删除,更新,新增,对应的 endpoint 资源都会更新;
kube-proxy 是 Kubernetes 的核心组件,部署在每个 Node 节点上,它是实现 Kubernetes Service
的通信与负载均衡机制的重要组件; kube-proxy 负责为 Pod 创建代理服务,从 apiserver 获取所有 server 信息,并根据 server 信息创建代理服务,实现server到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络;
kube-proxy 的路由转发规则是通过其后端的代理模块实现的,其中 kube-proxy 的代理模块目前有四种实现方案,userspace、iptables、ipvs、kernelspace ;
service 的 endpoints 和 kube-proxy 解决了容器的发现和负载均衡的问题,但是 service 服务如何被内部的服务找到呢,Kubernetes 支持两种基本的服务发现模式 —— 环境变量和 DNS;
其中 k8s 中推荐使用 DNS 来做 service 的服务发现,当前 kubernetes 集群默认使用 CoreDNS 作为默认的 DNS 服务,主要原因是 CoreDNS 是基于 Plugin 的方式进行扩展的,简单,灵活,并且不完全被Kubernetes所捆绑。
Original: https://www.cnblogs.com/ricklz/p/16796385.html
Author: ZhanLi
Title: k8s 中的 service 如何找到绑定的 Pod 以及如何实现 Pod 负载均衡
相关阅读
Title: Python3 – Flask之logging日志的使用
前言:
日志是一种可以追踪某些软件运行时所发生事件的方法;软件开发人员可以向他们的代码中调用日志记录相关的方法表明发生了某些事情;一个事件可以用一个可包含可选变量数据的消息来描述;事件也有重要性的概念,这个重要性也可以称为严重性级别(level)
0.实施步骤
Logging的使用就是三个步骤:
- 创建记录器
- 日志的格式、等级设置
- 添加记录器
1.日志等级说明
等级: DEBUG < INFO < WARNING < ERROR < CRITICAL
DEBUG : 最详细的日志信息,主要的应用场景问题的诊断,只限于开发人员使用的,用来在开发过程中进行调试
INFO : 详细程度仅次于debug模式,主要来记录关键节点的信息,确定程序是否正常如预期完成,一般的使用场景是重要的业务处理已经结束,我们通过这些INFO级别的日志信息,可以很快的了解应用正在做什么。
WARNING : 当某些不被期望的事情发生的时候,需要记录的信息,比如磁盘即将存满,注意当前的程序一依旧可以正常运行,不报错。也就是说发生这个级别的问题时,处理过程可以继续,但必须要对这个问题给予额外的关注。
ERROR : 出现严重问题,导致某些功能不能正常运行记录信息
CRITICAL: 系统即将崩溃或者已经崩溃
注意:
DEBUG: 设置的话,那么使用 app.logger 打印的日志仍会不分等级均记录,而系统运行日志才会按照设置的等级进行记录。
WARNING: 这个等级,那么不管是 app.logger 打印的日志, 还是系统运行日志, 均按照设置等级进行记录。
2.日志分割
LogHandle 封装: 自定义日志的格式、等级设置;
这里分别使用了以 文件大小分割和 时间分割日志方式实例:
import os
import logging
import time
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
def make_dir(make_dir_path):
path = make_dir_path.strip()
if not os.path.exists(path):
os.makedirs(path)
def getLogHandler():
log_dir_name = "../Logs"
log_file_name = 'logger-' + time.strftime('%Y-%m-%d', time.localtime(time.time())) + '.log'
log_file_folder = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir)) + os.sep + log_dir_name
make_dir(log_file_folder)
log_file_str = log_file_folder + os.sep + log_file_name
logging.basicConfig(level=logging.WARNING)
'''
# 基于时间分割
# 往文件里写入指定间隔时间自动生成文件的Handler
# 实例化TimedRotatingFileHandler
# interval是时间间隔,backupCount是备份文件的个数,如果超过这个个数,就会自动删除,when是间隔的时间单位,单位有以下几种:
# S 秒
# M 分
# H 小时
# D 天
# 'W0'-'W6' 每星期(interval=0时代表星期一:W0)
# midnight 每天凌晨
'''
file_log_time_handler = TimedRotatingFileHandler(filename=log_file_str, when='D', backupCount=10, encoding='utf-8')
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s')
file_log_time_handler.setFormatter(formatter)
return file_log_time_handler
3.创建记录器
def create_app():
app = Flask(__name__, static_folder='../static')
app.config.from_object(ProductionConfig)
app.logger.addHandler(LogHandle.getLogHandler())
@app.before_request
def log_each_request():
app.logger.info('【请求方法】{}【请求路径】{}【请求地址】{}'.format(request.method, request.path, request.remote_addr))
ctx = app.app_context()
ctx.push()
print(app.url_map)
return app
4.添加记录器
from flask import Flask, current_app
current_app.logger.info('info log')
current_app.logger.debug('debug log')
current_app.logger.warning('warning log')
current_app.logger.error('error log')
current_app.logger.critical('critical')
打印:
2022-03-18 16:41:23,388 - INFO - __init__.py - check_sign - 18 - info log
2022-03-18 16:41:23,388 - DEBUG - __init__.py - check_sign - 19 - debug log
2022-03-18 16:41:23,388 - WARNING - __init__.py - check_sign - 20 - warning log
2022-03-18 16:41:23,388 - ERROR - __init__.py - check_sign - 21 - error log
2022-03-18 16:41:23,388 - CRITICAL - __init__.py - check_sign - 22 - critical
需要注意: 测试时可能不打印, 放在生产环境即会打印.
Original: https://blog.csdn.net/qq_31810357/article/details/123579728
Author: 韩俊强
Title: Python3 – Flask之logging日志的使用
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/363397/
转载文章受原作者版权保护。转载请注明原作者出处!