Cloud Native 13 min read

How KubeVela Transforms an AppFile into a Kubernetes Application

This article explains how KubeVela converts an AppFile into a Kubernetes Application by outlining the two‑stage process, showing the relevant Go source code for the `vela up` command, detailing the AppFile and Application data structures, and describing the controller logic that renders the final Kubernetes resources.

Alibaba Cloud Native
Alibaba Cloud Native
Alibaba Cloud Native
How KubeVela Transforms an AppFile into a Kubernetes Application

Overview

KubeVela is a cloud‑native application management engine built on Kubernetes and the Open Application Model (OAM). It transforms a user‑defined AppFile into an OAM Application object, which is then reconciled into native Kubernetes workloads such as Deployments and Services.

AppFile to Application conversion

An AppFile describes an application in a simple YAML format. Example:

# vela.yaml
name: test
services:
  nginx:
    type: webservice
    image: nginx
    env:
    - name: NAME
      value: kubevela
    svc:
      type: NodePort
      ports:
      - port: 80
        nodePort: 32017

The command vela up reads this file, creates an OAM Application, and applies it to the cluster.

The entry point for vela up is defined in cli/up.go:

// NewUpCommand creates the "up" command
func NewUpCommand(c types.Args, ioStream cmdutil.IOStreams) *cobra.Command {
    cmd := &cobra.Command{
        Use:   "up",
        Short: "Apply an appfile",
        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.Flags().StringP(appFilePath, "f", "", "specify file path for appfile")
    return cmd
}

The Run method loads the AppFile (see appfile/api/appfile.go) and calls BuildOAMApplication to produce an Application object.

Key data structures:

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
}

type Service map[string]interface{}

The conversion logic iterates over each service, renders it to an OAM component, and assembles the components:

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)
    for serviceName, svc := range app.GetServices() {
        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
}

Rendering a service separates workload configuration from traits:

func (s Service) RenderServiceToApplicationComponent(tm template.Manager, serviceName string) (v1alpha2.ApplicationComponent, error) {
    workloadKeys := map[string]interface{}{}
    var traits []v1alpha2.ApplicationTrait
    wtype := s.GetType()
    comp := v1alpha2.ApplicationComponent{Name: serviceName, WorkloadType: wtype}
    for k, v := range s.GetApplicationConfig() {
        if tm.IsTrait(k) {
            traits = append(traits, v1alpha2.ApplicationTrait{Name: k})
            continue
        }
        workloadKeys[k] = v
    }
    pt, err := json.Marshal(workloadKeys)
    if err != nil { return comp, err }
    settings := &runtime.RawExtension{}
    if err := settings.UnmarshalJSON(pt); err != nil { return comp, err }
    comp.Settings = *settings
    if len(traits) > 0 { comp.Traits = traits }
    return comp, nil
}

Application controller flow

After vela up creates the Application, the application controller reconciles it, generating an ApplicationConfiguration and the corresponding Component objects.

func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
    ctx := context.Background()
    app := new(v1alpha2.Application)
    if err := r.Get(ctx, client.ObjectKey{Name: req.Name, Namespace: req.Namespace}, app); err != nil { return ctrl.Result{}, err }
    appfile, err := appParser.GenerateAppFile(ctx, app.Name, app)
    ac, comps, err := appParser.GenerateApplicationConfiguration(appfile, app.Namespace)
    if err := handler.apply(ctx, ac, comps); err != nil { return ctrl.Result{}, err }
    return ctrl.Result{}, r.UpdateStatus(ctx, app)
}

ApplicationConfiguration controller flow

The controller renders each component’s workload and traits into native Kubernetes objects and applies them.

func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) {
    ctx := context.Background()
    ac := &v1alpha2.ApplicationConfiguration{}
    if err := r.client.Get(ctx, req.NamespacedName, ac); err != nil { return reconcile.Result{}, err }
    workloads, depStatus, err := r.components.Render(ctx, ac)
    applyOpts := []apply.ApplyOption{apply.MustBeControllableBy(ac.GetUID()), applyOnceOnly(ac, r.applyOnceOnlyMode, log)}
    if err := r.workloads.Apply(ctx, ac.Status.Workloads, workloads, applyOpts...); err != nil { return reconcile.Result{}, err }
    return reconcile.Result{RequeueAfter: waitTime}, nil
}

Resulting resources

The generated .vela/deploy.yaml shows the OAM Application representation of the example:

apiVersion: core.oam.dev/v1alpha2
kind: Application
metadata:
  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: {}

After the controllers run, the following native resources exist in the cluster:

# kubectl get application
NAMESPACE   NAME   AGE
default     test   24h

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

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

References

KubeVela GitHub repository: https://github.com/oam-dev/kubevela

OAM website: https://oam.dev

KubeVela diagram
KubeVela diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Cloud NativeKubernetesKubeVelaOAMapplicationAppFile
Alibaba Cloud Native
Written by

Alibaba Cloud Native

We publish cloud-native tech news, curate in-depth content, host regular events and live streams, and share Alibaba product and user case studies. Join us to explore and share the cloud-native insights you need.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.