TAPP

TAPP

Kubernetes现有应用类型(如:Deployment、StatefulSet等)无法满足很多非微服务应用的需求,比如:操作(升级、停止等)应用中的指定pod、应用支持多版本的pod。如果要将这些应用改造为适合于这些workload的应用,需要花费很大精力,这将使大多数用户望而却步。

为解决上述复杂应用管理场景,基于Kubernetes CRD开发了一种新的应用类型TAPP,它是一种通用类型的workload,同时支持service和batch类型作业,满足绝大部分应用场景,它能让用户更好的将应用迁移到Kubernetes集群。

TAPP 特点

功能点DeploymentStatefulSetTAPP
Pod唯一性每个Pod有唯一标识每个Pod有唯一标识
Pod存储独占仅支持单容器支持支持
存储随Pod迁移不支持支持支持
自动扩缩容支持不支持支持
批量升级支持不支持支持
严格顺序更新不支持支持不支持
自动迁移问题节点支持不支持支持
多版本管理同时只有1个版本可保持2个版本可保持多个版本
Pod原地升级不支持不支持支持

如果用Kubernetes的应用类型类比,TAPP ≈ Deployment + StatefulSet + Job ,它包含了Deployment、StatefulSet、Job的绝大部分功能,同时也有自己的特性,并且和原生Kubernetes相同的使用方式完全一致。

  1. 实例具有可以标识的id

    实例有了id,业务就可以将很多状态或者配置逻辑和该id做关联,当容器迁移时,通过TAPP的容器实例标识,可以识别该容器原来对应的数据,实现带云硬盘或者数据目录迁移

  2. 每个实例可以绑定自己的存储

    通过TAPP的容器实例标识,能很好地支持有状态的作业。在实例发生跨机迁移时,云硬盘能跟随实例一起迁移

  3. 实现真正的灰度升级/回退

    Kubernetes中的灰度升级概念应为滚动升级,kubernetes将pod”逐个”的更新,但现实中多业务需要的是稳定的灰度,即同一个app,需要有多个版本同时稳定长时间的存在,TAPP解决了此类问题

  4. 可以指定实例id做删除、停止、重启等操作

    对于微服务app来说,由于没有固定id,因此无法对某个实例做操作,而是全部交给了系统去维持足够的实例数

  5. 对每个实例都有生命周期的跟踪

    对于一个实例,由于机器故障发生了迁移、重启等操作,很难跟踪和监控其生命周期。通过TAPP的容器实例标识,获得了实例真正的生命周期的跟踪,对于判断业务和系统是否正常服务具有特别重要的意义。TAPP还可以记录事件,以及各个实例的运行时间,状态,高可用设置等。

TAPP 资源结构

TApp定义了一种用户自定义资源(CRD),TAPP controller是TAPP对应的controller/operator,它通过kube-apiserver监听TApp、Pod相关的事件,根据TApp spec和status进行相应的操作:创建、删除pod等。

// TApp represents a set of pods with consistent identities.
type TApp struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    // Spec defines the desired identities of pods in this tapp.
    Spec TAppSpec `json:"spec,omitempty"`

    // Status is the current status of pods in this TApp. This data
    // may be out of date by some window of time.
    Status TAppStatus `json:"status,omitempty"`
}
// A TAppSpec is the specification of a TApp.
type TAppSpec struct {
    // Replicas 指定Template的副本数,尽管共享同一个Template定义,但是每个副本仍有唯一的标识
    Replicas int32 `json:"replicas,omitempty"`

    // 同Deployment的定义,标签选择器,默认为Pod Template上的标签
    Selector *metav1.LabelSelector `json:"selector,omitempty"`

    // Template 默认模板,描述将要被初始创建/默认缩放的pod的对象,在TApp中可以被添加到TemplatePool中
    Template corev1.PodTemplateSpec `json:"template"`

    // TemplatePool 描述不同版本的pod template, template name --> pod Template
    TemplatePool map[string]corev1.PodTemplateSpec `json:"templatePool,omitempty"`

    // Statuses 用来指定对应pod实例的目标状态,instanceID --> desiredStatus ["Running","Killed"]
    Statuses map[string]InstanceStatus `json:"statuses,omitempty"`

    // Templates 用来指定运行pod实例所使用的Template,instanceID --> template name
    Templates map[string]string `json:"templates,omitempty"`

    // UpdateStrategy 定义滚动更新策略
    UpdateStrategy TAppUpdateStrategy `json:"updateStrategy,omitempty"`

    // ForceDeletePod 定义是否强制删除pod,默认为false
    ForceDeletePod bool `json:"forceDeletePod,omitempty"`

    // 同Statefulset的定义
    VolumeClaimTemplates []corev1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty"`
}

// 滚动更新策略
type TAppUpdateStrategy struct {
    // 滚动更新的template name
    Template string `json:"template,omitempty"`
    // 滚动更新时的最大不可用数, 如果不指定此配置,滚动更新时不限制最大不可用数
    MaxUnavailable *int32 `json:"maxUnavailable,omitempty"`
}

// 定义TApp的状态
type TAppStatus struct {
    // most recent generation observed by controller.
    ObservedGeneration int64 `json:"observedGeneration,omitempty"`

    // Replicas 描述副本数
    Replicas int32 `json:"replicas"`

    // ReadyReplicas 描述Ready副本数
    ReadyReplicas int32 `json:"readyReplicas"`

    // ScaleSelector 是用于对pod进行查询的标签,它与HPA使用的副本计数匹配
    ScaleLabelSelector string `json:"scaleLabelSelector,omitempty"`

    // AppStatus 描述当前Tapp运行状态, 包含"Pending","Running","Failed","Succ","Killed"
    AppStatus AppStatus `json:"appStatus,omitempty"`

    // Statues 描述实例的运行状态 instanceID --> InstanceStatus ["NotCreated","Pending","Running","Updating","PodFailed","PodSucc","Killing","Killed","Failed","Succ","Unknown"]
    Statuses map[string]InstanceStatus `json:"statuses,omitempty"`
}

使用示例

本节以一个TApp应用部署,配置,升级,扩容以及杀死删除的操作步骤来说明TApp的使用。

创建TApp应用

创建TApp应用,副本数为3,TApp-controller将根据默认模板创建出的pod

$ cat tapp.yaml
apiVersion: apps.tkestack.io/v1
kind: TApp
metadata:
  name: example-tapp
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: example-tapp
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
$ kubect apply -f tapp.yaml

查看TApp应用

$ kubectl get tapp XXX
NAME           AGE
example-tapp   20m

$ kubectl descirbe tapp example-tapp
Name:         example-tapp
Namespace:    default
Labels:       app=example-tapp
Annotations:  <none>
API Version:  apps.tkestack.io/v1
Kind:         TApp
...
Spec:
...
Status:
  App Status:            Running
  Observed Generation:   2
  Ready Replicas:        3
  Replicas:              3
  Scale Label Selector:  app=example-tapp
  Statuses:
    0:  Running
    1:  Running
    2:  Running
Events:
  Type    Reason            Age   From             Message
  ----    ------            ----  ----             -------
  Normal  SuccessfulCreate  12m   tapp-controller  Instance: example-tapp-1
  Normal  SuccessfulCreate  12m   tapp-controller  Instance: example-tapp-0
  Normal  SuccessfulCreate  12m   tapp-controller  Instance: example-tapp-2

升级TApp应用

当前3个pod实例运行的镜像版本为nginx:1.7.9,现在要升级其中的一个pod实例的镜像版本为nginx:latest,在spec.templatPools中创建模板,然后在spec.templates中指定模板pod, 指定“1”:“test”表示使用模板test创建pod 1。

如果只更新镜像,Tapp controller将对pod进行原地升级,即仅更新重启对应的容器,否则将按k8s原生方式删除pod并重新创建它们。

apiVersion: apps.tkestack.io/v1
kind: TApp
metadata:
  name: example-tapp
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: example-tapp
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
  templatePool:
    "test":
      metadata:
        labels:
          app: example-tapp
      spec:
        containers:
        - name: nginx
          image: nginx:latest
  templates:
    "1": "test"

操作成功后,查看instanceID为'1’的pod已升级,镜像版本为nginx:latest

# kubectl describe tapp example-tapp
Name:         example-tapp
Namespace:    default
Labels:       app=example-tapp
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"apps.tkestack.io/v1","kind":"TApp","metadata":{"annotations":{},"name":"example-tapp","namespace":"default"},"spec":{"repli...
API Version:  apps.tkestack.io/v1
Kind:         TApp
...
Spec:
...
  Templates:
    1:  test
  Update Strategy:
Status:
  App Status:            Running
  Observed Generation:   4
  Ready Replicas:        3
  Replicas:              3
  Scale Label Selector:  app=example-tapp
  Statuses:
    0:  Running
    1:  Running
    2:  Running
Events:
  Type    Reason            Age   From             Message
  ----    ------            ----  ----             -------
  Normal  SuccessfulCreate  25m   tapp-controller  Instance: example-tapp-1
  Normal  SuccessfulCreate  25m   tapp-controller  Instance: example-tapp-0
  Normal  SuccessfulCreate  25m   tapp-controller  Instance: example-tapp-2
  Normal  SuccessfulUpdate  10m   tapp-controller  Instance: example-tapp-1

# kubectl get pod | grep example-tapp
example-tapp-0         1/1     Running   0          27m
example-tapp-1         1/1     Running   1          27m
example-tapp-2         1/1     Running   0          27m

# kubectl get pod example-tapp-1 -o template --template='{{range .spec.containers}}{{.image}}{{end}}'
nginx:latest

上述升级过程可根据实际需求灵活操作,可以指定多个pod的版本,帮助用户实现灵活的应用升级策略 同时可以指定updateStrategy升级策略,保证升级是最大不可用数为1,即保证滚动升级时每次仅更新和重启一个容器或pod

# cat tapp.yaml
apiVersion: apps.tkestack.io/v1
kind: TApp
metadata:
  name: example-tapp
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: example-tapp
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
  templatePool:
    "test":
      metadata:
        labels:
          app: example-tapp
      spec:
        containers:
        - name: nginx
          image: nginx:latest
  templates:
    "1": "test"
    "2": "test"
    "0": "test"
  updateStrategy:
    template: test
    maxUnavailable: 1
# kubectl apply -f tapp.yaml

杀死指定pod

在spec.statuses中指定pod的状态,tapp-controller根据用户指定的状态控制pod实例,例如,如果spec.statuses为“1”:“killed”,tapp控制器会杀死pod 1。

# cat tapp.yaml
kind: TApp
metadata:
  name: example-tapp
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: example-tapp
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
  templatePool:
    "test":
      metadata:
        labels:
          app: example-tapp
      spec:
        containers:
        - name: nginx
          image: nginx:latest
  templates:
    "1": "test"
    "2": "test"
    "0": "test"
  updateStrategy:
    template: test
    maxUnavailable: 1
  statuses:
    "1": "Killed"
# kubectl apply -f tapp.yaml

查看pod状态变为Terminating

# kubectl get pod
NAME                   READY   STATUS        RESTARTS   AGE
example-tapp-0         1/1     Running       1          59m
example-tapp-1         0/1     Terminating   1          59m
example-tapp-2         1/1     Running       1          59m

扩容TApp应用

如果你想要扩展TApp使用默认的spec.template模板,只需增加spec.replicas的值,否则你需要在spec.templates中指定使用哪个模板。kubectl scale也适用于TApp。

kubectl scale --replicas=3 tapp/example-tapp

删除TApp应用

kubectl delete tapp example-tapp

其它

Tapp还支持其他功能,如HPA、volume templates,它们与k8s中的其它工作负载类型类似。