Adding Admission Webhooks to an Ansible-based Operator

Background

For general background on what admission webhooks are, why to use them, and how to build them, please refer to the official Kubernetes documentation on Extensible Admission Controllers

This guide will assume that you understand the above content, and that you have an existing admission webhook server. You will likely need to make a few modifications to the webhook server container.

When integrating an admission webhook server into your Ansible-based Operator, we recommend that you deploy it as a sidecar container alongside your operator. This allows you to make use of the proxy server that the operator deploys, as well as the cache that backs it. The sidecar will be defined in the deploy/operator.yaml and it will look like:

  1. ...
  2. # This deploys the webhook
  3. - name: webhook
  4. # Replace this with the built image name
  5. image: "REPLACE_WEBHOOK_IMAGE"
  6. imagePullPolicy: "Always"
  7. volumeMounts:
  8. - mountPath: /etc/tls/
  9. name: webhook-cert
  10. ...
  11. ## Ensuring the webhook server uses the caching proxy
  12. When an Ansible-based Operator runs, it creates a Kubernetes proxy server and serves it on
  13. `http://localhost:8888`. This proxy server does not require any authorization, so all you need to
  14. do to make use of the proxy is ensure that your Kubernetes client is pointing at `http://localhost:8888`
  15. and that it does not attempt to verify SSL. If you use the default in-cluster configuration, you will
  16. be hitting the real API server and will not get caching for free.
  17. ## Deploying the webhook server
  18. To deploy the webhook server as a sidecar alongside your operator, all you need to do is add the container
  19. specification to your `deploy/operator.yaml`. You may also need to add a volume for mounting in TLS secrets,
  20. as your webhook server is required to have a valid SSL configuration. Below is a sample updated container
  21. specification that deploys a webhook:
  22. ```yaml
  23. containers:
  24. - name: my-operator
  25. # Replace this with the built image name
  26. image: "REPLACE_IMAGE"
  27. imagePullPolicy: "Always"
  28. volumeMounts:
  29. - mountPath: /tmp/ansible-operator/runner
  30. name: runner
  31. env:
  32. - name: WATCH_NAMESPACE
  33. valueFrom:
  34. fieldRef:
  35. fieldPath: metadata.namespace
  36. - name: POD_NAME
  37. valueFrom:
  38. fieldRef:
  39. fieldPath: metadata.name
  40. - name: OPERATOR_NAME
  41. value: "validating-operator"
  42. - name: ANSIBLE_GATHERING
  43. value: explicit
  44. # This deploys the webhook
  45. - name: webhook
  46. # Replace this with the built image name
  47. image: "REPLACE_WEBHOOK_IMAGE"
  48. imagePullPolicy: "Always"
  49. volumeMounts:
  50. - mountPath: /etc/tls/
  51. name: webhook-cert
  52. volumes:
  53. - name: runner
  54. emptyDir: {}
  55. # This assumes there is a secret called webhook-cert containing TLS certificates
  56. # Projects like cert-manager can create these certificates
  57. - name: webhook-cert
  58. secret:
  59. secretName: webhook-cert

This will run your webhook server alongside the operator, but Kubernetes will not yet call the webhooks before resources can be created. In order to let Kubernetes know about your webhooks, you must create specific API resources.

Making Kubernetes call your webhooks

In order to make your webhooks callable at all, first you must create a Service that points at your webhook server. Below is a sample service that creates a Service named my-operator-webhook, that will send traffic on port 443 to port 5000 in a Pod that matches the selector name=my-operator. Modify these values to match your environment.

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: my-operator-webhook
  5. spec:
  6. ports:
  7. - name: webhook
  8. port: 443
  9. protocol: TCP
  10. # Change targetPort to match the port your server is listening on
  11. targetPort: 5000
  12. selector:
  13. # Change this selector to match the labels on your operator pod
  14. name: my-operator
  15. type: ClusterIP

Now that you have a Service directing traffic to your webhook server, you will need to create MutatingWebhookConfiguration or ValidatingWebhookConfiguration objects (depending on what type of webhook you have deployed), which will tell Kubernetes to send certain API requests through your webhooks before writing to etcd.

Below are examples of both MutatingWebhookConfiguration and ValidatingWebhookConfiguration objects, which will tell Kubernetes to call the my-operator-webhook service when samples.example.com Example resources are created. The mutating webhook is served on the /mutating path in my example webhook server, and the validating webhook is served on /validating. Update these values as needed to reflect your environment and desired behavior. These objects are thoroughly documented in the official Kubernetes documentation on Extensible Admission Controllers

  1. ---
  2. apiVersion: admissionregistration.k8s.io/v1
  3. kind: MutatingWebhookConfiguration
  4. metadata:
  5. name: mutating.example.com
  6. webhooks:
  7. - name: "mutating.example.com"
  8. rules:
  9. - apiGroups: ["samples.example.com"]
  10. apiVersions: ["*"]
  11. operations: ["CREATE"]
  12. resources: ["examples"]
  13. scope: "Namespaced"
  14. clientConfig:
  15. service:
  16. # Replace this with the namespace your service is in
  17. namespace: REPLACE_NAMESPACE
  18. name: my-operator-webhook
  19. path: /mutating
  20. admissionReviewVersions: ["v1"]
  21. sideEffects: None
  22. ---
  23. apiVersion: admissionregistration.k8s.io/v1
  24. kind: ValidatingWebhookConfiguration
  25. metadata:
  26. name: validating.example.com
  27. webhooks:
  28. - name: validating.example.com
  29. rules:
  30. - apiGroups: ["samples.example.com"]
  31. apiVersions: ["*"]
  32. operations: ["CREATE"]
  33. resources: ["examples"]
  34. scope: "Namespaced"
  35. clientConfig:
  36. service:
  37. # Replace this with the namespace your service is in
  38. namespace: REPLACE_NAMESPACE
  39. name: my-operator-webhook
  40. path: /validating
  41. admissionReviewVersions: ["v1"]
  42. failurePolicy: Fail
  43. sideEffects: None

If these resources are configured properly you will now have an admissions webhook that can reject or mutate incoming resources before they are written to the Kubernetes database.

Summary

To deploy an existing admissions webhook to validate or mutate your Kubernetes resources alongside an Ansible-based Operator, you must

  1. Configure your admissions webhook to use the proxy server running on http://localhost:8888 in the operator pod
  2. Add the webhook container to your operator deployment
  3. Create a Service pointing to your webhook
  4. Make sure your webhook is reachable via the Service over https
  5. Create MutatingWebhookConfiguration or ValidatingWebhookConfiguration mapping the resource you want to mutate/validate to the Service you created

Last modified January 1, 0001