元原生技术之Kubernetes08-Deployment-让应用永不宕机
08-Deployment-让应用永不宕机
Deployment
"Deployment”,是专门用来部署应用程序的,能够让应用永不宕机,多用来发布无状态的应用,是 Kubernetes 里最常用也是最有用的一个对象。
为什么要有 Deployment
在线业务远不是单纯启动一个 Pod 这么简单,还有多实例、高可用、版本更新等许多复杂的操作。比如最简单的多实例需求,为了提高系统的服务能力,应对突发的流量和压力,我们需要创建多个应用的副本,还要即时监控它们的状态。如果还是只使用 Pod,那就会又走回手工管理的老路,没有利用好 Kubernetes 自动化运维的优势。
使用 YAML 描述 Deployment
我们先用命令 kubectl api-resources 来看看 Deployment 的基本信息:
YAML |
Deployment 的简称是“deploy”,它的 apiVersion 是“apps/v1”,kind 是“Deployment”。
按照前面的经验就可以知道YAML头部怎么写了
YAML |
也可以使用命令 kubectl create 来创建 Deployment 的 YAML 样板
Bash |
得到的YAML大概是这样:
YAML |
Deployment 的关键字段
replicas 字段的含义就是“副本数量”的意思,指定要在 Kubernetes 集群里运行多少个 Pod 实例。
selector,它的作用是“筛选”出要被 Deployment 管理的 Pod 对象,下属字段“matchLabels”定义了 Pod 对象应该携带的 label,它必须和“template”里 Pod 定义的“labels”完全相同,否则 Deployment 就会找不到要控制的 Pod 对象,apiserver 也会告诉你 YAML 格式校验错误无法创建。
YAML |
Kubernetes 采用的是“贴标签”的方式,通过在对象的“metadata”元信息里加各种标签(labels),我们就可以筛选出具有特定标识的那些对象。
创建 Deployment
使用 kubectl apply 来创建对象
YAML |
使用 kubectl get 命令 查看 Deployment
YAML |
显示的信息很重要:
- READY 表示运行的 Pod 数量,前面的数字是当前数量,后面的数字是期望数量,所以“2/2”的意思就是要求有两个 Pod 运行,现在已经启动了两个 Pod。
- UP-TO-DATE 指的是当前已经更新到最新状态的 Pod 数量。因为如果要部署的 Pod 数量很多或者 Pod 启动比较慢,Deployment 完全生效需要一个过程,UP-TO-DATE 就表示现在有多少个 Pod 已经完成了部署,达成了模板里的“期望状态”。
- AVAILABLE 要比 READY、UP-TO-DATE 更进一步,不仅要求已经运行,还必须是健康状态,能够正常对外提供服务,它才是我们最关心的 Deployment 指标。
- AGE 表示 Deployment 从创建到现在所经过的时间,也就是运行的时间。
Deployment 管理的是 Pod,我们最终用的也是 Pod,所以还需要用 kubectl get pod 命令来看看 Pod 的状态:
YAML |
是时候来验证一下 Deployment 部署的应用是否真的可以做到“永不宕机”?
Bash |
被删除的 Pod 确实是消失了,但 Kubernetes 在 Deployment 的管理之下,很快又创建出了一个新的 Pod,保证了应用实例的数量始终是我们在 YAML 里定义的数量。
应用伸缩
在 Deployment 部署成功之后,你还可以随时调整 Pod 的数量,实现“应用伸缩”。
Bash |
应用滚动升级
如何定义应用版本
在 Kubernetes 里应用都是以 Pod 的形式运行的,而 Pod 通常又会被 Deployment 等对象来管理,所以应用的“版本更新”实际上更新的是整个 Pod。
应用的版本变化就是 template 里 Pod 的变化,哪怕 template 里只变动了一个字段,那也会形成一个新的版本,也算是版本变化。
Kubernetes 使用了“摘要”功能,用摘要算法计算 template 的 Hash 值作为“版本号”。
如何实现应用更新
部署应用 http-app:v1,实例数设为 4。
http-app-v1.yaml
YAML |
部署应用
YAML |
可以通过 curl 观察到应用的版本
YAML |
编写一个新版本 http-app-v2.yaml , 修改镜像的版本为 v2
因为 Kubernetes 的动作太快了,为了能够观察到应用更新的过程,我们需要添加一个字段 minReadySeconds,让 Kubernetes 在更新过程中等待一点时间,确认 Pod 没问题才继续其余 Pod 的创建工作。
YAML |
执行命令 kubectl apply 来更新应用,因为改动了镜像名,Pod 模板变了,就会触发“版本更新”,然后用一个新命令:kubectl rollout status,来查看应用更新的状态
YAML |
再执行 kubectl get pod ,可以看到 pod 都更新成了新版本。
仔细查看 kubectl rollout status 的输出信息,你可以发现,Kubernetes 不是把旧 Pod 全部销毁再一次性创建出新 Pod,而是在逐个地创建新 Pod,同时也在销毁旧 Pod,保证系统里始终有足够数量的 Pod 在运行,不会中断服务。
新 Pod 数量增加的过程有点像是“滚雪球”,从零开始,越滚越大,所以这就是所谓的“滚动更新”(rolling update)。
使用命令 kubectl describe 可以更清楚地看到 Pod 的变化情况:
YAML |
- 一开始的时候 V1 Pod(即 http-app-dep-68b9b69985)的数量是 4;
- 当“滚动更新”开始的时候,Kubernetes 创建 1 个 V2 Pod(即 http-app-dep-6c86b44b68 ),并且把 V1 Pod 数量减少到 3;
- 接着再增加 V2 Pod 的数量到 2,同时 V1 Pod 的数量变成了 1;
- 最后 V2 Pod 的数量达到预期值 4,V1 Pod 的数量变成了 0,整个更新过程就结束了
其实“滚动更新”就是由 Deployment 控制的两个同步进行的“应用伸缩”操作,老版本缩容到 0,同时新版本扩容到指定值,大家通过下面这张图再理解一个 滚动更新 的过程
如何管理应用更新
在应用更新的过程中,我们可以随时使用 kubectl rollout pause 来暂停更新,检查、修改Pod,或者测试验证,如果确认没问题,再用 kubectl rollout resume 来继续更新。
如果更新的版本比较多,我们想查看更新历史,可以使用命令 kubectl rollout history:
kubectl rollout history 的列表输出的有用信息太少,可以在命令后加上参数 --revision 来查看每个版本的详细信息,包括标签、镜像名、环境变量、存储卷等等,通过这些就可以大致了解每次都变动了哪些关键字段:
如果我们刚上线的v2版本发现有BUG,希望回退到V1版本,可以使用命令 kubectl rollout undo,也可以加上参数 --to-revision 回退到任意一个历史版本。
YAML |
kubectl rollout undo 的操作过程其实和 kubectl apply 是一样的,执行的仍然是“滚动更新”,只不过使用的是旧版本 Pod 模板,把新版本 Pod 数量收缩到 0,同时把老版本 Pod 扩展到指定值。
下图是从 v2 到 v1 版本降级的过程:
添加更新描述
kubectl rollout history 的版本列表好像有点太简单了呢?只有一个版本更新序号,而另一列 CHANGE-CAUSE 总是显示成 <none> ,能不能加上说明信息,当然是可以的,只需要在 Deployment 的 metadata 里加上一个新的字段 annotations。
annotations 字段的含义是“注解” “注释”,形式上和 labels 一样,都是 Key-Value,也都是给 API 对象附加一些额外的信息,但是用途上区别很大。
- annotations 添加的信息一般是给内部的各种对象使用的,有点像是“扩展属性”;
- labels 主要用来筛选、过滤对象的;
annotations 里的值可以任意写,Kubernetes 会自动忽略不理解的 Key-Value,但要编写更新说明就需要使用特定的字段 kubernetes.io/change-cause。
YAML |
YAML |
控制滚动更新的参数
- maxSurge表示在进行滚动更新期间,允许超过副本数量的额外Pod副本数。它指定了可以同时创建的超过副本数的Pod的最大数量。默认情况下,maxSurge的值为25%。例如,如果你的副本数为4,maxSurge设置为1,则在滚动更新期间,可以同时存在5个Pod。
- maxUnavailable表示在进行滚动更新期间,允许不可用的最大Pod副本数。它指定了可以同时终止的不可用Pod的最大数量。默认情况下,maxUnavailable的值为25%。例如,如果你的副本数为4,并且maxUnavailable设置为1,那意味着在滚动更新期间,允许不可用的最大Pod副本数为1。这意味着在滚动更新期间,最多只能终止1个Pod,而剩下的3个Pod将保持可用状态。
http-app-v4.yaml
YAML |
应用部署策略
滚动部署
在滚动部署中,应用的新版本逐步替换旧版本。实际的部署发生在一段时间内。在此期间,新旧版本会共存,而不会影响功能和用户体验。这个过程可以轻易的回滚。
但是滚动升级有一个问题,在开始滚动升级后,流量会直接流向已经启动起来的新版本,但是这个时候,新版本是不一定可用的,比如需要进一步的测试才能确认。那么在滚动升级期间,整个系统就处于非常不稳定的状态,如果发现了问题,也比较难以确定是新版本还是老版本造成的问题。
蓝绿部署
蓝绿发布提供了一种零宕机的部署方式。不停老版本,部署新版本进行测试,确认OK,将流量切到新版本,然后老版本同时也升级到新版本。始终有两个版本同时在线,有问题可以快速切换。
蓝绿发布的特点:
在部署应用的过程中,应用始终在线。并且新版本上线过程中,不会修改老版本的任何内容,在部署期间老版本状态不受影响。只要老版本的资源不被删除,可以在任何时间回滚到老版本。
以下示意图可描述蓝绿发布的大致流程:先切分20%的流量到新版本,若表现正常,逐步增加流量占比,继续测试新版本表现。若新版本一直很稳定,那么将所有流量都切分到新版本,并下线老版本。
切分20%的流量到新版本后,新版本出现异常,则快速将流量切回老版本。
蓝绿部署要求在升级过程中,同时运行两套程序,对硬件的要求就是日常所需的二倍,比如日常运行时,需要10台服务器支撑业务,那么使用蓝绿部署,你就需要购置二十台服务器。
金丝雀/灰度发布
在生产环境上引一部分实际流量对一个新版本进行测试,测试新版本的性能和表现,在保证系统整体稳定运行的前提下,尽早发现新版本在实际环境上的问题。
“为什么叫金丝雀发布呢,是因为金丝雀对矿场中的毒气比较敏感,所以在矿场开工前工人们会放一只金丝雀进去,以验证矿场是否存在毒气,这便是金丝雀发布名称的由来。”
金丝雀发布的特点:
金丝雀发布,又称为灰度发布。它能够缓慢的将修改推广到一小部分用户,验证没有问题后,再推广到全部用户,以降低生产环境引入新功能带来的风险。
步骤一:将流量从待部署节点移出,更新该节点服务到待发布状态,将该节点称为金丝雀节点;
步骤二:根据不同策略,将流量引入金丝雀节点。策略可以根据情况指定,比如随机样本策略(随机引入)、狗粮策略(就是内部用户或员工先尝鲜)、分区策略(不同区域用户使用不同版本)、用户特征策略(这种比较复杂,需要根据用户个人资料和特征进行分流,类似于千人千面);
步骤三:金丝雀节点验证通过后,选取更多的节点称为金丝雀节点,重复步骤一和步骤二,直到所有节点全部更新。
应用保障策略
容器资源配额
如何限制容器对资源的使用
Kubernetes如何限制容器对资源的需求,只要在 Pod 容器的描述部分添加一个新字段 resources 就可以了。
YAML |
需要重点学习的是 containers.resources,它下面有两个字段:
- “requests”,意思是容器要申请的资源,也就是说要求 Kubernetes 在创建 Pod 的时候必须分配这里列出的资源,否则容器就无法运行。
- “limits”,意思是容器使用资源的上限,不能超过设定值,否则就有可能被强制停止运行。
内存的写法和磁盘容量一样,使用 Ki、Mi、Gi 来表示 KB、MB、GB,比如 512Ki、100Mi、0.5Gi 等。
CPU 因为在计算机中数量有限,非常宝贵,所以 Kubernetes 允许容器精细分割 CPU,即可以 1 个、2 个地完整使用 CPU,也可以用小数 0.1、0.2 的方式来部分使用 CPU。不过 CPU 时间也不能无限分割,Kubernetes 里 CPU 的最小使用单位是 0.001,为了方便表示用了一个特别的单位 m,也就是“milli”“毫”的意思,比如说 500m 就相当于 0.5。 1000m=1核心
如果 Pod 不写 resources 字段,Kubernetes 会如何处理呢?
如果预估错误,Pod 申请的资源太多,系统无法满足会怎么样呢?
Worker 节点资源不足,k8s 驱逐
容器的服务质量
- QoS 服务质量分为三类
- Guaranteed
- Burstable
- BestEffort
- Guaranteed 的 Pod:
- Pod 中的每个容器都必须指定内存限制和内存请求。
- 对于 Pod 中的每个容器,内存限制必须等于内存请求。
- Pod 中的每个容器都必须指定 CPU 限制和 CPU 请求。
- 对于 Pod 中的每个容器,CPU 限制必须等于 CPU 请求。
YAML |
YAML |
- Burstable 的 Pod
- Pod 不符合 Guaranteed QoS 类的标准。
- Pod 中至少一个容器具有内存或 CPU 请求。
YAML |
YAML |
- BestEffort 的 Pod
- 没有设置内存和 CPU 限制或请求
YAML |
YAML |
Kubernetes 资源超卖
Kubernetes 在计算资源时是使用 request 字段进行计算的。 一个 worker 节点如果有 10 个 CPU 的资源。 Pod A 申请 request:5,limit:10, Pod B 申请 request 5,limit 10,是可以申请成功的。 而如果再启动一个 Pod C 申请 request 5, limit 10, 就会失败(Pod 处于 pending 状态, 一直在等待资源)。 因为 kubernetes 并不是按 limit 字段来计算资源用量而是使用 request 字段进行计算。
上面的资源申请方式在 Kubernetes 就被称作 资源超卖。
资源超卖 的意思就是说本来系统只有 10 个 CPU 的资源, 但是容器 A、B、C、D 都各自需要申请 5 个 CPU 的资源,这明显不够用。 但是如果 A、B、C、D 不可能在同一时刻都占满 5 个 CPU 资源, 因为每个服务都有它业务的高峰期和低谷期的。 高峰期的时候可以占满 5 个 CPU, 但是服务大部分时间都处于低谷期,可能只占用 1,2 个 CPU。 所以如果直接写 request:5 的话,很多时候资源是浪费的( kubernetes 里即便容器没有使用到那么多资源, 也会为容器预留 request 字段的资源)。 所以我们可以为容器申请这样的资源: request:1, limit:5。 这样上面 4 个容器加起来只申请了 4 个 CPU 的资源, 而系统里有 10 个 CPU, 是完全可以申请到的。 而每个容器的 limit 又设置成了 5, 所以每个容器又都可以去使用 5 个 CPU 资源。
容器状态探针
什么是容器探针
运行在Kubernetes中的应用需要用什么手段来检查应用的健康状态呢?
Kubernetes 用来判断应用是否“健康”的功能被命名为“探针”(Probe),也可以叫“探测器” 。
Kubernetes 为检查应用状态定义了三种探针,它们分别对应容器不同的状态:
- Startup,启动探针,用来检查应用是否已经启动成功,适合那些有大量初始化工作要做,启动很慢的应用。
- Liveness,存活探针,用来检查应用是否正常运行,是否存在死锁、死循环。
- Readiness,就绪探针,用来检查应用是否可以接收流量,是否能够对外提供服务
需要注意这三种探针是递进的关系:应用程序先启动,加载完配置文件等基本的初始化数据就进入了 Startup 状态,之后如果没有什么异常就是 Liveness 存活状态,但可能有一些准备作没有完成,还不一定能对外提供服务,只有到最后的 Readiness 状态才是一个容器最健康可用的状态。
Kubernetes 在启动容器后就会不断地调用探针来检查容器的状态:
- 如果 Startup 探针失败,Kubernetes 会认为容器没有正常启动,就会尝试反复重启,当然其后面的 Liveness 探针和 Readiness 探针也不会启动。
- 如果 Liveness 探针失败,Kubernetes 就会认为容器发生了异常,也会重启容器。
- 如果 Readiness 探针失败,Kubernetes 会认为容器虽然在运行,但内部有错误,不能正常提供服务,就会把容器从 Service 对象的负载均衡集合中排除,不会给它分配流量。
使用容器探针
startupProbe、livenessProbe、readinessProbe 这三种探针的配置方式都是一样的,关键字段有这么几个:
- periodSeconds,执行探测动作的时间间隔,默认是 10 秒探测一次。
- timeoutSeconds,探测动作的超时时间,如果超时就认为探测失败,默认是 1 秒。
- successThreshold,连续几次探测成功才认为是正常,对于 startupProbe 和livenessProbe 来说它只能是 1。
- failureThreshold,连续探测失败几次才认为是真正发生了异常,默认是 3 次。
探测方式,Kubernetes 支持 3 种:Shell、TCP Socket、HTTP GET,它们也需要在探针里配置:
- exec,执行一个 Linux 命令,比如 ps、cat 等等,和 container 的 command 字段很类似。
- tcpSocket,使用 TCP 协议尝试连接容器的指定端口。 Telnet port
- httpGet,连接端口并发送 HTTP GET 请求。 Curl http://ip/ 200
我们看一个使用探针的例子,还是以 Nginx 作为示例,用 ConfigMap 编写一个配置文件:
nginx-conf-cm.yaml
YAML |
nginx-dep-probe.yaml
YAML |
- StartupProbe 使用了 Shell 方式,使用 cat 命令检查 Nginx 存在磁盘上的进程号文件(/var/run/nginx.pid),如果存在就认为是启动成功,它的执行频率是每2秒探测一次。
- LivenessProbe 使用了 TCP Socket 方式,尝试连接 Nginx 的 80 端口,每 5 秒探测一次。
- ReadinessProbe 使用的是 HTTP GET 方式,访问容器的 /ready 路径,每 5 秒发一次请求。
我们使用 kubectl apply 创建 nginx deployment
YAML |
观察 pod 状态
使用 kubectl logs 命令查看 nginx 日志,可以看到探针的执行情况
通过上图可以看到 Kubernetes 正是以大约 5 秒一次的频率,向 URI /ready 发送 HTTP 请求,不断地检查容器是否处于就绪状态。
为了验证另两个探针的工作情况,我们可以修改探针,比如把命令改成检查错误的文件、错误的端口号:
YAML |
当 StartupProbe 探测失败的时候,Kubernetes 就会不停地重启容器,现象就是 RESTARTS 次数不停地增加,而 livenessProbe 和 readinessProbePod 没有执行,Pod 虽然是 Running 状态,也永远不会 READY:
测试 Liveness 和 readinessProbe 执行频率
YAML |
ReplicaSet 控制器
Kubernetes 中的 ReplicaSet 主要的作用是维持一组 Pod 副本的运行,它的主要作用就是保证一定数量的 Pod 能够在集群中正常运行,它会持续监听这些 Pod 的运行状态,在 Pod 发生故障重启数量减少时重新运行新的 Pod 副本。
nginx-reps.yaml
YAML |
Deployment 是一个可以拥有 ReplicaSet 并使用声明式方式在服务器端完成对 Pod 滚动更新的对象。 尽管 ReplicaSet 可以独立使用,目前它们的主要用途是提供给 Deployment 作为编排 Pod 创建、删除和更新的一种机制。当使用 Deployment 时,你不必关心如何管理它所创建的 ReplicaSet,Deployment 拥有并管理其ReplicaSet。 因此,建议你在需要 ReplicaSet 时使用 Deployment。
ReplicationController 控制器
- 点赞
- 收藏
- 关注作者
评论(0)