Kubernetes中的失常敞开和零停机部署
TL; DR: 在本文中,您将学习如何在Pod启动或敞开时避免断开的连贯。您还将学习如何失常敞开长时间运行的工作。
Kubernetes中的失常敞开和零停机部署
您能够在此处以PDF格局下载此便捷图表。
在Kubernetes中,创立和删除Pod是最常见的工作之一。
当您执行滚动更新,扩大部署,每个新发行版,每个作业和cron作业等时,都会创立Pod。
然而在驱赶之后,Pods也会被删除并从新创立-例如,当您将节点标记为不可调度时。
如果这些Pod的性质是如此短暂,那么当Pod在响应申请的过程中却被告知敞开时会产生什么呢?
申请在敞开之前是否已实现?
接下来的申请呢,那些申请被重定向到其余中央了吗?
在探讨删除Pod时会产生什么之前,有必要讨论一下创立Pod时会产生什么。
假如您要在集群中创立以下Pod:
pod.yaml
apiVersion: v1kind: Podmetadata: name: my-podspec: containers: - name: web image: nginx ports: - name: web containerPort: 80
您能够应用以下形式将YAML定义提交给集群:
重击
kubectl apply -f pod.yaml
输出命令后,kubectl便将Pod定义提交给Kubernetes API。
这是旅程的终点。
在数据库中保留集群的状态
API接管并查看Pod定义,而后将其存储在数据库etcd中。
Pod也将增加到调度程序的队列中。
调度程序:
查看定义
收集无关工作负载的详细信息,例如CPU和内存申请,而后
确定哪个节点最适宜运行它(通过称为过滤器和谓词的过程)。
在过程完结时:
在etcd中将Pod标记为Scheduled。
为Pod调配了一个节点。
Pod的状态存储在etcd中。
然而Pod依然不存在。
调度程序为该Pod调配最佳节点,并且Pod的状态更改为Pending。 Pod仅存在于etcd中。
3 /3
以前的
调度程序为该Pod调配最佳节点,并且Pod的状态更改为Pending。Pod仅存在于etcd中。
先前的工作产生在管制立体中,并且状态存储在数据库中。
那么谁在您的节点中创立Pod?
Kubelet-Kubernetes代理
kubelet的工作是轮询管制立体以获取更新。
您能够设想kubelet一直地向主节点询问:“我关照工作节点1,是否对我有任何新的Pod?”。
当有Pod时,kubelet会创立它。
有点。
kubelet不会自行创立Pod。而是将工作委托给其余三个组件:
容器运行时接口(CRI) -为Pod创立容器的组件。
容器网络接口(CNI) -将容器连贯到群集网络并调配IP地址的组件。
容器存储接口(CSI) -在容器中装载卷的组件。
在大多数状况下,容器运行时接口(CRI)的工作相似于:
重击
docker run -d <my-container-image>
容器网络接口(CNI)有点乏味,因为它负责:
为Pod生成无效的IP地址。
将容器连贯到网络的其余部分。
能够设想,有几种办法能够将容器连贯到网络并调配无效的IP地址(您能够在IPv4或IPv6之间进行抉择,也能够调配多个IP地址)。
例如,Docker创立虚构以太网对并将其连贯到网桥,而AWS-CNI将Pods间接连贯到虚构公有云(VPC)的其余部分。
当容器网络接口实现其工作时,该Pod已连贯到网络的其余部分,并调配了无效的IP地址。
只有一个问题。
Kubelet晓得IP地址(因为它调用了容器网络接口),然而管制立体却不晓得。
没有人通知主节点,该Pod已调配了IP地址,并且曾经筹备好接管流量。
就管制立体而言,仍在创立Pod。
kubelet的工作是收集Pod的所有详细信息(例如IP地址)并将其报告回管制立体。
您能够设想查看etcd不仅能够显示Pod的运行地位,还能够显示其IP地址。
Kubelet轮询管制立体以获取更新。
1 /5
Kubelet轮询管制立体以获取更新。
下一个
如果Pod不是任何服务的一部分,那么这就是旅程的起点。
Pod已创立并能够应用。
如果Pod是服务的一部分,则还须要执行几个步骤。
豆荚和服务
创立服务时,通常须要留神两点信息:
选择器,用于指定将接管流量的Pod。
本targetPort-通过舱体应用的端口接管的流量。
服务的典型YAML定义如下所示:
service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 3000
selector:
name: app
将Service提交给集群时kubectl apply,Kubernetes会找到所有具备与选择器(name: app)雷同标签的Pod,并收集其IP地址-但前提是它们已通过Readiness探针。
而后,对于每个IP地址,它将IP地址和端口连贯在一起。
如果IP地址为10.0.0.3和,targetPort则3000Kubernetes将两个值连接起来并称为端点。
IP address + port = endpoint
10.0.0.3 + 3000 = 10.0.0.3:3000
端点存储在etcd中另一个名为Endpoint的对象中。
使困惑?
Kubernetes是指:
eIP地址+端口对(10.0.0.3:3000)是端点(在本文和Learnk8s材料中称为小写端点)。
端点(在本文和Learnk8s材料中被称为大写E端点)是端点的汇合。
端点对象是Kubernetes中的实在对象,对于每个服务Kubernetes都会主动创立一个端点对象。
您能够应用以下办法进行验证:
重击
kubectl get services,endpoints
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
service/my-service-1 ClusterIP 10.105.17.65 <none> 80/TCP
service/my-service-2 ClusterIP 10.96.0.1 <none> 443/TCP
NAME ENDPOINTS
endpoints/my-service-1 172.17.0.6:80,172.17.0.7:80
endpoints/my-service-2 192.168.99.100:8443
端点从Pod收集所有IP地址和端口。
但不仅仅是一次。
在以下状况下,将应用新的端点列表刷新Endpoint对象:
创立一个Pod。
Pod已删除。
在Pod上批改了标签。
因而,您能够设想,每次创立Pod并在kubelet将其IP地址公布到主节点后,Kubernetes都会更新所有端点以反映更改:
重击
kubectl get services,endpoints
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
service/my-service-1 ClusterIP 10.105.17.65 <none> 80/TCP
service/my-service-2 ClusterIP 10.96.0.1 <none> 443/TCP
NAME ENDPOINTS
endpoints/my-service-1 172.17.0.6:80,172.17.0.7:80,172.17.0.8:80
endpoints/my-service-2 192.168.99.100:8443
很好,端点存储在管制立体中,并且端点对象已更新。
在此图中,集群中部署了一个Pod。 Pod属于服务。 如果要查看etcd,则能够找到Pod的详细信息以及服务。
1 /8
在此图中,集群中部署了一个Pod。Pod属于服务。如果要查看etcd,则能够找到Pod的详细信息以及服务。
下一个
您筹备好开始应用Pod了吗?
还有更多。
多很多!
应用Kubernetes中的端点
端点由Kubernetes中的几个组件应用。
Kube-proxy应用端点在节点上设置iptables规定。
因而,每次对端点(对象)进行更改时,kube-proxy都会检索IP地址和端口的新列表并编写新的iptables规定。
让咱们思考具备两个Pod且不蕴含Service的三节点群集。 Pod的状态存储在etcd中。
1 /6
让咱们思考具备两个Pod且不蕴含Service的三节点群集。Pod的状态存储在etcd中。
下一个
Ingress控制器应用雷同的端点列表。
入口控制器是群集中将内部流量路由到群集中的那个组件。
设置Ingress清单时,通常将Service指定为指标:
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
http:
paths:backend:
service:name: my-serviceport: number: 80
path: /
pathType: Prefix
实际上,流量不会路由到服务。
取而代之的是,Ingress控制器设置了一个订阅,每次该服务的端点更改时都将失去告诉。
Ingress会将流量间接路由到Pod,从而跳过服务。
能够设想,每次更改端点(对象)时,Ingress都会检索IP地址和端口的新列表,并将控制器重新配置为包含新的Pod。
在这张照片中,有一个Ingress控制器,它带有两个正本和一个Service的Deployment。
1 /9
在这张照片中,有一个Ingress控制器,它带有两个正本和一个Service的Deployment。
下一个
还有更多的Kubernetes组件示例能够订阅对端点的更改。
集群中的DNS组件CoreDNS是另一个示例。
如果您应用Headless类型的服务,则每次增加或删除端点时,CoreDNS都必须订阅对端点的更改并重新配置本身。
雷同的端点被Istio或Linkerd之类的服务网格所应用,云提供商也创立了type:LoadBalancer有数运营商的服务。
您必须记住,有几个组件订阅了对端点的更改,它们可能会在不同工夫收到无关端点更新的告诉。
够了吗,还是在创立Pod之后有什么事产生?
这次您实现了!
疾速回顾一下创立Pod时产生的状况:
Pod存储在etcd中。
调度程序调配一个节点。它将节点写入etcd。
向kubelet告诉新的和预约的Pod。
kubelet将创立容器的委托委派给容器运行时接口(CRI)。
kubelet代表将容器附加到容器网络接口(CNI)。
kubelet将容器中的装置卷委派给容器存储接口(CSI)。
容器网络接口调配IP地址。
Kubelet将IP地址报告给管制立体。
IP地址存储在etcd中。
如果您的Pod属于服务:
Kubelet期待胜利的“就绪”探测。
告诉所有相干的端点(对象)更改。
端点将新端点(IP地址+端口对)增加到其列表中。
告诉Kube-proxy端点更改。Kube-proxy更新每个节点上的iptables规定。
告诉端点变动的入口控制器。控制器将流量路由到新的IP地址。
CoreDNS会收到无关端点更改的告诉。如果服务的类型为Headless,则更新DNS条目。
向云提供商告诉端点更改。如果服务为type: LoadBalancer,则将新端点配置为负载平衡器池的一部分。
端点更改将告诉群集中装置的所有服务网格。
订阅端点更改的任何其余操作员也会收到告诉。
如此长的列表令人诧异地是一项常见工作-创立一个Pod。
Pod正在运行。当初是时候探讨删除它时会产生什么。
删除豆荚
您可能曾经猜到了,然而删除Pod时,必须遵循雷同的步骤,然而要相同。
首先,应从端点(对象)中删除端点。
这次将疏忽“就绪”探针,并立刻将其从管制立体移除。
顺次触发所有事件到kube-proxy,Ingress控制器,DNS,服务网格等。
这些组件将更新其外部状态,并进行将流量路由到IP地址。
因为组件可能忙于执行其余操作,因而无奈保障从其外部状态中删除IP地址须要破费多长时间。
对于某些人来说,可能须要不到一秒钟的工夫。对于其他人,可能须要更多工夫。
如果要应用kubectl delete pod删除Pod,则该命令首先达到Kubernetes API。
1 /5
如果您要应用删除Pod kubectl delete pod,则该命令将首先达到Kubernetes API。
下一个
同时,etcd中Pod的状态更改为Termination。
将告诉kubelet更改并委托:
将任何卷从容器卸载到容器存储接口(CSI)。
从网络上拆散容器并将IP地址开释到容器网络接口(CNI)。
将容器销毁到容器运行时接口(CRI)。
换句话说,Kubernetes遵循与创立Pod完全相同的步骤,但相同。
如果要应用kubectl delete pod删除Pod,则该命令首先达到Kubernetes API。
1 /3
如果您要应用删除Pod kubectl delete pod,则该命令将首先达到Kubernetes API。
下一个
然而,存在轻微但必不可少的差别。
当您终止Pod时,将同时删除端点和发给kubelet的信号。
首次创立Pod时,Kubernetes期待kubelet报告IP地址,而后启动端点流传。
然而,当您删除Pod时,事件将并行开始。
这可能会导致相当多的较量条件。
如果在流传端点之前删除Pod,该怎么办?
删除端点和删除Pod会同时产生。
1 /3
删除端点和删除Pod会同时产生。
下一个
失常关机
当Pod在终结点从kube-proxy或Ingress控制器中删除之前终止时,您可能会遇到停机工夫。
而且,如果您考虑一下,这是有情理的。
Kubernetes仍将流量路由到IP地址,但Pod不再存在。
Ingress控制器,kube-proxy,CoreDNS等没有足够的工夫从其外部状态中删除IP地址。
现实状况下,在删除Pod之前,Kubernetes应该期待集群中的所有组件具备更新的端点列表。
然而Kubernetes不能那样工作。
Kubernetes提供了强壮的原语来散发端点(即Endpoint对象和更高级的形象,例如Endpoint Slices)。
然而,Kubernetes不会验证订阅端点更改的组件是否是集群状态的最新信息。
那么,如何防止这种竞争状况并确保在流传端点之后删除Pod?
你应该等一下
当Pod行将被删除时,它会收到SIGTERM信号。
您的应用程序能够捕捉该信号并开始敞开。
因为端点不太可能立刻从Kubernetes中的所有组件中删除,因而您能够:
请稍等片刻,而后退出。
只管应用了SIGTERM,依然能够解决传入的流量。
最初,敞开现有的长期连贯(兴许是数据库连贯或WebSocket)。
敞开该过程。
你应该等多久?
默认状况下,Kubernetes将发送SIGTERM信号并期待30秒,而后强制终止该过程。
因而,您能够在最后的15秒内持续操作,因为什么都没有产生。
心愿该距离应足以将端点删除流传到kube-proxy,Ingress控制器,CoreDNS等。
因而,越来越少的流量将达到您的Pod,直到进行为止。
15秒后,能够平安地敞开与数据库的连贯(或任何长久连贯)并终止该过程。
如果您认为须要更多工夫,则能够在20或25秒时进行该过程。
然而,您应该记住,Kubernetes将在30秒后强行终止该过程(除非您更改terminationGracePeriodSecondsPod定义中的)。
如果您无奈更改代码以期待更长的工夫怎么办?
您能够调用脚本以期待固定的工夫,而后退出应用程序。
在调用SIGTERM之前,KubernetespreStop在Pod中公开一个钩子。
您能够将preStop钩子设置为期待15秒。
让咱们看一个例子:
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: web image: nginx ports: - name: web containerPort: 80 lifecycle: preStop: exec: command: ["sleep", "15"]
该preStop挂钩是Pod LifeCycle挂钩之一。
倡议提早15秒吗?
这要视状况而定,但这可能是开始测试的理智办法。
以下是您能够抉择的选项的概述:
您曾经晓得,当删除Pod时,会告诉kubelet更改。
1 /5
您曾经晓得,当删除Pod时,会告诉kubelet更改。
下一个
宽限期和滚动更新
失常关机实用于要删除的Pod。
然而,如果您不删除Pod,该怎么办?
即便您不这样做,Kubernetes也会始终删除Pod。
特地是,每次部署较新版本的应用程序时,Kubernetes都会创立和删除Pod。
在部署中更改映像时,Kubernetes会逐渐推出更改。
pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 3
selector:
matchLabels: name: app
template:
metadata: labels: name: appspec: containers: - name: app # image: nginx:1.18 OLD image: nginx:1.19 ports: - containerPort: 3000
如果您有三个正本,并且一旦提交新的YAML资源Kubernetes,则:
用新的容器图像创立一个Pod。
销毁现有的Pod。
期待Pod准备就绪。
并反复上述步骤,直到将所有Pod迁徙到较新的版本。
Kubernetes仅在新Pod筹备好接管流量(换句话说,它通过“就绪”查看)后才反复每个周期。
Kubernetes是否在期待Pod被删除之后再移到下一个Pod?
不。
如果您有10个Pod,并且Pod须要2秒钟的筹备工夫和20个敞开的工夫,则会产生以下状况:
创立第一个Pod,并终止前一个Pod。
Kubernetes创立一个新的Pod之后,须要2秒钟的筹备工夫。
同时,被终止的Pod会终止20秒
20秒后,所有新Pod均已启用(10个Pod ,在2秒后就绪),并且所有之前的10个Pod都将终止(第一个Terminated Pod将要退出)。
总共,您在短时间内将Pod的数量减少了一倍(运行10次,终止10次)。
滚动更新和失常关机
与“就绪”探针相比,宽限期越长,您同时具备“运行”(和“终止”)的Pod越多。
不好吗
不肯定,因为您要小心不要断开连接。
终止长时间运行的工作
那长期工作呢?
如果您要对大型视频进行转码,是否有任何办法能够提早进行Pod?
假如您有一个蕴含三个正本的Deployment。
每个正本都调配有一个视频进行转码,该工作可能须要几个小时能力实现。
当您触发滚动更新时,Pod会在30秒内实现工作,而后将其杀死。
如何防止提早敞开Pod?
您能够将其terminationGracePeriodSeconds减少到几个小时。
然而,此时Pod的端点不可达。
无奈达到的豆荚
如果公开指标以监督Pod,则您的设施将无法访问Pod。
为什么?
诸如Prometheus之类的工具依赖于Endpoints来将群集中的Pod抓取。
然而,一旦删除Pod,端点删除就会在群集中流传,甚至流传到Prometheus!
您应该思考为每个新版本创立一个新的部署,而不是减少宽限期。
当您创立全新的部署时,现有的部署将放弃不变。
长时间运行的作业能够照常持续解决视频。
实现后,您能够手动删除它们。
如果心愿主动删除它们,则可能须要设置一个主动缩放器,当它们用尽工作时,能够将部署扩大到零个正本。
这种Pod主动定标器的一个示例是Osiris,它是Kubernetes的通用,从零缩放的组件。
该技术有时被称为Rainbow部署,并且在每次您必须使之前的Pod的运行工夫超过宽限期时都十分有用。
另一个很好的例子是WebSockets。
如果您正在向用户流式传输实时更新,则可能不心愿在每次公布时都终止WebSocket。
如果您白天常常出游,则可能会导致实时Feed屡次中断。
为每个版本创立一个新的部署是一个不太显著但更好的抉择。
现有的用户能够持续流更新,而最新的Deployment服务于新用户。
当用户断开与旧Pod的连贯时,您能够逐步缩小正本数并退出过来的Deployment。
概括
您应该留神Pod从群集中删除,因为它们的IP地址可能仍用于路由流量。
与其立刻敞开Pods,不如思考在应用程序中期待更长的工夫或设置一个preStop钩子。
仅在将集群中的所有终结点流传并从kube-proxy,Ingress控制器,CoreDNS等中删除后,才应删除Pod。
如果您的Pod运行诸如视频转码或应用WebSockets进行实时更新之类的长期工作,则应思考应用Rainbow部署。
在Rainbow部署中,您为每个发行版创立一个新的Deployment,并在耗尽连贯(或工作)后删除上一个发行版。
长时间运行的工作实现后,您能够手动删除较旧的部署。
或者,您能够主动将部署扩大到零个正本以主动执行该过程。