Creating Kubernetes Operators with operator-sdk

This page summarizes the projects mentioned and recommended in the original post on dev.to

Our great sponsors
  • InfluxDB - Power Real-Time Data Analytics at Scale
  • WorkOS - The modern identity platform for B2B SaaS
  • SaaSHub - Software Alternatives and Reviews
  • k8s-operator-talk

    k8s operator example

  • The first command generates all the files necessary to create the CRD. The second generates a docker container and pushes it to the indicated repository. The last command installs the generated container on the cluster. Tip: You can automate controller generation and installation in your development environment using Tilt. This project's repository has a Tiltfile that does all this work. To learn more about Tilt, check out my post about the tool.

  • operator-sdk

    SDK for building Kubernetes applications. Provides high level APIs, useful abstractions, and project scaffolding.

  • To illustrate what we can do with an operator, I will create a proof of concept using operator-sdk. According to the official website::

  • InfluxDB

    Power Real-Time Data Analytics at Scale. Get real-time insights from all types of time series data with InfluxDB. Ingest, query, and analyze billions of data points in real-time with unbounded cardinality.

    InfluxDB logo
  • kubernetes

    Production-Grade Container Scheduling and Management

  • Kubernetes is a project created by Google in mid-2015 that quickly became the standard for managing container execution. You can host it on your machines or use a solution delivered by one of the big cloud players like AWS, Google, and DigitalOcean.

  • hub-feedback

    Feedback and bug reports for the Docker Hub

  • func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { l := log.FromContext(ctx) var app minettodevv1alpha1.Application //recupera os detalhes do objeto sendo gerenciado if err := r.Get(ctx, req.NamespacedName, &app); err != nil { if apierrors.IsNotFound(err) { return ctrl.Result{}, nil } l.Error(err, "unable to fetch Application") return ctrl.Result{}, err } /* The finalizer is essential because it tells K8s we need control over object deletion. After all, how we will create other resources must be excluded together. Without the finalizer, there is no time for the K8s garbage collector to delete, and we risk having useless resources in the cluster. */ if !controllerutil.ContainsFinalizer(&app, finalizer) { l.Info("Adding Finalizer") controllerutil.AddFinalizer(&app, finalizer) return ctrl.Result{}, r.Update(ctx, &app) } if !app.DeletionTimestamp.IsZero() { l.Info("Application is being deleted") return r.reconcileDelete(ctx, &app) } l.Info("Application is being created") return r.reconcileCreate(ctx, &app) } func (r *ApplicationReconciler) reconcileCreate(ctx context.Context, app *minettodevv1alpha1.Application) (ctrl.Result, error) { l := log.FromContext(ctx) l.Info("Creating deployment") err := r.createOrUpdateDeployment(ctx, app) if err != nil { return ctrl.Result{}, err } l.Info("Creating service") err = r.createService(ctx, app) if err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil } func (r *ApplicationReconciler) createOrUpdateDeployment(ctx context.Context, app *minettodevv1alpha1.Application) error { var depl appsv1.Deployment deplName := types.NamespacedName{Name: app.ObjectMeta.Name + "-deployment", Namespace: app.ObjectMeta.Name} if err := r.Get(ctx, deplName, &depl); err != nil { if !apierrors.IsNotFound(err) { return fmt.Errorf("unable to fetch Deployment: %v", err) } /*If there is no Deployment, we will create it. An essential section in the definition is OwnerReferences, as it indicates to k8s that an Application is creating this resource. This is how k8s knows that when we remove an Application, it must also remove all the resources it created. Another important detail is that we use data from our Application to create the Deployment, such as image information, port, and replicas. */ if apierrors.IsNotFound(err) { depl = appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: app.ObjectMeta.Name + "-deployment", Namespace: app.ObjectMeta.Name, Labels: map[string]string{"label": app.ObjectMeta.Name, "app": app.ObjectMeta.Name}, Annotations: map[string]string{"imageregistry": "https://hub.docker.com/"}, OwnerReferences: []metav1.OwnerReference{ { APIVersion: app.APIVersion, Kind: app.Kind, Name: app.Name, UID: app.UID, }, }, }, Spec: appsv1.DeploymentSpec{ Replicas: &app.Spec.Replicas, Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{"label": app.ObjectMeta.Name}, }, Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"label": app.ObjectMeta.Name, "app": app.ObjectMeta.Name}, }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Name: app.ObjectMeta.Name + "-container", Image: app.Spec.Image, Ports: []v1.ContainerPort{ { ContainerPort: app.Spec.Port, }, }, }, }, }, }, }, } err = r.Create(ctx, &depl) if err != nil { return fmt.Errorf("unable to create Deployment: %v", err) } return nil } } /*The controller also needs to manage the update because if the dev changes any information in an existing Application, this must impact other resources.*/ depl.Spec.Replicas = &app.Spec.Replicas depl.Spec.Template.Spec.Containers[0].Image = app.Spec.Image depl.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort = app.Spec.Port err := r.Update(ctx, &depl) if err != nil { return fmt.Errorf("unable to update Deployment: %v", err) } return nil } func (r *ApplicationReconciler) createService(ctx context.Context, app *minettodevv1alpha1.Application) error { srv := v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: app.ObjectMeta.Name + "-service", Namespace: app.ObjectMeta.Name, Labels: map[string]string{"app": app.ObjectMeta.Name}, OwnerReferences: []metav1.OwnerReference{ { APIVersion: app.APIVersion, Kind: app.Kind, Name: app.Name, UID: app.UID, }, }, }, Spec: v1.ServiceSpec{ Type: v1.ServiceTypeNodePort, ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, Selector: map[string]string{"app": app.ObjectMeta.Name}, Ports: []v1.ServicePort{ { Name: "http", Port: app.Spec.Port, Protocol: v1.ProtocolTCP, TargetPort: intstr.FromInt(int(app.Spec.Port)), }, }, }, Status: v1.ServiceStatus{}, } _, err := controllerutil.CreateOrUpdate(ctx, r.Client, &srv, func() error { return nil }) if err != nil { return fmt.Errorf("unable to create Service: %v", err) } return nil } func (r *ApplicationReconciler) reconcileDelete(ctx context.Context, app *minettodevv1alpha1.Application) (ctrl.Result, error) { l := log.FromContext(ctx) l.Info("removing application") controllerutil.RemoveFinalizer(app, finalizer) err := r.Update(ctx, app) if err != nil { return ctrl.Result{}, fmt.Errorf("Error removing finalizer %v", err) } return ctrl.Result{}, nil }

NOTE: The number of mentions on this list indicates mentions on common posts plus user suggested alternatives. Hence, a higher number means a more popular project.

Suggest a related project

Related posts