Interpretación del código fuente: cómo KubeVela convierte el archivo de aplicación en objetos de recursos específicos de K8

1.png

Autor | Fan Dayong

KubeVela es un motor de administración de aplicaciones nativas en la nube simple, fácil de usar y altamente escalable basado en Kubernetes y el modelo de desarrollo de aplicaciones nativas en la nube OAM lanzado conjuntamente por Alibaba Cloud y Microsoft Cloud.

KubeVela construye una implementación específica basada en el modelo OAM, escrito en Golang, puede construir una plataforma de aplicaciones nativa de la nube para usuarios de un extremo a otro, proporcionando una solución relativamente completa.

El proyecto KubeVela se lanzó en la comunidad en julio de 2020. Ha sido recibido por voluntarios de la comunidad, incluidos ingenieros de Alibaba, Microsoft, Crossplane y otras empresas, y se han unido al trabajo de desarrollo del proyecto juntos. Resumieron varias experiencias y lecciones en la práctica de OAM en el proyecto KubeVela.

El propósito principal de este artículo es explorar cómo KubeVela convierte un archivo de aplicación en un objeto de recurso específico en K8s.

El proceso generalmente se divide en dos etapas:

  1. Convierta el archivo de aplicación a la aplicación en K8s
  2. la aplicación se convierte en el objeto de recurso correspondiente de K8s
# vela.yaml
name: test
services:
  nginx:
    type: webservice
    image: nginx
    env:
    - name: NAME
      value: kubevela

    # svc trait
    svc:
      type: NodePort
      ports:
      - port: 80
        nodePort: 32017

El despliegue se puede completar con el comando vela up.

comando vela arriba

Sugerencia: antes de mirar el código de la herramienta de línea de comandos vela, echemos un vistazo breve al marco cobra.

// references/cli/up.go
// NewUpCommand will create command for applying an AppFile
func NewUpCommand(c types.Args, ioStream cmdutil.IOStreams) *cobra.Command {
  cmd := &cobra.Command{
    Use:                   "up",
    DisableFlagsInUseLine: true,
    Short:                 "Apply an appfile",
    Long:                  "Apply an appfile",
    Annotations: map[string]string{
      types.TagCommandType: types.TypeStart,
    },
    PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
      return c.SetConfig()
    },
    RunE: func(cmd *cobra.Command, args []string) error {
      velaEnv, err := GetEnv(cmd)
      if err != nil {
        return err
      }
      kubecli, err := c.GetClient()
      if err != nil {
        return err
      }

      o := &common.AppfileOptions{
        Kubecli: kubecli,
        IO:      ioStream,
        Env:     velaEnv,
      }
      filePath, err := cmd.Flags().GetString(appFilePath)
      if err != nil {
        return err
      }
      return o.Run(filePath, velaEnv.Namespace, c)
    },
  }
  cmd.SetOut(ioStream.Out)

  cmd.Flags().StringP(appFilePath, "f", "", "specify file path for appfile")
  return cmd
}

El código fuente anterior muestra el punto de entrada del comando vela up.

En la función PresistentPreRunE, la información de configuración de Kuberentes kubeconfig se inyecta llamando a c.SetConfig ().

En la función RunE:

  • Primero, obtenga la variable env de vela, velaEnv.Namespace corresponde al espacio de nombres de Kubernetes.

  • En segundo lugar, obtenga el cliente de Kubernetes, kubectl.

  • A continuación, utilice el cliente de Kubernetes y vleaEnv para crear las AppfileOptions necesarias para representar el archivo de aplicaciones.

  • Finalmente, llame a o.Run (filePath, velaEnv.Namespace, c).
    • Esta función requiere tres parámetros, entre los cuales filePath se usa para especificar la ubicación del archivo de aplicación, velaEnv.Namespace yc se usan para crear la aplicación renderizada en el espacio de nombres especificado.
      • filePath: ruta del archivo de aplicación
      • velaEnv.Namespace: correspondiente al espacio de nombres de K8s
      • c: cliente de K8s

Cómo convertir un archivo de aplicación en una aplicación en Kubernetes

  • Punto de partida: archivo de aplicación

  • Fin: aplicación

  • 路径 : archivo de aplicación -> aplicación (servicios -> componente)
    • comp [carga de trabajo, rasgos]

1. Punto de partida: AppFile

// references/appfile/api/appfile.go
// AppFile defines the spec of KubeVela Appfile
type AppFile struct {
  Name       string             `json:"name"`
  CreateTime time.Time          `json:"createTime,omitempty"`
  UpdateTime time.Time          `json:"updateTime,omitempty"`
  Services   map[string]Service `json:"services"`
  Secrets    map[string]string  `json:"secrets,omitempty"`

  configGetter config.Store
  initialized  bool
}

// NewAppFile init an empty AppFile struct
func NewAppFile() *AppFile {
  return &AppFile{
    Services:     make(map[string]Service),
    Secrets:      make(map[string]string),
    configGetter: &config.Local{},
  }
}
// references/appfile/api/service.go
// Service defines the service spec for AppFile, it will contain all related information including OAM component, traits, source to image, etc...
type Service map[string]interface{}

Los dos fragmentos de código anteriores son las declaraciones de AppFile en el lado del cliente. Vela leerá el archivo yaml en la ruta especificada y lo asignará a un AppFile.

// references/appfile/api/appfile.go
// LoadFromFile will read the file and load the AppFile struct
func LoadFromFile(filename string) (*AppFile, error) {
  b, err := ioutil.ReadFile(filepath.Clean(filename))
  if err != nil {
    return nil, err
  }
  af := NewAppFile()
  // Add JSON format appfile support
  ext := filepath.Ext(filename)
  switch ext {
  case ".yaml", ".yml":
    err = yaml.Unmarshal(b, af)
  case ".json":
    af, err = JSONToYaml(b, af)
  default:
    if json.Valid(b) {
      af, err = JSONToYaml(b, af)
    } else {
      err = yaml.Unmarshal(b, af)
    }
  }
  if err != nil {
    return nil, err
  }
  return af, nil
}

Los siguientes son los datos cargados en AppFile después de leer el archivo vela.yaml:

# vela.yaml
name: test
services:
  nginx:
    type: webservice
    image: nginx
    env:
    - name: NAME
      value: kubevela

    # svc trait
    svc:
      type: NodePort
      ports:
      - port: 80
        nodePort: 32017
Name: test
CreateTime: 0001-01-01 00:00:00 +0000 UTC
UpdateTime: 0001-01-01 00:00:00 +0000 UTC
Services: map[
             nginx: map[
               env: [map[name: NAME value: kubevela]] 
               image: nginx 
               svc: map[ports: [map[nodePort: 32017 port: 80]] type: NodePort] 
               type: webservice
            ]
          ]
Secrets    map[]
configGetter: 0x447abd0 
initialized: false

2. Punto final: aplicación

// apis/core.oam.dev/application_types.go
type Application struct {
  metav1.TypeMeta   `json:",inline"`
  metav1.ObjectMeta `json:"metadata,omitempty"`

  Spec   ApplicationSpec `json:"spec,omitempty"`
  Status AppStatus       `json:"status,omitempty"`
}

// ApplicationSpec is the spec of Application
type ApplicationSpec struct {
  Components []ApplicationComponent `json:"components"`

  // TODO(wonderflow): we should have application level scopes supported here

  // RolloutPlan is the details on how to rollout the resources
  // The controller simply replace the old resources with the new one if there is no rollout plan involved
  // +optional
  RolloutPlan *v1alpha1.RolloutPlan `json:"rolloutPlan,omitempty"`
}

El código anterior es la declaración de la aplicación, combinado con .vela / deploy.yaml (ver el código a continuación), se puede ver que representar un AppFile como una aplicación es principalmente convertir los servicios de AppFile en componentes de la aplicación.

# .vela/deploy.yaml
apiVersion: core.oam.dev/v1alpha2
kind: Application
metadata:
  creationTimestamp: null
  name: test
  namespace: default
spec:
  components:
  - name: nginx
    scopes:
      healthscopes.core.oam.dev: test-default-health
    settings:
      env:
      - name: NAME
        value: kubevela
      image: nginx
    traits:
    - name: svc
      properties:
        ports:
        - nodePort: 32017
          port: 80
        type: NodePort
    type: webservice
status: {}

3. Ruta: Servicios -> Componentes

Combinando el contenido anterior, se puede ver que convertir Appfile en Aplicación es principalmente renderizar Servicios en Componentes.

// references/appfile/api/appfile.go
// BuildOAMApplication renders Appfile into Application, Scopes and other K8s Resources.
func (app *AppFile) BuildOAMApplication(env *types.EnvMeta, io cmdutil.IOStreams, tm template.Manager, silence bool) (*v1alpha2.Application, []oam.Object, error) {
  ...
  servApp := new(v1alpha2.Application)
  servApp.SetNamespace(env.Namespace)
  servApp.SetName(app.Name)
  servApp.Spec.Components = []v1alpha2.ApplicationComponent{}
  for serviceName, svc := range app.GetServices() {
    ...
    // 完成 Service 到 Component 的转化
    comp, err := svc.RenderServiceToApplicationComponent(tm, serviceName)
    if err != nil {
      return nil, nil, err
    }
    servApp.Spec.Components = append(servApp.Spec.Components, comp)
  }
  servApp.SetGroupVersionKind(v1alpha2.SchemeGroupVersion.WithKind("Application"))
  auxiliaryObjects = append(auxiliaryObjects, addDefaultHealthScopeToApplication(servApp))
  return servApp, auxiliaryObjects, nil
}

El código anterior es donde vela convierte Appfile en código de aplicación. Entre ellos, comp, err: = svc.RenderServiceToApplicationComponent (tm, serviceName) completa la transformación de Servicio a Componente.

// references/appfile/api/service.go
// RenderServiceToApplicationComponent render all capabilities of a service to CUE values to KubeVela Application.
func (s Service) RenderServiceToApplicationComponent(tm template.Manager, serviceName string) (v1alpha2.ApplicationComponent, error) {

  // sort out configs by workload/trait
  workloadKeys := map[string]interface{}{}
  var traits []v1alpha2.ApplicationTrait

  wtype := s.GetType()
  comp := v1alpha2.ApplicationComponent{
    Name:         serviceName,
    WorkloadType: wtype,
  }

  for k, v := range s.GetApplicationConfig() {
    // 判断是否为 trait
    if tm.IsTrait(k) {
      trait := v1alpha2.ApplicationTrait{
        Name: k,
      }
      ....
      // 如果是 triat 加入 traits 中
      traits = append(traits, trait)
      continue
    }
    workloadKeys[k] = v
  }

  // Handle workloadKeys to settings
  settings := &runtime.RawExte nsion{}
  pt, err := json.Marshal(workloadKeys)
  if err != nil {
    return comp, err
  }
  if err := settings.UnmarshalJSON(pt); err != nil {
    return comp, err
  }
  comp.Settings = *settings

  if len(traits) > 0 {
    comp.Traits = traits
  }

  return comp, nil
}

4. Resumen

Ejecute el comando vela up, renderice el archivo de la aplicación como Aplicación, escriba los datos en .vela / deploy.yaml y créelo en K8s.

2.png

¿Cómo se convierte la aplicación en el objeto de recurso correspondiente de K8s?

  • Punto de partida: Aplicación
  • 中点 : ApplicationConfiguration, Componente
  • Punto final: implementación, servicio
  • sendero:
    • application_controller
    • controlador de configuración de aplicaciones

[Sugerencia] > Más información sobre el contenido:> - cliente a

  • controlador-tiempo de ejecución
  • operador

1. Aplicación

# 获取集群中的 Application
$ kubectl get application
NAMESPACE   NAME   AGE
default     test   24h

2. ApplicationConfiguration 和 Componente

Una vez que el controlador de la aplicación obtiene el objeto de recurso de la aplicación, creará la configuración de la aplicación y el componente correspondientes en función de su contenido.

# 获取 ApplicationConfiguration 和 Component
$ kubectl get ApplicationConfiguration,Component
NAME                                         AGE
applicationconfiguration.core.oam.dev/test   24h

NAME                           WORKLOAD-KIND   AGE
component.core.oam.dev/nginx   Deployment      24h

Importar componente por nombre en ApplicationiConfiguration:

3.png

3. controlador de aplicaciones

Lógica básica:
  • Obtenga un objeto de recurso de aplicación.

  • Representar objetos de recursos de aplicaciones como ApplicationConfiguration y Component.

  • Cree objetos de recurso ApplicationConfiguration y Component.
Código:
// pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go

// Reconcile process app event
func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
  ctx := context.Background()
  applog := r.Log.WithValues("application", req.NamespacedName)

  // 1. 获取 Application
  app := new(v1alpha2.Application)
  if err := r.Get(ctx, client.ObjectKey{
    Name:      req.Name,
    Namespace: req.Namespace,
  }, app); err != nil {
    ...
  }

  ...

  // 2. 将 Application 转换为 ApplicationConfiguration 和 Component
  handler := &appHandler{r, app, applog}
  ...
  appParser := appfile.NewApplicationParser(r.Client, r.dm)
  ...
  appfile, err := appParser.GenerateAppFile(ctx, app.Name, app)
  ...
  ac, comps, err := appParser.GenerateApplicationConfiguration(appfile, app.Namespace)
  ...

  // 3. 在集群中创建 ApplicationConfiguration 和 Component 
  // apply appConfig & component to the cluster
  if err := handler.apply(ctx, ac, comps); err != nil {
    applog.Error(err, "[Handle apply]")
    app.Status.SetConditions(errorCondition("Applied", err))
    return handler.handleErr(err)
  }

  ...
  return ctrl.Result{}, r.UpdateStatus(ctx, app)
}

4. controlador de configuración de la aplicación

Lógica básica:
  • Obtenga el objeto de recurso ApplicationConfiguration.

  • Recorra, obtenga cada componente y represente la carga de trabajo y el rasgo en los objetos de recursos de K8 correspondientes.

  • Cree el objeto de recurso correspondiente de K8s.
Código:
// pkg/controller/core.oam.dev/v1alpha2/applicationcinfiguratioin/applicationconfiguratioin.go

// Reconcile an OAM ApplicationConfigurations by rendering and instantiating its
// Components and Traits.
func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) {
  ...
  ac := &v1alpha2.ApplicationConfiguration{}
  // 1. 获取 ApplicationConfiguration
  if err := r.client.Get(ctx, req.NamespacedName, ac); err != nil {
    ...
  }
  return r.ACReconcile(ctx, ac, log)
}

// ACReconcile contains all the reconcile logic of an AC, it can be used by other controller
func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2.ApplicationConfiguration,
  log logging.Logger) (result reconcile.Result, returnErr error) {

  ...
  // 2. 渲染
  // 此处 workloads 包含所有Component对应的的 workload 和 tratis 的 k8s 资源对象
  workloads, depStatus, err := r.components.Render(ctx, ac)
  ...

  applyOpts := []apply.ApplyOption{apply.MustBeControllableBy(ac.GetUID()), applyOnceOnly(ac, r.applyOnceOnlyMode, log)}

  // 3. 创建 workload 和 traits 对应的 k8s 资源对象
  if err := r.workloads.Apply(ctx, ac.Status.Workloads, workloads, applyOpts...); err != nil {
    ...
  }

  ...

  // the defer function will do the final status update
  return reconcile.Result{RequeueAfter: waitTime}, nil
}

5. Resumen

Cuando vela up representa un AppFile como una aplicación, el controlador de la aplicación y el controlador de configuración de la aplicación completan el proceso posterior.

4.png

Sobre el Autor

Dayong Fan, ingeniero de I + D de Teamsun Tiancheng, ID de GitHub: @ just-do1.

Únete a OAM

Supongo que te gusta

Origin blog.51cto.com/13778063/2677650
Recomendado
Clasificación