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