蓝鲸容器服务(Blueking Container Service,如下简称BCS)是腾讯 IEG 互动娱乐事业群的容器上云平台,底层基于腾讯云容器服务(Tencent Kubernetes Engine, TKE),为 IEG 的自研游戏业务上云提供容器化和微服务化的建设工做。 区别于通常互联网业务,腾讯游戏业务具备大规模、低时延、网络敏感、超高可靠性要求等一系列众多特色,大量使用共享内存通讯等技术,对云原生上云是一个巨大的挑战。BCS 在服务于各游戏业务的容器上云过程当中,结合业务需求与社区方案,开发了两个加强版的 Kubernetes 工做负载 operator:GameStatefulSet 和 GameDeployment,更贴近业务场景,知足复杂多样的容器上云需求。python
游戏类业务具备多种类型,如房间类游戏、MMO 游戏。不管是哪一种类型的游戏,都有诸如大规模的在线玩家、对网络时延和抖动异常敏感、多区多服等特色,游戏后台服务在设计时为了知足这些需求,自然地会追求实时高速通讯、性能最大化,大量地使用了进程间共享内存通讯、数据预加载进内存、跨主机 TCP 通讯等技术,极少使用远程数据、RPC,这其实与微服务的要求有点背道而驰。
结合容器化上云的需求,总结来讲,游戏类服务通常具备如下特性:git
全部这些特色,对于 Kubernetes 和云原生上云都是巨大的挑战。Kubernetes 原生适合微服务架构,把全部实例看成牲畜而不是宠物。即使是推出了 StatefulSet(最开始起名为 PetSet) 来支持有状态服务,也只是给每一个实例设定一个网络和存储的编号,即便实例挂了,拉起一个相同编号的实例代替便可,并不涉及到共享内存丢失、数据搬迁、路由变动等复杂的流程。这也是后来 PetSet 被更名为 StatefulSet 的缘由。
要支持游戏这类复杂业务的上云,咱们须要更进一步,开发更贴合业务场景的 workload,下降业务接入的门槛和成本。github
BCS 在服务于腾讯 IEG 众多不一样类型的包括但不限于游戏业务的容器上云过程当中,与各游戏业务及平台探讨业务场景,抽象业务共性和需求,同时积极学习和借鉴云原生社区的优秀开源项目如 OpenKruise,argo-rollouts,flagger 等,在 Kubernetes 原生及其它开源项目的基础上,研发了 bcs-gamedeployment-operator 和 bcs-gamestatefulset-operator 两个 operator,分别对应 GameDeployment 和 GameStatefulSet 两个加强版的 Kubernetes 工做负载,在原生的 Deployment 和 StatefulSet 基础上实现了一系列加强的特性和性能提高,以知足复杂业务的云原生上云需求。
GameDeployment 和 GameStatefulSet 虽然是在服务于游戏业务的的场景中产生,但咱们为其抽象出来的特性,其实能契合大多数类型业务特别是复杂业务的需求,更强的可控性,更贴近业务的研发和运维发布场景,能极大提高云原生上云的能力。web
Kubernetes 原生的 Deployment 是面向无状态服务的工做负载,其底层是基于 ReplicaSet 来实现,一个 Deployment 经过控制底层多个版本的 ReplicaSet 的版本数量来实现应用的滚动更新和回滚。
虽然是无状态服务,大多数应用仍有 pod 原地升级、pod 镜像热更新(下文单独)等其它一些需求,而原生的 Deployment 因为是基于多个版本的 ReplicaSet 迭代来实现,实现较为复杂,想要在其中添加原地升级等功能比较困难。
咱们在借鉴原生的 Deployment 和 StatefulSet 的代码实现的基础上,参考了其它开源项目,研发实现了一个加强版的 Deployment: GameDeployment,以知足复杂的无状态应用的更多高阶需求。
相比 Deployment,GameDeployment 具备如下一些核心特性:docker
apiVersion: tkex.tencent.com/v1alpha1 kind: GameDeployment metadata: name: test-gamedeployment labels: app: test-gamedeployment spec: replicas: 5 selector: matchLabels: app: test-gamedeployment template: metadata: labels: app: test-gamedeployment spec: containers: - name: python image: python:3.5 imagePullPolicy: IfNotPresent command: ["python"] args: ["-m", "http.server", "8000" ] ports: - name: http containerPort: 8000 preDeleteUpdateStrategy: hook: templateName: test updateStrategy: type: InplaceUpdate partition: 1 maxUnavailable: 2 canary: steps: - partition: 3 - pause: {} - partition: 1 - pause: {duration: 60} - hook: templateName: test - pause: {} inPlaceUpdateStrategy: gracePeriodSeconds: 30
以上是一个示例的 GameDeployment yaml 配置,与 Deployment 的配置差异不大,大部分继承 Deployment 的参数含义。咱们将逐个介绍不一样或新增之处:json
Kubernetes 原生的 StatefulSet 是面向有状态应用的工做负载,每一个应用实例都有一个单独的网络和存储编号,实例在更新和缩容时是有序进行的。StatefulSet
为了面对上文描述的一些更为复杂的有状态应用的需求,咱们在原生的 StatefulSet 的基础上,开发实现了加强版本: GameStatefulSet。
相比 StatefulSet, GameStatefulSet 主要包含如下新增特性:api
apiVersion: tkex.tencent.com/v1alpha1 kind: GameStatefulSet metadata: name: test-gamestatefulset spec: serviceName: "test" podManagementPolicy: Parallel replicas: 5 selector: matchLabels: app: test preDeleteUpdateStrategy: hook: templateName: test updateStrategy: type: InplaceUpdate rollingUpdate: partition: 1 inPlaceUpdateStrategy: gracePeriodSeconds: 30 canary: steps: - partition: 3 - pause: {} - partition: 1 - pause: {duration: 60} - hook: templateName: test - pause: {} template: metadata: labels: app: test spec: containers: - name: python image: python:latest imagePullPolicy: IfNotPresent command: ["python"] args: ["-m", "http.server", "8000" ] ports: - name: http containerPort: 8000
以上是一个 GameStatefulSet 的 yaml 示例,相关参数介绍以下:网络
GameDeployment 和 GameStatefulSet 都支持 InplaceUpdate 更新策略。
原地升级是指,在更新 pod 版本时,保持 pod 的生命周期不变,只重启 pod 中的一个或多个容器,于是在升级期间,pod 的共享内存 IPC 等能保持不丢失。使用原地升级的实例更新方式,有如下收益:架构
Kubernetes 原生的 Deployment 和 StatefulSet 等工做负载都没有直接支持原地升级的更新方式,但 kubelet 组件隐藏地支持了这一能力。针对一个处于 running 状态的 Pod,咱们只须要经过 patch 的方式更新 pod spec 中的 image 版本,kubelet 监控到了这一变化后,就会自动地杀掉对应的旧版本镜像的容器并拉起一个新版本镜像的容器,即实现了 Pod 的原地升级。
咱们经过 ReadinessGate 和 inPlaceUpdateStrategy/gracePeriodSeconds 的结合,来实现原地升级当中的流量服务的平滑切换。app
原地升级的更新策略下,能够配置 spec/updateStrategy/inPlaceUpdateStrategy/gracePeriodSeconds 参数,假设配置为 30 秒,那么 GameStatefulSet/GameDeployment 在原地更新一个 pod 前,会经过 ReadinessGate 先把这个 pod 设置为 unready 状态,30 秒事后才会真正去原地重启 pod 中的容器。这样,在这 30 秒的时间内由于 pod 变为 unready 状态,k8s 会把该 pod 实例从 service 的 endpoints 中剔除。等原地升级成功后,GameStatefulSet/GameDeployment 再把该 pod 设为 ready 状态,以后 k8s 才会从新把该 pod
实例加入到 service 的 endpoints 当中。
经过这样的逻辑,在整个原地升级过程当中,能保证服务流量的无损。
gracePeriodSeconds 的默认值为 0 ,当为 0 时,GameStatefulSet/GameDeployment 会马上原地升级 pod 中的容器,可能会致使服务流量的丢失。
InplaceUpdate 一样支持灰度发布 partition 配置,用于配置灰度发布的比例。
GameDeployment InplaceUpdate 使用示例
GameStatefulSet InplaceUpdate 使用示例
原地升级更新策略虽然能保持 pod 的生命周期和 IPC 共享内存,但始终是要重启容器的。对于游戏对局类的 GameServer 容器,若有玩家正在进行对局服务,原地升级 GameServer 容器会中断玩家的服务。
有些业务为了实现不停服更新,使用了服务进程 reload 技术,reload 过程当中新版本的进程接替旧版本的进程提供服务,内存数据不丢失,升级过程当中玩家无感知。
为了知足这类业务的容器上云需求,咱们调研了 docker 镜像 merge 的增量更新策略,修改 docker 源码增长了一个容器镜像热更新的接口。在对一个运行着的容器调用镜像热更新接口进行镜像版本的更新时,容器的生命周期不变,容器内的进程也保持不变,但容器的基础镜像会替换为新的版本。
经过对 docker 的这种改动,对一个运行状态的容器进行镜像热更新后,容器状态不变,但其基础镜像的版本及数据已实现了增量更新。假如容器中的进程实现了 reload 功能,而基础镜像中的 so 文件或配置都已更新为新版本,此时只须要往容器中的进程发送 reload 信号,就能完成服务进程的热更新,实现不停服升级。
为了在 Kubernetes 中实现容器镜像热更新的能力,咱们修改了 kubelet 的代码,在 kubelet 原地升级能力的基础上,当 pod 中加了指定的 annotation 时,kubelet 对 pod 的更新就会从原地升级操做变为容器镜像热更新操做,调用 docker 的镜像热更新接口完成容器的镜像热更新。
关于在 docker 和 kubelet 上对容器镜像热更新的详细实现,咱们后续将在另外的文章中详细阐述。
GameStatefulSet/GameDeployment 集成了容器镜像热更新的功能,当把 spec/updateStrategy/type 配置为 HotPatchUpdate 时,就会经过更新 pod 中的容器镜像版本并添加 annotation 的方式,联动 kubelet 和docker 完成容器镜像热更新的功能。在整个过程当中,pod 及其容器的生命周期都是没有变化的,此后,用户能够经过向容器中进程发送信号的方式,完成业务进程的 reload,保证服务的不中断。
HotPatchUpdate 一样支持灰度发布 partition 配置,用于配置灰度发布的比例。
HotPatchUpdate 的更新策略须要结合咱们定制化的 kubelet 和 docker 版本才能生效。
GameDeployment HotPatchUpdate 使用示例
GameStatefulSet HotPatchUpdate 使用示例
上文中咱们提到,多数复杂类应用在发布更新过程当中有许多外部依赖或应用自己的数据指标依赖,如上面咱们提到的:实例扩缩容或更新前须要进行数据搬迁;缩容一个实例前须要先完成路由变动;实例缩容或更新前须要等待游戏对局结束。此外,在灰度发布时,有时咱们须要从 Prometheus 监控数据中查看指标是否符合预期,以决定是否继续灰度更多的实例。
这其实能够看做为应用发布过程当中的各类 hook 勾子,经过 hook 的结果来判断是否能够继续下一步的发布流程。不管是面向无状态应用的 GameDeployment 仍是面向有状态应用的 GameStatefulSet,都有这种发布需求。
咱们在深入挖掘业务需求和调研解决方案后,在 Kubernetes 层面抽象出了一个通用的 operator: bcs-hook-operator。
bcs-hook-operator 主要职责是根据 hook 模板执行 hook 操做并记录 hook 的状态,GameDeployment 或 GameStatefulSet watch hook 的最终状态,根据 hook 结果来决定下一步执行何种操做。
bcs-hook-operator 定义了两种 CRD:
apiVersion: tkex.tencent.com/v1alpha1 kind: HookTemplate metadata: name: test spec: args: - name: service-name value: test-gamedeployment-svc.default.svc.cluster.local - name: PodName metrics: - name: webtest count: 2 interval: 60s failureLimit: 0 successCondition: "asInt(result) < 30" provider: web: url: http://1.1.1.1:9091 jsonPath: "{$.age}"
HookTemplate 用来定义一个 hook 的模板。在一个 HookTemplate 中能够定义多个 metric,每一个 metric 都是须要执行的一个 hook。在 metric 中能够定义 hook 的次数、两次之间的间隔、成功的条件、provider等等多个参数。provider 定义的是 hook 的类型,目前支持两种类型的 hook:webhook 和 prometheus。
apiVersion: tkex.tencent.com/v1alpha1 kind: HookRun metadata: name: test-gamedeployment-67864c6f65-4-test namespace: default spec: metrics: - name: webtest provider: web: jsonPath: '{$.age}' url: http://1.1.1.1:9091 successCondition: asInt(result) < 30 terminate: true status: metricResults: - count: 1 failed: 1 measurements: - finishedAt: "2020-11-09T10:08:49Z" phase: Failed startedAt: "2020-11-09T10:08:49Z" value: "32" name: webtest phase: Failed phase: Failed startedAt: "2020-11-09T10:08:49Z"
HookRun 是根据模板 HookTemplate 建立的一个实际运行的 hook CRD,bcs-hook-operator 监测并控制 HookRun 的运行状态和生命周期,根据其 metrics 中的定义来执行 hook 操做,并实时记录 hook 调用的结果。
关于 bcs-hook-operator 的更详细介绍可参考:bcs-hook-operator
GameDeployment/GameStatefulSet 与 bcs-hook-operator 在应用发布过程当中使用 hook 时的交互架构图:
GameDeployment & GameStatefulSet 支持智能化的分步骤分批灰度发布功能,容许用户配置灰度发布的自动化步骤,经过配置多个灰度发布步骤,达到分批发布的目的,自动监测发布的效果,实现灰度发布的智能化控制。
当前,能够在灰度发布步骤中配置如下 4 种步骤:
... spec: ... updateStrategy: type: InplaceUpdate rollingUpdate: partition: 1 inPlaceUpdateStrategy: gracePeriodSeconds: 30 canary: steps: - partition: 3 # 该批灰度发布的个数 - pause: {} # 暂停发布 - partition: 1 # 该批灰度发布的个数 - pause: {duration: 60} # 暂停60秒后再继续发布 - hook: # 定义 hook 步骤 templateName: test # 使用名为test的HookTemplate - pause: {} # 暂停发布 ...
在 GameDeployment 和 GameStatefulSet 上进行智能式分步骤灰度发布的配置和使用方式基本一致,详细使用教程可参考:智能式分步骤灰度发布教程
在上文 “基于hook的应用交互式发布” 章节咱们提到,应用在发布更新过程当中有许多外部依赖或应用自己的数据指标依赖。特别是在缩容实例或升级实例版本时,须要删掉旧版本的实例,但每每实例上仍然有服务不能中断,若有玩家在进行游戏对战。此时,实例的缩容或更新是有依赖的,不能立刻进行缩容或更新,须要查询条件,当条件知足后再进行缩容或更新。
咱们根据 bcs-hook-operator 的抽象,在 GameDeployment 和 GameStatefulSet 上开发了 PreDeleteHook 的功能,实现优雅地删除和更新应用 Pod 实例。
apiVersion: tkex.tencent.com/v1alpha1 ... spec: preDeleteUpdateStrategy: hook: templateName: test # 使用的HookTemplate updateStrategy: ... inPlaceUpdateStrategy: gracePeriodSeconds: 30
在 GameDeployment/GameStatefulSet 的 spec/preDeleteUpdateStrategy 中指定 HookTemplate,那么当缩容或更新 Pod 实例时,针对每个待删除或更新的 Pod,GameDeployment/GameStatefulSet 都会根据 HookTemplate 模板建立一个 HookRun,而后 watch 这个 HookRun 的状态。bcs-hook-operator 控制 HookRun 的运行并实时记录其状态。当 HookRun 运行完成后,GameDeployment/GameStatefulSet watch 到其最终状态,依据其最终状态来决定是否能正常删除或更新 Pod。
更进一步地,咱们在 HookTemplate 和 HookRun 中支持了一些常见参数的自动渲染,如 PodName, PodNamespace, PodIP 等。
例如,假设 PreDeleteHook 中须要运行的 hook 是应用实例自己的一个 http 接口,暴露在容器的 8080 端口,那么咱们能够定义这样一个 HookTemplate:
apiVersion: tkex.tencent.com/v1alpha1 kind: HookTemplate metadata: name: test spec: args: - name: PodIP metrics: - name: webtest count: 3 interval: 60s failureLimit: 2 successCondition: "asInt(result) > 30" provider: web: url: http://{{ args.PodIP }}:8080 jsonPath: "{$.age}"
这样,GameDeployment/GameStatefulSet 在针对待删除或更新的 Pod 建立 HookRun 时,会把 Pod IP 渲染进 webhook url 中,最终建立和执行的是对应用 Pod 自己提供的 http 接口的 webhook 调用。
在 GameDeployment 和 GameStatefulSet 上进行 PreDeleteHook 的配置和使用方式基本一致,详细使用教程可参考:PreDeleteHook:优雅地删除和更新 Pod
使用 Pod 原地升级是为了最大程度上提高发布的效率,并减小服务中断的时间。但一个 Pod 的原地升级过程当中,最大的时间消耗在于拉取新版本镜像的时间,特别是当镜像很大的时候。
所以,业务在使用原地升级的过程当中,向咱们反馈的最多的问题就是原地升级的速度仍然过慢,与理想中的速度有差距。
基于此,咱们与欢乐游戏工做室的公共支持团队合做共建了 GameStatefulSet&GameDeployment 的原地升级镜像预热方案。
以 GameDeployment 为例,镜像预热方案的流程架构以下图所示:
使用这个方案,能保证 Kubernetes 工做负载 GameDeployment&GameStatefulSet 与镜像预热方案的解耦,假设要支持更多的 Kubernetes 工做负载的镜像预热,只须要在 bcs-webhook-server 上添加对这个工做负载 CRD 的支持便可。
基于此,咱们重构开发了 bcs-webhook-server,支持以插件化的方式添加 webhook:
镜像预热方案及 bcs-webhook-server 的更多实现细节,请参考:bcs-webhook-server
BCS 团队在基于 TKE 构建云原生上云平台的过程当中,与不一样业务团队进行探讨,挖掘业务需求,抽象需求共性,并结合社区的开源方案,研发了 GameDeployment 和 GameStatefulSet 这两个 Kubernetes 工做负载。这两个工做负载及其特性虽然是为复杂的游戏业务上云而产生,但基本能覆盖大多数互联网业务的需求,更贴近各类业务的运维和发布场景。
后续,咱们也将继续与各业务团队进行探讨和合做,抽象更多需求特性,不断迭代,持续加强 GameStatefulSet 和 GameDeployment 的能力。
蓝鲸容器服务 BCS 已经开源,更多容器上云方案和细节请参考咱们的开源项目:BK-BCS
【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!