|
| 1 | +--- |
| 2 | +title: "Kubernetes Webhooks made easy with OpenFaaS" |
| 3 | +description: "In this post you'll learn how to write Kubernetes Admission webhooks using OpenFaaS functions" |
| 4 | +date: 2020-10-27 |
| 5 | +image: /images/2020-10-27-k8s-validatingwebhook-openfaas/admission-controller-phases.png |
| 6 | +categories: |
| 7 | + - arkade |
| 8 | + - kubectl |
| 9 | + - faas-cli |
| 10 | + - admissionwebhooks |
| 11 | + - validatingadmissionwebhooks |
| 12 | + - k8s extensibility |
| 13 | +author_staff_member: developer-guy |
| 14 | +dark_background: true |
| 15 | +--- |
| 16 | + |
| 17 | +In this post you'll learn how to write Kubernetes Admission webhooks using OpenFaaS functions |
| 18 | + |
| 19 | +<p align="center"> |
| 20 | +<img height="128" src="/images/openfaas/kubernetes.png"> |
| 21 | +</p> |
| 22 | + |
| 23 | +## Introduction to Kubernetes Admission webhooks |
| 24 | +Admission webhooks are HTTP callbacks that receive admission requests and do something with them. You can define two types of admission webhooks, validating admission webhook and mutating admission webhook. Mutating admission webhooks are invoked first, and can modify objects sent to the API server to enforce custom defaults. After all object modifications are complete, and after the incoming object is validate by the API server, validating admission webhooks are invoked and can reject requests to enforce custom policies. |
| 25 | + |
| 26 | +Using OpenFaaS in this design, we can focus on our core logic more than designing the microservice itself and simply create application without being worry about how to build and deploy. |
| 27 | + |
| 28 | +## The Scenario |
| 29 | +Let's assume, in our company, we have some requirements that we must meet while deploying applications onto the Kubernetes cluster. We need to set some required labels to our Kubernetes manifest. Unless we specify the required labels our request will reject. |
| 30 | + |
| 31 | +So, in order to apply those requirements to the Kubernetes cluster to ensure the best practices, we can use Kubernetes [ValidatingAdmissionWebhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook) and [OpenFaaS](https://www.openfaas.com) together. Since ValidatingAdmissionWebhooks intercepts requests to the apiserver, OpenFaaS functions includes a little code to check required labels and determines the request either allowed or not. |
| 32 | + |
| 33 | +Webhook Admission Server is just plain http server that adhere to Kubernetes API. For each Pod create request to the apiserver(I said Pod because we specify which kind of resources that we consider while registering our webhook to the apiserver using [ValidatingWebhookConfiguration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#validatingwebhookconfiguration-v1-admissionregistration-k8s-io) resource) the ValidatingAdmissionWebhook sends an admissionReview([API](https://github.com/kubernetes/kubernetes/blob/release-1.19/pkg/apis/admission/types.go) for reference) to the relevant webhook admission server. The webhook admission server gathers information like object, oldobject, and userInfo from admissionReview's AdmissionRequest and sends AdmissionRequest to the serverless function through the OpenFaaS Gateway. The function checks the required labels exist on Pod and determines the request either valid or not and then sends back the AdmissionResponse whose Allowed and Result fields are filled with the admission decision to the webhook admission server then the webhook admission servers sends back a admissionReview to the apiserver. |
| 34 | + |
| 35 | +* Kubernetes API -> Webhook (w/TLS) -> OpenFaaS Gateway (w/HTTP) -> OpenFaaS Function |
| 36 | + |
| 37 | +Supporting TLS for external webhook server is also required because admission is a high security operation. As part of the process, we need to create a TLS certificate signed by the Kubernetes CA to secure the communication between the webhook server and apiserver. |
| 38 | + |
| 39 | +### Prerequisites |
| 40 | +##### Arkade |
| 41 | + |
| 42 | +* [arkade](https://get-arkade.dev) is The OpenFaaS community built tool for Kubernetes developers, with arkade you can easily install all necessary cli tools to your host and deploy apps to the cluster. |
| 43 | + |
| 44 | +```sh |
| 45 | +$ curl -sLS https://dl.get-arkade.dev | sudo sh |
| 46 | +``` |
| 47 | + |
| 48 | +##### KinD (Kubernetes in Docker) |
| 49 | + |
| 50 | +* Kubernetes is our recommendation for teams running at scale, but in this demo we will be using [KinD](https://kind.sigs.k8s.io/docs/user/quick-start/) for the sake of simplicity. |
| 51 | + |
| 52 | +```sh |
| 53 | +$ arkade get kind |
| 54 | +``` |
| 55 | + |
| 56 | +##### kubectl |
| 57 | + |
| 58 | +* You can control your cluster using [kubectl](https://github.com/kubernetes/kubectl) CLI. |
| 59 | + |
| 60 | +```sh |
| 61 | +$ arkade get kubectl |
| 62 | +``` |
| 63 | + |
| 64 | +##### faas-cli |
| 65 | + |
| 66 | +* [faas-cli](https://github.com/openfaas/faas-cli) is an official CLI for OpenFaaS , with "faas-cli" you can build and deploy functions easily. |
| 67 | + |
| 68 | +```sh |
| 69 | +$ arkade get faas-cli |
| 70 | +``` |
| 71 | + |
| 72 | +### Setup |
| 73 | + |
| 74 | +### 1. Setup a Kubernetes Cluster with KinD |
| 75 | + |
| 76 | +You can start a Kubernetes cluster with KinD if you don't have one already |
| 77 | + |
| 78 | +```bash |
| 79 | +$ arkade get kind |
| 80 | +$ kind create cluster |
| 81 | +``` |
| 82 | + |
| 83 | +### 2. Deploy OpenFaaS to our local Kubernetes Cluster with arkade: |
| 84 | + |
| 85 | +* Install a OpenFaaS |
| 86 | + |
| 87 | +```sh |
| 88 | +$ arkade install openfaas |
| 89 | +``` |
| 90 | + |
| 91 | +Read the output from the installation and run the commands given to you. |
| 92 | + |
| 93 | +You can access them again at any time with `arkade info openfaas` |
| 94 | + |
| 95 | +### 3. Clone the project |
| 96 | + |
| 97 | +* Clone the sample from GitHub |
| 98 | + |
| 99 | +```sh |
| 100 | +$ git clone https://github.com/developer-guy/admission-webhook-example-with-openfaas |
| 101 | +$ cd admission-webhook-example-with-openfaas |
| 102 | +``` |
| 103 | + |
| 104 | +* Let's explore the structure of the project. |
| 105 | + |
| 106 | +``` |
| 107 | +deployment/ --> includes necessary manifests and scripts for the deployment of the project |
| 108 | +functions/ --> includes templates and the requiredlabel function itself |
| 109 | +Dockerfile --> includes instructions to build an image of the project |
| 110 | +build --> automated way to build and push an image of the project |
| 111 | +``` |
| 112 | + |
| 113 | +### 4. Deploy ValidatingAdmissionWebhook |
| 114 | + |
| 115 | +* We will generate the TLS certificates required for the ValidatingAdmissionWebhook using the following: [Kubernetes TLS Certificates Management](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/) |
| 116 | + |
| 117 | +```sh |
| 118 | +$ cd deployment |
| 119 | +$ sh webhook-create-signed-cert.sh |
| 120 | +``` |
| 121 | + |
| 122 | +* Get the Certificate Authority (CA) from the local cluster |
| 123 | + |
| 124 | +```sh |
| 125 | +$ export CA_BUNDLE=$(kubectl config view --minify --flatten -o json | jq -r '.clusters[] | select(.name == "'$(kubectl config current-context)'") | .cluster."certificate-authority-data"') |
| 126 | +$ sed -e "s|\${CA_BUNDLE}|${CA_BUNDLE}|g" validatingwebhook.yaml | kubectl apply -f - |
| 127 | +$ cd .. |
| 128 | +``` |
| 129 | + |
| 130 | +* Build the project |
| 131 | + |
| 132 | +```sh |
| 133 | +$ export DOCKER_USER="docker-hub-username" |
| 134 | +$ ./build |
| 135 | +``` |
| 136 | + |
| 137 | +Now edit `deployment.yaml` and set 'DOCKER_USER' to the above. |
| 138 | + |
| 139 | +* Deploy it project |
| 140 | + |
| 141 | +```sh |
| 142 | +$ cd deployment |
| 143 | +$ kubectl apply -f rbac.yaml,service.yaml,deployment.yaml |
| 144 | +``` |
| 145 | + |
| 146 | +### 5. Build and Deploy OpenFaaS Function (Optional) |
| 147 | + |
| 148 | +* Pull the [golang-middleware](https://github.com/openfaas-incubator/golang-http-template) template from [OpenFaaS Official Template Store](https://github.com/openfaas/store) |
| 149 | + |
| 150 | +```sh |
| 151 | +$ faas-cli template store list # check available templates in store |
| 152 | +$ faas-cli template store describe golang-middleware # describe the specific template |
| 153 | +$ faas-cli template store pull golang-middleware |
| 154 | +``` |
| 155 | + |
| 156 | +* Create the function |
| 157 | + |
| 158 | +```sh |
| 159 | +$ export OPENFAAS_PREFIX=$DOCKER_USER |
| 160 | +$ faas-cli new requiredlabel --lang go-middleware |
| 161 | +$ cd requiredlabel |
| 162 | +$ go mod init requiredlabel |
| 163 | +$ # fill the handler.go with the corresponding code |
| 164 | +$ go get |
| 165 | +``` |
| 166 | + |
| 167 | +* Deploy the function |
| 168 | + |
| 169 | +```sh |
| 170 | +$ cd functions |
| 171 | +$ faas-cli up -f requiredlabel.yml --build-arg GO111MODULE=on # (build-push-deploy) make sure you are using your docker hub username. i.e: devopps |
| 172 | +``` |
| 173 | + |
| 174 | +* Verify the functions that are working in `openfaas-fn` namespace |
| 175 | + |
| 176 | +```sh |
| 177 | +$ kubectl get pods --namespace openfaas-fn |
| 178 | +``` |
| 179 | + |
| 180 | +### 6. Test the whole workflow |
| 181 | + |
| 182 | +* The purpose of this PoC is that to validate that pods has required `labels`. Which means you must have that labels: |
| 183 | + |
| 184 | +```yaml |
| 185 | +app.kubernetes.io/name: sleep |
| 186 | +app.kubernetes.io/instance: sleep |
| 187 | +app.kubernetes.io/version: "0.1" |
| 188 | +app.kubernetes.io/component: dummy |
| 189 | +app.kubernetes.io/part-of: admission-webhook-example |
| 190 | +app.kubernetes.io/managed-by: kubernetes |
| 191 | +``` |
| 192 | +
|
| 193 | +* Any Pod who have above labels is valid for us. |
| 194 | +
|
| 195 | +```sh |
| 196 | +`deployment/sleep.yaml` -> Incorrect, not-valid (We should deny this creation request.) |
| 197 | +`deployment/sleep-no-validation.yaml` -> Skip-validation (Based on `admission-webhook-example.qikqiak.com/validate: "false"` annotation, we skipped validation.) |
| 198 | +`deployment/sleep-with-labels.yaml` -> Correct, valid (We should accept this creation request.) |
| 199 | +``` |
| 200 | +
|
| 201 | +### 7. A way of extend the operation event and function |
| 202 | +
|
| 203 | +In this demo, we only consider the Pod create request by specifying _operations_ at the ValidatingWebhookConfiguration's [matching request rules](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-rules) section in the [deployment/validatingwebhook.yaml](https://github.com/developer-guy/admission-webhook-example-with-openfaas/blob/master/deployment/validatingwebhook.yaml) file. |
| 204 | +
|
| 205 | +If we want to extend the operations, we can add a new [operation](https://github.com/kubernetes/kubernetes/blob/release-1.19/pkg/apis/admission/types.go#L158) for the Pods like _DELETE_, _UPDATE_, _CONNECT_ etc. By specifying a new operation, now apiserver being started to send a new event for this operation additional to create request. |
| 206 | +
|
| 207 | +Now we can specify more than one serverless function for the operation types by checking request operation type. For [example](https://github.com/developer-guy/admission-webhook-example-with-openfaas/blob/master/functions/requiredlabel/handler.go#L109): |
| 208 | +
|
| 209 | +```go |
| 210 | +switch req.Kind.Kind { |
| 211 | + case "Pod": |
| 212 | + switch req.Operation { |
| 213 | + case v1beta1.Create: |
| 214 | + // do something for create operation |
| 215 | + case v1beta1.Delete: |
| 216 | + // do something for delete operation |
| 217 | + } |
| 218 | +} |
| 219 | +``` |
| 220 | + |
| 221 | +Also you can specify function name and namespace that you want to use while deploying the webhook server using these environment variables: |
| 222 | + |
| 223 | +```yaml |
| 224 | + env: |
| 225 | + - name: FUNCTION_NAME |
| 226 | + value: requiredlabel |
| 227 | + - name: FUNCTION_NAMESPACE |
| 228 | + value: openfaas-fn |
| 229 | +``` |
| 230 | +
|
| 231 | +### Taking it further |
| 232 | +
|
| 233 | +* Mutating webhooks |
| 234 | +
|
| 235 | +You could take this example and convert it from validating webhooks to mutating webhooks. This is useful when a user wants to upgrade or modify objects that are created, such as adding which user created them, or adding a compulsory memory limit. |
| 236 | +
|
| 237 | +* Adding more functions |
| 238 | +
|
| 239 | +In the example I used a single function, however, you could register more than one function, so that you can then have a function for validating memory limits, and a separate one for checking that a minimum set of labels are present |
| 240 | +
|
| 241 | +### Join the community |
| 242 | +
|
| 243 | +Have you got questions, comments, or suggestions? Join the community on [Slack](https://slack.openfaas.io). |
| 244 | +
|
| 245 | +Would you like help to set up your OpenFaaS installation, or someone to call when things don't quite go to plan? [Our Premium Subscription plan](https://www.openfaas.com/support/) gives you a say in the project roadmap, a support contact, and access to Enterprise-grade authentication with OIDC. |
| 246 | +
|
| 247 | +### Acknowledgements |
| 248 | +
|
| 249 | +* Special Thanks to [Alex Ellis](https://twitter.com/alexellisuk) for all guidance and for merging changes into OpenFaaS to better support this workflow. |
| 250 | +* Special Thanks to [Furkan Türkal](https://twitter.com/furkanturkaI) for all the support. |
| 251 | +
|
| 252 | +### References |
| 253 | +
|
| 254 | +* https://medium.com/ibm-cloud/diving-into-kubernetes-mutatingadmissionwebhook-6ef3c5695f74 |
| 255 | +* https://blog.alexellis.io/get-started-with-openfaas-and-kind/ |
| 256 | +* https://github.com/morvencao/kube-mutating-webhook-tutorial |
| 257 | +* https://github.com/developer-guy/admission-webhook-example-with-openfaas |
0 commit comments