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