Friday, July 16, 2021

Kubernetes Patterns

In this post I want to write down a few notes of a freely available book by Red Hat, named "Kubernetes patterns - Reusable Elements for Designing Cloud-Native Applications". Specifically, the book classifies common k8s patterns in:

  • Foundational
    1. predictable demands - containers shall declare a resource profile and limit to the specified requirements; this is the case of resources.requests and resources.limits (e.g. cpu and memory).
    2. declarative deployment - the creation of pod is actuated by a declarative deployment resource instead of an imperative pod definition;
    3. health probe - any container shall expose health-specific APIs to allow for its observability; this is the case of containers[x].livenessProbe and containers[x].readinessProbe, for instance using an existing REST interface as httpGet.{path, port} or as exec.command;
    4. managed lifecycle - defines how applications shall react to lifecycle events, such as SIGTERM (graceful shutdown) and SIGKILL (forced shutdown), as well as poststart hooks (i.e., containers[x].lifecycle.poststart.exec.command to run a specific command after the container is created, but asynchronously with the main container's process) and prestop hooks (i.e., containers[x].lifecycle.prestop.command) ran right before a container is terminated with a SIGTERM notification; instead of a command, an httpGet.{port, path} can be used;
    5. automated placement - defines a method to manage container distribution in multi node clusters; this is the case of node selectors, such as spec.nodeSelector.{disktype, ..,}, as well as node affinity/antiaffinity (i.e. spec.affinity.nodeAffinity) and inter-pod affinity/antiaffinity (spec.affinity.podAffinity).
  • Behavioral
    1. batch Job - a batch workload to be run once to completion; this is the case of the Job resource.
    2. periodic Job - a batch workload to be scheduled for periodic run; this is the case of the CronJob resource.
    3. daemon service - defines how pods can be run as daemons on specific nodes; In unix, the name daemon denotes a long-running, background process generally providing infrastructural functionalities, such as disk or network resources. This matches the k8s daemonset resource, meant to generate pods that run on all or selected cluster nodes in order to provide additional capabilities to the rest of the cluster pods;
    4. singleton service - ensures that only one instance of a service is running at time; this is generally achieved in out-of-application locking (without application-specific locking mechanisms) through a single-replica statefulSet or replicaSet, or in case of in-application locking (e.g. when using zookeeper as coordination middleware) to elect a leader out of any simple deployment - or generally a group of non-strictly related pods; Viceversa, a PodDisruptionBudget can be set to limit the max number of instances that are simultaneously down at a time (to prevent unavailability).
    5. stateful service - defines approaches to run stateful loads; this is the case of StatefulSets, which, as opposed to ReplicaSets, provide means for identifying connected network resources - given that any pod generated by the set has a name and an ordinal index, startup ordering - i.e. the sequence in which instances are scaled up/down -, as well as storage, given that instead of a persistentVolumeClaim a volumeClaimTemplate can be used to generate PVCs and uniquely bind them to the pods that constitute the StatefulSet;
    6. service discovery - defines mechanisms for discovery of services and mapped pod instances; this is commonly achieved using the app selector (i.e. spec.selector.app);
    7. self awareness - defines methods for introspection and metadata injection; for instance, it is possible to set environment values with information that will be available after the pod is started, such as env[x].valueFrom.fieldRef.fieldPath.{status.PodIP, spec.nodeName, metadata.uid, ...} or env[x].valueFrom.resourceFieldRef.container;
  • Structural
    1. init container - provides a way of running initialization tasks along with application containers; those containers can be listed in the spec.initContainers: [] section;
    2. sidecar container - runs an additional container to extend the application container with additional functionalities; this can be done by running multiple containers, which may interact via a shared mount point or a socket;
    3. adapter - a sidecar meant to expose a different interface of the one initially provided by application container, without modifying it; For instance, we may expose additional monitoring metrics for an existing container image by accessing monitoring data of the application container at a shared disk/network and exposing them on a REST interface.
    4. ambassador - a proxy to be used by the application container to interact with other services; For instance, an additional service, such as a distributed cache or key-value store may be accessed using a specific client, which is started as sidecar and exposes a local interface; the interface can be accessed locally (at localhost, given that they run on the same pod) by the application container which has thus access to the distributed service;
  • Configuration
    1. envVar configuration - defines how variables are used to store configurations, for instance those can be set i) from a literal, ii) using env.valueFrom.configMapKeyRef{name, key} to retrieve it from an existing config map or iii) env.valueFrom.secretKeyRef{name, key} to retrieve it from an existing secret; alternatively an entire config map or secret can be loaded using envFrom{configMapRef.name, prefix};
    2. configuration resource - uses config maps and secrets to store config information; those can then be referenced as specified above or mounted as files;
    3. immutable configuration -  uses built container images to store immutable configs; then mount the data from the image and access it from the application container;
    4. configuration template - uses configuration templates to generate various versions only differing slightly;
  • Advanced
    1. controller - defines methods to watch for changes on resource definitions - such as labels (indexed for query but only alphanumeric keys can be used), annotations (unindexed though relaxed constraint on key type) and configmaps - and react accordingly; this is typically an observe-analyze-act cycle, also called reconciliation loop, which can be run as am event-handling routine along with those available by default, for instance to manage ReplicaSet, Deployments and so on;
    2. operator - defines controllers watching changes on a custom resource; this is thus more flexible than a controller, since custom resource can be defined and the k8s api extended fully (as opposed to a configmap that won't define a new k8s resource type);
    3. elastic scale - defines dynamic scaling, both horizontally and vertically; there exists a HorizontalPodAutoscaler that can be set on a minimum and maximum resource (e.g. cpu, memory, custom ones) range the Pods are expected to run on before the scale process occurs - respectively by ramping up additional pods or terminating superfluous ones (desiredReplicas = currentReplicas * (currentMetricValue/desiredMetricValue)). A VerticalPodAutoscaler is often preferred to the horizontal one, given that adding and removing pods in stateful applications can be a disruptive process; Vertical scaling allows for tuning the requests and limits for containers as based on their actual usage; This can affect both creation of new Pods or update existing ones, which may be evicted and rescheduled with different requests/limits values.
    4. image builder - defines approaches for image build within the cluster; this is opposed to building images outside the cluster using CI pipelines. For instance, Openshift provides a BuildConfig resource that can specify both docker build and source-to-image build processes and directly push to the internal registry.

That's all folks!

Bibliography

No comments:

Post a Comment