From d3f771534428630b8cfcd37f01888aee6ee9ccf5 Mon Sep 17 00:00:00 2001 From: FeynmanZhou Date: Mon, 28 Feb 2022 14:51:27 +0800 Subject: [PATCH] add k8s operator blog Signed-off-by: FeynmanZhou --- .../en/blogs/what-are-kubernetes-operators.md | 139 ++++++++++++++++++ .../en/what-are-operators/k8s-operators.png | Bin 0 -> 21281 bytes 2 files changed, 139 insertions(+) create mode 100644 content/en/blogs/what-are-kubernetes-operators.md create mode 100644 static/images/blogs/en/what-are-operators/k8s-operators.png diff --git a/content/en/blogs/what-are-kubernetes-operators.md b/content/en/blogs/what-are-kubernetes-operators.md new file mode 100644 index 000000000..82226fbe6 --- /dev/null +++ b/content/en/blogs/what-are-kubernetes-operators.md @@ -0,0 +1,139 @@ +--- +title: 'Kubernetes Operators You Need to Use' +tag: 'Kubernetes, Operator' +keywords: Kubernetes, Operator, kubernetes applications, custom resources +description: What are Kubernetes Operators and How to Use Them? This article explains the concept of Kubernetes Operators and looks into some examples of using Operators in Kubernetes. +createTime: '2022-02-27' +author: 'Yitaek, Felix' +snapshot: '/images/blogs/en/what-are-operators/k8s-operators.png' +--- + +One of Kubernetes’s key principles is the [control loop](https://kubernetes.io/docs/concepts/architecture/controller/), which aims to continuously match the current state of at least one Kubernetes resource type to its desired state. While this property unlocks Kubernetes’s self-healing capabilities, as more complex applications are onboarded onto Kubernetes, managing and operating those applications requires features beyond just what Kubernetes provides out of the box. Such operations may include complex consensus logic for highly available, distributed databases, cross-regional failover, or automated backup and restore for critical information. + +Back in 2016, the team at CoreOS introduced the Kubernetes Operator pattern to fill in that gap. The goal was to encapsulate operational logic into a format that is compatible with the Kubernetes API in the form of an application-specific controller. While the original focus of the operator pattern was to facilitate the management of stateful applications, Kubernetes Operators are now popular paradigms used in all types of Kubernetes applications. The Operator Pattern can be extended in any use cases where the operational knowledge needed to create, configure, and maintain complex applications can be automated with an abstraction layer. + + +## What are Kubernetes Operators? + +Kubernetes Operators extend the core Kubernetes API and act as controllers for a [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/), which can represent a resource not captured by Kubernetes primitives such as Pods, Deployments, and Services. Just as the Kubernetes controller watches and compares the state of the resources it manages, Kubernetes Operator follows the same control loop pattern and matches the state of its custom resources to the desired state. Besides the additional operational knowledge that a Kubernetes Operator encapsulates, the behavior of a Kubernetes Controller and a Kubernetes Operator are identical. + +To be more precise, the Operator patterns consists of three parts: + + + +1. The application +2. Domain-specific knowledge and its desired state declared in a custom manner +3. A controller that watches the state of the application as well as domain-specific behavior and acts accordingly to reconcile desired state. + +In practice, the domain-specific knowledge is encapsulated via a Custom Resource Definition (CRD) and deployed alongside its controller in the cluster. + + +## CRDs in Kubernetes + +As mentioned above, CRDs define the behaviors of a custom resource, such as its desired state as well as the topology of the object. Let’s take a look at an example taken from the [CNCF whitepaper](https://github.com/cncf/tag-app-delivery/blob/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md): + + +``` +apiVersion: example-app.appdelivery.cncf.io/v1alpha1 +kind: ExampleApp +metadata: + name: appdelivery-example-app +spec: + appVersion: 0.0.1 + features: + exampleFeature1: true + exampleFeature2: false + backup: + enabled: true + storageType: "s3" + host: "my-backup.example.com" + bucketName: "example-backup" +status: + currentVersion: 0.0.1 + url: https://myloadbalancer/exampleapp/ + authSecretName: appdelivery-example-app-auth + backup: + lastBackupTime: 12:00 +``` + + +The above CRD defines a custom resource called “appdelivery-example-app” of the kind “ExampleApp”. The desired state is captured under the spec section. This example app enables exampleFeature1, disables exampleFeature2, with backup enabled to s3 bucket, “example-backup”. + +The status section is the output given to the operator (i.e. user of the custom resource). This example CRD returns the current version, as well as URL and the last backup time. If you are familiar with the NOTES.txt section of a Helm chart, the status section often contains similar information (e.g. how to connect to the application). + + +## How KubeSphere uses Kubernetes Operators + +The Operator pattern is widely used underneath the hood at KubeSphere. In fact, the core [Logging System](https://kubesphere.io/docs/pluggable-components/logging/) used for log collection, querying, and management is implemented via the [FluentBit Operator](https://github.com/fluent/fluentbit-operator). The log collection is implemented through Fluent Bit, however, vanilla deployment of Fluent Bit can be difficult. To address this challenge, the KubeSphere observability team developed an open-source Fluent Bit management tool called [FluentBit Operator](https://github.com/fluent/fluentbit-operator), which has been donated to the upstream Fluent community. The FluentBit Operator creates and manages Fluent Bit pods through CRDs, and dynamically updates the Fluent Bit configurations and pods, which makes it easier to deploy, configure, and update Fluent Bit instances. + + +## How to Use an Operator in KubeSphere + +Using other Operators in KubeSphere is also simple. Log onto the KubeSphere console as admin, and use kubectl from the toolbox in the lower-right hand corner (alternatively, have kube context pointed to the KubeSphere cluster if you have access). + +Next, locate the YAML file that holds all the Operator manifests. For example, for Prometheus Operator, it’s in a file called [bundle.yaml](https://github.com/prometheus-operator/prometheus-operator/blob/main/bundle.yaml): + +Then simply apply the file: + +`kubectl create -f bundle.yaml` + +Similarly, for other Operators such as Strimzi (Kafka Operator), you can run: + +`​​kubectl create -f 'https://strimzi.io/install/latest?namespace=kafka' -n kafka` + +For more examples, see: + + + +* [Deploy ClickHouse Operator and a ClickHouse Cluster on KubeSphere](https://kubesphere.io/docs/application-store/external-apps/deploy-clickhouse/) +* [Deploy TiDB Operator and a TiDB Cluster on KubeSphere](https://kubesphere.io/docs/application-store/external-apps/deploy-tidb/) + + +## Writing Your Own Operator + +If there isn’t a preexisting Operator for your use, you can create your own using some popular frameworks/libraries: + + + +* [Charmed Operator Framework](https://juju.is/) +* [kubebuilder](https://book.kubebuilder.io/) +* [KubeOps](https://buehler.github.io/dotnet-operator-sdk/) (.NET operator SDK) +* [KUDO](https://kudo.dev/) (Kubernetes Universal Declarative Operator) +* [Metacontroller](https://metacontroller.github.io/metacontroller/intro.html) along with WebHooks that you implement yourself +* [Operator Framework](https://operatorframework.io/) +* [Shell-operator](https://github.com/flant/shell-operator) +* [CNCF Operator Framework](https://github.com/operator-framework) +* [Kopf](https://github.com/nolar/kopf) (Kubernetes Operator Pythonic Framework) + +If none of these frameworks work for your use case, you can also write an Operator with any language using a client library for the Kubernetes API. + + +## FAQ + +- What is a Kubernetes Operator? + +> A Kubernetes Operator is an extension of Kubernetes that utilizes custom resources to capture operational knowledge for complex applications. + +- How do you use Operators in Kubernetes? + +> To use an Operator, create Custom Resource Definitions and deploy it with its controller to the cluster. + +- How do you create a Kubernetes Operator? + +> To create a Kubernetes Operator, use an open-source Operator framework or libraries that can communicate with the core Kubernetes API. + +- How is a Kubernetes Operator different than a Helm chart? + +> Helm is a package manager for Kubernetes resources. While Helm can also carry out some of the operational tasks that an Operator can (e.g. bundling together multiple components, firing hooks upon install), it is limited in its functionality. However, these tools are complementary. In fact, some Helm charts install CRDs prior to deploying the application and its Operator. + +- What are some popular Operators? + +> Prometheus Operator and etcd Operators were the first two created by the CoreOS team. Strimzi Operator for Kafka is also popular to deploy a production-grade Kafka cluster on Kubernetes. + +- Are Operators only useful for StatefulSets? + +> No, while StatefulSets present typical challenges that an Operator was meant to help with, any application with operational complexity can benefit from an Operator + +- When should you not use an Operator? + +> Operators present another layer of abstraction for the user. If Kubernetes can natively handle a lot of the operational burden, you might be better off investing time into CI/CD pipelines to automate some tasks rather than creating an entirely new Operator. \ No newline at end of file diff --git a/static/images/blogs/en/what-are-operators/k8s-operators.png b/static/images/blogs/en/what-are-operators/k8s-operators.png new file mode 100644 index 0000000000000000000000000000000000000000..789ee434c3e89f818263001dffd08aa9546ab5e6 GIT binary patch literal 21281 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 literal 0 HcmV?d00001