根据初始连接将通信量定向到Kubernetes吊舱

根据初始连接将通信量定向到Kubernetes吊舱,kubernetes,istio,stateful,Kubernetes,Istio,Stateful,我想将流量从Kubernetes中的负载平衡器定向到部署。但是,我希望每个连接都连接到一个特定的pod,并维护到该pod的连接,而不是试图在部署的所有pod之间实现均匀负载。我将把GRPC请求发送到pod上的一个有状态实例,客户端的GRPC请求不发送到其他pod是至关重要的 我当前的实现可能是不必要的复杂。以下是伪代码: 使用自定义python调度程序初始化集群。 创建了几个具有有状态应用程序的pod,每个pod都有一个节点端口服务和唯一的节点端口。 客户机使用套接字接口与python调度程序对

我想将流量从Kubernetes中的负载平衡器定向到部署。但是,我希望每个连接都连接到一个特定的pod,并维护到该pod的连接,而不是试图在部署的所有pod之间实现均匀负载。我将把GRPC请求发送到pod上的一个有状态实例,客户端的GRPC请求不发送到其他pod是至关重要的

我当前的实现可能是不必要的复杂。以下是伪代码:

使用自定义python调度程序初始化集群。 创建了几个具有有状态应用程序的pod,每个pod都有一个节点端口服务和唯一的节点端口。 客户机使用套接字接口与python调度程序对话,并被分配一个端口。 客户端使用分配的节点端口与pod对话。 客户端或调度程序终止pod。 我受到端口数量的限制,由于AKS的节点端口限制,我无法使用AKS引导流量。此外,尽管调度器的优点是客户端可以请求不同资源的pod,但它太难测试和维护


是否有更好的解决方案将外部流量定向到单个有状态的pod?

默认的iptables服务代理实现使用一个非常简单的随机循环算法来选择要使用的pod。如果您改用IPVS实现,它确实提供了更多的选项,尽管在像AKS这样的托管提供商上,这不太可能是一个选项。因此,您可以使用支持gRPC(如Traefik或Istio入口)的用户空间代理。选择一个已超出SO的范围,但大多数代理都支持某种形式的连接粘性。

默认iptables服务代理实现使用非常简单的随机循环算法来选择要使用的pod。如果您改用IPVS实现,它确实提供了更多的选项,尽管在像AKS这样的托管提供商上,这不太可能是一个选项。因此,您可以使用支持gRPC(如Traefik或Istio入口)的用户空间代理。选择一个已超出范围,因此,大多数代理都支持某种形式的连接粘性。

这可能被认为是一种反模式,但这最终成为occam的剃刀,我们需要它使集群脱离地面

python调度器被大大简化,以便在客户机请求应用程序实例时创建一个新的pod。调度器通过查询pod等待应用程序运行,然后向客户端提供pod本身的ip地址。由于我们的应用程序正被Azure上同一网络上的jupyterhub群集访问,因此我们可以看到pod,因为我们使用的是Azure CNI网络

不,我们没有使用负载均衡器,但是这个解决方案允许我们根据用户的请求生成不同大小的vcpu和ram。此外,如果无法请求pod,Python调度程序可以向用户发送有用的消息,例如可用RAM/CPU不足

虽然这没有利用kubernetes负载平衡,但我们不需要负载平衡,我们需要具有可变资源分配的n-可伸缩隔离容器。Kubernetes擅长生成pod,通过自定义python调度程序,可以很容易地获取这些pod的IP地址,并在必要时通过在调度程序中生成线程来终止它们,只要创建了pod

我仍然认为coderanger拥有最多的kubernetes答案,但是对于那些不想研究其他kubernetes框架的人来说,这个解决方案也是一个选择

如果有人感兴趣,以下是计划程序的消毒版本:

!/usr/bin/python3.7 输入信号 导入套接字 导入urllib3 从线程导入计时器 导入tarfile 从临时文件导入临时文件 导入日志记录 进口稀土 进口yaml 导入时间 导入线程 导入操作系统 随机输入 导入字符串 从线程导入线程 从urllib3.exceptions导入协议错误 导入argparse 从kubernetes导入客户端、配置、监视、UTIL 从kubernetes.client.rest导入ApiException 从kubernetes.stream导入流 从kubernetes进口手表 名称\u屏蔽\u端口\u名称='NAME\u屏蔽端口' 名称空间='name\u masked' 名称\u屏蔽\u GRPC\u端口=49999 服务器端口=29999 默认_TIMEOUT=3600终止运行时间超过此值的作业 默认保存保留=False 存储\u机密\u名称='STORAGE SECRET' 在NCPU 默认的CPU请求=0.5 默认\u CPU\u限制=1 以GB为单位 默认内存请求=1 默认内存限制=2 不安全警告 urllib3.disable_WarningURLLIB3.exceptions.InsureRequestWarning LOG=logging.getLogger\uuu名称__ LOG.setLevel'DEBUG' def螺纹fn: 使用线程调用函数 def包装器*args,**kwargs: thread=Threadtarget=fn,args=args,kwargs=kwargs thread.daemon=kwargs.pop'daemon',True thread.start ret 瓮线 返回包装器 def解码请求请求: 要求格式化为 “{n_cpu_请求},{ram_请求},{n_cpu_限制},{ram_限制},{command},{instance_timeout}{assign_port}” 请求解码 LOG.debug“收到客户端请求%s”,请求 n_cpu_请求,ram_请求,n_cpu_限制,ram_限制,命令,pod_超时,u=evalrequest 命令的格式可以是无字符串 如果命令==无: 命令=无 返回n_cpu_请求、ram_请求、n_cpu_限制、ram_限制、命令、pod_超时 def random_STRINGLENGTH=10: 生成固定长度的随机字符串 字母=字符串。ascii_小写 rangestringLength中i的return.joinrandom.ChoiceLitters NAME\u MASKED\u IMAGE='NAME\u maskedhelm.azurecr.io/NAME\u MASKED\u lite:v0.1' IMAGE\u SECRET\u NAME='containersecret' 使用MPI 启动\u NAME\u MASKED='/company\u inc/bin' NFS_NAME_MASKED_VOLUME={'NAME':'NFS-NAME_MASKED-VOLUME', 'nfs':{'server':'10.0.0.12',gobetween “路径”:“/mnt/company_inc”, “只读”:True} 名称\u屏蔽\u NFS\u容器={ “name”:“name_masked-ctr”, “图像”:名称\u掩码\u图像, '命令':['/bin/sh', “-ec”, 启动\u NAME\u MASKED], 'volumeMounts':[{'name':'nfs-name_masked-volume','mountPath':'/company_inc'}], 'resources':{'requests':{'cpu':'500m','memory':'512Mi'}, 'limits':{'cpu':'1000m','memory':'1024Mi'} SOMENAME\u作业\u NFS={ “apiVersion”:“批处理/v1”, “种类”:“工作”, 'metadata':{'name':'name_masked-UNNAMED', “名称空间”:名称空间}, 'spec':{'backoffLimit':1, 'template':{'spec':{'restartPolicy':'Never', “回退限制”:1, “容器”:[NAME\u MASKED\u NFS\u CONTAINER], 'imagePullSecrets':[{'name':IMAGE\u SECRET\u name}], “卷”:[NFS_NAME_MASKED_VOLUME]} } 基本\u SOMENAME\u POD={ “apiVersion”:“v1”, “种类”:“豆荚”, 'metadata':{'name':'name_masked', “名称空间”:名称空间}, 'spec':{'restartPolicy':'Never', “回退限制”:1, “容器”:[NAME\u MASKED\u NFS\u CONTAINER], 'imagePullSecrets':[{'name':IMAGE\u SECRET\u name}], “卷”:[NFS_NAME_MASKED_VOLUME]} IMAGE_SECRET={'apiVersion':'v1', 'type':'kubernetes.io/dockerconfigjson', “种类”:“秘密”, 'metadata':{'name':图像\u秘密\u名称, “名称空间”:名称空间}, '数据':{.dockerconfigjson':'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'} } 上述机密是通过以下方式创建的: kubectl创建秘密docker注册表容器secret-docker server=name_maskedhelm.azurecr.io-docker username=name_maskedhelm-docker password=IZUV0FI/XXXzove9KLa7FOvikO6eKFLt-docker电子邮件=name@company.com-命名空间名称\u已屏蔽 def open_loggerloglevel='DEBUG': 打开日志 如果日志已初始化,则不要添加其他处理程序 如果hasattropen_记录器“log”: 打开\u logger.log.handlers[0]。setLevelloglevel.upper 其他: log=logging.getLogger ch=logging.StreamHandler 通道setLevelloglevel.upper formatstr='%asctimes.%msecs03d%levelnames%模块-%funcNames:%messages' ch.setFormatterlogging.Formatterformatstr log.addHandlerch 打开日志记录器 返回open_logger.log 类播客调度程序: 吊舱调度器 def_uuuinit_uuuself,log_level='DEBUG', 储备目标=4, 清除_on_init=False, 服务器端口=服务器端口, block=True, 实例超时=默认超时, 图像=无, 默认cpu请求=默认cpu请求, 默认cpu限制=默认cpu限制, 默认内存请求=默认内存请求, 默认内存限制=默认内存限制, keep_reserve=默认的keep_reserve, 在退出时清除\u=真: 初始化作业控制器 打开日志日志级别 self.deployment\u name=None 自我保护目标=保护目标 self.enable_watch=True 自激活=真 self.\u事件=[] self.\服务器\端口=服务器\端口 self.\u服务器=无 self._clear_on_exit=清除_on_exit self.\u实例\u状态\u锁定=真 self._赋值={} self.\u pods=无 self.\u terminate=False 自我形象=形象 self.\u purge\u dangling\u resources=False self.\u job\u count=0 self.\u lock\u count=False self._请求的_pods=[] self.\u超时={} self.\u name\u lock=False self.\u default\u cpu\u request=default\u cpu\u request 自己 _默认\u cpu\u限制=默认\u cpu\u限制 self.\u default\u ram\u request=default\u ram\u request self.\u default\u ram\u limit=默认\u ram\u limit LOG.info“在%d秒时默认剔除SOMENAME实例”,实例\u超时 self.\u实例\u超时=实例\u超时 基于在kubernetes吊舱内运行的if加载凭据 如果操作系统环境中的“KUBERNETES_服务_主机”: config.load\u incluster\u config 其他: 配置文件必须包含正确的IP地址、证书和管理员密码 从microk8s服务器复制,包括: microk8s.kubectl配置视图-raw>$HOME/.kube/config path=os.path.dirnameos.path.realpath\uu文件__ config.load\u kube\u configos.path.joinpath,“config” configuration=client.configuration configuration.verify_ssl=False对于microk8s是必需的 client.Configuration.set\u defaultconfiguration 连接到kubernetes api self.core\u api=client.CoreV1Api 如果名称不存在,则创建名称屏蔽名称空间 self.create_namespace名称空间 添加秘密 自我。\u添加\u图像\u秘密 如果在_init上清除_: LOG.info“初始化时清除” 自清 启动端口分配线程 self.start\u端口\u服务器 如果是块: 自阻塞执行 定义(添加)图像(保密): 初始化Azure容器资源机密 如果图像\u SECRET\u名称不在self.SECRET\u名称中: self.core\u api.create\u namespaced\u secretNAMESPACE,IMAGE\u SECRET LOG.infof'Added image secret{image\u secret\u NAME}' 其他: LOG.infof'Secret{IMAGE\u Secret\u NAME}已存在' def_移除_图像_加密自身: 删除Azure容器资源机密 如果self.SECRET\u名称中的图像\u SECRET\u名称: response=self.core\u api.delete\u namespaced\u secretIMAGE\u SECRET\u NAME, 名称空间 LOG.infof'Removed image secret{image\u secret\u NAME}' @财产 def secret_name self: 机密名称=[] 对于self.secrets中的秘密: secret\u names.appendsecret.metadata.name 返回机密名称 @财产 国防部长本人: 名称\u屏蔽的命名空间机密 返回self.core\u api.list\u namespaced\u secretNAMESPACE.items def clearself: 删除“name_masked”命名空间中的所有服务、作业和吊舱 self.delete\u所有\u服务 self.delete\u所有作业 self.delete_all_pod self.\u实例={} @财产 def podsself: 蒙面豆荚的名称 self.\u pods=self.core\u api.list\u namespace\u podnespace.items 返回自我 @财产 def active_podsself: 跑步舱 活动_pods=[] 对于self.pods中的pod: 状态=吊舱状态 元数据=pod.metadata 如果status.container\u状态为: 容器状态=状态。容器状态[0] 如果状态为“就绪”: 如果容器_status.ready: 活动_pods.appendmetadata.name 返回活动的_pods def delete_all_podsself: 删除所有作业 对于self.pods中的pod: self.delete_pod.metadata.name def delete_podself,pod_名称: 删除一个名称屏蔽的命名空间pod 尝试: self.core\u api.delete\u名称空间\u podpod\u名称,名称空间 LOG.infof'Deleted pod{pod_name}' 例外情况除外: LOG.errorf'无法删除pod{pod_name}' def_wait_for_podself,pod_name,超时=20: pod就绪时返回pod yaml。否则,将引发异常 tstart=time.time 而time.time-tstart<超时: 时间是0.5 pod=self.core\u api.read\u namespaced\u podpod\u名称,名称空间 状态=吊舱状态 元数据=pod.metadata 如果状态。条件: 如果状态.conditions[0]。原因==“不可计划”: 原因=状态。条件[0]。消息 引发运行时错误F'无法创建pod:{reason}' 如果status.container\u状态为: 容器状态=状态。容器状态[0] 如果状态为“就绪”: 如果容器_status.ready: 返回舱 如果状态为“状态”: 如果容器_status.state.terminated: 如果hasattrcontainer\u status.state.terminated,“退出\u代码”: 如果容器_status.state.terminated.exit_代码: 原因=容器\状态.state.terminated.reason 引发运行时错误F'无法创建pod:{reason}' 如果hasattrcontainer\u status.state,' 等待': 如果容器_status.state.waiting不是无: 原因=容器\u status.state.waiting.message 如果原因不是无: 引发运行时错误F'无法创建pod:{reason}' 在{TIMEOUT}秒时引发RuntimeErrorf'TIMEOUT:\n无法创建pod:日志:\n{pod}' def_build_pod_yamlself,cpu_request=None,ram_request=None, cpu\u限制=无,ram\u限制=无,自定义\u命令=无: 建立一个职业团体 复制作业yaml并修改它 pod_名称=自身。_分配_pod_名称 pod\u yaml=dictBASE\u SOMENAME\u pod pod_yaml['metadata']['name']=pod_名称 如果cpu_请求为无: cpu\u请求=自。\默认\u cpu\u请求 如果cpu_限制为无: cpu\u限制=自身。\u默认\u cpu\u限制 如果ram_请求为无: ram\U请求=自身。\默认\U ram\U请求 如果ram_限制为无: ram\u limit=self.\u默认值\u ram\u limit 容器=pod_yaml['spec']['containers'][0] 限制=容器['resources']['limits'] 请求=容器['resources']['requests'] 限制['cpu']='%.2f'%floatcpu\u限制 限制['memory']='%.2fGi'%floatram\u限制 请求['cpu']='%.2f'%floatcpu\u请求 请求['memory']='%.2fGi'%floatram\u请求 请求的LOG.infof'Pod{Pod_name}为:' LOG.infof'Requests:{Requests}' LOG.infof'Limits:{Limits}' 配置名称\u屏蔽的CPU数量 如果自定义_命令不是“无”: 命令=自定义命令 其他: command=LAUNCH\u NAME\u MASKED.replace'-grpc','-np%d-grpc'-intcpu\u limit LOG.infof'Launching NAME_被{command}屏蔽' 容器['command'][-1]=命令 返回舱 def_spawn_podself,pod_yaml: 初始化pod并返回pod名称 由于潜在的命名冲突,多次尝试创建作业 resp=无 pod_name=pod_yaml['metadata']['name'] resp为“无”: 尝试: resp=self.core\u api.create\u namespaced\u podNAMESPACE,pod\u yaml 例外情况除外: 有时,与工作名称存在冲突 由于多个同时请求 如果StreException中的'AlreadyExists': pod_名称=自身。_分配_pod_名称 pod_yaml['metadata']['name']=pod_名称 其他: 引发异常 可能已经改变了 返回pod_名称 def create_name_masked_podself,cpu_request=None,ram_request=None, cpu_限制=无,ram_限制=无,超时=120, 自定义_命令=无,pod_超时=无: 创建一个名称\u屏蔽吊舱 pod_yaml=自身。_构建_pod_yamlcpu_请求,ram_请求, cpu\u限制、ram\u限制、自定义\u命令 pod\u name=self.\u spawn\u pod\u yaml 设置pod超时倒计时线程 self.\u kill\u pod\u timeoutpod\u name,pod\u timeout 等待创建一个pod 尝试: pod=self.\u等待\u pod\u名称 例外情况除外,如e: 时间1 self.delete_pod_名称 提出例外 返回舱 def_分配_pod_名称自身: 生成唯一的pod名称 自我时。\u name\u lock: 时间0.001 self.\u name\u lock=True pod_name='name_掩码-%s'%random\u string10 而pod_名称在self中。\u请求了\u pod: pod_name='name_掩码-%s'%random\u string10 self.\u请求的\u pods.appendpod\u名称 self.\u name\u lock=False 返回pod_名称 @螺纹 def_kill_pod_timeoutself,pod_name,pod_timeout=None: 一旦超过超时时间,杀死一个吊舱 如果pod_超时为无: pod\u timeout=self.\u实例\u timeout elif pod_超时>86400:确保pod超时不能超过1天 pod_超时=86400 elif pod_超时<20:最短20秒 pod_超时=20 LOG.infof'Configured timeout for{pod_name}到{pod_timeout}秒' time.sleeppod\u超时 LOG.infof'triggeredtimeout for{pod_name}超过了实例超时'+ f'of{pod_timeout}秒' self.delete_pod_名称 def create_名称空间自身,名称空间: 创建名称空间 名称空间_yaml={ 版本:v1, 种类:名称空间, 元数据:{ 名称:名称空间, 标签:{ 名称:名称空间 } } } 名称空间_exists=False 对于self.namespace中的_名称空间: 如果名称空间==\u namespace.metadata.name: 名称空间_exists=True 打破 如果没有 t名称空间_存在: response=self.core\u api.create\u namespace\u yaml LOG.debugf'Created namespace{namespace}' 其他: LOG.debugf'Namespace{Namespace}存在' @财产 def名称空间自身: 群集名称空间 返回self.core\u api.list\u namespace.items @螺纹 定义分配客户端自身,客户端: 从客户端接收请求并返回ip地址 尽管如此: 从客户机接收的数据 请求=client.recv1024 如果没有要求: 打破 cpu_请求、ram_请求、cpu_限制、ram_限制、命令、pod_超时=解码_请求 尝试: 分配端口 pod=self.create\u name\u masked\u podcpu\u request=cpu\u request, ram_请求=ram_请求, cpu\u限制=cpu\u限制, ram_limit=ram_limit, 自定义命令=命令, pod_超时=pod_超时 ip是pod ip ip=pod.status.pod\U ip 端口=49999 message=f'{ip}:{port}' client.sendmessage.encode 例外情况除外,如e: 消息='异常:%s'%s!' client.sendmessage.encode client.close @螺纹 def启动_端口_服务器自身: 接受服务器端口上的传入连接并返回端口 self.\u server=socket.socketsocket.AF\u INET,socket.SOCK\u流 self.\u server.bind,self.\u server\u端口 将插座置于收听模式 self.\u server.listen5 LOG.infof'在端口{self.\u server\u port}上侦听' 无限期地倾听新客户 尽管如此: 与客户建立联系 客户端,addr=self.\u server.accept 日志信息'正在从%s打开连接:%s',straddr[0],straddr[1] self.\u分配\u客户端 def block_executionself: 阻止python退出的主线程。 可以通过两种方式优雅地退出此实例: -使用Ctrl-c -与SIGTERM 收到def_sigterm*参数: 处理SIGTERM 日志信息“已收到SIGTERM” self.\u terminate=True 听一听sigterm LOG.info'PID:%d'%os.getpid signal.signalsignal.SIGTERM,已接收\u SIGTERM LOG.debug'阻止执行。按Ctrl-c打开断点' user\u break=False 而不是自己。终止: 尝试: 时间。睡眠0.1 除键盘中断外: 进口pdb;pdb.set_跟踪 user\u break=True resp=输入中断?[是/否]。下 如果resp==或resp=='y': 打破 LOG.info“由于在退出时清除,删除所有名称屏蔽的作业和POD=True” 如果在退出时自动清除: 自清 self.\u关闭\u服务器 自我定义: self.\u关闭\u服务器 def exitself,clear_jobs=True: 清除作业并关闭服务器 如果清除作业: 自清 self.\u关闭\u服务器 def_关闭_服务器自身: 关闭与服务器的连接 如果self.\u服务器不是None: self.\u server.close LOG.infof'关闭端口{self.\u server\u port}上的服务器' 如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu': 支持使用命令行参数直接调用gui parser=argparse.ArgumentParserdescription='name\u掩码作业控制器' parser.add_参数'-loglevel',metavar='DEBUG',type=str, required=False,默认值为'DEBUG', help='Log level以使用调试、信息、警告、错误' parser.add_参数'-timeout',type=str,metavar=, 必需=错误, help='interactive name_masked sessions的超时(秒)' parser.add_argument'-default_cpu_request',type=str,metavar=, 必需=错误, help='要请求的CPU数量' parser.add_argument'-default_cpu_limit',type=str,metavar=, 必需=错误, help='要限制的CPU数量' parser.add_argument'-default_ram_request',type=str,metavar=, 必需=错误, help='GB中的RAM请求' parser.add_argument'-default_ram_limit',type=str,metavar=, 必需=错误, help='RAM限制(GB' parser.add_参数'-keep_reserve', help='保留最少数量的名称\u屏蔽实例', action=store\u true args=parser.parse_args 脚本=无 如果args.default\u cpu\u请求: 违约_ cpu\u请求=args.default\u cpu\u请求 其他: 默认\u cpu\u请求=默认\u cpu\u请求 如果args.default\u cpu\u限制: default\u cpu\u limit=args.default\u cpu\u limit 其他: 默认\u cpu\u限制=默认\u cpu\u限制 如果args.default\u ram\u请求: default\u ram\u request=args.default\u ram\u request 其他: 默认内存请求=默认内存请求 如果args.default\u ram\u limit: default\u ram\u limit=args.default\u ram\u limit 其他: 默认内存限制=默认内存限制 如果args.keep_reserve: keep_reserve=args.keep_reserve 其他: 保留=默认保留 默认超时时间为一小时 如果args.timeout: timeout=intargs.timeout 其他: 超时=默认超时 PodSchedulerclear\u on\u init=False, log_level='INFO', 实例\超时=超时, keep_reserve=keep_reserve, 默认cpu请求=默认cpu请求, 默认cpu限制=默认cpu限制, 默认内存请求=默认内存请求, 默认内存限制=默认内存限制
这可能被认为是一种反模式,但这最终成为我们需要的occam剃须刀,以使集群脱离地面

python调度器被大大简化,以便在客户机请求应用程序实例时创建一个新的pod。调度器通过查询pod等待应用程序运行,然后向客户端提供pod本身的ip地址。由于我们的应用程序正被Azure上同一网络上的jupyterhub群集访问,因此我们可以看到pod,因为我们使用的是Azure CNI网络

不,我们没有使用负载均衡器,但是这个解决方案允许我们根据用户的请求生成不同大小的vcpu和ram。此外,如果无法请求pod,Python调度程序可以向用户发送有用的消息,例如可用RAM/CPU不足

虽然这没有利用kubernetes负载平衡,但我们不需要负载平衡,我们需要具有可变资源分配的n-可伸缩隔离容器。Kubernetes擅长生成pod,通过自定义python调度程序,可以很容易地获取这些pod的IP地址,并在必要时通过在调度程序中生成线程来终止它们,只要创建了pod

我仍然认为coderanger拥有最多的kubernetes答案,但是对于那些不想研究其他kubernetes框架的人来说,这个解决方案也是一个选择

如果有人感兴趣,以下是计划程序的消毒版本:

!/usr/bin/python3.7 输入信号 导入套接字 导入urllib3 从线程导入计时器 导入tarfile 从临时文件导入临时文件 导入日志记录 进口稀土 进口yaml 导入时间 导入线程 导入操作系统 随机输入 导入字符串 从线程导入线程 从urllib3.exceptions导入协议错误 导入argparse 从kubernetes导入客户端、配置、监视、UTIL 从kubernetes.client.rest导入ApiException 从kubernetes.stream导入流 从kubernetes进口手表 名称\u屏蔽\u端口\u名称='NAME\u屏蔽端口' 名称空间='name\u masked' 名称\u屏蔽\u GRPC\u端口=49999 服务器端口=29999 默认_TIMEOUT=3600终止运行时间超过此值的作业 默认保存保留=False 存储\u机密\u名称='STORAGE SECRET' 在NCPU 默认的CPU请求=0.5 默认\u CPU\u限制=1 以GB为单位 默认内存请求=1 默认内存限制=2 不安全警告 urllib3.disable_WarningURLLIB3.exceptions.InsureRequestWarning LOG=logging.getLogger\uuu名称__ LOG.setLevel'DEBUG' def螺纹fn: 使用线程调用函数 def包装器*args,**kwargs: thread=Threadtarget=fn,args=args,kwargs=kwargs thread.daemon=kwargs.pop'daemon',True thread.start 回位螺纹 返回包装器 def解码请求请求: 要求格式化为 “{n_cpu_请求},{ram_请求},{n_cpu_限制},{ram_限制},{command},{instance_timeout}{assign_port}” 请求解码 LOG.debug“收到客户端请求%s”,请求 n_cpu_请求,ram_请求,n_cpu_限制,ram_限制,命令,pod_超时,u=evalrequest 命令的格式可以是无字符串 如果命令==无: 命令=无 返回n_cpu_请求、ram_请求、n_cpu_限制、ram_限制、命令、pod_超时 def random_STRINGLENGTH=10: 生成固定长度的随机字符串 字母=字符串。ascii_小写 rangestringLength中i的return.joinrandom.ChoiceLitters NAME\u MASKED\u IMAGE='NAME\u maskedhelm.azurecr.io/NAME\u MASKED\u lite:v0.1' IMAGE\u SECRET\u NAME='containersecret' 使用MPI 启动\u NAME\u MASKED='/company\u inc/bin' NFS_NAME_MASKED_VOLUME={'NAME':'NFS-NAME_MASKED-VOLUME', 'nfs':{'server':'10.0.0.12',gobetween “路径”:“/mnt/company_inc”, “自述 仅限“:真}” 名称\u屏蔽\u NFS\u容器={ “name”:“name_masked-ctr”, “图像”:名称\u掩码\u图像, '命令':['/bin/sh', “-ec”, 启动\u NAME\u MASKED], 'volumeMounts':[{'name':'nfs-name_masked-volume','mountPath':'/company_inc'}], 'resources':{'requests':{'cpu':'500m','memory':'512Mi'}, 'limits':{'cpu':'1000m','memory':'1024Mi'} SOMENAME\u作业\u NFS={ “apiVersion”:“批处理/v1”, “种类”:“工作”, 'metadata':{'name':'name_masked-UNNAMED', “名称空间”:名称空间}, 'spec':{'backoffLimit':1, 'template':{'spec':{'restartPolicy':'Never', “回退限制”:1, “容器”:[NAME\u MASKED\u NFS\u CONTAINER], 'imagePullSecrets':[{'name':IMAGE\u SECRET\u name}], “卷”:[NFS_NAME_MASKED_VOLUME]} } 基本\u SOMENAME\u POD={ “apiVersion”:“v1”, “种类”:“豆荚”, 'metadata':{'name':'name_masked', “名称空间”:名称空间}, 'spec':{'restartPolicy':'Never', “回退限制”:1, “容器”:[NAME\u MASKED\u NFS\u CONTAINER], 'imagePullSecrets':[{'name':IMAGE\u SECRET\u name}], “卷”:[NFS_NAME_MASKED_VOLUME]} IMAGE_SECRET={'apiVersion':'v1', 'type':'kubernetes.io/dockerconfigjson', “种类”:“秘密”, 'metadata':{'name':图像\u秘密\u名称, “名称空间”:名称空间}, '数据':{.dockerconfigjson':'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'} } 上述机密是通过以下方式创建的: kubectl创建秘密docker注册表容器secret-docker server=name_maskedhelm.azurecr.io-docker username=name_maskedhelm-docker password=IZUV0FI/XXXzove9KLa7FOvikO6eKFLt-docker电子邮件=name@company.com-命名空间名称\u已屏蔽 def open_loggerloglevel='DEBUG': 打开日志 如果日志已初始化,则不要添加其他处理程序 如果hasattropen_记录器“log”: 打开\u logger.log.handlers[0]。setLevelloglevel.upper 其他: log=logging.getLogger ch=logging.StreamHandler 通道setLevelloglevel.upper formatstr='%asctimes.%msecs03d%levelnames%模块-%funcNames:%messages' ch.setFormatterlogging.Formatterformatstr log.addHandlerch 打开日志记录器 返回open_logger.log 类播客调度程序: 吊舱调度器 def_uuuinit_uuuself,log_level='DEBUG', 储备目标=4, 清除_on_init=False, 服务器端口=服务器端口, block=True, 实例超时=默认超时, 图像=无, 默认cpu请求=默认cpu请求, 默认cpu限制=默认cpu限制, 默认内存请求=默认内存请求, 默认内存限制=默认内存限制, keep_reserve=默认的keep_reserve, 在退出时清除\u=真: 初始化作业控制器 打开日志日志级别 self.deployment\u name=None 自我保护目标=保护目标 self.enable_watch=True 自激活=真 self.\u事件=[] self.\服务器\端口=服务器\端口 self.\u服务器=无 self._clear_on_exit=清除_on_exit self.\u实例\u状态\u锁定=真 self._赋值={} self.\u pods=无 self.\u terminate=False 自我形象=形象 self.\u purge\u dangling\u resources=False self.\u job\u count=0 self.\u lock\u count=False self._请求的_pods=[] self.\u超时={} self.\u name\u lock=False self.\u default\u cpu\u request=default\u cpu\u request self.\u default\u cpu\u limit=默认\u cpu\u limit self.\u default\u ram\u request=default\u ram\u request self.\u default\u ram\u limit=默认\u ram\u limit LOG.info“在%d秒时默认剔除SOMENAME实例”,实例\u超时 self.\u实例\u超时=实例\u超时 基于在kubernetes吊舱内运行的if加载凭据 如果操作系统环境中的“KUBERNETES_服务_主机”: config.load\u incluster\u config 其他: 配置文件必须包含正确的IP地址、证书和管理员密码 从microk8s服务器复制,包括: microk8s.kubectl配置视图-raw>$HOME/.kube/config path=os.path.dirnameos.path.realpath\uu文件__ config.load\u kube\u configos.path.joinpath,“config” configuration=client.configuration configuration.verify_ssl=False对于microk8s是必需的 client.Configuration.set\u defaultconfiguration 连接到kubernetes api self.core\u api=client.CoreV1Api 如果名称不存在,则创建名称屏蔽名称空间 赛尔夫 创建名称空间名称空间 添加秘密 自我。\u添加\u图像\u秘密 如果在_init上清除_: LOG.info“初始化时清除” 自清 启动端口分配线程 self.start\u端口\u服务器 如果是块: 自阻塞执行 定义(添加)图像(保密): 初始化Azure容器资源机密 如果图像\u SECRET\u名称不在self.SECRET\u名称中: self.core\u api.create\u namespaced\u secretNAMESPACE,IMAGE\u SECRET LOG.infof'Added image secret{image\u secret\u NAME}' 其他: LOG.infof'Secret{IMAGE\u Secret\u NAME}已存在' def_移除_图像_加密自身: 删除Azure容器资源机密 如果self.SECRET\u名称中的图像\u SECRET\u名称: response=self.core\u api.delete\u namespaced\u secretIMAGE\u SECRET\u NAME, 名称空间 LOG.infof'Removed image secret{image\u secret\u NAME}' @财产 def secret_name self: 机密名称=[] 对于self.secrets中的秘密: secret\u names.appendsecret.metadata.name 返回机密名称 @财产 国防部长本人: 名称\u屏蔽的命名空间机密 返回self.core\u api.list\u namespaced\u secretNAMESPACE.items def clearself: 删除“name_masked”命名空间中的所有服务、作业和吊舱 self.delete\u所有\u服务 self.delete\u所有作业 self.delete_all_pod self.\u实例={} @财产 def podsself: 蒙面豆荚的名称 self.\u pods=self.core\u api.list\u namespace\u podnespace.items 返回自我 @财产 def active_podsself: 跑步舱 活动_pods=[] 对于self.pods中的pod: 状态=吊舱状态 元数据=pod.metadata 如果status.container\u状态为: 容器状态=状态。容器状态[0] 如果状态为“就绪”: 如果容器_status.ready: 活动_pods.appendmetadata.name 返回活动的_pods def delete_all_podsself: 删除所有作业 对于self.pods中的pod: self.delete_pod.metadata.name def delete_podself,pod_名称: 删除一个名称屏蔽的命名空间pod 尝试: self.core\u api.delete\u名称空间\u podpod\u名称,名称空间 LOG.infof'Deleted pod{pod_name}' 例外情况除外: LOG.errorf'无法删除pod{pod_name}' def_wait_for_podself,pod_name,超时=20: pod就绪时返回pod yaml。否则,将引发异常 tstart=time.time 而time.time-tstart<超时: 时间是0.5 pod=self.core\u api.read\u namespaced\u podpod\u名称,名称空间 状态=吊舱状态 元数据=pod.metadata 如果状态。条件: 如果状态.conditions[0]。原因==“不可计划”: 原因=状态。条件[0]。消息 引发运行时错误F'无法创建pod:{reason}' 如果status.container\u状态为: 容器状态=状态。容器状态[0] 如果状态为“就绪”: 如果容器_status.ready: 返回舱 如果状态为“状态”: 如果容器_status.state.terminated: 如果hasattrcontainer\u status.state.terminated,“退出\u代码”: 如果容器_status.state.terminated.exit_代码: 原因=容器\状态.state.terminated.reason 引发运行时错误F'无法创建pod:{reason}' 如果hasattrcontainer\u status.state“waiting”: 如果容器_status.state.waiting不是无: 原因=容器\u status.state.waiting.message 如果原因不是无: 引发运行时错误F'无法创建pod:{reason}' 在{TIMEOUT}秒时引发RuntimeErrorf'TIMEOUT:\n无法创建pod:日志:\n{pod}' def_build_pod_yamlself,cpu_request=None,ram_request=None, cpu\u限制=无,ram\u限制=无,自定义\u命令=无: 建立一个职业团体 复制作业yaml并修改它 pod_名称=自身。_分配_pod_名称 pod\u yaml=dictBASE\u SOMENAME\u pod pod_yaml['metadata']['name']=pod_名称 如果cpu_请求为无: cpu\u请求=自。\默认\u cpu\u请求 如果cpu_限制为无: cpu\u限制=自身。\u默认\u cpu\u限制 如果ram_请求为无: ram\U请求=自身。\默认\U ram\U请求 如果ram_限制为无: ram\u limit=self.\u默认值\u ram\u limit 容器=pod_yaml['spec']['containers'][0] 限度 s=容器['resources']['limits'] 请求=容器['resources']['requests'] 限制['cpu']='%.2f'%floatcpu\u限制 限制['memory']='%.2fGi'%floatram\u限制 请求['cpu']='%.2f'%floatcpu\u请求 请求['memory']='%.2fGi'%floatram\u请求 请求的LOG.infof'Pod{Pod_name}为:' LOG.infof'Requests:{Requests}' LOG.infof'Limits:{Limits}' 配置名称\u屏蔽的CPU数量 如果自定义_命令不是“无”: 命令=自定义命令 其他: command=LAUNCH\u NAME\u MASKED.replace'-grpc','-np%d-grpc'-intcpu\u limit LOG.infof'Launching NAME_被{command}屏蔽' 容器['command'][-1]=命令 返回舱 def_spawn_podself,pod_yaml: 初始化pod并返回pod名称 由于潜在的命名冲突,多次尝试创建作业 resp=无 pod_name=pod_yaml['metadata']['name'] resp为“无”: 尝试: resp=self.core\u api.create\u namespaced\u podNAMESPACE,pod\u yaml 例外情况除外: 有时,与工作名称存在冲突 由于多个同时请求 如果StreException中的'AlreadyExists': pod_名称=自身。_分配_pod_名称 pod_yaml['metadata']['name']=pod_名称 其他: 引发异常 可能已经改变了 返回pod_名称 def create_name_masked_podself,cpu_request=None,ram_request=None, cpu_限制=无,ram_限制=无,超时=120, 自定义_命令=无,pod_超时=无: 创建一个名称\u屏蔽吊舱 pod_yaml=自身。_构建_pod_yamlcpu_请求,ram_请求, cpu\u限制、ram\u限制、自定义\u命令 pod\u name=self.\u spawn\u pod\u yaml 设置pod超时倒计时线程 self.\u kill\u pod\u timeoutpod\u name,pod\u timeout 等待创建一个pod 尝试: pod=self.\u等待\u pod\u名称 例外情况除外,如e: 时间1 self.delete_pod_名称 提出例外 返回舱 def_分配_pod_名称自身: 生成唯一的pod名称 自我时。\u name\u lock: 时间0.001 self.\u name\u lock=True pod_name='name_掩码-%s'%random\u string10 而pod_名称在self中。\u请求了\u pod: pod_name='name_掩码-%s'%random\u string10 self.\u请求的\u pods.appendpod\u名称 self.\u name\u lock=False 返回pod_名称 @螺纹 def_kill_pod_timeoutself,pod_name,pod_timeout=None: 一旦超过超时时间,杀死一个吊舱 如果pod_超时为无: pod\u timeout=self.\u实例\u timeout elif pod_超时>86400:确保pod超时不能超过1天 pod_超时=86400 elif pod_超时<20:最短20秒 pod_超时=20 LOG.infof'Configured timeout for{pod_name}到{pod_timeout}秒' time.sleeppod\u超时 LOG.infof'triggeredtimeout for{pod_name}超过了实例超时'+ f'of{pod_timeout}秒' self.delete_pod_名称 def create_名称空间自身,名称空间: 创建名称空间 名称空间_yaml={ 版本:v1, 种类:名称空间, 元数据:{ 名称:名称空间, 标签:{ 名称:名称空间 } } } 名称空间_exists=False 对于self.namespace中的_名称空间: 如果名称空间==\u namespace.metadata.name: 名称空间_exists=True 打破 如果名称空间不存在: response=self.core\u api.create\u namespace\u yaml LOG.debugf'Created namespace{namespace}' 其他: LOG.debugf'Namespace{Namespace}存在' @财产 def名称空间自身: 群集名称空间 返回self.core\u api.list\u namespace.items @螺纹 定义分配客户端自身,客户端: 从客户端接收请求并返回ip地址 尽管如此: 从客户机接收的数据 请求=client.recv1024 如果没有要求: 打破 cpu_请求、ram_请求、cpu_限制、ram_限制、命令、pod_超时=解码_请求 尝试: 分配端口 pod=self.create\u name\u masked\u podcpu\u request=cpu\u request, ram_请求=ram_请求, cpu\u限制=cpu\u限制, ram_limit=ram_limit, 自定义命令=命令, pod_超时=pod_超时 ip是pod ip ip=pod.status.pod\U ip 端口=49999 message=f'{ip}:{port}' client.sendmessage.encode 例外情况除外,如e: 消息='异常:%s'%s!' client.sendmessage.encode client.close @螺纹 def启动_端口_服务器自身: 接受服务器端口上的传入连接并返回端口 self.\u server=socket.socketsocket.AF\u INET,socket.SOCK\u流 self.\u server.bind,self.\u server\u端口 将插座置于收听模式 self.\u server.listen5 LOG.infof'在端口{self.\u server\u port}上侦听' 无限期地倾听新客户 尽管如此: 与客户建立联系 客户端,addr=self.\u server.accept 日志信息'正在从%s打开连接:%s',straddr[0],straddr[1] self.\u分配\u客户端 def block_executionself: 阻止python退出的主线程。 可以通过两种方式优雅地退出此实例: -使用Ctrl-c -与SIGTERM 收到def_sigterm*参数: 处理SIGTERM 日志信息“已收到SIGTERM” self.\u terminate=True 听一听sigterm LOG.info'PID:%d'%os.getpid signal.signalsignal.SIGTERM,已接收\u SIGTERM LOG.debug'阻止执行。按Ctrl-c打开断点' user\u break=False 而不是自己。终止: 尝试: 时间。睡眠0.1 除键盘中断外: 进口pdb;pdb.set_跟踪 user\u break=True resp=输入中断?[是/否]。下 如果resp==或resp=='y': 打破 LOG.info“由于在退出时清除,删除所有名称屏蔽的作业和POD=True” 如果在退出时自动清除: 自清 self.\u关闭\u服务器 自我定义: self.\u关闭\u服务器 def exitself,clear_jobs=True: 清除作业并关闭服务器 如果清除作业: 自清 self.\u关闭\u服务器 def_关闭_服务器自身: 关闭与服务器的连接 如果self.\u服务器不是None: self.\u server.close LOG.infof'关闭端口{self.\u server\u port}上的服务器' 如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu': 支持使用命令行参数直接调用gui parser=argparse.ArgumentParserdescription='name\u掩码作业控制器' parser.add_参数'-loglevel',metavar='DEBUG',type=str, required=False,默认值为'DEBUG', help='Log level以使用调试、信息、警告、错误' parser.add_参数'-timeout',type=str,metavar=, 必需=错误, help='interactive name_masked sessions的超时(秒)' parser.add_argument'-default_cpu_request',type=str,metavar=, 必需=错误, help='要请求的CPU数量' parser.add_argument'-default_cpu_limit',type=str,metavar=, 必需=错误, help='要限制的CPU数量' parser.add_argument'-default_ram_request',type=str,metavar=, 必需=错误, help='GB中的RAM请求' parser.add_argument'-default_ram_limit',type=str,metavar=, 必需=错误, help='RAM限制(GB' parser.add_参数'-keep_reserve', help='保留最少数量的名称\u屏蔽实例', action=store\u true args=parser.parse_args 脚本=无 如果args.default\u cpu\u请求: default\u cpu\u request=args.default\u cpu\u request 其他: 默认\u cpu\u请求=默认\u cpu\u请求 如果args.default\u cpu\u限制: default\u cpu\u limit=args.default\u cpu\u limit 其他: 默认\u cpu\u限制=默认\u cpu\u限制 如果args.default\u ram\u请求: default\u ram\u request=args.default\u ram\u request 其他: 默认内存请求=默认内存请求 如果args.default\u ram\u limit: default\u ram\u limit=args.default\u ram\u limit 其他: 默认内存限制=默认内存限制 如果args.keep_reserve: keep_reserve=args.keep_reserve 其他: 保留=默认保留 默认超时时间为一小时 如果args.timeout: timeout=intargs.timeout 其他: 超时=默认超时 PodSchedulerclear\u on\u init=False, log_level='INFO', 实例\超时=超时, keep_reserve=keep_reserve, 默认cpu请求=默认cpu请求, 默认cpu限制=默认cpu限制, 默认内存请求=默认内存请求 乌斯特, 默认内存限制=默认内存限制