From 0cfd14f46c75d2eef2ed792f2cb27c68415a6be1 Mon Sep 17 00:00:00 2001 From: Olaf Bohlen <olbohlen@eenfach.de> Date: Tue, 21 Mar 2023 14:58:48 +0100 Subject: [PATCH] add README.org --- README.org | 257 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 257 insertions(+), 0 deletions(-) diff --git a/README.org b/README.org new file mode 100644 index 0000000..8df5128 --- /dev/null +++ b/README.org @@ -0,0 +1,257 @@ +* I need a cookie recipe database! + +...and because it makes total sense, we are going to abuse the K8s API for it. + +- thankfully we can extend K8s with Custom Resource Definitions (CRDs) +- but how does it work? +- =CustomResourceDefinitions= are themselves a =Resource=, based on a =ResourceDefinition= + +#+begin_example +$ oc api-resources | egrep "(NAME|CustomResourceDefinition)" +NAME SHORTNAMES APIVERSION NAMESPACED KIND +customresourcedefinitions crd,crds apiextensions.k8s.io/v1 false CustomResourceDefinition +$ oc explain crds +KIND: CustomResourceDefinition +VERSION: apiextensions.k8s.io/v1 + +DESCRIPTION: + CustomResourceDefinition represents a resource that should be exposed on + the API server. Its name MUST be in the format <.spec.name>.<.spec.group>. +[...] +#+end_example + +* Preparing the cookierecipes CRD + +Let's create the CRD from https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/cookie-crd.yaml + +#+begin_example +$ oc new-project kitchen +Now using project "kitchen" on server "https://api.crc.testing:6443". +$ oc create -f cookie-crd.yaml +Error from server (Forbidden): error when creating "cookie-crd.yaml": +customresourcedefinitions.apiextensions.k8s.io is forbidden: User "developer" +cannot create resource "customresourcedefinitions" in API group "apiextensions.k8s.io" +at the cluster scope + +$ oc login -u kubeadmin +$ oc create -f cookie-crd.yaml +customresourcedefinition.apiextensions.k8s.io/cookierecipes.de.eenfach.olbohlen created +$ oc login -u developer +#+end_example + +Now the cluster knows about the CRD and we could store recipes! + +* Storing some sample recipes + +We try to store sample cookie recipes...but: + +#+begin_example +$ oc create -f sample-cookie.yaml +Error from server (Forbidden): error when creating "sample-cookie.yaml": +cookierecipes.de.eenfach.olbohlen is forbidden: User "developer" cannot create +resource "cookierecipes" in API group "de.eenfach.olbohlen" in the namespace "kitchen" +Error from server (Forbidden): error when creating "sample-cookie.yaml": +cookierecipes.de.eenfach.olbohlen is forbidden: User "developer" cannot create +resource "cookierecipes" in API group "de.eenfach.olbohlen" in the namespace "kitchen" +#+end_example + +We need to set up some RBAC resources first: +- a =ClusterRole= that allows viewing recipes +- a =ClusterRole= that allows editing recipes +- and a =ClusterRoleBinding= that allows that for authenticated users + +* Creating RBAC resources + +Apply the RBAC definitions from: https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/cookie-rbac.yaml +#+begin_example +$ oc login -u kubeadmin +$ oc create -f cookie-rbac.yaml +clusterrole.rbac.authorization.k8s.io/cookierecipe-edit created +clusterrole.rbac.authorization.k8s.io/cookierecipe-view created +clusterrolebinding.rbac.authorization.k8s.io/cookierecipe-edit created +#+end_example + +The =ClusterRoleBinding= "cookierecipe-edit" allows =system:authenticated:oauth= +group members to edit =cookierecipes=. +=system:authenticated:oauth= contains all users that logged in via the OAuth +service (via an =IdentityProvider=). + +* Storing some sample recipes (hopefully this time!!) + +Now we should be able to create the sample recipes: + +#+begin_example +$ oc login -u developer +$ oc create -f sample-cookie.yaml +cookierecipe.de.eenfach.olbohlen/vintage-chocolate-chip created +cookierecipe.de.eenfach.olbohlen/double-dipped-shortbread created +$ oc get cookierecipe +NAME AGE +double-dipped-shortbread 17s +vintage-chocolate-chip 17s +#+end_example + +There is no functionality here - we just stored the recipes in the etcd via the K8s API. + +* Now can we do anything with our recipes? +Of course we can *oc get -o yaml* for example on them and filter: + +#+begin_example +$ oc get cookierecipe vintage-chocolate-chip -o yaml | yq -y .spec.ingredients[0] +amount: 150 +name: salted butter +remarks: softened +unit: grams +#+end_example + +This is handy, as we can extract exactly the data which we need at a time. + +But...it's a lot of manual work... + +* I'm an operator with my pocket calculator + +Operators were introduced as "Kubernetes Native Applications" and that actually +means nothing. Operators are in the end just =Pods=. + +These Pods run one or more containers, but one container should run a =Controller= +that can interprete your =CustomResources=. + +So let's write a CookieRecipe Operator. In shell-script... :) + +Of course this Operator is not compatible with the =OperatorLifecycyleManager= (=OLM=), +so we have to install it manually. + +* What do we need? + +We need: +- a ContainerImage +- and therefore probably a *Containerfile* +- Controller code + +Then we are going to build the Operator ContainerImage and push it to a Registry. + +* Let's review the Containerfile + +The Containerfile is here: + +https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/Containerfile + +the base image is a "kshbase" image, which itself is based upon ubi9 containing also a ksh93 +and an oc client. + +* Have a look at the Controller + +The controller is written in KornShell 93 (ksh93), which is mostly bash compatible :) + +The code is here: + +https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/recipe-processor.ksh + +* Now let's also have a look at the deployment + +Note: this deployment does not use an =ImageStream=, so it would work also on native k8s + +https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/cookie-operator-deployment.yaml + +This deployment requires a =ServiceAccount= called "cookieprocessor", this =ServiceAccount= provides +a Token to authenticate against the API (which we use in the controller script). + +* The ServiceAccount + +We need a =ServiceAccount=, but that alone will not help. The =ServiceAccount= is NOT member +of =system:authenticated:oauth=, so it can't read =cookierecipes= based on the =ClusterRoleBinding= we created earlier. +For that reason we also create a =RoleBinding= (namespaced!) that allows reading recipes: + +https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/cookieprocessor-sa.yaml + +* Building the stuff together + +#+begin_example +$ oc create -f cookieprocessor-sa.yaml +serviceaccount/cookieprocessor created +rolebinding.rbac.authorization.k8s.io/cookierecipe-view created +#+end_example + +The registry docker.eenfach.de requires login credentials, so we need to set up a secret and link it. +First login to the registry with *podman login*, then pick the resulting auth.json: + +#+begin_example +$ podman login -u olbohlen docker.eenfach.de +Password: +Login Succeeded! +$ oc create secret generic docker-eenfach-de \ +> --from-file=.dockerconfigjson=${XDG_RUNTIME_DIR}/containers/auth.json \ +> --type kubernetes.io/dockerconfigjson +secret/docker-eenfach-de created +$ oc secrets link cookieprocessor docker-eenfach-de --for pull +#+end_example + +* Deploying the Operator + +Now that we have everything in place, we will just deploy the Operator Pod: + +#+begin_example +$ oc create -f cookie-operator-deployment.yaml +deployment.apps/recipe-processor created +$ oc get pod +NAME READY STATUS RESTARTS AGE +recipe-processor-7f9969697b-qt9lv 1/1 Running 0 17s +$ oc logs -f recipe-processor-7f9969697b-qt9lv + + +New recipe found: double-dipped-shortbread +-------------------------------------------------------------------------- + +Pre: we heat up the oven to 180 degrees Celsius + +Fetching ingredients from recipe: +---------------------------------- +Fetching 200grams of salted butter (softened) +[...] +#+end_example + +The Operator will process both sample recipes. + +* Test the Operator + +We should test if the Operator notices new recipes, so let's create a third +recipe from +https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/oaty-hazelnut-cookies.yaml + +#+begin_example +$ oc create -f oaty-hazelnut-cookies.yaml +cookierecipe.de.eenfach.olbohlen/oaty-hazelnut created +#+end_example + +After a few seconds, we should see in the Operator log: +#+begin_example +New recipe found: oaty-hazelnut +-------------------------------------------------------------------------- + +Pre: we heat up the oven to 180 degrees Celsius +[...] +#+end_example + +* Test for updated recipes + +But what if we update a resource? +Keep the *oc logs -f* on the Operator Pod open, and in another terminal let's patch a recipe. + +#+begin_example +$ oc patch cookierecipes double-dipped-shortbread --type merge \ +> -p '{"spec":{"temperature":172}}' +cookierecipe.de.eenfach.olbohlen/double-dipped-shortbread patched +#+end_example + +And again in the log you should see + +#+begin_example +New recipe found: double-dipped-shortbread +-------------------------------------------------------------------------- + +Pre: we heat up the oven to 172 degrees Celsius + +Fetching ingredients from recipe: +---------------------------------- +[...] +#+end_example -- Gitblit v1.9.3