本文首次为大家带来 OAM 体系中的实战演练介绍,先讲解 OAM Workload 和 Trait 相关知识及它们的交互逻辑,接下来手把手教大家如何通过实现自定义 CRD 和 Controller 实现一个 OAM 的扩展 Trait。以及通过展示这个 Trait 的部署使用来更深入的理解 OAM 的运行机制。

背景

众所周知,在 OAM 中,一个应用描述可能会包含三个核心概念:

第一个核心概念是组成应用程序的组件(Component),它可能是一个微服务、一个容器、一个虚拟机、一个数据库实例或者一个 Function 等各种各样的工作负载(Workload)的描述。

第二个核心概念是每个组件所需要的运维特征(Trait)。例如:弹性伸缩、负载均衡、灰度发布、证书鉴权、监控等一系列运维能力的描述。它们对应用程序的运行至关重要,但在不同环境中其实现方式各不相同;

第三个核心概念其实就是将前两个概念按照一定规则组合到一起,就会得到一个自包含的、具体的应用程序描述了(Application Configuration)。

你可以在《深度解读!阿里统一应用管理架构升级的教训与实践》一文中了解更多 OAM 的由来以及相关背景。

为什么需要 Trait?

实际上,在 Kubernetes 这样一个“一切皆对象”的项目当中,只要你稍微留心,就会发现 Kubernetes 提供的很多能力或者说 API 对象其实都属于 Trait(运维特征)的范畴。比如一个应用运行所需要的 HPA(HorizontalPodAutoScaler),暴露端口和访问信息所需要的 Service 和 Ingress,网络配置策略 NetworkPolicy 等等。此外,得益于 CRD 和 Operator 机制的成熟,Kubernetes 社区中也有越来越丰富的“应用运维能力”涌现出来,如具备多种发布策略的 Flagger 项目,具备强大的流量管理能力的 Istio 项目,这些 Kubernetes 插件提供的“能力”也都属于 OAM 中的 Traits。

所以说,OAM 中将 Kubernetes 对象划分为“组件”和“运维特征”的思想其实是非常自然的一件事情。简而言之,Kubernetes 中的对象,要么是用来描述“我要运行什么”,要么就是用来描述“怎么运维这个东西”。后者在 Kubernetes 中就是一个运维能力对象,也就是 OAM 规范中的 Trait。而更重要的是,在一个企业级应用管理系统当中,凡是对于属于“运维特征”的 API 对象,往往是需要做特殊的管理和编排的,这个 Trait 管理的细节我们会在后面的系列文章中做详细介绍。

另外一个问题是,为什么在 OAM 作为一个 Kubernetes 的应用定义规范,一定要求把这些“运维特征”独立定义在一个单独的对象(Trait)当中,而不是像有些项目那样直接定义为工作负载的 spec 的一部分呢?

这里的原因,一方面是出于对“关注点分离”的考虑,即系统本身对于运维侧的能力能够通过一个单独的 API 暴露出来而不是同业务研发侧关心的工作负载 spec 耦合在一起。而另一方面则是出于实际运维场景和稳定性的需要。举一个简单的例子,假设我们在工作负载的 spec 中同时定义了这个组件的自动扩缩容策略,即“最多 10 个实例,最少 3 个实例”,如下所示:

那么假设后续系统容量发生了变化,我需要把扩容策略的上限减小到 5 个实例,该怎么做呢?当然是把 maxScale 修改为 5 了。然而这时候问题出现了:这个 maxScale 的信息本身是属于 MyWorkload 的一部分的,所以这个对扩容策略的修改,就会被系统识别为一次“变更”。而这次变更,就会被系统捕捉到产生一条新的变更记录,甚至如果你的系统是自动发布流水线的话,它还有可能触发一次线上发布。在生产环境中,这种原因不明的变更和发布可能会引发很多不可预知的故障,比如:业务容器被无缘无故的重启甚至带来预期外的服务中断。

所以说,OAM 规范建议将对运维能力的配置定义在一个单独的 API 对象中(Trait)而不是作为工作负载的一部分(无论是 spec 还是 annotation),从而避免我们对运维能力的配置被系统识别为一次变更。更重要的是,在这种工作负载与运维能力解耦情况下,运维能力的 API 对象还可以锁定工作负载的某个固定版本(Revision)上,相当于在运维侧引入了人工审批环节,从根本上避免错误的变更引发不可预知的故障。

到这里,相信你已经对 Trait 背后的设计思想有了更加深入的了解了。那么我们现在来看下一些实际的、通过 OAM 规范定义应用的案例吧。

案例 1:一个由 Deployment + Flagger Canary 组成的应用

我们首先来定义一个应用,这个应用的工作负载是 Deployment,需要的运维能力是 Flagger 项目提供的金丝雀发布策略(Canary)。

这个应用通过 OAM 规范的定义方法非常简单,我们直接把 Flagger Canary 的 CR 定义为一个 OAM Trait ,然后把它绑定给一个 Deployment 工作负载即可。如下所示:

apiVersion: core.oam.dev/v1alpha2

kind: Component

metadata:

name: frontend

spec:

workload:

apiVersion: apps/v1

kind: Deployment

spec:

containers:

- name: wordpress

image: wordpress:4.6.1-apache

ports:

- containerPort: 80

name: wordpress

---

apiVersion: core.oam.dev/v1alpha2

kind: ApplicationConfiguration

metadata:

name: example-appconfig

spec:

components:

- componentName: frontend

traits:

- trait:

apiVersion: flagger.app/v1beta1

kind: Canary

spec:

targetRef:

apiVersion: apps/v1

kind: Deployment

name: wordpress

progressDeadlineSeconds: 60

analysis:

interval: 1m

threshold: 5

...

这样,这个 ApplicationConfiguration 就成为了一个由“Deployment(工作负载) + Canary (运维能力)”组成的标准的、符合 OAM 规范的应用定义对象了,你可以把它交给任何一个支持 OAM 的 Kubernetes 集群部署起来。

案例 2:一个由 Deployment + 自定义 Ingress 组成的应用

平时我们创建一个 Kubernetes 应用,如果需要外部访问,通常会创建一个 Ingress。但是,Kubernetes 对 Ingress 的设计是比较“奇葩”的,你光有一个 Ingress 不够,必须创建一个 Kubernetes Service 才能够让这个 Ingress 生效。

所以在这里,大家一定会有一个自然而然的想法,我能不能自己在 Kubernetes 里做一个 Ingress 能力,只要用户提交一个 Ingress YAML,系统就自动创建出来一个 Ingress 和对应的 Service 对象呢?

答案当然是可以的!而且显然你不需要去改写 Kubernetes Ingress Controller,只需要使用 CRD 基于现有 Ingress 和 Service 对象做一个封装,根据用户的提交自动生成这些 API 资源即可。这种基于现有 Kubernetes API 对象封装成一个更加用户友好的上层 API 对象的过程,就叫做“构建上层抽象”。

我们来看一下这个自定义 Ingress (我们把它取名叫做 IngressTrait)的实际使用效果:

apiVersion: core.oam.dev/v1alpha2

kind: Component

metadata:

name: example-deploy

spec:

workload:

apiVersion: apps/v1

kind: Deployment

metadata:

name: web

spec:

selector:

matchLabels:

app: test

template:

metadata:

labels:

app: test

spec:

containers:

- name: nginx

image: nginx:1.17

ports:

- containerPort: 80

name: web

---

apiVersion: core.oam.dev/v1alpha2

kind: ApplicationConfiguration

metadata:

name: example-appconfig

spec:

components:

- componentName: example-deploy

traits:

- trait:

apiVersion: extended.oam.dev/v1beta2

kind: IngressTrait

metadata:

name: example-ingress-trait

spec:

rules:

- host: nginx.oam.com

paths:

- path: /

backend:

serviceName: deploy-test

servicePort: 8080

上面这个 ApplicationConfiguration YAML 文件,就是一个由 Deployment 工作负载和自定义 Ingress 运维特征组成的、符合 OAM 规范的应用定义了。只要你把它提交给任何一个支持 OAM 的 Kubernetes 集群,它就能够自动创建出来你这个应用运行所需要的 Deployment,Ingress,还有 Service 对象了。更重要的是,用户再也不需要纠结为什么还需要给 Ingress 创建 Service 的问题:只要这个 Service 不存在,那么 IngressTrait 会自动把这个 Service 创建出来。

不知道你有没有发现,上面这个 YAML 文件中,其实并没有显示的给出这个 Service 的详细信息,那么这个 IngressTrait CRD 对应的 Controller 是如何生成 Service 对象的呢?

实际上,当你提交一个 ApplicationConfiguration 对象给 Kubernetes 之后,OAM 插件(OAM Runtime)就会为你自动创建这个对象里定义的 Workload(工作负载)和 Trait(运维特征)对应的 API 对象。但与此同时,OAM Runtime 在生成 Trait 对象的时候会做一个小小的处理,它会根据具体情况来在 Trait 对象上加上一个字段叫做 workloadRef ,所以上例中的 IngressTrait 实际的 CR 会如下所示:

apiVersion: extended.oam.dev/v1alpha2

kind: IngressTrait

metadata:

name: example-ingress-trait

spec:

workloadRef:

apiVersion: apps/v1

kind: Deployment

name: web

rules:

- host: nginx.oam.com

paths:

- path: /

backend:

serviceName: deploy-test

servicePort: 8080

这个小秘密,就是为什么你自己编写的 IngressTrait Controller 可以在用户不显式定义 Service 信息(比如:Label、Port 等)的情况下,生成出该 Service 对象的关键所在:上述这些信息实际上都是 OAM Runtime 通过 workloadRef 字段反查 Component 中的 spec.workload 字段获取、拼装得到的。当然,这也意味着这个 Service 对象的类型是固定的,都是 ClusterIP 类型(因为这完全是一个内部使用的 Service)。

从这个角度来说,OAM 不仅仅是 Kubernetes 上的应用定义规范,也为你提供了一个构建上层抽象的标准化框架。

IngressTrait 的实现细节

综上所述,IngressTrait 的实现是一个标准的 Kubernetes CRD + Controller 的编写过程,我是使用 kubebuilder 来做的,大家也可以使用自己喜欢的框架。这个项目的地址在:https://github.com/oam-dev/catalog/tree/master/traits/ingresstrait

构建项目

我们使用 kubebuilder init 命令初始化一个新项目,并用 kubebuilder create api 命令创建一个新的API,注意我们不需要创建 webhook。由此两步我们便可成功得到 CRD 和 Controller 的模板:

详细步骤参考文档:https://book.kubebuilder.io/quick-start.html。

编写 Controller 代码

kubebuilder 已经为我们生成了较为完整的框架,之后我们主要编辑 ingresstrait_types.go 和 ingresstrait_controller.go 两个文件,来自定义 CRD 和 Controller 逻辑。

类型定义

具体代码可以查看 ingresstrait_types.go[1] 文件,其中最核心的是 IngressTraitSpec 结构体中定义,其中WorkloadReference就是上文提到 workloadRef 辅助字段。

控制器逻辑

Trait 的控制逻辑都在 Controller 的 Reconcile 函数中实现,具体代码可查看 ingresstrait_controller.go[2] 文件,主要分为 4 个步骤。

获取 Trait 对象,通过传入的 req.NamespacedName 获取需要调谐(Reconcile)的 Trait 对象。

根据获取到的 Trait 对象,去获取其引用的 Workload 对象。

获取目标资源对象,首先需要确定 Workload 对象的类型,若是自定义 API 类型(比如用户自己在 Deployment 上又做了自己的封装),则其子资源才是我们需要的目标资源对象;若是 Kubernetes 内置的 API 类型(比如 Deployment),则 Workload 对象就是我们需要的目标资源对象。具体 DetermineWorkloadType[3] 函数:此处示例是根据 APIVersion 来做判断,若是自定义的 OAM workload 则用 util.FetchWorkloadDefinition[4] 去获取 workload 的子资源并返回;若是 Kubernetes CR 则直接将 workload 作为返回值即可。

执行 Trait 逻辑,IngressTrait 的逻辑是:若目标资源对象没有 Service,则为其 同时创建 Kubernetes Ingress 和 Service 对象,其中 Service 对象的信息根据 Workload 对象和 IngressTrait 对象的 spec 生成出来;若目标资源对象已有 Service,则只为其创建一个 Ingress 对象即可。

作用于其他类型的工作负载

需要注意的是,我们编写的这个 IngressTrait 还是非常通用的,它不仅可以作用于 Deployment,也可以作用于 StatefulSet,还可以作用于你自己通过 CRD + Operator 定义的各种 Workload。这里面也有很多有意思的细节,比如当作用于 StatefulSet 的时候,IngressTrait Controller 会自动获取 StatefulSet 中的 spec.template.spec.container.ports 字段和 spec.serviceName 字段为其创建 Service 和 Ingress。关于上述 IngressTrait 详细的使用方法,大家可以参考这个样例库:https://github.com/oam-dev/catalog/tree/master/traits/ingresstrait/config/samples。

总结

在 OAM 规范下,定义一个应用运行所需的运维特征是非常简单的,而基于 Kubernetes 开发一个运维能力的上层抽象,也变得有章可循,并且可以立即被整个 OAM 生态复用起来。社区近期还在规划的标准化 Trait 包括:TrafficManagement(基于 Istio 的流量治理),LogCollector(用户友好的日志收集 Spec)、MetricCollector( 用户友好的 Metric 收集 Spec)、Rollout(Kubernetes 原生的灰度发布能力)、Let'sEncrypt(证书管理)等等,具体细节大家可以持续关注 OAM Workload/Trait 仓库 中的 Issue 和 PR。

Q&A

Q:webhook 什么情况下使用?A:在实现层面需要做拦截修改(注入)、验证的时候推荐使用。

Q:kubebuilder 的 webhook 最佳实践有吗?A:kubebuilder 自带的 webhook 比较难用,可以考虑直接编写 admission webhook controller。

Q:在自定义控制器的情况下,容器原地重启核心原理是什么?A:这个可以参考下 OpenKruise 项目 https://github.com/openkruise/kruise

相关链接:

https://github.com/oam-dev/catalog/blob/master/traits/ingresstrait/api/v1alpha2/ingresstrait_types.go

https://github.com/oam-dev/catalog/blob/master/traits/ingresstrait/controllers/ingresstrait_controller.go

https://github.com/oam-dev/catalog/blob/master/traits/ingresstrait/controllers/helper.go

https://github.com/crossplane/addon-oam-kubernetes-local/blob/master/pkg/oam/util/helper.go

oracle oam nginx,如何基于 OAM 编写一个扩展 Trait?相关推荐

  1. 基于PYQT编写一个人脸识别软件(2)

    前言 以前在博客:基于PYQT编写一个人脸识别软件 中给出了我自己用PYQT编写的一个小软件.鉴于使用的是开源库--face_recogniton,尽管使用很简单,但是还有些问题,比如:识别黄种人时效 ...

  2. 基于Python编写一个B站全自动抽奖的小程序

    本文将利用Python编写一个B站全自动抽奖的小程序,可以实时监控自己关注的UP主,如果关注的UP主中有人发布了抽奖的动态,就自动参与这个抽奖.这样就能不错过任何一个可以暴富的机会了.需要的可以参考一 ...

  3. Android开发:基于Kotlin编写一个简易计算器

    目录 前言 Kotlin学习tips 界面绘制及控件绑定 UI界面绘制 控件绑定 Button点击事件 运算逻辑 整体逻辑 边界情况 输入展示 点击数字键 点击运算符键 点击"=" ...

  4. 基于OpenGL编写一个简易的2D渲染框架-03 渲染基本几何图形

    阅读文章前需要了解的知识,你好,三角形:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ 要 ...

  5. 基于PYQT编写一个人脸识别软件

    转载请注明出处:http://blog.csdn.net/hongbin_xu 或 http://hongbin96.com/ 文章链接:http://blog.csdn.net/hongbin_xu ...

  6. vue代码生成器可视化界面_手把手教你基于SqlSugar4编写一个可视化代码生成器(生成实体,以SqlServer为例,文末附源码)...

    在开发过程中免不了创建实体类,字段少的表可以手动编写,但是字段多还用手动创建的话不免有些浪费时间,假如一张表有100多个字段,手写有些不现实. 这时我们会借助一些工具,如:动软代码生成器.各种ORM框 ...

  7. 基于asyncio编写一个telegram爬虫机器人

    原文链接 前言 aiotg 可以通过异步调用telegram api的方式来构建bot,因为决定开发一个爬虫功能的bot,所以网络请求阻塞是比较严重的性能障碍.而asyncio的异步非阻塞特性能够完美 ...

  8. 基于python编写一个名片管理系统

    我们小做一个名片管理系统,首先要确定系统的框架,要实现增删改查的功能,可以定义 1:增加 2:查看 3:查询(并修改)4:退出系统,我们确定了这几项功能,然后我们就可以编写主程序了. 功能列表 1 新 ...

  9. 基于OpenGL编写一个简易的2D渲染框架-07 鼠标事件和键盘事件

    这次为程序添加鼠标事件和键盘事件 当检测到鼠标事件和键盘事件的信息时,捕获其信息并将信息传送到需要信息的对象处理.为此,需要一个可以分派信息的对象,这个对象能够正确的把信息交到正确的对象. 实现思路: ...

最新文章

  1. 使程序变为后台运行代码
  2. Zuul 查看所有路由路径与filter(过滤器)
  3. mechanism and analysis
  4. Mysql学习(一)之简单介绍
  5. 《设计师要懂心理学》-第五章-人如何集中注意力
  6. POJ1509 Glass Beads [后缀自动机]
  7. android手机编译可运行的linux程序
  8. 现代女性都有哪些烦恼?
  9. 普通域用户设置共享文件夹
  10. 特斯拉CEO马斯克:可能明年3月左右在中国推出Model S Plaid
  11. I/O的一些简单操作
  12. 缺少训练样本怎么做实体识别?小样本下的NER解决方法汇总
  13. 雷电模拟器脚本编写_你有好的引流话术, 还需配上脚本这样的全自动引流工具, 才是高效的引流方法...
  14. 结合环境专业计算机思维论文,环境艺术设计思维的表达论文范文
  15. IB学生喜欢申请哪些英国院校?
  16. GLFore便携式声学成像仪G100功能
  17. html5+++map+清除,HTML5+google map自我瞎学习
  18. 凭借ZTMAPGIS开发系统,打造智慧林业巡护“一块屏”,全面加强森林资源保护新时代
  19. 裁剪图片,拼接的时候中间出现白线
  20. ffmpeg 保存MP4后前端无法打开,并且缺失宽高等基础信息

热门文章

  1. 如何判断及预防常见的几种网络病毒及攻击(软考)
  2. 帆软报表日常操作记录
  3. 【实战】android网页源代码查看器
  4. angular数据交互
  5. 软件下载网站源码 自适应手机电脑
  6. 还在使用NoSQL数据库?为IoT选择TSDB
  7. 微信支付-电商收付通开发-04.支付
  8. OpenCv中,waitkey()函数失效原因
  9. 计算机用户文件夹加密,怎么把文件夹加密(怎么给电脑文件夹加密)
  10. 截取android正在播放音乐的audio音频流(后台获取android音频流)