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:
- Convierta el archivo de aplicación a la aplicación en K8s
- 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
- 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.
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.
¿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. 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.
Sobre el Autor
Dayong Fan, ingeniero de I + D de Teamsun Tiancheng, ID de GitHub: @ just-do1.
Únete a OAM
-
Sitio web oficial de OAM:
https://oam.dev - Dirección del proyecto KubeVela GitHub:
https://github.com/oam-dev/kubevela