github.com/jmrodri/operator-sdk@v0.5.0/doc/ansible/user-guide.md (about) 1 # User Guide 2 3 This guide walks through an example of building a simple memcached-operator 4 powered by Ansible using tools and libraries provided by the Operator SDK. 5 6 ## Prerequisites 7 8 - [git][git_tool] 9 - [docker][docker_tool] version 17.03+. 10 - [kubectl][kubectl_tool] version v1.9.0+. 11 - [ansible][ansible_tool] version v2.6.0+ 12 - [ansible-runner][ansible_runner_tool] version v1.1.0+ 13 - [ansible-runner-http][ansible_runner_http_plugin] version v1.0.0+ 14 - [dep][dep_tool] version v0.5.0+. (Optional if you aren't installing from source) 15 - [go][go_tool] version v1.10+. (Optional if you aren't installing from source) 16 - Access to a kubernetes v.1.9.0+ cluster. 17 18 **Note**: This guide uses [minikube][minikube_tool] version v0.25.0+ as the 19 local kubernetes cluster and quay.io for the public registry. 20 21 ## Install the Operator SDK CLI 22 23 The Operator SDK has a CLI tool that helps the developer to create, build, and 24 deploy a new operator project. 25 26 Checkout the desired release tag and install the SDK CLI tool: 27 28 ```sh 29 $ mkdir -p $GOPATH/src/github.com/operator-framework 30 $ cd $GOPATH/src/github.com/operator-framework 31 $ git clone https://github.com/operator-framework/operator-sdk 32 $ cd operator-sdk 33 $ git checkout master 34 $ make dep 35 $ make install 36 ``` 37 38 This installs the CLI binary `operator-sdk` at `$GOPATH/bin`. 39 40 ## Create a new project 41 42 Use the CLI to create a new Ansible-based memcached-operator project: 43 44 ```sh 45 $ operator-sdk new memcached-operator --api-version=cache.example.com/v1alpha1 --kind=Memcached --type=ansible 46 $ cd memcached-operator 47 ``` 48 49 This creates the memcached-operator project specifically for watching the 50 Memcached resource with APIVersion `cache.example.com/v1apha1` and Kind 51 `Memcached`. 52 53 To learn more about the project directory structure, see [project 54 layout][layout_doc] doc. 55 56 #### Operator scope 57 58 A namespace-scoped operator (the default) watches and manages resources in a single namespace, whereas a cluster-scoped operator watches and manages resources cluster-wide. Namespace-scoped operators are preferred because of their flexibility. They enable decoupled upgrades, namespace isolation for failures and monitoring, and differing API definitions. However, there are use cases where a cluster-scoped operator may make sense. For example, the [cert-manager](https://github.com/jetstack/cert-manager) operator is often deployed with cluster-scoped permissions and watches so that it can manage issuing certificates for an entire cluster. 59 60 If you'd like to create your memcached-operator project to be cluster-scoped use the following `operator-sdk new` command instead: 61 ``` 62 $ operator-sdk new memcached-operator --cluster-scoped --api-version=cache.example.com/v1alpha1 --kind=Memcached --type=ansible 63 ``` 64 65 Using `--cluster-scoped` will scaffold the new operator with the following modifications: 66 * `deploy/operator.yaml` - Set `WATCH_NAMESPACE=""` instead of setting it to the pod's namespace 67 * `deploy/role.yaml` - Use `ClusterRole` instead of `Role` 68 * `deploy/role_binding.yaml`: 69 * Use `ClusterRoleBinding` instead of `RoleBinding` 70 * Set the subject namespace to `REPLACE_NAMESPACE`. This must be changed to the namespace in which the operator is deployed. 71 72 ### Watches file 73 74 The Watches file contains a list of mappings from custom resources, identified 75 by it's Group, Version, and Kind, to an Ansible Role or Playbook. The Operator 76 expects this mapping file in a predefined location: `/opt/ansible/watches.yaml` 77 78 * **group**: The group of the Custom Resource that you will be watching. 79 * **version**: The version of the Custom Resource that you will be watching. 80 * **kind**: The kind of the Custom Resource that you will be watching. 81 * **role** (default): This is the path to the role that you have added to the 82 container. For example if your roles directory is at `/opt/ansible/roles/` 83 and your role is named `busybox`, this value will be 84 `/opt/ansible/roles/busybox`. This field is mutually exclusive with the 85 "playbook" field. 86 * **playbook**: This is the path to the playbook that you have added to the 87 container. This playbook is expected to be simply a way to call roles. This 88 field is mutually exclusive with the "role" field. 89 * **reconcilePeriod** (optional): The reconciliation interval, how often the 90 role/playbook is run, for a given CR. 91 * **manageStatus** (optional): When true (default), the operator will manage 92 the status of the CR generically. Set to false, the status of the CR is 93 managed elsewhere, by the specified role/playbook or in a separate controller. 94 95 An example Watches file: 96 97 ```yaml 98 --- 99 # Simple example mapping Foo to the Foo role 100 - version: v1alpha1 101 group: foo.example.com 102 kind: Foo 103 role: /opt/ansible/roles/Foo 104 105 # Simple example mapping Bar to a playbook 106 - version: v1alpha1 107 group: bar.example.com 108 kind: Bar 109 playbook: /opt/ansible/playbook.yml 110 111 # More complex example for our Baz kind 112 # Here we will disable requeuing and be managing the CR status in the playbook 113 - version: v1alpha1 114 group: baz.example.com 115 kind: Baz 116 playbook: /opt/ansible/baz.yml 117 reconcilePeriod: 0 118 manageStatus: false 119 ``` 120 121 ## Customize the operator logic 122 123 For this example the memcached-operator will execute the following 124 reconciliation logic for each `Memcached` Custom Resource (CR): 125 - Create a memcached Deployment if it doesn't exist 126 - Ensure that the Deployment size is the same as specified by the `Memcached` 127 CR 128 129 ### Watch the Memcached CR 130 131 By default, the memcached-operator watches `Memcached` resource events as shown 132 in `watches.yaml` and executes Ansible Role `Memcached`: 133 134 ```yaml 135 --- 136 - version: v1alpha1 137 group: cache.example.com 138 kind: Memcached 139 ``` 140 141 #### Options 142 **Role** 143 Specifying a `role` option in `watches.yaml` will configure the operator to use 144 this specified path when launching `ansible-runner` with an Ansible Role. By 145 default, the `new` command will fill in an absolute path to where your role 146 should go. 147 ```yaml 148 --- 149 - version: v1alpha1 150 group: cache.example.com 151 kind: Memcached 152 role: /opt/ansible/roles/memcached 153 ``` 154 155 **Playbook** 156 Specifying a `playbook` option in `watches.yaml` will configure the operator to 157 use this specified path when launching `ansible-runner` with an Ansible 158 Playbook 159 ```yaml 160 --- 161 - version: v1alpha1 162 group: cache.example.com 163 kind: Memcached 164 playbook: /opt/ansible/playbook.yaml 165 ``` 166 167 ## Building the Memcached Ansible Role 168 169 The first thing to do is to modify the generated Ansible role under 170 `roles/memcached`. This Ansible Role controls the logic that is executed when a 171 resource is modified. 172 173 ### Define the Memcached spec 174 175 Defining the spec for an Ansible Operator can be done entirely in Ansible. The 176 Ansible Operator will simply pass all key value pairs listed in the Custom 177 Resource spec field along to Ansible as 178 [variables](https://docs.ansible.com/ansible/2.5/user_guide/playbooks_variables.html#passing-variables-on-the-command-line). 179 The names of all variables in the spec field are converted to snake_case 180 by the operator before running ansible. For example, `serviceAccount` in 181 the spec becomes `service_account` in ansible. 182 It is recommended that you perform some type validation in Ansible on the 183 variables to ensure that your application is receiving expected input. 184 185 First, set a default in case the user doesn't set the `spec` field by modifying 186 `roles/memcached/defaults/main.yml`: 187 ```yaml 188 size: 1 189 ``` 190 191 ### Defining the Memcached deployment 192 193 Now that we have the spec defined, we can define what Ansible is actually 194 executed on resource changes. Since this is an Ansible Role, the default 195 behavior will be to execute the tasks in `roles/memcached/tasks/main.yml`. We 196 want Ansible to create a deployment if it does not exist which runs the 197 `memcached:1.4.36-alpine` image. Ansible 2.5+ supports the [k8s Ansible 198 Module](https://docs.ansible.com/ansible/2.6/modules/k8s_module.html) which we 199 will leverage to control the deployment definition. 200 201 Modify `roles/memcached/tasks/main.yml` to look like the following: 202 ```yaml 203 --- 204 - name: start memcached 205 k8s: 206 definition: 207 kind: Deployment 208 apiVersion: apps/v1 209 metadata: 210 name: '{{ meta.name }}-memcached' 211 namespace: '{{ meta.namespace }}' 212 spec: 213 replicas: "{{size}}" 214 selector: 215 matchLabels: 216 app: memcached 217 template: 218 metadata: 219 labels: 220 app: memcached 221 spec: 222 containers: 223 - name: memcached 224 command: 225 - memcached 226 - -m=64 227 - -o 228 - modern 229 - -v 230 image: "docker.io/memcached:1.4.36-alpine" 231 ports: 232 - containerPort: 11211 233 234 ``` 235 236 It is important to note that we used the `size` variable to control how many 237 replicas of the Memcached deployment we want. We set the default to `1`, but 238 any user can create a Custom Resource that overwrites the default. 239 240 ### Build and run the operator 241 242 Before running the operator, Kubernetes needs to know about the new custom 243 resource definition the operator will be watching. 244 245 Deploy the CRD: 246 247 ```sh 248 $ kubectl create -f deploy/crds/cache_v1alpha1_memcached_crd.yaml 249 ``` 250 251 Once this is done, there are two ways to run the operator: 252 253 - As a pod inside a Kubernetes cluster 254 - As a go program outside the cluster using `operator-sdk` 255 256 #### 1. Run as a pod inside a Kubernetes cluster 257 258 Running as a pod inside a Kubernetes cluster is preferred for production use. 259 260 Build the memcached-operator image and push it to a registry: 261 ``` 262 $ operator-sdk build quay.io/example/memcached-operator:v0.0.1 263 $ docker push quay.io/example/memcached-operator:v0.0.1 264 ``` 265 266 Kubernetes deployment manifests are generated in `deploy/operator.yaml`. The 267 deployment image in this file needs to be modified from the placeholder 268 `REPLACE_IMAGE` to the previous built image. To do this run: 269 ``` 270 $ sed -i 's|REPLACE_IMAGE|quay.io/example/memcached-operator:v0.0.1|g' deploy/operator.yaml 271 ``` 272 273 If you created your operator using `--cluster-scoped=true`, update the service account namespace in the generated `ClusterRoleBinding` to match where you are deploying your operator. 274 ``` 275 $ export OPERATOR_NAMESPACE=$(kubectl config view --minify -o jsonpath='{.contexts[0].context.namespace}') 276 $ sed -i "s|REPLACE_NAMESPACE|$OPERATOR_NAMESPACE|g" deploy/role_binding.yaml 277 ``` 278 279 **Note** 280 If you are performing these steps on OSX, use the following commands instead: 281 ``` 282 $ sed -i "" 's|REPLACE_IMAGE|quay.io/example/memcached-operator:v0.0.1|g' deploy/operator.yaml 283 $ sed -i "" "s|REPLACE_NAMESPACE|$OPERATOR_NAMESPACE|g" deploy/role_binding.yaml 284 ``` 285 286 Deploy the memcached-operator: 287 288 ```sh 289 $ kubectl create -f deploy/service_account.yaml 290 $ kubectl create -f deploy/role.yaml 291 $ kubectl create -f deploy/role_binding.yaml 292 $ kubectl create -f deploy/operator.yaml 293 ``` 294 295 Verify that the memcached-operator is up and running: 296 297 ```sh 298 $ kubectl get deployment 299 NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE 300 memcached-operator 1 1 1 1 1m 301 ``` 302 303 #### 2. Run outside the cluster 304 305 This method is preferred during the development cycle to speed up deployment and testing. 306 307 **Note**: Ensure that [Ansible Runner][ansible_runner_tool] and [Ansible Runner 308 HTTP Plugin][ansible_runner_http_plugin] is installed or else you will see 309 unexpected errors from Ansible Runner when a Custom Resource is created. 310 311 It is also important that the `role` path referenced in `watches.yaml` exists 312 on your machine. Since we are normally used to using a container where the Role 313 is put on disk for us, we need to manually copy our role to the configured 314 Ansible Roles path (e.g `/etc/ansible/roles`. 315 316 Run the operator locally with the default kubernetes config file present at 317 `$HOME/.kube/config`: 318 319 ```sh 320 $ operator-sdk up local 321 INFO[0000] Go Version: go1.10 322 INFO[0000] Go OS/Arch: darwin/amd64 323 INFO[0000] operator-sdk Version: 0.0.5+git 324 ``` 325 326 Run the operator locally with a provided kubernetes config file: 327 328 ```sh 329 $ operator-sdk up local --kubeconfig=config 330 INFO[0000] Go Version: go1.10 331 INFO[0000] Go OS/Arch: darwin/amd64 332 INFO[0000] operator-sdk Version: 0.0.5+git 333 ``` 334 335 ### Create a Memcached CR 336 337 Modify `deploy/cr.yaml` as shown and create a `Memcached` custom resource: 338 339 ```sh 340 $ cat deploy/cr.yaml 341 apiVersion: "cache.example.com/v1alpha1" 342 kind: "Memcached" 343 metadata: 344 name: "example-memcached" 345 spec: 346 size: 3 347 348 $ kubectl apply -f deploy/crds/cache_v1alpha1_memcached_cr.yaml 349 ``` 350 351 Ensure that the memcached-operator creates the deployment for the CR: 352 353 ```sh 354 $ kubectl get deployment 355 NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE 356 memcached-operator 1 1 1 1 2m 357 example-memcached 3 3 3 3 1m 358 ``` 359 360 Check the pods to confirm 3 replicas were created: 361 362 ```sh 363 $ kubectl get pods 364 NAME READY STATUS RESTARTS AGE 365 example-memcached-6fd7c98d8-7dqdr 1/1 Running 0 1m 366 example-memcached-6fd7c98d8-g5k7v 1/1 Running 0 1m 367 example-memcached-6fd7c98d8-m7vn7 1/1 Running 0 1m 368 memcached-operator-7cc7cfdf86-vvjqk 1/1 Running 0 2m 369 ``` 370 371 ### Update the size 372 373 Change the `spec.size` field in the memcached CR from 3 to 4 and apply the 374 change: 375 376 ```sh 377 $ cat deploy/cr.yaml 378 apiVersion: "cache.example.com/v1alpha1" 379 kind: "Memcached" 380 metadata: 381 name: "example-memcached" 382 spec: 383 size: 4 384 385 $ kubectl apply -f deploy/crds/cache_v1alpha1_memcached_cr.yaml 386 ``` 387 388 Confirm that the operator changes the deployment size: 389 390 ```sh 391 $ kubectl get deployment 392 NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE 393 example-memcached 4 4 4 4 5m 394 ``` 395 396 ### Cleanup 397 398 Clean up the resources: 399 400 ```sh 401 $ kubectl delete -f deploy/crds/cache_v1alpha1_memcached_cr.yaml 402 $ kubectl delete -f deploy/operator.yaml 403 $ kubectl delete -f deploy/role_binding.yaml 404 $ kubectl delete -f deploy/role.yaml 405 $ kubectl delete -f deploy/service_account.yaml 406 $ kubectl delete -f deploy/crds/cache_v1alpha1_memcached_cr.yaml 407 ``` 408 409 [layout_doc]:./project_layout.md 410 [dep_tool]:https://golang.github.io/dep/docs/installation.html 411 [git_tool]:https://git-scm.com/downloads 412 [go_tool]:https://golang.org/dl/ 413 [docker_tool]:https://docs.docker.com/install/ 414 [kubectl_tool]:https://kubernetes.io/docs/tasks/tools/install-kubectl/ 415 [minikube_tool]:https://github.com/kubernetes/minikube#installation 416 [ansible_tool]:https://docs.ansible.com/ansible/latest/index.html 417 [ansible_runner_tool]:https://ansible-runner.readthedocs.io/en/latest/install.html 418 [ansible_runner_http_plugin]:https://github.com/ansible/ansible-runner-http