Olaf Bohlen
2023-03-21 3b58aa1ef847d9594b48fe1584d47e7aeb1da64d
commit | author | age
3b58aa 1
OB 2 # Table of Contents
3
4 1.  [I need a cookie recipe database!](#org651bf34)
5 2.  [Preparing the cookierecipes CRD](#org58b8bb6)
6 3.  [Storing some sample recipes](#orgbb687b0)
7 4.  [Creating RBAC resources](#org2d91ada)
8 5.  [Storing some sample recipes (hopefully this time!!)](#org69aa53f)
9 6.  [Now can we do anything with our recipes?](#orgbd83100)
10 7.  [I'm an operator with my pocket calculator](#org3bb5d31)
11 8.  [What do we need?](#orgcebea86)
12 9.  [Let's review the Containerfile](#org6c2e7f3)
13 10. [Have a look at the Controller](#org3165f3c)
14 11. [Now let's also have a look at the deployment](#org6ec9a40)
15 12. [The ServiceAccount](#orgcba3d6d)
16 13. [Building the stuff together](#orga6fa80a)
17 14. [Deploying the Operator](#org22de8a4)
18 15. [Test the Operator](#org1cbedf9)
19 16. [Test for updated recipes](#org40457b2)
20
21
22 <a id="org651bf34"></a>
23
24 # I need a cookie recipe database!
25
26 &#x2026;and because it makes total sense, we are going to abuse the K8s API for it.
27
28 -   thankfully we can extend K8s with Custom Resource Definitions (CRDs)
29 -   but how does it work?
30 -   `CustomResourceDefinitions` are themselves a `Resource`, based on a `ResourceDefinition`
31
32     $ oc api-resources | egrep "(NAME|CustomResourceDefinition)"
33     NAME                       SHORTNAMES  APIVERSION               NAMESPACED  KIND
34     customresourcedefinitions  crd,crds    apiextensions.k8s.io/v1  false       CustomResourceDefinition
35     $ oc explain crds
36     KIND:     CustomResourceDefinition
37     VERSION:  apiextensions.k8s.io/v1
38     
39     DESCRIPTION:
40          CustomResourceDefinition represents a resource that should be exposed on
41          the API server. Its name MUST be in the format <.spec.name>.<.spec.group>.
42     [...]
43
44
45 <a id="org58b8bb6"></a>
46
47 # Preparing the cookierecipes CRD
48
49 Let's create the CRD from <https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/cookie-crd.yaml>
50
51     $ oc new-project kitchen
52     Now using project "kitchen" on server "https://api.crc.testing:6443".
53     $ oc create -f cookie-crd.yaml
54     Error from server (Forbidden): error when creating "cookie-crd.yaml": 
55     customresourcedefinitions.apiextensions.k8s.io is forbidden: User "developer"
56     cannot create resource "customresourcedefinitions" in API group "apiextensions.k8s.io"
57     at the cluster scope
58     
59     $ oc login -u kubeadmin
60     $ oc create -f cookie-crd.yaml
61     customresourcedefinition.apiextensions.k8s.io/cookierecipes.de.eenfach.olbohlen created
62     $ oc login -u developer
63
64 Now the cluster knows about the CRD and we could store recipes!
65
66
67 <a id="orgbb687b0"></a>
68
69 # Storing some sample recipes
70
71 We try to store sample cookie recipes&#x2026;but:
72
73     $ oc create -f sample-cookie.yaml
74     Error from server (Forbidden): error when creating "sample-cookie.yaml": 
75     cookierecipes.de.eenfach.olbohlen is forbidden: User "developer" cannot create 
76     resource "cookierecipes" in API group "de.eenfach.olbohlen" in the namespace "kitchen"
77     Error from server (Forbidden): error when creating "sample-cookie.yaml": 
78     cookierecipes.de.eenfach.olbohlen is forbidden: User "developer" cannot create 
79     resource "cookierecipes" in API group "de.eenfach.olbohlen" in the namespace "kitchen"
80
81 We need to set up some RBAC resources first:
82
83 -   a `ClusterRole` that allows viewing recipes
84 -   a `ClusterRole` that allows editing recipes
85 -   and a `ClusterRoleBinding` that allows that for authenticated users
86
87
88 <a id="org2d91ada"></a>
89
90 # Creating RBAC resources
91
92 Apply the RBAC definitions from: <https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/cookie-rbac.yaml>
93
94     $ oc login -u kubeadmin
95     $ oc create -f cookie-rbac.yaml
96     clusterrole.rbac.authorization.k8s.io/cookierecipe-edit created
97     clusterrole.rbac.authorization.k8s.io/cookierecipe-view created
98     clusterrolebinding.rbac.authorization.k8s.io/cookierecipe-edit created
99
100 The `ClusterRoleBinding` "cookierecipe-edit" allows `system:authenticated:oauth`
101 group members to edit `cookierecipes`.
102 `system:authenticated:oauth` contains all users that logged in via the OAuth
103 service (via an `IdentityProvider`).
104
105
106 <a id="org69aa53f"></a>
107
108 # Storing some sample recipes (hopefully this time!!)
109
110 Now we should be able to create the sample recipes:
111
112     $ oc login -u developer
113     $ oc create -f sample-cookie.yaml
114     cookierecipe.de.eenfach.olbohlen/vintage-chocolate-chip created
115     cookierecipe.de.eenfach.olbohlen/double-dipped-shortbread created
116     $ oc get cookierecipe
117     NAME                       AGE
118     double-dipped-shortbread   17s
119     vintage-chocolate-chip     17s
120
121 There is no functionality here - we just stored the recipes in the etcd via the K8s API.
122
123
124 <a id="orgbd83100"></a>
125
126 # Now can we do anything with our recipes?
127
128 Of course we can **oc get -o yaml** for example on them and filter:
129
130     $ oc get cookierecipe vintage-chocolate-chip -o yaml | yq -y .spec.ingredients[0]
131     amount: 150
132     name: salted butter
133     remarks: softened
134     unit: grams
135
136 This is handy, as we can extract exactly the data which we need at a time.
137
138 But&#x2026;it's a lot of manual work&#x2026;
139
140
141 <a id="org3bb5d31"></a>
142
143 # I'm an operator with my pocket calculator
144
145 Operators were introduced as "Kubernetes Native Applications" and that actually
146 means nothing. Operators are in the end just `Pods`.
147
148 These Pods run one or more containers, but one container should run a `Controller`
149 that can interprete your `CustomResources`.
150
151 So let's write a CookieRecipe Operator. In shell-script&#x2026; :)
152
153 Of course this Operator is not compatible with the `OperatorLifecycyleManager` (`OLM`),
154 so we have to install it manually.
155
156
157 <a id="orgcebea86"></a>
158
159 # What do we need?
160
161 We need:
162
163 -   a ContainerImage
164 -   and therefore probably a **Containerfile**
165 -   Controller code
166
167 Then we are going to build the Operator ContainerImage and push it to a Registry.
168
169
170 <a id="org6c2e7f3"></a>
171
172 # Let's review the Containerfile
173
174 The Containerfile is here:
175
176 <https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/Containerfile>
177
178 the base image is a "kshbase" image, which itself is based upon ubi9 containing also a ksh93
179 and an oc client.
180
181
182 <a id="org3165f3c"></a>
183
184 # Have a look at the Controller
185
186 The controller is written in KornShell 93 (ksh93), which is mostly bash compatible :)
187
188 The code is here:
189
190 <https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/recipe-processor.ksh>
191
192
193 <a id="org6ec9a40"></a>
194
195 # Now let's also have a look at the deployment
196
197 Note: this deployment does not use an `ImageStream`, so it would work also on native k8s
198
199 <https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/cookie-operator-deployment.yaml>
200
201 This deployment requires a `ServiceAccount` called "cookieprocessor", this `ServiceAccount` provides 
202 a Token to authenticate against the API (which we use in the controller script).
203
204
205 <a id="orgcba3d6d"></a>
206
207 # The ServiceAccount
208
209 We need a `ServiceAccount`, but that alone will not help. The `ServiceAccount` is NOT member
210 of `system:authenticated:oauth`, so it can't read `cookierecipes` based on the `ClusterRoleBinding` we created earlier.
211 For that reason we also create a `RoleBinding` (namespaced!) that allows reading recipes:
212
213 <https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/cookieprocessor-sa.yaml>
214
215
216 <a id="orga6fa80a"></a>
217
218 # Building the stuff together
219
220     $ oc create -f cookieprocessor-sa.yaml
221     serviceaccount/cookieprocessor created
222     rolebinding.rbac.authorization.k8s.io/cookierecipe-view created
223
224 The registry docker.eenfach.de requires login credentials, so we need to set up a secret and link it.
225 First login to the registry with **podman login**, then pick the resulting auth.json:
226
227     $ podman login -u olbohlen docker.eenfach.de
228     Password: 
229     Login Succeeded!
230     $ oc create secret generic docker-eenfach-de \
231     > --from-file=.dockerconfigjson=${XDG_RUNTIME_DIR}/containers/auth.json \
232     > --type kubernetes.io/dockerconfigjson
233     secret/docker-eenfach-de created
234     $ oc secrets link cookieprocessor docker-eenfach-de --for pull
235
236
237 <a id="org22de8a4"></a>
238
239 # Deploying the Operator
240
241 Now that we have everything in place, we will just deploy the Operator Pod:
242
243     $ oc create -f cookie-operator-deployment.yaml 
244     deployment.apps/recipe-processor created
245     $ oc get pod
246     NAME                                 READY   STATUS    RESTARTS   AGE
247     recipe-processor-7f9969697b-qt9lv   1/1     Running   0          17s
248     $ oc logs -f recipe-processor-7f9969697b-qt9lv
249     
250     
251     New recipe found: double-dipped-shortbread
252     --------------------------------------------------------------------------
253     
254     Pre: we heat up the oven to 180 degrees Celsius
255     
256     Fetching ingredients from recipe:
257     ----------------------------------
258     Fetching 200grams of salted butter (softened)
259     [...]
260
261 The Operator will process both sample recipes.
262
263
264 <a id="org1cbedf9"></a>
265
266 # Test the Operator
267
268 We should test if the Operator notices new recipes, so let's create a third
269 recipe from
270 <https://www.eenfach.de/gitblit/blob/~olbohlen!cookie-operator.git/master/oaty-hazelnut-cookies.yaml>
271
272     $ oc create -f oaty-hazelnut-cookies.yaml
273     cookierecipe.de.eenfach.olbohlen/oaty-hazelnut created
274
275 After a few seconds, we should see in the Operator log:
276
277     New recipe found: oaty-hazelnut
278     --------------------------------------------------------------------------
279     
280     Pre: we heat up the oven to 180 degrees Celsius
281     [...]
282
283
284 <a id="org40457b2"></a>
285
286 # Test for updated recipes
287
288 But what if we update a resource?
289 Keep the **oc logs -f** on the Operator Pod open, and in another terminal let's patch a recipe.
290
291     $ oc patch cookierecipes double-dipped-shortbread --type merge \
292     > -p '{"spec":{"temperature":172}}'
293     cookierecipe.de.eenfach.olbohlen/double-dipped-shortbread patched
294
295 And again in the log you should see
296
297     New recipe found: double-dipped-shortbread
298     --------------------------------------------------------------------------
299     
300     Pre: we heat up the oven to 172 degrees Celsius
301     
302     Fetching ingredients from recipe:
303     ----------------------------------
304     [...]
305