乐趣区

关于k8s:Kubernetes中的正常关闭和零停机部署

Kubernetes 中的失常敞开和零停机部署
TL; DR:在本文中,您将学习如何在 Pod 启动或敞开时避免断开的连贯。您还将学习如何失常敞开长时间运行的工作。

Kubernetes 中的失常敞开和零停机部署
您能够在此处以 PDF 格局下载此便捷图表。

在 Kubernetes 中,创立和删除 Pod 是最常见的工作之一。

当您执行滚动更新,扩大部署,每个新发行版,每个作业和 cron 作业等时,都会创立 Pod。

然而在驱赶之后,Pods 也会被删除并从新创立 - 例如,当您将节点标记为不可调度时。

如果这些 Pod 的性质是如此短暂,那么当 Pod 在响应申请的过程中却被告知敞开时会产生什么呢?

申请在敞开之前是否已实现?

接下来的申请呢,那些申请被重定向到其余中央了吗?

在探讨删除 Pod 时会产生什么之前,有必要讨论一下创立 Pod 时会产生什么。

假如您要在集群中创立以下 Pod:

pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  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-service
      port:
        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: app
spec:
  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,并在耗尽连贯(或工作)后删除上一个发行版。

长时间运行的工作实现后,您能够手动删除较旧的部署。

或者,您能够主动将部署扩大到零个正本以主动执行该过程。

退出移动版