
























Disclaimer: Django is a general-purpose web framework, while Kubernetes is, well, a container orchestrator, if you will. Obviously, so different projects shouldn't be compared at all. However, in this series, I try to demystify Kubernetes and show that its API is a pretty much normal HTTP API and that it can be extended in rather familiar ways. Hence, the title. And no, below you won't find a true comparison of Kubernetes and Django, sorry :)
There are many ways to extend Kubernetes with custom functionality, starting from writing kubectl plugins and ending with implementing scheduler extensions. The exhaustive list of extension points can be found in the official docs, but if there were a ranking based on the hype around the approach, I bet developing custom controllers or operators, if you will, would win.
The idea behind Kubernetes controllers is simple yet powerful - you describe the desired state of the system, persist it to Kubernetes, and then wait until controllers do their job and bring the actual state of the cluster close enough to the desired one (or report a failure).
However, while controllers get a lot of the press attention, in my opinion, writing custom controllers most of the time should be seen as just one (potentially optional) part of the broader task of extending the Kubernetes API. But to notice that, a decent familiarity with a typical workflow is required.
While the Kubernetes community offers a broader and more generic definition of a controller, after more than a year of dealing with Kubernetes controllers in the wild, I've come up with the following explanation that covers the majority of the custom controllers I've seen so far:
But what, in essence, is a resource? In Kubernetes own words:
A resource is an endpoint in the Kubernetes API that stores a collection of API objects of a certain kind; for example, the built-in pods resource contains a collection of Pod objects.
Thus, if resources are merely Kubernetes API endpoints, writing a controller for a resource is just a fancy way to bind a request handler to an API endpoint! 🙈
The controller's logic will be triggered (well, in particular) whenever there is a creation or modification request to the primary resource endpoint. An instance of the primary resource type that triggered the control loop iteration serves as a data transfer object for the request parameters (object's spec field) and the response status (object's status field).
The main difference between controller-based handlers and more traditional request handlers is that the processing happens asynchronously to the actual API request. The API requests creating or modifying Kubernetes objects (e.g., POST, PUT, PATCH) just schedule the work for controllers (via recording the intent), and the API requests fetching the objects (GET, WATCH) are used to get back the processing status.
If adding request handlers to the Kubernetes API happens through writing controllers, how adding new API endpoints is done?
Before answering this question, it's important to understand that there are two types of endpoints in the Kubernetes API:
/metrics, /logs, or /apis are the most prominent examples of that other type of endpoints. Such endpoints are either baked into the Kubernetes API server or implemented using the API Aggregation Layer.— Ivan Velichko (@iximiuz) February 6, 2022Are there some prominent examples when the Kubernetes API was extended using the Aggregation Layer?
Or it's always Custom Resources paired with admission hooks and controllers?
Controllers typically work with the first type of endpoints. So, how a new endpoint serving a user-defined type of objects can be added to the API?
First, a CustomResourceDefinition (CRD) needs to be written. The CRD itself is an object that describes the new, custom resource. Most importantly, the CRD should contain the name and the versioned object schema (i.e., the fields) of the new resource type.
An example of a CustomResourceDefinition that registers a new resource monkeys serving objects of the new kind Monkey:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: monkeys.chaos.iximiuz.com
spec:
# group name to use for REST API: /apis/<group>/<version>
group: chaos.iximiuz.com
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: monkeys
# singular name to be used as an alias on the CLI and for display
singular: monkey
# kind is normally the PascalCased singular name of the schema
kind: Monkey
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
# Monkey objects will have just two properties: .count and .selector
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
count:
type: number
minimum: 1
selector:
type: string
maxLength: 1024
Applying the above snippet to a cluster and examining the updated list of the API endpoints gives the following:
$ kubectl get --raw / | jq .
{
"paths": [
...
"/apis/apps",
"/apis/apps/v1",
...
👉 "/apis/chaos.iximiuz.com",
👉 "/apis/chaos.iximiuz.com/v1",
...
"/apis/storage.k8s.io",
"/apis/storage.k8s.io/v1",
...
]
}
$ kubectl get --raw /apis/chaos.iximiuz.com/v1 | jq .
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "chaos.iximiuz.com/v1",
"resources": [
{
"name": "monkeys",
"singularName": "monkey",
"namespaced": true,
"kind": "Monkey",
"verbs": [
"delete",
"deletecollection",
"get",
"list",
"patch",
"create",
"update",
"watch"
],
"storageVersionHash": "OrLI4mAJgSw="
}
]
}
Then, the CRD needs to be submitted to the cluster. Applying a CRD to a cluster creates a new Kubernetes API endpoint serving a Custom Resource type. As simple as just that!
Objects of the custom resource type look and behave much like the built-in Kubernetes objects, they benefit from the common API features (CRUD, field validation, discovery, etc.), and at the same time, they have attributes required to solve your custom use case.
Getting back to the request handling...
The superpower of Kubernetes controllers is attributed to their asynchronous nature, but so is their greatest limitation. Requests to the Kubernetes API creating, modifying, or deleting objects work as a record of intent - the actual processing logic is deferred until the next control loop iteration. But what if synchronous request handling is required?
Well, it's also possible in Kubernetes! But for that, you'd need to intervene in the resource request processing by the Kubernetes API server.
Upon arrival to the API server, a request passes through the following stages before the change is persisted in etcd (or alike):
And most (or all?) of the above stages can be extended with custom logic!
Thus, configuring an admission webhook will make the Kubernetes API server send the resource instance (wrapped into an envelope called AdmissionReview) to a custom HTTPS endpoint before actually persisting it.
Registering an admission webhook is as simple as just applying the following manifest:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: my-validating-webhook-config
webhooks:
- name: my-validating-webhook-1
admissionReviewVersions:
- v1
# what endpoint to call
clientConfig:
# either a Kubernetes service
service:
name: my-webhook-service
namespace: my-namespace
path: /validate-monkey
# ...or a regular URL (beware, it must use HTTPS)
url: https://my-webhook-service.org/validate-monkey
rules:
# what API resource to react on
- apiGroups:
- /apis/chaos.iximiuz.com
apiVersions:
- v1
resources:
- monkeys
# what operations on the resource to react on
operations:
- CREATE
- UPDATE
sideEffects: None # Sweet!
In the above example, every POST/PUT request to the /apis/chaos.iximiuz.com/v1 endpoint will trigger a blocking side-request to the validation endpoint hosted wherever you want and doing whatever you want with the passed Monkey object. And by replacing ValidatingWebhookConfiguration with MutatingWebhookConfiguration, you can even mutate the objects before persisting.
Calling an admission webhook endpoint blocks the request processing by the Kubernetes API server. The implementation of the admission webhook can perform arbitrary validation logic, populate object's attributes with non-trivial defaults, label or annotate the object, or even modify other Kubernetes resources or make changes to external systems!
Thus, webhooks is an easy way to bind synchronous request handlers to Kubernetes API endpoints. And this completes the feature parity of the Kubernetes API with any other traditional HTTP API 🎉
Let's try to put everything on one diagram. Here is how a more or less comprehensive Kubernetes API extension workflow can be depicted:
Hopefully, it's clear now that custom controllers is just one part of the bigger task of extending the Kubernetes API.
I hope, following the above explanation you've also noticed how Kubernetes is no different from good old tech we're all familiar with:
So, Kubernetes is not so much different from Django, folks! 🙈
Seriously, though, drawing analogy with the familiar stuff generally helps me understand the new concepts faster. But when just understanding is not enough and fluency is required, practice is what usually helps me to internalize the concepts for real. However, this is a topic for another write-up. Stay tuned!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。