|
| 1 | +// Module included in the following assemblies: |
| 2 | +// |
| 3 | +// * operators/osdk-helm.adoc |
| 4 | + |
| 5 | +[id='building-operator-with-helm-using-osdk_{context}'] |
| 6 | += Building an Operator with Helm charts using the Operator SDK |
| 7 | + |
| 8 | +This procedure walks through an example of building a simple Nginx Operator |
| 9 | +powered by a Helm chart using tools and libraries provided by the Operator SDK. |
| 10 | + |
| 11 | +[TIP] |
| 12 | +==== |
| 13 | +It is best practice to build a new Operator for each chart. This can allow for |
| 14 | +more native-behaving Kubernetes APIs (e.g., `oc get Nginx`) and flexibility if |
| 15 | +you ever want to write a fully-fledged Operator in Go, migrating away from a |
| 16 | +Helm-based Operator. |
| 17 | +==== |
| 18 | + |
| 19 | +.Prerequisites |
| 20 | + |
| 21 | +- Operator SDK CLI installed on the development workstation |
| 22 | +- Access to a Kubernetes-based cluster v1.11.3+ (for example {product-title} 4.0) |
| 23 | +using an account with `cluster-admin` permissions |
| 24 | +- link:https://kubernetes.io/docs/tasks/tools/install-kubectl/[`kubectl`] v1.11.3+ |
| 25 | +(can alternatively use `oc`) |
| 26 | + |
| 27 | +.Procedure |
| 28 | + |
| 29 | +. *Create a new project.* |
| 30 | ++ |
| 31 | +To create a new Helm-based, namespace-scoped `nginx-operator` project, use the |
| 32 | +`operator-sdk new` command: |
| 33 | ++ |
| 34 | +---- |
| 35 | +$ operator-sdk new nginx-operator \ |
| 36 | + --api-version=example.com/v1alpha1 --kind=Nginx --type=helm |
| 37 | +$ cd nginx-operator |
| 38 | +---- |
| 39 | ++ |
| 40 | +[TIP] |
| 41 | +==== |
| 42 | +See |
| 43 | +xref:operators-appendices.adoc#operator-project-scaffolding-layout_operator-appendices[Appendices] |
| 44 | +to learn about the project directory structure created by the previous commands. |
| 45 | +==== |
| 46 | ++ |
| 47 | +This creates the `nginx-operator` project specifically for watching the Nginx |
| 48 | +resource with APIVersion `example.com/v1apha1` and Kind `Nginx`. |
| 49 | ++ |
| 50 | +.Operator scope |
| 51 | ++ |
| 52 | +A namespace-scoped Operator (the default) watches and manages resources in a |
| 53 | +single namespace, whereas a cluster-scoped operator watches and manages |
| 54 | +resources cluster-wide. Namespace-scoped operators are preferred because of |
| 55 | +their flexibility. They enable decoupled upgrades, namespace isolation for |
| 56 | +failures and monitoring, and differing API definitions. |
| 57 | ++ |
| 58 | +However, there are use cases where a cluster-scoped operator may make sense. For |
| 59 | +example, the `cert-manager` operator is often deployed with cluster-scoped |
| 60 | +permissions and watches so that it can manage issuing certificates for an entire |
| 61 | +cluster. |
| 62 | ++ |
| 63 | +If you would like to create your `nginx-operator` project to be cluster-scoped, |
| 64 | +use the following `operator-sdk new` command instead: |
| 65 | ++ |
| 66 | +---- |
| 67 | +$ operator-sdk new nginx-operator \ |
| 68 | + --cluster-scoped --api-version=example.com/v1alpha1 \ |
| 69 | + --kind=Nginx --type=helm |
| 70 | +---- |
| 71 | ++ |
| 72 | +Using the `--cluster-scoped` flag will scaffold the new Operator with the |
| 73 | +following modifications: |
| 74 | ++ |
| 75 | +-- |
| 76 | +* `deploy/operator.yaml`: Set `WATCH_NAMESPACE=""` instead of setting it to the |
| 77 | +Pod's namespace. |
| 78 | +* `deploy/role.yaml`: Use `ClusterRole` instead of `Role`. |
| 79 | +* `deploy/role_binding.yaml`: |
| 80 | +** Use `ClusterRoleBinding` instead of `RoleBinding`. |
| 81 | +** Set the subject namespace to `REPLACE_NAMESPACE`. This must be changed to the |
| 82 | +namespace in which the Operator is deployed. |
| 83 | +-- |
| 84 | + |
| 85 | +. *Customize the Operator logic.* |
| 86 | ++ |
| 87 | +For this example, the `nginx-operator` executes the following reconciliation |
| 88 | +logic for each Nginx Custom Resource (CR): |
| 89 | ++ |
| 90 | +-- |
| 91 | +* Create a Nginx Deployment if it does not exist. |
| 92 | +* Create a Nginx Service if it does not exist. |
| 93 | +* Create a Nginx Ingress if it is enabled and does not exist. |
| 94 | +* Ensure that the Deployment, Service, and optional Ingress match the desired |
| 95 | +configuration (e.g., replica count, image, service type) as specified by the |
| 96 | +Nginx CR. |
| 97 | +-- |
| 98 | ++ |
| 99 | +By default, the `nginx-operator` watches `Nginx` resource events as shown in the |
| 100 | +`watches.yaml` file and executes Helm releases using the specified chart: |
| 101 | ++ |
| 102 | +---- |
| 103 | +--- |
| 104 | +- version: v1alpha1 |
| 105 | + group: example.com |
| 106 | + kind: Nginx |
| 107 | + chart: /opt/helm/helm-charts/nginx |
| 108 | +---- |
| 109 | + |
| 110 | +.. *Review the Nginx Helm chart.* |
| 111 | ++ |
| 112 | +When a Helm Operator project is created, the Operator SDK creates an example Helm chart that contains a set of templates for a simple Nginx release. |
| 113 | ++ |
| 114 | +For this example, templates are available for Deployment, Service, and Ingress |
| 115 | +resources, along with a `NOTES.txt` template, which Helm chart developers use to |
| 116 | +convey helpful information about a release. |
| 117 | ++ |
| 118 | +If you are not already familiar with Helm Charts, take a moment to review the |
| 119 | +link:https://docs.helm.sh/developing_charts/[Helm Chart developer documentation]. |
| 120 | + |
| 121 | +.. *Understand the Nginx CR spec.* |
| 122 | ++ |
| 123 | +Helm uses a concept called |
| 124 | +link:https://docs.helm.sh/using_helm/#customizing-the-chart-before-installing[values] |
| 125 | +to provide customizations to a Helm chart's defaults, which are defined in the |
| 126 | +Helm chart's `values.yaml` file. |
| 127 | ++ |
| 128 | +Override these defaults by setting the desired values in the CR spec. You can |
| 129 | +use the number of replicas as an example: |
| 130 | + |
| 131 | +... First, inspect the `helm-charts/nginx/values.yaml` file to find that the chart |
| 132 | +has a value called `replicaCount` and it is set to `1` by default. To have 2 |
| 133 | +Nginx instances in your deployment, your CR spec must contain `replicaCount: 2`. |
| 134 | ++ |
| 135 | +Update the `deploy/crds/example_v1alpha1_nginx_cr.yaml` file to look like the |
| 136 | +following: |
| 137 | ++ |
| 138 | +---- |
| 139 | +apiVersion: example.com/v1alpha1 |
| 140 | +kind: Nginx |
| 141 | +metadata: |
| 142 | + name: example-nginx |
| 143 | +spec: |
| 144 | + replicaCount: 2 |
| 145 | +---- |
| 146 | + |
| 147 | +... Similarly, the default service port is set to `80`. To instead use `8080`, |
| 148 | +update the `deploy/crds/example_v1alpha1_nginx_cr.yaml` file again by adding the |
| 149 | +service port override: |
| 150 | ++ |
| 151 | +---- |
| 152 | +apiVersion: example.com/v1alpha1 |
| 153 | +kind: Nginx |
| 154 | +metadata: |
| 155 | + name: example-nginx |
| 156 | +spec: |
| 157 | + replicaCount: 2 |
| 158 | + service: |
| 159 | + port: 8080 |
| 160 | +---- |
| 161 | ++ |
| 162 | +The Helm Operator applies the entire spec as if it was the contents of a values |
| 163 | +file, just like the `helm install -f ./overrides.yaml` command works. |
| 164 | + |
| 165 | +. *Deploy the CRD.* |
| 166 | ++ |
| 167 | +Before running the Operator, Kubernetes needs to know about the new custom |
| 168 | +resource definition (CRD) the operator will be watching. Deploy the following CRD: |
| 169 | ++ |
| 170 | +---- |
| 171 | +$ kubectl create -f deploy/crds/example_v1alpha1_nginx_crd.yaml |
| 172 | +---- |
| 173 | + |
| 174 | +. *Build and run the Operator.* |
| 175 | ++ |
| 176 | +There are two ways to build and run the Operator: |
| 177 | ++ |
| 178 | +-- |
| 179 | +* As a Pod inside a Kubernetes cluster. |
| 180 | +* As a Go program outside the cluster using the `operator-sdk up` command. |
| 181 | +-- |
| 182 | ++ |
| 183 | +Choose one of the following methods. |
| 184 | + |
| 185 | +.. _Option 1:_ Run as a Pod inside a Kubernetes cluster. This is the preferred |
| 186 | +method for production use. |
| 187 | ++ |
| 188 | +Build the `nginx-operator` image and push it to a registry: |
| 189 | ++ |
| 190 | +---- |
| 191 | +$ operator-sdk build quay.io/example/nginx-operator:v0.0.1 |
| 192 | +$ docker push quay.io/example/nginx-operator:v0.0.1 |
| 193 | +---- |
| 194 | ++ |
| 195 | +Kubernetes deployment manifests are generated in the `deploy/operator.yaml` |
| 196 | +file. The deployment image in this file needs to be modified from the |
| 197 | +placeholder `REPLACE_IMAGE` to the previous built image. To do this, run: |
| 198 | ++ |
| 199 | +---- |
| 200 | +$ sed -i 's|REPLACE_IMAGE|quay.io/example/nginx-operator:v0.0.1|g' deploy/operator.yaml |
| 201 | +---- |
| 202 | ++ |
| 203 | +If you created your Operator using the `--cluster-scoped=true` flag, update the |
| 204 | +service account namespace in the generated `ClusterRoleBinding` to match where |
| 205 | +you are deploying your Operator: |
| 206 | ++ |
| 207 | +---- |
| 208 | +$ export OPERATOR_NAMESPACE=$(kubectl config view --minify -o jsonpath='{.contexts[0].context.namespace}') |
| 209 | +$ sed -i "s|REPLACE_NAMESPACE|$OPERATOR_NAMESPACE|g" deploy/role_binding.yaml |
| 210 | +---- |
| 211 | ++ |
| 212 | +[NOTE] |
| 213 | +==== |
| 214 | +If you are performing these steps on OSX, use the following commands instead: |
| 215 | +
|
| 216 | +---- |
| 217 | +$ sed -i "" 's|REPLACE_IMAGE|quay.io/example/nginx-operator:v0.0.1|g' deploy/operator.yaml |
| 218 | +$ sed -i "" "s|REPLACE_NAMESPACE|$OPERATOR_NAMESPACE|g" deploy/role_binding.yaml |
| 219 | +---- |
| 220 | +==== |
| 221 | ++ |
| 222 | +Deploy the `nginx-operator`: |
| 223 | ++ |
| 224 | +---- |
| 225 | +$ kubectl create -f deploy/service_account.yaml |
| 226 | +$ kubectl create -f deploy/role.yaml |
| 227 | +$ kubectl create -f deploy/role_binding.yaml |
| 228 | +$ kubectl create -f deploy/operator.yaml |
| 229 | +---- |
| 230 | ++ |
| 231 | +Verify that the `nginx-operator` is up and running: |
| 232 | ++ |
| 233 | +---- |
| 234 | +$ kubectl get deployment |
| 235 | +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE |
| 236 | +nginx-operator 1 1 1 1 1m |
| 237 | +---- |
| 238 | + |
| 239 | +.. _Option 2:_ Run outside the cluster. This method is preferred during the |
| 240 | +development cycle to speed up deployment and testing. |
| 241 | ++ |
| 242 | +It is important that the chart path referenced in the `watches.yaml` file exists |
| 243 | +on your machine. By default, the `watches.yaml` file is scaffolded to work with |
| 244 | +an Operator image built with the `operator-sdk build` command. When developing |
| 245 | +and testing your operator with the `operator-sdk up local` command, the SDK |
| 246 | +looks in your local file system for this path. |
| 247 | ++ |
| 248 | +It is recommend to create a symlink at this location to point to your Helm |
| 249 | +chart's path: |
| 250 | ++ |
| 251 | +---- |
| 252 | +$ sudo mkdir -p /opt/helm/helm-charts |
| 253 | +$ sudo ln -s $PWD/helm-charts/nginx /opt/helm/helm-charts/nginx |
| 254 | +---- |
| 255 | ++ |
| 256 | +To run the Operator locally with the default Kubernetes configuration file |
| 257 | +present at `$HOME/.kube/config`: |
| 258 | ++ |
| 259 | +---- |
| 260 | +$ operator-sdk up local |
| 261 | +INFO[0000] Go Version: go1.10.3 |
| 262 | +INFO[0000] Go OS/Arch: linux/amd64 |
| 263 | +INFO[0000] operator-sdk Version: v0.3.0+git |
| 264 | +---- |
| 265 | ++ |
| 266 | +To run the Operator locally with a provided Kubernetes configuration file: |
| 267 | ++ |
| 268 | +---- |
| 269 | +$ operator-sdk up local --kubeconfig=<path_to_config> |
| 270 | +INFO[0000] Go Version: go1.10.3 |
| 271 | +INFO[0000] Go OS/Arch: linux/amd64 |
| 272 | +INFO[0000] operator-sdk Version: v0.3.0+git |
| 273 | +---- |
| 274 | + |
| 275 | +. *Deploy the Nginx custom resource.* |
| 276 | ++ |
| 277 | +Apply the Nginx CR that you modified earlier: |
| 278 | ++ |
| 279 | +---- |
| 280 | +$ kubectl apply -f deploy/crds/example_v1alpha1_nginx_cr.yaml |
| 281 | +---- |
| 282 | ++ |
| 283 | +Ensure that the `nginx-operator` creates the Deployment for the CR: |
| 284 | ++ |
| 285 | +---- |
| 286 | +$ kubectl get deployment |
| 287 | +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE |
| 288 | +example-nginx-b9phnoz9spckcrua7ihrbkrt1 2 2 2 2 1m |
| 289 | +---- |
| 290 | ++ |
| 291 | +Check the Pods to confirm two replicas were created: |
| 292 | ++ |
| 293 | +---- |
| 294 | +$ kubectl get pods |
| 295 | +NAME READY STATUS RESTARTS AGE |
| 296 | +example-nginx-b9phnoz9spckcrua7ihrbkrt1-f8f9c875d-fjcr9 1/1 Running 0 1m |
| 297 | +example-nginx-b9phnoz9spckcrua7ihrbkrt1-f8f9c875d-ljbzl 1/1 Running 0 1m |
| 298 | +---- |
| 299 | ++ |
| 300 | +Check that the Service port is set to `8080`: |
| 301 | ++ |
| 302 | +---- |
| 303 | +$ kubectl get service |
| 304 | +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE |
| 305 | +example-nginx-b9phnoz9spckcrua7ihrbkrt1 ClusterIP 10.96.26.3 <none> 8080/TCP 1m |
| 306 | +---- |
| 307 | + |
| 308 | +. *Update the `replicaCount` and remove the port.* |
| 309 | ++ |
| 310 | +Change the `spec.replicaCount` field from `2` to `3`, remove the `spec.service` |
| 311 | +field, and apply the change: |
| 312 | ++ |
| 313 | +---- |
| 314 | +$ cat deploy/crds/example_v1alpha1_nginx_cr.yaml |
| 315 | +apiVersion: "example.com/v1alpha1" |
| 316 | +kind: "Nginx" |
| 317 | +metadata: |
| 318 | + name: "example-nginx" |
| 319 | +spec: |
| 320 | + replicaCount: 3 |
| 321 | +
|
| 322 | +$ kubectl apply -f deploy/crds/example_v1alpha1_nginx_cr.yaml |
| 323 | +---- |
| 324 | ++ |
| 325 | +Confirm that the Operator changes the Deployment size: |
| 326 | ++ |
| 327 | +---- |
| 328 | +$ kubectl get deployment |
| 329 | +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE |
| 330 | +example-nginx-b9phnoz9spckcrua7ihrbkrt1 3 3 3 3 1m |
| 331 | +---- |
| 332 | ++ |
| 333 | +Check that the Service port is set to the default `80`: |
| 334 | ++ |
| 335 | +---- |
| 336 | +$ kubectl get service |
| 337 | +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE |
| 338 | +example-nginx-b9phnoz9spckcrua7ihrbkrt1 ClusterIP 10.96.26.3 <none> 80/TCP 1m |
| 339 | +---- |
| 340 | + |
| 341 | +. *Clean up the resources:* |
| 342 | ++ |
| 343 | +---- |
| 344 | +$ kubectl delete -f deploy/crds/example_v1alpha1_nginx_cr.yaml |
| 345 | +$ kubectl delete -f deploy/operator.yaml |
| 346 | +$ kubectl delete -f deploy/role_binding.yaml |
| 347 | +$ kubectl delete -f deploy/role.yaml |
| 348 | +$ kubectl delete -f deploy/service_account.yaml |
| 349 | +$ kubectl delete -f deploy/crds/example_v1alpha1_nginx_cr.yaml |
| 350 | +---- |
0 commit comments