QCon全球软件开发大会8折优惠倒计时,购票立减¥1760!了解详情 >>> 了解详情
写点什么

零停机给 Kubernetes 集群节点打系统补丁

2021 年 5 月 13 日

零停机给Kubernetes集群节点打系统补丁

背景简介

Salesforce 的 Einstein Vision 和语言服务部署在 AWS Elastic Kubernetes Service(EKS)集群上。其中有一个最主要的安全和合规性需求,就是给集群节点的操作系统打补丁。部署服务的集群节点需要通过打补丁的方式进行系统的定期更新。这些补丁减少了可能让虚拟机暴露于攻击之下的漏洞。

打补丁的过程

爱因斯坦服务以 Kubernetes Pod 的形式部署在不可变的 EC2 节点组(也称为 AWS 自动伸缩组,缩写为 ASG)中。打补丁的过程包括构建新的 Amazon Machine Image (AMI),镜像中包含了所有更新的安全补丁。新的 AMI 用于更新节点组,每一次需要启动一个新的 EC2 实例。当新实例通过运行健康状况检查后,旧实例将被终止。这个过程将会持续下去,直到节点组中的所有 EC2 实例都被新实例替换,这个过程也称为滚动更新。


然而,这个打补丁的过程给我们带来了一个挑战。当旧的 EC2 实例被终止时,在这些 EC2 实例上运行的服务 Pod 也会被终止。如果 Pod 的终止过程没有得到妥善处理,可能会导致用户请求处理失败。要优雅地终止 Pod,需要基础设施组件(Kubernetes API 和 AWS ASG)和应用程序组件(服务/应用程序容器)的支持。

优雅终止应用程序

在这个过程中,首先要优雅地终止应用程序。终止一个 Pod 可能会导致 Pod 中的 Docker 容器突然终止,在 Docker 容器中运行的进程也会突然终止。这可能会导致正在处理中的请求被终止,最终导致当时正在调用应用程序的上游服务调用失败。


当一个 EC2 实例在打补丁过程中被终止,该实例上的 Pod 也将被驱逐。Pod 被标志为终止,在 EC2 实例上运行的 kubelet 就开始了关闭 Pod 的过程。kubelet 将发出 SIGTERM 信号。如果在 Pod 中运行的应用程序没有处理 SIGTERM 信号的逻辑,正在执行的任务可能会被突然终止。因此,你需要更新应用程序来处理这个信号,并实现优雅的终止。


例如,对于 Java 应用程序,有一种方法可以实现优雅的终止(不同的框架处理方式有所不同):


public static final int gracefulShutdownTimeoutSeconds = 30;@Overridepublic void onApplicationEvent(@NotNull ContextClosedEvent contextClosedEvent) {    this.connector.pause();    Executor executor = this.connector.getProtocolHandler().getExecutor();    if (executor instanceof ThreadPoolExecutor) {    try {        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;        threadPoolExecutor.shutdown();        logger.warn("Gracefully shutdown the service.");        if (!threadPoolExecutor.awaitTermination(gracefulShutdownTimeoutSeconds, TimeUnit.SECONDS)) {        logger.warn("Forcefully shutdown the service after {} seconds.", gracefulShutdownTimeoutSeconds);        threadPoolExecutor.shutdownNow();        }    } catch (InterruptedException ex) {        Thread.currentThread().interrupt();    }    }}
复制代码


在上面的代码片段中,关闭信号被触发,并在 30 秒后强制终止应用程序,这给了应用程序 30 秒的时间来处理正在执行中的任务。


如果 Pod 由多个容器组成,并且容器终止的顺序很重要,那么最好要定义一个容器preStop钩子,以确保容器能以正确顺序终止(例如,在终止日志边车容器前先终止应用程序容器)。


在关闭 Pod 的过程中,kubelet 会执行容器生命周期钩子(如果定义了的话)。在我们的例子中,一个 Pod 中有多个容器,因此,对我们来说,终止顺序很重要。我们为应用程序容器定义了一个 preStop 钩子,如下所示:


lifecycle:    preStop:        exec:          command:          - /bin/sh          - -c          - kill -SIGTERM 1 && while ps -p 1 > /dev/null; do sleep 1; done;
复制代码


preStop 钩子中定义的动作将向 Docker 容器中的进程(PID 1)发送一个 SIGTERM 信号,并以 1 秒为等待时间间隔,直到进程成功终止。进程可以完成任何一个挂起的任务,并正常终止。


preStop 钩子的默认超时时间是 30 秒。在我们的例子中,这提供了足够多的时间让进程优雅地终止。如果默认的时间不够,可以在 preStop 钩子中使用terminationGracePeriodSeconds字段来指定其他值。

优雅地终止 EC2 实例

如上所述,我们的服务运行在 EC2 实例的节点组上。优雅地终止 EC2 实例可以通过使用 AWS ASG 生命周期钩子和 AWS Lambda 服务来实现。

AWS EC2 自动伸缩生命周期钩子

有了生命周期钩子,我们就可以实现在启动新实例或终止旧实例前暂停实例状态,并执行自定义操作。一旦实例被暂停,你就可以通过触发 Lambda 函数或在实例上运行命令来完成生命周期操作。实例会一直保持等待状态,直到生命周期操作完成。


我们使用 Terminating:Wait 生命周期钩子将要终止的实例置于等待状态。有关 ASG 生命周期钩子的更多细节,请参阅AWS文档

AWS Lambda

我们使用SAM框架来部署 Lambda 函数(这个 Lambda 函数是内部开发的,我们把它叫作 node-drainer),当发生特定的 ASG 生命周期钩子事件时被触发。下图显示了优雅地终止节点组中的 EC2 实例所涉及的事件序列。



  • 当 Patching Automation 请求终止实例时,生命周期钩子将启动,并将实例置于 Terminating:Wait 状态。

  • 当实例处于 terminate:Wait 状态,生命周期钩子就会触发 AWS Lambda 函数。

  • Lambda 函数调用 Kubernetes API 并隔离被终止的实例。隔离实例可防止在被终止的实例上启动新的 Pod。

  • 隔离实例后,该实例所有的 Pod 都将被驱逐,并放在一个正常的节点上。

  • Kubernetes 负责为健康实例提供新的 Pod。

  • 生命周期钩子等待,直到所有 Pod 被驱逐出实例,并且新 Pod 出现在一个正常的实例中。

  • 一旦节点被完全清空,生命周期钩子将移除 WAIT 状态,并继续执行终止操作。

  • 这确保了全部现有的请求都已处理完成,然后将 Pod 从节点中移除。

  • 在这样做的同时,我们要确保新 Pod 能处理新的请求。

  • 这种优雅的关闭过程确保没有 Pod 是被突然关闭的,也不会出现服务中断。

RBAC(基于角色的访问控制)

为了能从 AWS Lambda 函数访问 Kubernetes 资源,我们创建了一个 IAM 角色、一个clusterrole和一个clusterrolebinding。IAM 角色用于授予访问 ASG 的权限,clusterroleclusterrolebindingnode-drainer Lambda 函数授予驱逐 Kubernetes Pod 的权限。

IAM 角色策略

{    "Version": "2012-10-17",    "Statement": [        {            "Action": [                "autoscaling:CompleteLifecycleAction",                "ec2:DescribeInstances",                "eks:DescribeCluster",                "sts:GetCallerIdentity"            ],            "Resource": "*",            "Effect": "Allow"        }    ]}
复制代码

Clusterrole

kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1metadata:  name: lambda-cluster-accessrules:  - apiGroups: [""]    resources: ["pods", "pods/eviction", "nodes"]    verbs: ["create", "list", "patch"]
复制代码

Clusterrolebinding

kind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:  name: lambda-user-cluster-role-bindingsubjects:  - kind: User    name: lambda    apiGroup: rbac.authorization.k8s.ioroleRef:  kind: ClusterRole  name: lambda-cluster-access  apiGroup: rbac.authorization.k8s.io
复制代码

结论

通过结合使用 AWS Lambda、AWS EC2 自动伸缩生命周期钩子和优雅的应用程序进程终止,我们确保了在打补丁期间实现零停机频繁滚动更新 EC2 实例。


原文链接:


https://engineering.salesforce.com/zero-downtime-node-patching-in-a-kubernetes-cluster-cdceb21c8c8c

2021 年 5 月 13 日 13:382200
用户头像

发布了 114 篇内容, 共 26.6 次阅读, 收获喜欢 292 次。

关注

评论 1 条评论

发布
用户头像
做不到这点就不叫 K8s 了,升级的原理很简单,一个集群假设有三个节点,新加入的节点承接即将要升级的节点的所有 Pod,再让一个旧节点下线升级补丁,以此类推所有集群。最后去掉新加入的节点即可。
2021 年 05 月 17 日 08:55
回复
没有更多了
发现更多内容

了解操作系统的那些事儿,从这篇文章开始

飞天小牛肉

Java 面试 操作系统 程序员· 2月春节不断更

【STM32】ST-LINK下载器下载后需复位,程序才运行的问题

AXYZdong

硬件 stm32 2月春节不断更

架构师week12作业

Geek_xq

一文搞懂TCP的三次握手和四次挥手

不脱发的程序猿

三次握手 四次挥手 TCP/IP 网络通信协议 二月春节不断更

收藏从未停止!阿里Spring全栈学习宝典2021全新开源

程序员小毕

Java spring 程序员 面试 springmvc

CoralCache:一个提高微服务可用性的中间件

华为云开发者社区

数据库 微服务 中间件 内存 CoralCache

DIY一款4路USB转TTL串口调试模块

不脱发的程序猿

DIY 电路设计 硬件设计 USB电路 USB转TTL

Elasticsearch Search API 基础语法

escray

elastic 七日更 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

最新金三银四阿里巴巴内部Java架构师面试突击面试题手册,面试前必看

Java架构追梦

Java 阿里巴巴 架构 面试题 金三银四

数据中心决策如何快人一步?一块大屏轻松实现3D数据可视化

一只数据鲸鱼

物联网 数据中心 数据可视化 IDC 机房管理

架构师week12心得

Geek_xq

开源数据库管理系统现在比商业产品更受欢迎

PostgreSQLChina

数据库 postgresql 软件 开源社区

android进阶之光!还有人不知道什么是AndroidX的吗?通用流行框架大全

欢喜学安卓

android 程序员 面试 移动开发

对DevOps的九大误解,是时候纠正了!

禅道项目管理

开源 DevOps 敏捷 自动化 持续交付

日记 2021年2月19日(周五)

Changing Lin

2月春节不断更

技术秘籍 | 如何简单优雅的适配textview行间距?

百度开发者中心

前端 TextView

【LeetCode】最大连续1的个数三Java题解

HQ数字卡

算法 LeetCode 2月春节不断更

万字长文:解读区块链7类共识算法

华为云开发者社区

区块链 公有链 拜占庭容错 共识算法 公式算法

诊所数字化:就诊流程标准化和产品SOP设计

boshi

数字化转型 医疗 七日更

翻译:《实用的Python编程》01_07_Functions

codists

Python 人工智能 后端 数据结构与算法 函数

一、MongoDB简介

Kylin

数据库 mongodb 学习笔记 七日更 二月春节不断更

TDD测试驱动开发的实践心得

御剑

架构 TDD 测试驱动开发

竞猜商城系统软件制作

v16629866266

Kafka.01 - 简介

insight

kafka 2月春节不断更

2021金三银四想进字节大厂必看:LeetCode算法收割机+算法刷题宝典

比伯

Java 编程 架构 面试 算法

面试官:2个线程交替打印大小写英文字母,你会怎么实现?

Crud的程序员

Java 架构

前端学习总结,经验分享,项目经验分享过程

魔王哪吒

学习 程序员 Vue 前端 2月春节不断更

LeetCode题解:1143. 最长公共子序列,动态规划,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

Elasticsearch Query DSL 概述

escray

elastic 七日更 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

15. Python 程序运行速度如何提高十倍?第一遍滚雪球学 Python 收工

梦想橡皮擦

Python 2月春节不断更

Hive HMS Canary 时间较长异常分析

笨小康

大数据 hadoop hive

移动应用开发的下一站

移动应用开发的下一站

零停机给Kubernetes集群节点打系统补丁-InfoQ