New file |
| | |
| | | |
| | | # Table of Contents |
| | | |
| | | 1. [I need a cookie recipe database!](#org651bf34) |
| | | 2. [Preparing the cookierecipes CRD](#org58b8bb6) |
| | | 3. [Storing some sample recipes](#orgbb687b0) |
| | | 4. [Creating RBAC resources](#org2d91ada) |
| | | 5. [Storing some sample recipes (hopefully this time!!)](#org69aa53f) |
| | | 6. [Now can we do anything with our recipes?](#orgbd83100) |
| | | 7. [I'm an operator with my pocket calculator](#org3bb5d31) |
| | | 8. [What do we need?](#orgcebea86) |
| | | 9. [Let's review the Containerfile](#org6c2e7f3) |
| | | 10. [Have a look at the Controller](#org3165f3c) |
| | | 11. [Now let's also have a look at the deployment](#org6ec9a40) |
| | | 12. [The ServiceAccount](#orgcba3d6d) |
| | | 13. [Building the stuff together](#orga6fa80a) |
| | | 14. [Deploying the Operator](#org22de8a4) |
| | | 15. [Test the Operator](#org1cbedf9) |
| | | 16. [Test for updated recipes](#org40457b2) |
| | | |
| | | |
| | | <a id="org651bf34"></a> |
| | | |
| | | # 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` |
| | | |
| | | $ 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>. |
| | | [...] |
| | | |
| | | |
| | | <a id="org58b8bb6"></a> |
| | | |
| | | # Preparing the cookierecipes CRD |
| | | |
| | | Let's create the CRD from <https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/cookie-crd.yaml> |
| | | |
| | | $ 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 |
| | | |
| | | Now the cluster knows about the CRD and we could store recipes! |
| | | |
| | | |
| | | <a id="orgbb687b0"></a> |
| | | |
| | | # Storing some sample recipes |
| | | |
| | | We try to store sample cookie recipes…but: |
| | | |
| | | $ 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" |
| | | |
| | | 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 |
| | | |
| | | |
| | | <a id="org2d91ada"></a> |
| | | |
| | | # Creating RBAC resources |
| | | |
| | | Apply the RBAC definitions from: <https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/cookie-rbac.yaml> |
| | | |
| | | $ 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 |
| | | |
| | | 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`). |
| | | |
| | | |
| | | <a id="org69aa53f"></a> |
| | | |
| | | # Storing some sample recipes (hopefully this time!!) |
| | | |
| | | Now we should be able to create the sample recipes: |
| | | |
| | | $ 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 |
| | | |
| | | There is no functionality here - we just stored the recipes in the etcd via the K8s API. |
| | | |
| | | |
| | | <a id="orgbd83100"></a> |
| | | |
| | | # Now can we do anything with our recipes? |
| | | |
| | | Of course we can **oc get -o yaml** for example on them and filter: |
| | | |
| | | $ oc get cookierecipe vintage-chocolate-chip -o yaml | yq -y .spec.ingredients[0] |
| | | amount: 150 |
| | | name: salted butter |
| | | remarks: softened |
| | | unit: grams |
| | | |
| | | This is handy, as we can extract exactly the data which we need at a time. |
| | | |
| | | But…it's a lot of manual work… |
| | | |
| | | |
| | | <a id="org3bb5d31"></a> |
| | | |
| | | # 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. |
| | | |
| | | |
| | | <a id="orgcebea86"></a> |
| | | |
| | | # 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. |
| | | |
| | | |
| | | <a id="org6c2e7f3"></a> |
| | | |
| | | # 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. |
| | | |
| | | |
| | | <a id="org3165f3c"></a> |
| | | |
| | | # 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> |
| | | |
| | | |
| | | <a id="org6ec9a40"></a> |
| | | |
| | | # 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). |
| | | |
| | | |
| | | <a id="orgcba3d6d"></a> |
| | | |
| | | # 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> |
| | | |
| | | |
| | | <a id="orga6fa80a"></a> |
| | | |
| | | # Building the stuff together |
| | | |
| | | $ oc create -f cookieprocessor-sa.yaml |
| | | serviceaccount/cookieprocessor created |
| | | rolebinding.rbac.authorization.k8s.io/cookierecipe-view created |
| | | |
| | | 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: |
| | | |
| | | $ 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 |
| | | |
| | | |
| | | <a id="org22de8a4"></a> |
| | | |
| | | # Deploying the Operator |
| | | |
| | | Now that we have everything in place, we will just deploy the Operator Pod: |
| | | |
| | | $ 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) |
| | | [...] |
| | | |
| | | The Operator will process both sample recipes. |
| | | |
| | | |
| | | <a id="org1cbedf9"></a> |
| | | |
| | | # 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> |
| | | |
| | | $ oc create -f oaty-hazelnut-cookies.yaml |
| | | cookierecipe.de.eenfach.olbohlen/oaty-hazelnut created |
| | | |
| | | After a few seconds, we should see in the Operator log: |
| | | |
| | | New recipe found: oaty-hazelnut |
| | | -------------------------------------------------------------------------- |
| | | |
| | | Pre: we heat up the oven to 180 degrees Celsius |
| | | [...] |
| | | |
| | | |
| | | <a id="org40457b2"></a> |
| | | |
| | | # 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. |
| | | |
| | | $ oc patch cookierecipes double-dipped-shortbread --type merge \ |
| | | > -p '{"spec":{"temperature":172}}' |
| | | cookierecipe.de.eenfach.olbohlen/double-dipped-shortbread patched |
| | | |
| | | And again in the log you should see |
| | | |
| | | New recipe found: double-dipped-shortbread |
| | | -------------------------------------------------------------------------- |
| | | |
| | | Pre: we heat up the oven to 172 degrees Celsius |
| | | |
| | | Fetching ingredients from recipe: |
| | | ---------------------------------- |
| | | [...] |
| | | |