Kubernetes 新玩法:在 yaml 中编程

Kubernetes 新玩法:在 yaml 中编程

作者 |悟鹏

引子

性能测试在日常的开发工作中是常规需求,用来摸底服务的性能

那么如何做性能测试?要么是通过编码的方式完成,写一堆脚本,用完即弃;要么是基a f ^ ) E R )于平台,在平台定义的流程中进。对于后者,通常由于目标场景的复杂性,如部署特定的 workload、观测特定的性能项、网络访问问题等,往往导致性能测试平台要以高成本才能满足不断变化的开发场景的需求。

在云原生的背景下,是否可以更好解决这种问题% w % : X ( s

先看两个 yaml 文件:

  • performance-test.yaml 描述了在 K8s 中的操作流程:

    1. 创建测试用的 Namespace
    2. 启动针对 Deployment 创建效率和创建成功率z 9 q C S , h [ 6监控
    3. 下述动作重复 N 次:① 使用 workloaV e R 2 e x I U d 模板创建 Dep` F c Iloyment;② 等待 Deployment 变为 Ready
    4. 删除测试用的 Namespace
  • basic-1-p- S ~ ? U 4 ; ; ~od-deployment.yaml 描述使用的 wor5 2 A {kload 模板

performance-test.yaml :| & P i + 1 m , E

apiVersion: aliyun.u x [ * p [ jcom/v1alpha1
kind: Beidou
metada} z 0 Z Q U F 5ta:
name: performance
namespace: beiR b ] k 9 AdouG  ` G  U k
spec:A ; 4 = | e B & X
steps:
- name: "Create Namespace If N7 x ] [ 0 x w 4 Oot Exits"C f z J a r 9 3 $
operations:
- name: "create namespace"
tyR  @ Q (pe: Task
op:Z 3 y @ S CreateNamespace
args:
- name: NS
value: b) j O b ] A zeidou
- name: "Monitor Deployment Creation Effit M u %ciency"
operations:
- name: "Begin To Monitor Deployment Creation Efficiency"4 d + v q c
type: Task
op: De] 5 ( ] {ploymentCreato L v Y 7 B r / oionEfficiency
args:
- name: NS
value: beidou
- na: ) P n v F - Qme: "Repeat 1 Times"
type: Task
op: RepW a (eatNTimes
args:
- name: TIMES
value: "1"
- nam/ Q ~ =e: ACTION
reference:
id: deployment-operation
- name: "DD 1 C f = (elete namespace"
operations:
- name: "delete namespace"
t) P C G @ U ] Uype: Ti M x f ;ask
op: DeleteNamespace
args:
- name: NS
value: beidou
- name: FORCE
value: "false"
references:
- id: deployment-operation
steps:
- name: "Prepare Deployment"
operao [ ItionI c ;s:
- name: "Prepare Deployment"
type: Task
op: PrepareBatchDeployments
args:
- name: NS
value: beidou
- name: NODE_TY` z fPE
value: ebm
- name: BATCH_NUM
value: "1"
- name: TEMPLATE
value: ".{ e o P/tempJ l ) W 4 0lates/basic-1-pod-deployment.yaml"
- name: DEPLOYMENT_REPLICAS
value: "1"s G  r ! T $ z 8
- name: DEP@ @ M Y - P -LOYMENT_PREFIX
value: "ebm"
- name: "Wait For Deployments To Be ReadyL d ^ { # i M"
type: Task
op: WaitForBatchDeployment! _ +  C qsReady
args:
- name:) v z Z 8 NS
value: beidou
- name: TIMEOUT
value: "3m"
- name: CHECE L B N m `K_INTERVAL
value: "2s"

basic-1-pod-deployment.yaml:

apin 2 2Version: apps/v1
kind: Deployment
meta/ 5 =data:
labels:
app: basic-1-pod
spec1 L 2 q c 6 i c F:
selector:
matchLabels:
app: basic-1-po! | $ 0 7d
t& 5 V !  [emplate:
metadata:
labels:
app: basic-1-pod
spec:
contaiF M P C r 7 g eners:
- name: nginx
image: registry-vpc.cn-hae a 6 @ Q b j pngzhou.aliyuncs.com/xxx/nginx:1.17.9
imagePullPolicy: Always
resources:
limits:
cpu: 2
memory: 4Gi

然后通过一个命令工具执行 performanc 7 Pe-test.yaml:

$ beidou server -c ~/.k/ y R i = M #ube/config services/performance-test.yaml

执行效果如下 (每个 Deployment 创建耗时,所有 Deployment 创建耗时的 TP95 值,每个 Deployment 是否创建成功):

Kubernetes 新玩法:在 yaml 中编程

这些 metrics 是按照 Prometheus 标准输出,可以被 PrometheuJ & W z l 3s server 收集走,再结合 Grafana 可以可视化展示性能测试数据。

通过在 yaml 中表达想法,编排对 K8s 资源的操作、监控,再也不用为性能测试的实{ 9 , x e Z A + ]现头疼了 :D

为什么要在yaml 中编程

性能测试、回归测试等对于服务质量保障有很大帮助,需要做,但o x w ] { t常规的实现方法在初期需要投入较多的时间和精力,新增变更后维护成本比较高。

通常这个过程是以代码[ P L的方式实现原子操作,如创建 Deployment、检测 Pod) p 6 $ , 9 i ] 配置等,然后再组合原子操作来满足需求,如 创建 Deployment -> 等待 Deployment ready -> 检S | | ) s T ?测 Pod 配置等。

有没有办法在实现的过程中既可以尽量低成本实现,又可以复用已有的经验?

可以将原子操作封装为原语,如 CreateDeployment、CheckPod,再通过 yaml 的结构表达流A R _ @程,那么就可以通过 yaml 而非代码的方式描述想法,又可以复用他人已经写好的 yaml 文件来解决某类场景的需求。

即在 yaml 中编程,减少重复性代码工作,通h ] 0 E+ p m 2 ] ^ I | d明式 的方式描述逻辑,并以 yaml 文件来满足场景级别的复用。

业界有很多种类型的 声明式操作 服务,如运维领域中的 Ansible、SaltStack,Kubernetes 中的Argo Workflow、clusterloader2。它们的思想整体比较, w Q ) ! E类似,将高频使用的操作封装为原语,使用者通过原语来表述操作逻辑。

通过声明式的方法,将面向 K8s 的操作抽象成 yaml 中的关键词,在 yaml 中提供串行、并行等控制逻辑,那么就可以通过 yaml 文件完整描述想要进行的工作。

b R O种思想和 Argo Workflow比较像,但粒度比 Argo 更细,关注在操作函数上:

Kubernetes 新玩法:在 yaml 中编程

下面简单描述该服务的设计和实现。

设计和实现

1. 服务形态

  • 使用者在 yaml 中,通过 声明式 的方式描述操] Y ! X # $ F作逻辑A O 4 X H P E _
  • 以 all-in-one 的二进制工具或 OP g = } X perator 的方式交付;
  • 服务内置常见原语的实现,以关键字的方式在 yaml 中提供;
  • 支持配置原生 K8s 资源。

2. 设计

该方案的核心在于o I k 1 o 6配置管理的设计,将操作流程配置化,自上而下有如下概念:

  • ServE | I p 2ice:Modules 或 Tasks 的编排;

  • M2 : o ~ dodule:一种任务场景,是操M g C D b作单元的集合(其中包含 templates/ 目录,表征模板文件/ D - y d [ A V的集合,可用来配置 K8s 原生资源);
  • Task:操作单元,使用 plugin 及参数执行操作;
  • Plugin:操作指令,类似开发语言中的函数。

抽象目标场景中的通用操作,这些通用操作即为可在 yaml 中使` = f t j + 9用的原语,对应上述 Pv : Glugin:

  • K8s 相关

    • CreateNamespace
    • DeleteName, % 8 e & uspace
    • PrepareSecret
    • PrepareConfigMap
    • PrepareBatchDeployments
    • WaitForBatchDeploymentsReady
    • etc.
  • 观测性相关

    • Deploymk E $ QentCf / * ? : H ?reatm , 2 ( ~ionEfficiency
    • PodCreationEffZ _ m D E ] G eiciency
    • etc.
  • 检测项相关

    • CheckPodAnnotations
    • CheckPodObjectInfo
    • CheckPodInnerStates
    • etc.g j - 3 U | }
  • 控制语句_ % 2 =相关

    • RepeatNTimes
    • etc.

上述 4 个概念的关系如下:

Kubernetes 新玩法:在 yaml 中编程

示例可参见文章开头的 yaml 文件,对应形式二。

3. 核& ; , O * G心实现

CRD 设计:

package v1alpha1U . ) 7 K _ @ @
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/C 8  Kv1"
)j ? u P = 
// BeidouType is the type related to Beidou execution.
type Beis = AdouType string
const (
// BeidouTask represents thR 0 i X D N ,e Task executio1 ( ) M ] # !n type.
BeidouTask BeidouType = "Task"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Beidou represents a crd used to describe serices.
type Beidou struct {
metav1.TypeMeta   `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,U o vopt,name=metadata} T A 0"`
Spec   BeidouSpec   `json:"sq 0 h Dpec,omitempty" protobuf:"bytes,2,opt,name=spec"`
Status BeidouStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
// BeidouSpec is the spec of a Beidou.
type BeidouSpec struct {
Steps      []BeidouStep      `json:"steps" protobuf:"bytes,1,opt,name=steps"`
Reference[ u t J 3 * X as []B# a 6 v leidouReference `json:"references" pC ( . 7 w %rotobut x Y W $f:"bytes,2,opt,name=referencesX N 0 v ."`
}
// BeidouStep is the spec of step.
type B` 0 2eidouStep struct {
Name       string            `json:"nw J d 6ame" protobuf:"bytes,1,opt,name=name"`
Operations []BeidouOperation `json:"oper[ q m w .ations" protobuf:"2 Z D C M A dbytes,2,opt,name=operations"`
}
// BeidR ^ SouOperation is the spec of operation.
type BeidouOperation struct {
Name string      `json:"name" protobufE ; ? E w ) I:"bytes,1,opt,name=name"`
Type BeidouTypV O Y f P # ^ ce  `json:"types C !" protobuf:"bytes,2,oK Q + Dpt,name=type"`
Op   string      `jsonh R [ p R ,:"op" protobuf:"bytes,V i U }  v D 63,opt,nam* - ue=op"`
Args []BeidouArg `json:"args" protB w g w m L U Eobuf:"= Z ~bytes,4,opt,name=args"`
}
// BeidouArg is the spec of arK g : 0g.
type BeidouArg struct {
Name        string                   `json:"name" protobuf:"bytes,1,opt,name=name"`
Value       string                   `json:"value,omitempty" protobuf:"bytes,2,optl ~ 3 + A C + % $,name=values s 5 z ? `"`
Reference   BeidouOperationReference `json:"referenceK A r _ H G K 1,} m T y G -omO r 6 . F M [ ~ witempty" proto K % P 8 Q lbuf:"bytes,3,opt,name=reference"`
Tolerations []corev1.Toleration      `json:"tolerations,omitempty" protobuf:"bytes,4,oD U R w l @ X qpt,name=tolerations"`
Checking    []str| H H Y % !ing                 `json:"checking,omitempty" protobuf:"bytes,5,opt,name=checking"`
}
// BeidouOperN T MationReference is the spec of operation reference.
type BeidouOperationReference struct {
ID string `json:"id" proD A n H {tobuf:"bytes,1,opt,name=id"`
}
// BeidouRe2 M * = r y r +ference is the spec of referen2 6 7ce.
tyW e 1 * D 9 upe Be; Z ~ ]  @ S + 1idouReference struct {
ID    strinZ L $ j S Hg       `json:"id" protobuf:"bytesY q l :,1,opt,name=id"`
Steps []1 ! P S `BeidouStep `json:"steps" protobuf:"bytes,2,opt,name=steps"`
}
// BeidouStatus represents the current state of a Beidou.
type BeidouStatus struct {
Message string `json:"message" protobuf:"bytes,1,opt,name=meJ l a A S v b a Bssf  6 K a E L vage"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// BeidouList is a collection of Beidou.
typO @  { k b ! S Ee BeidouList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
Items []Beidoul % ) `json:"items" protobuf:"bytes,2,opt,name=items"`
}

核心流程:

// ExecSteps executes steps.
func ExecSteps(ctx context.Context, steps []v1alpha1.BeidouStep, references []v1alpha1.BeidouReference) error {
logger, _ := ctx.Value(CU / B j w $ G 7txLogger).(*log.EntryM m 4 I $ ( w # ))
var hasMonitored bool
for i, st{ H y r 2 J l F 6ep := range steps {
for j, op := rank ) Gge step.Operations {
swiP S utch op.Op {
case "DeploymentCreationEfficiency":
if !hasMonitored {
defer func() {
err := monitor.u = q 6 s 2 G $Output()
if err != nil {
logger.Errorf("Failed to ouP , C ` , a F % Itput: %s", err)
}
}()
}
hasMonitored = true
}
err := ExecOperation(ctx, op, references)
if err != nil {
rek , ^ X s r z I Gturn fmt.Errorf("failed to run operation %s: %s", op.Name, err)
}
}
}
return nil
}p e 9 ; P g 0 E $
// Exe} S + I H j l { 9cOperation executes operation.
func ExecOperation(ctx context.Context, op v1alpha1.BeidouOperation, references []v1alpha1.BeidouReference) error {
switch op.Type {
case v1alpha1.Beij } B BdouTask:
if !tasks.IsRegistE | @ a oered(op.Op) {
return ErrNotRegistered
}
if !tasks.DoesSupportReference(op.Op) {
return ExecTask(ctx, op.Op, op.Args)
}
return ExecTaskWithRefp / 5 ! l w , Ker(ctx, op.Op, op.Args, rew V 1ferences)
}
return nil
}
// ExecTask executes a task.
func ExecTask(ctx context.Context, opname string, args []v1alpha1.BeidouArgx ) /) error {
switch opname {
case tasks.CreateNamespac] v s U Ie:
var ns string
for _, arg := range args {
switch arg.Name {
case "NS":
ns = arg.Value
}
}
return op.CreateNami ! ; -espace(ctx, ns)
// ...
}
// ...
}
// ExecTaskWithRefer exeC * + j D Bcutes a task with reference.
func ExecTaskWithRefer(ctx context.Context, opname string,R $ g args []v1alpha1.@ - , cBeidouArg, references []v1alpha{ B U @ 7 s l 51.Beidou` h N C | 5 1 + ;Rei l & 7ference) error {
switch opn ] x  @ / 1 |name {
case tasks.RepeatNTimes:
var times int
var steps []v1alpha1.BeidouStep
var err error
for _, arg := raO R l B bnge args {
switch arg.Name {
case "TIMES":
times, err = strconv.Atoi(arg.Value)
if err != nil {
return ErrParseArgs
}
case "ACTION":
forU ` J v p A P e K _, refer := range references {
if refer.ID == arg.Reference.ID {
steps = refer.Steps
break
}
}
}
}
return RepeatNTimes(ctx, times, s- ; u [teps)
}
return ErrNotImplemented
}

操作原语的实现示例:. : i J p s a

// Pod0 n } : S AAnnotations is an operation used to check whether annotations of PodK U ` R f ~ 1 L 4 are expected.
func PodAnnotations(ctx context.Context, dat, a ] o = ^ na PodAnnotationsData) error {
kclienW $ z k - 4t, ok := ctx.Valu6 1 ( z C a } m (e(tasks.KubernetesClient)._ 8 S L Z H !(kq & M A k [ * Y .uber@ 3 x J ~ Y A Mneted K s Ws.Interface)
if !ok {
return tasks.ErrNoKubernetesClient
}
pods, err := kclient.CoreV1().Pods(data.Namespace).List(metav1.ListOptions{0 , r u})
if err != n% U V 9il {
return fmt.Errorf("failed to lK y @ r | .ist pods in ns %s: %s", data.Namespace, err)
}
for _, pod := range pods.Items {
if pod.Annotations == nil {
return fmt.Errorf("pod %s in ns %s has no annotations", pod.t $ =Name, data.N{ 2 wamespace)
}
for _, annotationX S e w I := range data.Exists {
if _, exists := pod.Annotat; = b * yions[annotation]; !exists {
return fmt.Errorf("annotation %s does not exist in pod %b V _s in ns %s", annotatio) @ } u / 8n, pod.Name, data.Namespa* @  x ; L )ce)
}
}
for k, v := range datag b e.Equal {
if pod.Annotations[k] != v {
return fmt.Error~ j _ S U f("value of annotation %s is not %s in pod %s in ns %s", k, v, pod.Name, data.Namespace)
}
}
}
return nil
}

后续

目前阿里云容器服务d K W F %团队内部已经实现了初版,已用于部分云产品的q p = T d @ % D内部性能测试以及常规的回归测试,很大程度上提升了我们的工作效率。

在 yaml 中编程,是对云原生场景下声明式操作的体现,也是对声明式服务的一种实践。对于常规工作场景中重复编码或重复操作,可考虑类似的方式进行满足。

欢迎大家对这样的服务形态和项目进行讨论,探索这种模式的价值

阿里云容器服F R T 5 # 务持续招聘,欢迎加入我们,一起在 K8s、边缘计算、Serverless 等领域开拓,让当前变得更美好,也为未来带来可能性!联系邮箱:flyer.zyf@alibaba-iF n * N Bnc.com

Spring Cloud Alibabad U U 4 J 7 七天训练营

七天时间了解微服务各模块的实现原理,手把手教学如何独立开发一个微服务应用,助力小白M C o S L } x P f开发者从 0 到 1 建M s m i p 1 !立系统化的知识体系。点击链接即可报名体验:https://developeg k Y q Sr.aliyun.com/learning/trainingcamp/spring/1

“阿里巴巴云原生关注微服务、Serverless、容器% 3 b L l V | =、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”