Olaf Bohlen
2023-01-04 c14f64b34920f8c2720cd5b0664baa349916467c
final
1 files added
1 files modified
720 ■■■■■ changed files
.gitignore 3 ●●●●● patch | view | raw | blame | history
ocp-presentation.org 717 ●●●●● patch | view | raw | blame | history
.gitignore
New file
@@ -0,0 +1,3 @@
# Compiled
*.elc
*~
ocp-presentation.org
@@ -7,50 +7,695 @@
#+LATEX_HEADER: \usepackage{textpos}
#+LATEX_HEADER: \RequirePackage{fancyvrb}
#+LATEX_HEADER: \DefineVerbatimEnvironment{verbatim}{Verbatim}{fontsize=\tiny}
#+LATEX_HEADER: \setbeamercolor{title}{fg=green}
#+LATEX_HEADER: \setbeamercolor{structure}{fg=black}
#+LATEX_HEADER: \setbeamercolor{section in head/foot}{fg=green}
#+LATEX_HEADER: \setbeamercolor{subsection in head/foot}{fg=green}
#+LATEX_HEADER: \setbeamercolor{item}{fg=green}
# +LATEX_HEADER: \setbeamercolor{title}{fg=green}
# +LATEX_HEADER: \setbeamercolor{structure}{fg=black}
# +LATEX_HEADER: \setbeamercolor{section in head/foot}{fg=green}
# +LATEX_HEADER: \setbeamercolor{subsection in head/foot}{fg=green}
# +LATEX_HEADER: \setbeamercolor{item}{fg=green}
#+LATEX_HEADER: \setbeamerfont{frametitle}{family=\ttfamily}
# logo
#+LATEX_HEADER: \addtobeamertemplate{frametitle}{}{ \begin{textblock*}{100mm}(0.85\textwidth,-0.8cm) \includegraphics[height=0.7cm,width=2cm]{niit-logo.png} \end{textblock*}}
#+OPTIONS: toc:nil title:nil ^:nil
# +LATEX_HEADER: \addtobeamertemplate{frametitle}{}{ \begin{textblock*}{100mm}(0.85\textwidth,-0.8cm) \includegraphics[height=0.7cm,width=2cm]{niit-logo.png} \end{textblock*}}
#+OPTIONS: toc:nil ^:nil
#+LANGUAGE: en
#+TITLE: Weird OpenShift Behaviours
etcd, no foreign keys
-> permissions on non existing users
k8s does not realize immediate node failures
k8s what happens if a container dies?
where run probes (startup, readiness, liveness)
API weirdness
 - how to program the API, discover the API
your frieds:
 - jq
 - yq
 - xq :)
 - jsonpath
- how to store cookie receipts in etcd via CRDs
- I'm an operator with my pocket calculator: build your own operator
- how to create ocp resources with etcd native
- how does s2i work, where runs git clone, where the build, where are the artifacts saved...
#+TITLE: Diving into OpenShift
* Helpful tools on the way
- *jq(1)* is a JSON query tool for the command line, a must have
- *yq* is a python script that does YAML queries
- yes, there's also *xq* for XML but that's for the JAVA folks ;)
- *jo* to produce JSON output on the shell
How to get?
- jq: it's very likely shipped as a package with your distribution
- yq: install via =pip install --user yq=
- xq: install via =pip install --user xq=
- jo: get the sources from https://github.com/jpmens/jo
* jq
The most basic usage is filter down a JSON for a specific attributes value.
#+begin_src js
{
    "kind": "ServiceAccount",
    "apiVersion": "v1",
    "metadata": {
        "name": "foobar",
        "creationTimestamp": null
    }
}
#+end_src
To get the name of the ServiceAccount you would run
=jq .metadata.name=
If you have a list, and want to see all items, use [], for example:
=jq .items[]=
If you want to get a specific list entry by index, run
=jq .items[5]= to get the sixth entry
* yq
*yq* works very similar in the basic usage as *jq*, however per default it outputs - confusing enough - JSON (because it's a wrapper for jq).
If you use the =-y= option, it will output YAML.
I find this very handy for reading data from =ConfigMaps=:
#+begin_example
$ oc get cm foobar -o yaml | yq -y .data
key: value
text: hello world
#+end_example
* jo
*jo* is a very special tool that can be very interesting if you have shell commands producing output that
should be converted into JSON. More examples can be found here: https://jpmens.net/2016/03/05/a-shell-command-to-create-json-jo/
#+begin_example
$ ps -fo uid,pid,ppid,stime,tty,time,args | nawk '$NF~/ksh/ {
> printf("uid=%s pid=%s ppid=%s stime=%s tty=%s cputime=%s comm=%s\n"
> , $1, $2, $3, $4, $5, $6, $7)}' | while read line; do jo ${line}
> done | jo -pa
[
   {
      "uid": 4100,
      "pid": 4880,
      "ppid": 4001,
      "stime": "Jan_01",
      "tty": "pts/2",
      "cputime": "00:00",
      "comm": "-ksh"
   },
   {
      "uid": 4100,
      "pid": 20599,
      "ppid": 4880,
      "stime": "23:46:19",
      "tty": "pts/2",
      "cputime": "00:00",
      "comm": "-ksh"
   }
]
#+end_example
* oc get -o jsonpath
If you use *oc* or *kubectl* to retreive JSON data, you can also filter down with JSONPATH
instead of using *jq*. The basic syntax is simple, imagine you want the hostname of a route,
which is in the =host= key under the =spec=:
#+begin_example
$ oc get -o jsonpath='{"http://"}{.spec.host}{"\n"}' route hi
http://hi-olbohlen-demo.apps-crc.testing
#+end_example
And because we are clever, we will create a shell alias from it:
#+begin_example
$ alias gr='oc get -o jsonpath="{\"http://\"}{.spec.host}{\"\n\"}" route '
[crc@lol cookie-operator]$ gr hi
http://hi-olbohlen-demo.apps-crc.testing
$ curl $(gr hi)
<html>
 <head>
  <title>hello php</title>
[...]
#+end_example
* Declarative versus Imperative
It's important to understand that Kubernetes works declarative.
What does that mean?
Traditionally we work imperative with computers, we tell them what and how to execute.
The problem here is, that the result is undefined, there are so many side-cases to catch.
If we work declarative, we declare our will to the computer and rely on algorithms that
shall fulfill our will. Of course this still can fail, but ideally the code results in "failed" or "completed".
* Controller Scheme
#+begin_src ditaa :file ocp-workshop1.png :cmdline -E -s 0.8
+---------------------------+       +-----------------------+       +-------------------------+
|{s}                        |       |    Kube API Server    |       | Kube Controller Manager |
| etcd                      |       |                       |       |                         |
|                           | <---> |etcd connector         | <---> | watches API for updates |
|/kubernetes.io/pods/ns/pod |       |                       |       | if update: run code     |
|                           |       |       REST Connector  |       |                         |
+---------------------------+       +-----------------------+       +-------------------------+
                                               ^
                                               | HTTP(S)/REST
                                               |  (JSON)
                                               v
                                    +-----------------------+
                                    |       REST Client     |
                                    |                       |
                                    | oc/kubectl            |
                                    |  (converts JSON to    |
                                    |   output format)      |
                                    |                       |
                                    +-----------------------+
#+end_src
* Syntactic sugar
OpenShift's *oc* client, but also *kubectl* have various sub-commands to make the start with Kubernetes easier.
Especially in the beginning the idea to declare everything in JSON or YAML doesn't sound convincing.
But if you look under the hood, commands like *oc new-app* or *kubectl create* will send declarations.
You can validate that by doing a dry-run:
#+begin_example
$ oc create --dry-run=client -o yaml sa foobar
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: null
  name: foobar
#+end_example
* Dry-Runs
There are two ways of dry-runs: =client= and =server=
If we do a client dry-run, nothing gets sent to the kubernetes API, so we basically only test
if we can convert the local YAML into JSON. Illegal values, missing required keys or immutable values cannot be detected.
if you however use a =server= dry-run, your declaration gets sent to the API, but it will not get persisted into the ETCD.
#+begin_example
$ oc create --dry-run=server -o yaml sa foobar
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2023-01-02T20:33:23Z"
  name: foobar
  namespace: kitchen
  uid: 3bfc1a4c-9a87-4bfa-9dd7-c0941a98ec1f
$ oc get sa foobar
Error from server (NotFound): serviceaccounts "foobar" not found
#+end_example
As you can see, you get an =uid= and a =creationTimestamp= back. However, it will not actually be created.
* Client Side debugging
Turn on the debugging in *oc* / *kubectl* with =--loglevel=x=, the higher the value, the more verbose.
If you want to see a lot of details, =--loglevel=9= is very helpful.
For example if you want to validate how server side dry-runs work, run:
#+begin_example
$ oc --loglevel=9 create --dry-run=server -o yaml sa foobar
I0102 21:39:10.590303 1993958 loader.go:372] Config loaded from file:  /home/crc/.kube/config
[...]
I0102 21:39:10.818643 1993958 request.go:1073] Request Body: {"kind":"ServiceAccount",
"apiVersion":"v1","metadata":{"name":"foobar","creationTimestamp":null}}
I0102 21:39:10.818703 1993958 round_trippers.go:466] curl -v -XPOST
-H "Content-Type: application/json" -H "Accept: application/json, */*"
-H "User-Agent: oc/4.11.0 (linux/amd64) kubernetes/1928ac4"
-H "Authorization: Bearer <masked>" 'https://api.crc.testing:6443/api/v1/namespaces/kitchen/
serviceaccounts?dryRun=All&fieldManager=kubectl-create&fieldValidation=Ignore'
I0102 21:39:10.822375 1993958 round_trippers.go:553] POST https://api.crc.testing:6443/api/
v1/namespaces/kitchen/serviceaccounts?dryRun=All&fieldManager=kubectl-create&
fieldValidation=Ignore 201 Created in 3 milliseconds
[...]
#+end_example
Very handy is that with =--loglevel=9=, *oc* will also print a *curl* command line that could be used to
simulate that call.
* Use curl to modify K8s resources
Using the log output from the previous slide, we can now try to create a =ServiceAccount= via *curl*.
We need of course the JSON declaration...thankfull we can generate that with a client dry-run:
#+begin_example
$ oc create --dry-run=client -o json sa foobar >/tmp/sa-foobar.json
$ curl -k -s -XPOST -d "@/tmp/sa-foobar.json" -H "Content-Type: application/json" \
> -H "Accept: application/json, */*" -H "User-Agent: oc/4.11.0 (linux/amd64) kubernetes/1928ac4" \
> -H "Authorization: Bearer $(oc whoami -t)" \
> 'https://api.crc.testing:6443/api/v1/namespaces/api-access/serviceaccounts?fieldManager=\
> kubectl-create&fieldValidation=Ignore'
$ oc get sa foobar
NAME     SECRETS   AGE
foobar   1         11s
#+end_example
And how to delete the =ServiceAccount=? Simple:
#+begin_example
$ curl -k -XDELETE -H "Content-Type: application/json" -H "Accept: application/json, */*" \
> -H "User-Agent: oc/4.11.0 (linux/amd64) kubernetes/1928ac4" \
> -H "Authorization: Bearer $(oc whoami -t)" \
> 'https://api.crc.testing:6443/api/v1/namespaces/api-access/serviceaccounts/foobar'
{"kind":"ServiceAccount","apiVersion":"v1","metadata":{"name":"foobar",
"namespace":"api-access","uid":"94a1c0b4-2177-41b7-bbe2-0ff5e3932785","resourceVersion":"1258915",
"creationTimestamp":"2023-01-02T20:57:35Z"},"secrets":[{"name":"foobar-dockercfg-brshk"}],
"imagePullSecrets":[{"name":"foobar-dockercfg-brshk"}]}
$ oc get sa foobar
Error from server (NotFound): serviceaccounts "foobar" not found
#+end_example
* Weird effects with the K8s API
- the API will ignore unkown keys, so you could put a "=Olaf: was here=" key-value pair anywhere you want, the API will happily ignore it!
- that means, if you *oc edit* a resource and damage the name of a key or dictionary which is not mandatory, it will throw away those definitions (=OAuth/cluster.spec.IdentityProviders=)
- You can create dependent resources on non-existing objects, i.e. you can give non-existing users privileges: ETCD does not support referential integrity, so no forein key constraints
- your declaration will be processed after your client terminated the REST connection, so always check the results!
* Short Refresh on RBAC
Kubernetes supports Role Based Access Controls (RBAC)
We have five different resources here:
- =Users=
- =Groups=
- =ServiceAccounts=
- =Roles= / =ClusterRoles=
- =RoleBindings= / =ClusterRoleBindings=
* Users and Groups
=Users= are a resource that have an access token, to talk to the external API.
=Groups= bundle =Users=, as you would expect.
There are also system Users (they contain colons..) which are *hard coded* and there is no way to list them via the API.
The same is true for system Groups!
System Users cannot authenticate via the external API.
* ServiceAccounts
If =Pods= would run under the User who created the =Pod=, others probably couldn't operate on them.
For that reason we need =ServiceAccounts=, they bundle permissions (=Roles=, =SCCs=), specific secrets (for pulling Images, etc)
and they have an API Access Token that we can use within the =Pod= to talk to the Kubernetes API.
* Roles / ClusterRoles
Where is the difference between =Roles= and =ClusterRoles=?
It just says if the resource itself is scoped in a =Namespace= or if it's available for the whole Cluster.
If you want to define a Role for all Namespaces in your Cluster, you create a =ClusterRole=.
If you need a local Role just for a specific Namespace, you create a =Role=.
* How does a ClusterRole look like?
A =Role= contains a list of =Rules=, specifying what is allowed if you own that =Role=.
The most simple one is the =cluster-admin= =ClusterRole=.
#+begin_example
$ oc get clusterrole cluster-admin -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  creationTimestamp: "2022-12-06T10:10:16Z"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: cluster-admin
  resourceVersion: "75"
  uid: d6fd1146-5bc7-4815-8170-3905ae1e2856
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs:
  - '*'
- nonResourceURLs:
  - '*'
  verbs:
  - '*'
#+end_example
So you can issue any command verb on any resource in any apiGroup. (iddqd)
* And what is a RoleBinding?
#+begin_src ditaa :file rbac.png :cmdline -E -s 0.8
+------+                                                  +------+
| User |                                                  | Role |
|      |--------+        +--------------------+     +-----|      |
+------+        +------->| RoleBinding        | <---+     +------+
                         |                    |
+----------------+  +--->|                    |
| ServiceAccount |--+    +--------------------+
|                |             ^
+----------------+             |
                               |
+-------+                      |
| Group |----------------------+
|       |
+-------+
#+end_src
#+begin_example
$ oc get rolebinding admin -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  creationTimestamp: "2023-01-02T20:55:05Z"
  name: admin
  namespace: api-access
  resourceVersion: "1258364"
  uid: a59f1f83-86b1-47eb-97ea-985f13d03102
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: kubeadmin
#+end_example
* And a ClusterRoleBinding?
Well, a =ClusterRoleBinding= binds on a Cluster-scope, so the permissions take effect cluster-wide.
Note here that there is no =metadata.namespace:= attribute
#+begin_example
$ oc get clusterrolebinding cluster-admin -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  creationTimestamp: "2022-12-06T10:10:17Z"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: cluster-admin
  resourceVersion: "159"
  uid: b971b2ac-e3a8-4db9-ba91-f017ec0a7a5f
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: system:masters
#+end_example
* I need a cookie receipt 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 cookiereceipts 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/cookiereceipts.de.eenfach.olbohlen created
$ oc login -u developer
#+end_example
Now the cluster knows about the CRD and we could store receipts!
* Storing some sample receipts
We try to store sample cookie receipts...but:
#+begin_example
$ oc create -f sample-cookie.yaml
Error from server (Forbidden): error when creating "sample-cookie.yaml":
cookiereceipts.de.eenfach.olbohlen is forbidden: User "developer" cannot create
resource "cookiereceipts" in API group "de.eenfach.olbohlen" in the namespace "kitchen"
Error from server (Forbidden): error when creating "sample-cookie.yaml":
cookiereceipts.de.eenfach.olbohlen is forbidden: User "developer" cannot create
resource "cookiereceipts" 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 receipts
- a =ClusterRole= that allows editing receipts
- 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/cookiereceipt-edit created
clusterrole.rbac.authorization.k8s.io/cookiereceipt-view created
clusterrolebinding.rbac.authorization.k8s.io/cookiereceipt-edit created
#+end_example
The =ClusterRoleBinding= "cookiereceipt-edit" allows =system:authenticated:oauth=
group members to edit =cookiereceipts=.
=system:authenticated:oauth= contains all users that logged in via the OAuth
service (via an =IdentityProvider=).
* Storing some sample receipts (hopefully this time!!)
Now we should be able to create the sample receipts:
#+begin_example
$ oc login -u developer
$ oc create -f sample-cookie.yaml
cookiereceipt.de.eenfach.olbohlen/vintage-chocolate-chip created
cookiereceipt.de.eenfach.olbohlen/double-dipped-shortbread created
$ oc get cookiereceipt
NAME                       AGE
double-dipped-shortbread   17s
vintage-chocolate-chip     17s
#+end_example
There is no functionality here - we just stored the receipts in the etcd via the K8s API.
* Can we validate that in the ETCD?
Yes, we can use *etcdctl* to look into the db
#+begin_example
$ oc login -u kubeadmin
$ oc rsh -n openshift-etcd -c etcdctl \
> etcd-crc-pbwlw-master-0 etcdctl get / --prefix --keys-only | grep -i cookie
/kubernetes.io/apiextensions.k8s.io/customresourcedefinitions/cookiereceipts.de.eenfach.olbohlen
/kubernetes.io/apiserver.openshift.io/apirequestcounts/cookiereceipts.v1.de.eenfach.olbohlen
/kubernetes.io/clusterrolebindings/cookiereceipt-edit
/kubernetes.io/clusterroles/cookiereceipt-edit
/kubernetes.io/clusterroles/cookiereceipt-view
/kubernetes.io/de.eenfach.olbohlen/cookiereceipts/kitchen/double-dipped-shortbread
/kubernetes.io/de.eenfach.olbohlen/cookiereceipts/kitchen/vintage-chocolate-chip
/kubernetes.io/secrets/openshift-machine-config-operator/cookie-secret
#+end_example
The =etcd pod= in the =openshift-etcd= project has a sidecar container called =etcdctl=
which contains the *etcdctl* utility and the correct environment.
The data hierarchy starts with =/= and with =--keys-only= we don't get the values.
* Let's look into a receipt
Check if we can *etcdctl get* on a known receipt:
#+begin_example
$ oc rsh -n openshift-etcd -c etcdctl etcd-crc-pbwlw-master-0 etcdctl \
> get /kubernetes.io/de.eenfach.olbohlen/cookiereceipts/kitchen/vintage-chocolate-chip
/kubernetes.io/de.eenfach.olbohlen/cookiereceipts/kitchen/vintage-chocolate-chip
{"apiVersion":"de.eenfach.olbohlen/v1","kind":"CookieReceipt","metadata":{
"creationTimestamp":"2023-01-02T15:18:49Z","generation":1,"managedFields":
[{"apiVersion":"de.eenfach.olbohlen/v1","fieldsType":"FieldsV1",
[...]
#+end_example
With *etcdctl put*, we could also update the entry and *etcdctl watch* allows us to
see updates to reosurces.
#+begin_example
$ oc rsh -n openshift-etcd -c etcdctl etcd-crc-pbwlw-master-0 etcdctl watch \
> --prefix /kubernetes.io/de.eenfach.olbohlen/cookiereceipts
PUT
/kubernetes.io/de.eenfach.olbohlen/cookiereceipts/kitchen/double-dipped-shortbread
{"apiVersion":"de.eenfach.olbohlen/v1","kind":"CookieReceipt","metadata":{"annotations":
{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":
\"de.eenfach.olbohlen/v1\",\"kind\":\"CookieReceipt\",\"metadata\":{
[...]
#+end_example
(run an *oc* apply/replace/create/delete in another terminal)
* Now can we do anything with our receipts?
Of course we can *oc get -o yaml* for example on them and filter:
#+begin_example
$ oc get cookiereceipt 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 CookieReceipt 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/receipt-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 =cookiereceipts= based on the =ClusterRoleBinding= we created earlier.
For that reason we also create a =RoleBinding= (namespaced!) that allows reading receipts:
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/cookiereceipt-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/receipt-processor created
$ oc get pod
NAME                                 READY   STATUS    RESTARTS   AGE
receipt-processor-7f9969697b-qt9lv   1/1     Running   0          17s
$ oc logs -f receipt-processor-7f9969697b-qt9lv
New receipt found: double-dipped-shortbread
--------------------------------------------------------------------------
Pre: we heat up the oven to 180 degrees Celsius
Fetching ingredients from receipt:
----------------------------------
Fetching 200grams of salted butter (softened)
[...]
#+end_example
The Operator will process both sample receipts.
* Test the Operator
We should test if the Operator notices new receipts, so let's create a third
receipt 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
cookiereceipt.de.eenfach.olbohlen/oaty-hazelnut created
#+end_example
After a few seconds, we should see in the Operator log:
#+begin_example
New receipt found: oaty-hazelnut
--------------------------------------------------------------------------
Pre: we heat up the oven to 180 degrees Celsius
[...]
#+end_example
* Test for updated receipts
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 receipt.
#+begin_example
$ oc patch cookiereceipts double-dipped-shortbread --type merge \
> -p '{"spec":{"temperature":172}}'
cookiereceipt.de.eenfach.olbohlen/double-dipped-shortbread patched
#+end_example
And again in the log you should see
#+begin_example
New receipt found: double-dipped-shortbread
--------------------------------------------------------------------------
Pre: we heat up the oven to 172 degrees Celsius
Fetching ingredients from receipt:
----------------------------------
[...]
#+end_example
* Thank You