本文首发在OPPO互联网公众号,欢迎点击转载 https://mp.weixin.qq.com/s/hRvZz_bZfchmP0tkF6M2OA

对于可变基础设施的思考

kubernetes中的可变与不可变基础设施

在云原生逐渐盛行的现在,不可变基础设施的理念已经逐渐深入人心。不可变基础设施最早是由Chad Fowler于2013年提出的,其核心思想为任何基础设施的实例一旦创建之后变成为只读状态,如需要修改和升级,则使用新的实例进行替换。这一理念的指导下,实现了运行实例的一致,因此在提升发布效率、弹性伸缩、升级回滚方面体现出了无与伦比的优势。

kubernetes是不可变基础设施理念的一个极佳实践平台。Pod作为k8s的最小单元,承担了应用实例这一角色。通过ReplicaSet从而对Pod的副本数进行控制,从而实现Pod的弹性伸缩。而进行更新时,Deployment通过控制两个ReplicaSet的副本数此消彼长,从而进行实例的整体替换,实现升级和回滚操作。

我们进一步思考,我们是否需要将Pod作为一个完全不可变的基础设施实例呢?其实在kubernetes本身,已经提供了一个替换image的功能,来实现Pod不变的情况下,通过更换image字段,实现Container的替换。这样的优势在于无需重新创建Pod,即可实现升级,直接的优势在于免去了重新调度等的时间,使得容器可以快速启动。

从这个思路延伸开来,那么我们其实可以将Pod和Container分为两层来看。将Container作为不可变的基础设施,确保应用实例的完整替换;而将Pod看为可变的基础设施,可以进行动态的改变,亦即可变层。

关于升级变化的分析

对于应用的升级变化种类,我们来进行一下分类讨论,将其分为以下几类:

升级变化类型 说明
规格的变化 cpu、内存等资源使用量的修改
配置的变化 环境变量、配置文件等的修改
镜像的变化 代码修改后镜像更新
健康检查的变化 readinessProbe、livenessProbe配置的修改
其他变化 调度域、标签修改等其他修改

针对不同的变化类型,我们做过一次抽样调查统计,可以看到下图的一个统计结果。

在一次升级变化中如果含有多个变化,则统计为多次。

可以看到支持镜像的替换可以覆盖一半左右的的升级变化,但是仍然有相当多的情况下导致不得不重新创建Pod。这点来说,不是特别友好。所以我们做了一个设计,将对于Pod的变化分为了三种Dynamic,Rebuild,Static三种。

修改类型 修改类型说明 修改举例 对应变化类型
Dynamic 动态修改 Pod不变,容器无需重建 修改了健康检查端口 健康检查的变化
Rebuild 原地更新 Pod不变,容器需要重新创建 更新了镜像、配置文件或者环境变量 镜像的变化,配置的变化
Static 静态修改 Pod需要重新创建 修改了容器规格 规格的变化

这样动态修改和原地更新的方式可以覆盖90%以上的升级变化。在Pod不变的情况下带来的收益也是显而易见的。

  1. 减少了调度、网络创建等的时间。
  2. 由于同一个应用的镜像大部分层都是复用的,大大缩短了镜像拉取的时间。
  3. 资源锁定,防止在集群资源紧缺时由于出让资源重新创建进入调度后,导致资源被其他业务抢占而无法运行。
  4. IP不变,对于很多有状态的服务十分友好。

Kubernetes与OpenKruise的定制

kubernetes的定制

那么如何来实现Dynamic和Rebuild更新呢?这里需要对kubernetes进行一下定制。

动态修改定制

liveness和readiness的动态修改支持相对来说较为简单,主要修改点在与prober_manager中增加了UpdatePod函数,用以判断当liveness或者readiness的配置改变时,停止原先的worker,重新启动新的worker。而后将UpdatePod嵌入到kubelet的HandlePodUpdates的流程中即可。

func (m *manager) UpdatePod(pod *v1.Pod) {
m.workerLock.Lock()
defer m.workerLock.Unlock() key := probeKey{podUID: pod.UID}
for _, c := range pod.Spec.Containers {
key.containerName = c.Name
{
key.probeType = readiness
worker, ok := m.workers[key]
if ok {
if c.ReadinessProbe == nil {
//readiness置空了,原worker停止
worker.stop()
} else if !reflect.DeepEqual(*worker.spec, *c.ReadinessProbe) {
//readiness配置改变了,原worker停止
worker.stop()
}
}
if c.ReadinessProbe != nil {
if !ok || (ok && !reflect.DeepEqual(*worker.spec, *c.ReadinessProbe)) {
//readiness配置改变了,启动新的worker
w := newWorker(m, readiness, pod, c)
m.workers[key] = w
go w.run()
}
}
}
{
//liveness与readiness相似
......
}
}
}
原地更新定制

kubernetes原生支持了image的修改,对于env和volume的修改是未做支持的。因此我们对env和volume也支持了修改功能,以便其可以进行环境变量和配置文件的替换。这里利用了一个小技巧,就是我们在增加了一个ExcludedHash,用于计算Container内,包含env,volume在内的各项配置。

func HashContainerExcluded(container *v1.Container) uint64 {
copyContainer := container.DeepCopy()
copyContainer.Resources = v1.ResourceRequirements{}
copyContainer.LivenessProbe = &v1.Probe{}
copyContainer.ReadinessProbe = &v1.Probe{}
hash := fnv.New32a()
hashutil.DeepHashObject(hash, copyContainer)
return uint64(hash.Sum32())
}

这样当env,volume或者image发生变化时,就可以直接感知到。在SyncPod时,用于在计算computePodActions时,发现容器的相关配置发生了变化,则将该容器进行Rebuild。

func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *kubecontainer.PodStatus) podActions {
......
for idx, container := range pod.Spec.Containers {
......
if expectedHash, actualHash, changed := containerExcludedChanged(&container, containerStatus); changed {
// 当env,volume或者image更换时,则重建该容器。
reason = fmt.Sprintf("Container spec exclude resources hash changed (%d vs %d).", actualHash, expectedHash)
restart = true
}
......
message := reason
if restart {
//将该容器加入到重建的列表中
message = fmt.Sprintf("%s. Container will be killed and recreated.", message)
changes.ContainersToStart = append(changes.ContainersToStart, idx)
}
......
return changes
}
Pod的生命周期

在Pod从调度完成到创建Running中,会有一个ContaienrCreating的状态用以标识容器在创建中。而原生中当image替换时,先前的一个容器销毁,后一个容器创建过程中,Pod状态会一直处于Running,容易有错误流量导入,用户也无法识别此时容器的状态。

因此我们为原地更新,在ContainerStatus里增加了ContaienrRebuilding的状态,同时在容器创建成功前Pod的Ready Condition置为False,以便表达容器整在重建中,应用在此期间不可用。利用此标识,可以在此期间方便识别Pod状态、隔断流量。

OpenKruise的定制

OpenKruise(https://openkruise.io/)是阿里开源的一个项目,提供了一套在Kubernetes核心控制器之外的扩展 workload 管理和实现。其中Advanced StatefulSet,基于原生 StatefulSet 之上的增强版本,默认行为与原生完全一致,在此之外提供了原地升级、并行发布(最大不可用)、发布暂停等功能。

Advanced StatefulSet中的原地升级即与本文中的Redbuild一致,但是原生只支持替换镜像。因此我们在OpenKruise的基础上进行了定制,使其不仅可以支持image的原地更新,也可以支持当env、volume的原地更新以及livenessProbe、readinessProbe的动态更新。这个主要在shouldDoInPlaceUpdate函数中进行一下判断即可。这里就不再做代码展示了。

还在生产运行中还发现了一个基础库的小bug,我们也顺带向社区做了提交修复。https://github.com/openkruise/kruise/pull/154。

另外,还有个小坑,就是在pod里为了标识不同的版本,加入了controller-revision-hash值。

[root@xxx ~]# kubectl get pod -n predictor  -o yaml predictor-0
apiVersion: v1
kind: Pod
metadata:
labels:
controller-revision-hash: predictor-85f9455f6
...

一般来说,该值应该只使用hash值作为value就可以了,但是OpenKruise中采用了{sts-name}+{hash}的方式,这带来的一个小问题就是sts-name就要因为label value的长度受到限制了。

写在最后

定制后的OpenKruise和kubernetes已经大规模在各个集群上上线,广泛应用在多个业务的后端运行服务中。经统计,通过原地更新覆盖了87%左右的升级部署需求,基本达到预期。

特别鸣谢阿里贡献的开源项目OpenKruise。

基于Kubernetes和OpenKruise的可变基础设施实践的更多相关文章

  1. 基于kubernetes集群的Vitess最佳实践

    概要 本文主要说明基于kubernetes集群部署并使用Vitess; 本文假定用户已经具备了kubernetes集群使用环境,如果不具备请先参阅基于minikube的kubernetes集群搭建, ...

  2. 039.[转] 基于 Kubernetes 和 Spring Cloud 的微服务化实践

    http://dockone.io/article/2967 基于 Kubernetes 和 Spring Cloud 的微服务化实践 写在前面 网易云容器平台期望能给实施了微服务架构的团队提供完整的 ...

  3. 网易云基于 Kubernetes 的深度定制化实践

    本文由  网易云发布. 2017 年,Kubernetes 超越 Mesos 和 Docker Swarm成为最受欢迎的容器编排技术.网易云从 2015 下半年开始向 Kubernetes 社区贡献代 ...

  4. 基于kubernetes自研容器管理平台的技术实践

    一.容器云的背景 伴随着微服务的架构的普及,结合开源的Dubbo和Spring Cloud等微服务框架,宜信内部很多业务线逐渐了从原来的单体架构逐渐转移到微服务架构.应用从有状态到无状态,具体来说将业 ...

  5. 基于Kubernetes 构建.NET Core技术中台

    今天下午在腾讯云+社区社区分享了<基于Kubernetes 构建.NET Core技术中台>,下面是演讲内容的文字实录. 我们为什么需要中台 我们现在处于企业信息化的新时代.为什么这样说呢 ...

  6. 基于Kubernetes 的Cloud Native 实战 培训课程安排

    课程安排: 基于Kubernetes 的Cloud Native 实战 课程介绍: 云计算.虚拟化.容器微服务PaaS 技术已经广泛应用于新兴互联网企业(如电商平台.搜索引擎.社交平台网站.位置服务平 ...

  7. 解读与部署(三):基于 Kubernetes 的微服务部署即代码

    在基于 Kubernetes 的基础设施即代码一文中,我概要地介绍了基于 Kubernetes 的 .NET Core 微服务和 CI/CD 动手实践工作坊使用的基础设施是如何使用代码描述的,以及它的 ...

  8. 白瑜庆:知乎基于Kubernetes的kafka平台的设计和实现

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文首发在云+社区,未经许可,不得转载. 自我介绍 我是知乎的技术中台工程师,现在是负责知乎的存储相关组件.我的分享主要基于三个,一个是简单 ...

  9. 腾讯基于Kubernetes的企业级容器云平台GaiaStack (转)

    GaiaStack介绍 GaiaStack是腾讯基于Kubernetes打造的容器私有云平台.这里有几个关键词: 腾讯:GaiaStack可服务腾讯内部所有BG的业务: Kubernetes:Gaia ...

随机推荐

  1. selenium中如何保证操作元素的成功率?也就是说如何保证我点击的元素一定是可以点击的?

    1.在寻找元素时,加上显示等待或者隐式等待,这样在对元素进行操作之前保证元素被找到,进而提高成功率: 2.在对元素操作之前,比如click,如果该元素未display(非hidden),就需要先滚动到 ...

  2. vue-cli @4安装

    10月16日,官方发布消息称Vue-cli 4.0正式版发布,安装和vue-cli3.0的是一模一样的,与3.0的脚手架,除了目录发生变化一些,其他的都一样,由于近期才推出,企业中还在使用3.0,但是 ...

  3. 【Flutter 混合开发】添加 Flutter 到 iOS

    Flutter 混合开发系列 包含如下: 嵌入原生View-Android 嵌入原生View-iOS 与原生通信-MethodChannel 与原生通信-BasicMessageChannel 与原生 ...

  4. DP百题练(二)

    目录 DP百题练(二) 区间 DP NOI1995 石子合并 IOI1998 Polygon CH5302 金字塔 USACO06FEB Treats for the Cows G/S LG1043 ...

  5. mysql 一主多从环境搭建(亲测)

    前期准备 三台服务器,服务器使用的是 centos7 mysql-5.7.24-linux-glibc2.12-x86_64 安装包 使用是版本是 mysql-5.7.24 数据库安装 将 mysql ...

  6. Elasticsearch原理解析与性能调优

    基本概念 定义 一个分布式的实时文档存储,每个字段 可以被索引与搜索 一个分布式实时分析搜索引擎 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据 用途 全文检索 结构化搜索 分 ...

  7. 《Web接口开发与自动化测试》学习笔记(一)

    一.Django的入门 学习思路:先安装Django,然后在建立一个项目,接着运行这个项目,最后修改一下这个项目的数据,学习一下Django的原理之类的. 1.安装Django $pip instal ...

  8. flex与bison的学习

    获取bison http://www.gnu.org/software/bison 获取flex http://flex.sourceforge.net/ 本书的范例 ftp://ftp.iecc.c ...

  9. 面试官问我redis数据类型,我回答了8种

    面试官:小明呀,redis 有几种数据结构呀? 小明:8 种 面试官:那你说一下分别是什么? 小明:raw,int,ht,zipmap,linkedlist,ziplist,intset,skipli ...

  10. Spring笔记(7) - Spring的事件和监听机制

    一.背景 事件机制作为一种编程机制,在很多开发语言中都提供了支持,同时许多开源框架的设计中都使用了事件机制,比如SpringFramework. 在 Java 语言中,Java 的事件机制参与者有3种 ...