Microservice Deployments


mit


Kubernetes Operatoren
Frank Müller
Oldenburg

Baujahr 1965

Team Lead PS & Solution Engineer

Kubermatic GmbH

frank.mueller@themue.dev

@themue
Auf dem Weg zum Microservice
Wandel von Monolithen zu Microservices
Wandel von Monolithen zu Microservices
Wandel von Monolithen zu Microservices
Flexible Architekturen
Chaos vermeiden
Kubernetes als Laufzeitumgebung
Abstraktion der Laufzeitumgebung
Wichtige Bausteine
Kontrollschleife für den Wunschzustand
Erweiterbar durch eigene Ressourcen-Typen
Controller reagieren auf diese Ressourcen
Standardisierung und Einfachheit
Vielfalt erschwert Wartung
Einen Rahmen geben
Dynamik
Der Weg zu eigenen Bausteinen
De
fi
nition einer Custom Resource
Versionierte API
• Versions-Nummern und Level

• Start mit v1alpha1, v1alpha2, ...

• Bei zunehmender Stabilität und Tests mit v1beta1

• Stabile Versionen ohne Level, wie v1, v2, ...
Bestandteile des Microservice Deployments
apiVersion: apiextensions.k8s.io/v1


kind: CustomResourceDe
fi
nition


metadata:


name: mechanic.k8s.tideland.dev


spec:


group: k8s.tideland.dev


scope: Namespaced


names:


plural: mechanics


singular: mechanic


kind: Mechanic


shortNames:


- mech
De
fi
nition in YAML – Rahmen
De
fi
nition in YAML – Spezi
fi
kation
versions:


- name: v1alpha1


served: true


storage: true


schema:


openAPIV3Schema:


type: object


properties:


spec:


....


status:


...
De
fi
nition in Go – Rahmen
package v1alpha1


import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"


type Mechanic struct {


metav1.TypeMeta `json:",inline"`


metav1.ObjectMeta `json:"metadata"`


Spec MechanicSpec `json:"spec"`


Status MechanicStatus `json:"status"`


}
De
fi
nition in Go – Spezi
fi
kation
package v1alpha1


import corev1 "k8s.io/api/core/v1"


type Microservice struct {


Scaling MicroserviceScaling `json:"scaling"`


Containers []Container `json:"containers"`


Volumes []corev1.Volume `json:"volumes,omitempty"`


}


...


type MechanicSpec struct {


Microservice Microservice `json:"microservice"`


Dependencies Dependencies `json:"dependencies"`


Network Network `json:"network"`


}
DeepCopy wird benötigt
func (in *Mechanic) DeepCopyInto(out *Mechanic) {


out.TypeMeta = in.TypeMeta


out.ObjectMeta = in.ObjectMeta


out.Spec = MechanicSpec{...}


}


func (in *Mechanic) DeepCopyObject() runtime.Object {


out := Mechanic{}


in.DeepCopyInto(&out)


return &out


}
Vorbereitung der Registratur (Schritt 1a)
package v1alpha1


var (


SchemaGroupVersion = schema.GroupVersion{


Group: "k8s.tideland.dev",


Version: "v1alpha1",


}


SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)


AddToScheme = SchemeBuilder.AddToScheme


)
Vorbereitung der Registratur (Schritt 1b)
package v1alpha1


func addKnownTypes(scheme *runtime.Scheme) error {


scheme.AddKnownTypes(SchemeGroupVersion,


&Mechanic{},


&MechanicList{},


)


metav1.AddToGroupVersion(scheme, SchemeGroupVersion)


return nil


}
Hinzufügen zum Schema (Schritt 2)
package main


import "k8s.io/client-go/kubernetes/scheme"


func main() {


...


v1alpha1.AddToScheme(scheme.Scheme)


...


}
REST Client bedarfsgerecht kapseln
Create eingepackt
func (mi *MechanicInterface) Create(


m *Mechanic,


opts metav1.CreateOptions) (*Mechanic, error) {


result := &Mechanic{}


err := mi.restClient().


Post().


Namespace(di.namespace).


Resource("mechanic").


Body(m).


VersionedParams(&opts, scheme.ParameterCodes).


Do().


Into(&result)


return result, err


}
Information über Änderungen
Lister für das Microservice Deployment
package v1alpha1


type MechanicLister interface {


List(selector labels.Selector) ([]*Mechanic, error)


Get(name string) (*Mechanic, error)


}


type mechanicLister struct {


// REST Client.


mif MechanicInterface


}


func (ml mechanicLister) Get(name string) (*Mechanic, error) {


return ml.mif.Get(name, metav1.GetOptions{})


}
Informer für das Microservice Deployment erzeugen
package v1alpha1


type MechanicInformer interface {


Informer() cache.SharedIndexInformer


Lister() MechanicLister


}


type mechanicInformer struct {


// REST Client.


mif MechanicInterface


}


func NewMechanicInformerWithInterface(mif MechanicInterface) MechanicInformer {


return &mechanicInformer{


mif: mif,


}


}
Informer für das Microservice Deployment nutzen
func (mi *mechanicInformer) Informer() cache.SharedIndexInformer {


return cache.NewSharedIndexInformer(


&cache.ListWatch{


ListFunc: func(opts metav1.ListOptions) (result runtime.Object, err error) {


return mi.mif.List(opts)


		 	 },


WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {


return mi.mif.Watch(opts)


		 	 },


},


&Mechanics{},


30*time.Second,


cache.Indexers{},


)


}
Von der API zum Controller
Aufgaben
• Kon
fi
guration

• Informer erzeugen und Handler registrieren

• Ein bis N Informer im Hintergrund arbeiten lassen

• Auf Callbacks entsprechend reagieren
De
fi
nition
package mechanic


type Mechanic struct {


con
fi
g *rest.Con
fi
g


client kubernetes.Interface


namespace string


mif mechanicv1alpha1.MechanicInterface


minfo cache.SharedIndexInformer


}
Den Controller erzeugen
package mechanic


func New(con
fi
g *rest.Con
fi
g, namespace string) (*Mechanic, error) {


m := &Mechanic{...}


namespaceableMIF, err := mechanicv1alpha1.NewForCon
fi
g(m.con
fi
g)


...


m.mif = namespaceableMIF.Namespace(namespace)


client, err := kubernetes.NewForCon
fi
g(con
fi
g)


...


m.client = client


m.minfo = mechanicv1alpha1.NewMechanicInformerWithInterface(m.mif).Informer()


return m, nil


}
Auf geht es zur Arbeit
package mechanic


func (m *Mechanic) Run(ctx context.Context) {


// Add handler functions to informer.


m.minfo.AddEventHandler(cache.ResourceEventHandlerFuncs{


AddFunc: m.addMechanicHandler,


UpdateFunc: m.updateMechanicHandler,


DeleteFunc: m.deleteMechanicHandler,


})


// Let it work.


go m.minfo.Run(wait.NeverStop)


// Run based on context.


select {


case <-ctx.Done:


}


}
Hinzufügen einer Instanz
package mechanic


func (m *Mechanic) addMechanicHandler(obj interface{}) {


mechanic := obj.(*mechanicv1alpha1Mechanic)


if mechanic.GetNamespace() != m.namespace {


return


}


m.addMicroservice(mechanic.Spec.Microservice, mechanic.Status)


m.addDependencies(mechanic.Spec.Dependencies, mechanic.Status)


m.addNetwork(mechanic.Spec.Network, mechanic.Status)


}
Hierarchische Abarbeitung
Externe Abhängigkeiten abstrahieren
• Struktur mit Name, Typ als String und Schlüssel-Wert-
Parametern

• Iteration über die Abhängigkeiten

• Instanzierung auf Basis des Typs

• Parameter übergeben

• Aktion ausführen
Auf geht es
Hauptprogramm
package main


func main() {


// Read
fl
ags and con
fi
guration.


...


con
fi
g, err := clientcmd.BuildCon
fi
gFromFlags(masterURL, kubecon
fi
g)


...


// Run mechanic.


m, err := mechanic.New(con
fi
g, namespace)


...


mechanicv1alpha1.AddToScheme(scheme.Scheme)


m.Run(context.Background())


}
Werkzeuge
Kubebuilder
• Manuelle Schritte zeigen die prinzipielle Arbeitsweise

• Kubebuilder unterstützt dies als Framework

• Basierend auf API Quellen mit Kommentaren wird
Standard-Code generiert

• Anstatt Handler werden Reconcile-Funktionen mit
Requests erzeugt und später aufgerufen

• Generator-Aufrufe sind idempotent
Fazit
Zusammenfassung
• Initial ein komplexer Weg zur Installation und dem Betrieb
von Anwendungssystemen im Netz

• Stellt komfortable Möglichkeiten aber auch gewünschte
Grenzen für die Installation von Microservices zur
Verfügung

• Keine Abweichung der Kon
fi
gurationssprache der
Anwendungen und der Anwendungsinstallation
Vielen Dank. Guten Appetit beim Kaffee.

2021 OOP - Kubernetes Operatoren

  • 1.
  • 2.
    Frank Müller Oldenburg Baujahr 1965 TeamLead PS & Solution Engineer
 Kubermatic GmbH frank.mueller@themue.dev @themue
  • 3.
    Auf dem Wegzum Microservice
  • 4.
    Wandel von Monolithenzu Microservices
  • 5.
    Wandel von Monolithenzu Microservices
  • 6.
    Wandel von Monolithenzu Microservices
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
    Erweiterbar durch eigeneRessourcen-Typen
  • 14.
    Controller reagieren aufdiese Ressourcen
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
    Der Weg zueigenen Bausteinen
  • 20.
  • 21.
    Versionierte API • Versions-Nummernund Level • Start mit v1alpha1, v1alpha2, ... • Bei zunehmender Stabilität und Tests mit v1beta1 • Stabile Versionen ohne Level, wie v1, v2, ...
  • 22.
  • 23.
    apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDe fi nition metadata: name:mechanic.k8s.tideland.dev spec: group: k8s.tideland.dev scope: Namespaced names: plural: mechanics singular: mechanic kind: Mechanic shortNames: - mech De fi nition in YAML – Rahmen
  • 24.
    De fi nition in YAML– Spezi fi kation versions: - name: v1alpha1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: .... status: ...
  • 25.
    De fi nition in Go– Rahmen package v1alpha1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" type Mechanic struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` Spec MechanicSpec `json:"spec"` Status MechanicStatus `json:"status"` }
  • 26.
    De fi nition in Go– Spezi fi kation package v1alpha1 import corev1 "k8s.io/api/core/v1" type Microservice struct { Scaling MicroserviceScaling `json:"scaling"` Containers []Container `json:"containers"` Volumes []corev1.Volume `json:"volumes,omitempty"` } ... type MechanicSpec struct { Microservice Microservice `json:"microservice"` Dependencies Dependencies `json:"dependencies"` Network Network `json:"network"` }
  • 27.
    DeepCopy wird benötigt func(in *Mechanic) DeepCopyInto(out *Mechanic) { out.TypeMeta = in.TypeMeta out.ObjectMeta = in.ObjectMeta out.Spec = MechanicSpec{...} } func (in *Mechanic) DeepCopyObject() runtime.Object { out := Mechanic{} in.DeepCopyInto(&out) return &out }
  • 28.
    Vorbereitung der Registratur(Schritt 1a) package v1alpha1 var ( SchemaGroupVersion = schema.GroupVersion{ Group: "k8s.tideland.dev", Version: "v1alpha1", } SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) AddToScheme = SchemeBuilder.AddToScheme )
  • 29.
    Vorbereitung der Registratur(Schritt 1b) package v1alpha1 func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Mechanic{}, &MechanicList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil }
  • 30.
    Hinzufügen zum Schema(Schritt 2) package main import "k8s.io/client-go/kubernetes/scheme" func main() { ... v1alpha1.AddToScheme(scheme.Scheme) ... }
  • 31.
  • 32.
    Create eingepackt func (mi*MechanicInterface) Create( m *Mechanic, opts metav1.CreateOptions) (*Mechanic, error) { result := &Mechanic{} err := mi.restClient(). Post(). Namespace(di.namespace). Resource("mechanic"). Body(m). VersionedParams(&opts, scheme.ParameterCodes). Do(). Into(&result) return result, err }
  • 33.
  • 34.
    Lister für dasMicroservice Deployment package v1alpha1 type MechanicLister interface { List(selector labels.Selector) ([]*Mechanic, error) Get(name string) (*Mechanic, error) } type mechanicLister struct { // REST Client. mif MechanicInterface } func (ml mechanicLister) Get(name string) (*Mechanic, error) { return ml.mif.Get(name, metav1.GetOptions{}) }
  • 35.
    Informer für dasMicroservice Deployment erzeugen package v1alpha1 type MechanicInformer interface { Informer() cache.SharedIndexInformer Lister() MechanicLister } type mechanicInformer struct { // REST Client. mif MechanicInterface } func NewMechanicInformerWithInterface(mif MechanicInterface) MechanicInformer { return &mechanicInformer{ mif: mif, } }
  • 36.
    Informer für dasMicroservice Deployment nutzen func (mi *mechanicInformer) Informer() cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(opts metav1.ListOptions) (result runtime.Object, err error) { return mi.mif.List(opts) }, WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { return mi.mif.Watch(opts) }, }, &Mechanics{}, 30*time.Second, cache.Indexers{}, ) }
  • 37.
    Von der APIzum Controller
  • 38.
    Aufgaben • Kon fi guration • Informererzeugen und Handler registrieren • Ein bis N Informer im Hintergrund arbeiten lassen • Auf Callbacks entsprechend reagieren
  • 39.
    De fi nition package mechanic type Mechanicstruct { con fi g *rest.Con fi g client kubernetes.Interface namespace string mif mechanicv1alpha1.MechanicInterface minfo cache.SharedIndexInformer }
  • 40.
    Den Controller erzeugen packagemechanic func New(con fi g *rest.Con fi g, namespace string) (*Mechanic, error) { m := &Mechanic{...} namespaceableMIF, err := mechanicv1alpha1.NewForCon fi g(m.con fi g) ... m.mif = namespaceableMIF.Namespace(namespace) client, err := kubernetes.NewForCon fi g(con fi g) ... m.client = client m.minfo = mechanicv1alpha1.NewMechanicInformerWithInterface(m.mif).Informer() return m, nil }
  • 41.
    Auf geht eszur Arbeit package mechanic func (m *Mechanic) Run(ctx context.Context) { // Add handler functions to informer. m.minfo.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: m.addMechanicHandler, UpdateFunc: m.updateMechanicHandler, DeleteFunc: m.deleteMechanicHandler, }) // Let it work. go m.minfo.Run(wait.NeverStop) // Run based on context. select { case <-ctx.Done: } }
  • 42.
    Hinzufügen einer Instanz packagemechanic func (m *Mechanic) addMechanicHandler(obj interface{}) { mechanic := obj.(*mechanicv1alpha1Mechanic) if mechanic.GetNamespace() != m.namespace { return } m.addMicroservice(mechanic.Spec.Microservice, mechanic.Status) m.addDependencies(mechanic.Spec.Dependencies, mechanic.Status) m.addNetwork(mechanic.Spec.Network, mechanic.Status) }
  • 43.
  • 44.
    Externe Abhängigkeiten abstrahieren •Struktur mit Name, Typ als String und Schlüssel-Wert- Parametern • Iteration über die Abhängigkeiten • Instanzierung auf Basis des Typs • Parameter übergeben • Aktion ausführen
  • 45.
  • 46.
    Hauptprogramm package main func main(){ // Read fl ags and con fi guration. ... con fi g, err := clientcmd.BuildCon fi gFromFlags(masterURL, kubecon fi g) ... // Run mechanic. m, err := mechanic.New(con fi g, namespace) ... mechanicv1alpha1.AddToScheme(scheme.Scheme) m.Run(context.Background()) }
  • 47.
  • 48.
    Kubebuilder • Manuelle Schrittezeigen die prinzipielle Arbeitsweise • Kubebuilder unterstützt dies als Framework • Basierend auf API Quellen mit Kommentaren wird Standard-Code generiert • Anstatt Handler werden Reconcile-Funktionen mit Requests erzeugt und später aufgerufen • Generator-Aufrufe sind idempotent
  • 49.
  • 50.
    Zusammenfassung • Initial einkomplexer Weg zur Installation und dem Betrieb von Anwendungssystemen im Netz • Stellt komfortable Möglichkeiten aber auch gewünschte Grenzen für die Installation von Microservices zur Verfügung • Keine Abweichung der Kon fi gurationssprache der Anwendungen und der Anwendungsinstallation
  • 51.
    Vielen Dank. GutenAppetit beim Kaffee.