Skip to content

Commit 1623901

Browse files
authored
Merge pull request #392 from mengqiy/webhook_gitbook
webhook gitbook
2 parents 34b31b4 + 9e6a9a1 commit 1623901

File tree

3 files changed

+319
-0
lines changed

3 files changed

+319
-0
lines changed

docs/book/SUMMARY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
* [Controllers For Core Resources](beyond_basics/controllers_for_core_resources.md)
3636
* [Controller Watch Functions](beyond_basics/controller_watches.md)
3737
* [Creating Events](beyond_basics/creating_events.md)
38+
* Webhooks
39+
* [What is a Webhook](beyond_basics/what_is_a_webhook.md)
40+
* [Webhook Example](beyond_basics/sample_webhook.md)
3841
* Deployment Workflow
3942
* [Deploying the manager in Cluster](beyond_basics/deploying_controller.md)
4043

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# Webhook Example
2+
3+
This chapter walks through a simple webhook implementation.
4+
5+
It uses the [controller-runtime](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/webhook) libraries to implement
6+
a Webhook Server and Manager.
7+
8+
Same as controllers, a Webhook Server is a
9+
[`Runable`](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/manager#Runnable) which needs to be registered to a manager.
10+
Arbitrary number of `Runable`s can be registered to a manager,
11+
so a webhook server can run with other controllers in the same manager.
12+
They will share the same dependencies provided by the manager. For example, shared cache, client, scheme, etc.
13+
14+
## Setup
15+
16+
#### Way to Deploy your Webhook Server
17+
18+
There are various ways to deploy the webhook server in terms of
19+
20+
1. Where the serving certificates live.
21+
1. In what environment the webhook server runs, in a pod or directly on a VM, etc.
22+
1. If in a pod, on what type of node, worker nodes or master node.
23+
24+
The recommended way to deploy the webhook server is
25+
26+
1. Run the webhook server as a regular pod on worker nodes through a workload API, e.g. Deployment or StatefulSet.
27+
1. Put the certificate in a k8s secret in the same namespace as the webhook server
28+
1. Mount the secret as a volume in the pod
29+
1. Create a k8s service to front the webhook server.
30+
31+
#### Creating a Handler
32+
33+
{% method %}
34+
35+
The business logic for a Webhook exists in a Handler.
36+
A Handler implements the `admission.Handler` interface, which contains a single `Handle` method.
37+
38+
If a Handler implements `inject.Client` and `inject.Decoder` interfaces,
39+
the manager will automatically inject the client and the decoder into the Handler.
40+
41+
Note: The `client.Client` provided by the manager reads from a cache which is lazily initialized.
42+
To eagerly initialize the cache, perform a read operation with the client before starting the server.
43+
44+
`podAnnotator` is a Handler, which implements the `admission.Handler`, `inject.Client` and `inject.Decoder` interfaces.
45+
46+
Details about how to implement an admission webhook podAnnotator is covered in a later section.
47+
48+
{% sample lang="go" %}
49+
```go
50+
type podAnnotator struct {
51+
client client.Client
52+
decoder types.Decoder
53+
}
54+
55+
// podAnnotator implements admission.Handler.
56+
var _ admission.Handler = &podAnnotator{}
57+
58+
func (a *podAnnotator) Handle(ctx context.Context, req types.Request) types.Response {
59+
...
60+
}
61+
62+
// podAnnotator implements inject.Client.
63+
var _ inject.Client = &podAnnotator{}
64+
65+
// InjectClient injects the client into the podAnnotator
66+
func (a *podAnnotator) InjectClient(c client.Client) error {
67+
a.client = c
68+
return nil
69+
}
70+
71+
// podAnnotator implements inject.Decoder.
72+
var _ inject.Decoder = &podAnnotator{}
73+
74+
// InjectDecoder injects the decoder into the podAnnotator
75+
func (a *podAnnotator) InjectDecoder(d types.Decoder) error {
76+
a.decoder = d
77+
return nil
78+
}
79+
```
80+
{% endmethod %}
81+
82+
83+
#### Configuring a Webhook and Registering the Handler
84+
85+
{% method %}
86+
87+
A Webhook configures what type of requests the Handler should accept from the apiserver. Options include:
88+
- The type of the Operations (CRUD)
89+
- The type of the Targets (Deployment, Pod, etc)
90+
- The type of the Handler (Mutating, Validating)
91+
92+
When the Server starts, it will register all Webhook Configurations with the apiserver to start accepting and
93+
routing requests to the Handlers.
94+
95+
[controller-runtime](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder) provides a useful package for
96+
building a webhook.
97+
You can incrementally set the configuration of a webhook and then invoke `Build` to complete building a webhook.
98+
99+
If you want to specify the name and(or) path for your webhook instead of using the default, you can invoke
100+
`Name("yourname")` and `Path("/yourpath")` respectively.
101+
102+
{% sample lang="go" %}
103+
```go
104+
wh, err := builder.NewWebhookBuilder().
105+
Mutating().
106+
Operations(admissionregistrationv1beta1.Create).
107+
ForType(&corev1.Pod{}).
108+
Handlers(&podAnnotator{}).
109+
WithManager(mgr).
110+
Build()
111+
if err != nil {
112+
// handle error
113+
}
114+
```
115+
{% endmethod %}
116+
117+
118+
#### Creating a Server
119+
120+
{% method %}
121+
122+
A Server registers Webhook Configuration with the apiserver and creates an HTTP server to route requests to the handlers.
123+
124+
The server is behind a Kubernetes Service and provides a certificate to the apiserver when serving requests.
125+
126+
The Server depends on a Kubernetes Secret containing this certificate to be mounted under `CertDir`.
127+
128+
If the Secret is empty, during bootstrapping the Server will generate a certificate and write it into the Secret.
129+
130+
A new webhook server can be created by invoking `webhook.NewServer`.
131+
The Server will be registered to the provided manager.
132+
You can specify `Port`, `CertDir` and various `BootstrapOptions`.
133+
For the full list of Server options, please see [GoDoc](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/webhook).
134+
135+
{% sample lang="go" %}
136+
```go
137+
svr, err := webhook.NewServer("foo-admission-server", mgr, webhook.ServerOptions{
138+
CertDir: "/tmp/cert",
139+
BootstrapOptions: &webhook.BootstrapOptions{
140+
Secret: &types.NamespacedName{
141+
Namespace: "default",
142+
Name: "foo-admission-server-secret",
143+
},
144+
145+
Service: &webhook.Service{
146+
Namespace: "default",
147+
Name: "foo-admission-server-service",
148+
// Selectors should select the pods that runs this webhook server.
149+
Selectors: map[string]string{
150+
"app": "foo-admission-server",
151+
},
152+
},
153+
},
154+
})
155+
if err != nil {
156+
// handle error
157+
}
158+
```
159+
{% endmethod %}
160+
161+
#### Registering a Webhook with the Server
162+
163+
You can register webhook(s) in the webhook server by invoking `svr.Register(wh)`.
164+
165+
166+
## Implementing Webhook Handler
167+
168+
169+
#### Implementing the Handler Business Logic
170+
171+
{% method %}
172+
173+
`decoder types.Decoder` is a decoder that knows how the decode all core type and your CRD types.
174+
175+
`client client.Client` is a client that knows how to talk to the API server.
176+
177+
The guideline of returning HTTP status code is that:
178+
- If the server decides to admit the request, it should return 200 and set
179+
[`Allowed`](https://github.com/kubernetes/api/blob/f456898a08e4bbc5891694118f3819f324de12ff/admission/v1beta1/types.go#L86-L87)
180+
to `true`.
181+
- If the server rejects the request due to an admission policy reason, it should return 200, set
182+
[`Allowed`](https://github.com/kubernetes/api/blob/f456898a08e4bbc5891694118f3819f324de12ff/admission/v1beta1/types.go#L86-L87)
183+
to `false` and provide an informational message as reason.
184+
- If the request is not well formatted, the server should reject it with 400 (Bad Request) and an error message.
185+
- If the server encounters an unexpected error during processing, it should reject the request with 500 (Internal Error).
186+
187+
`controller-runtime` provides various helper methods for constructing Response.
188+
- `ErrorResponse` for rejecting a request due to an error.
189+
- `PatchResponse` for mutating webook to admit a request with patches.
190+
- `ValidationResponse` for admitting or rejecting a request with a reason message.
191+
192+
{% sample lang="go" %}
193+
```go
194+
type podAnnotator struct {
195+
client client.Client
196+
decoder types.Decoder
197+
}
198+
199+
// podAnnotator Iimplements admission.Handler.
200+
var _ admission.Handler = &podAnnotator{}
201+
202+
// podAnnotator adds an annotation to every incoming pods.
203+
func (a *podAnnotator) Handle(ctx context.Context, req types.Request) types.Response {
204+
pod := &corev1.Pod{}
205+
206+
err := a.decoder.Decode(req, pod)
207+
if err != nil {
208+
return admission.ErrorResponse(http.StatusBadRequest, err)
209+
}
210+
copy := pod.DeepCopy()
211+
212+
err = a.mutatePodsFn(ctx, copy)
213+
if err != nil {
214+
return admission.ErrorResponse(http.StatusInternalServerError, err)
215+
}
216+
// admission.PatchResponse generates a Response containing patches.
217+
return admission.PatchResponse(pod, copy)
218+
}
219+
220+
// mutatePodsFn add an annotation to the given pod
221+
func (a *podAnnotator) mutatePodsFn(ctx context.Context, pod *corev1.Pod) error {
222+
if pod.Annotations == nil {
223+
pod.Annotations = map[string]string{}
224+
}
225+
pod.Annotations["example-mutating-admission-webhook"] = "foo"
226+
return nil
227+
}
228+
```
229+
{% endmethod %}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Webhook
2+
3+
Webhooks are HTTP callbacks, providing a way for notifications to be delivered to an external web server.
4+
A web application implementing webhooks will send an HTTP request (typically POST) to other application when certain event happens.
5+
In the kubernetes world, there are 3 kinds of webhooks:
6+
[admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#admission-webhooks),
7+
[authorization webhook](https://kubernetes.io/docs/reference/access-authn-authz/webhook/) and CRD conversion webhook.
8+
9+
In [controller-runtime](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/webhook) libraries,
10+
currently we only support admission webhooks.
11+
CRD conversion webhooks will be supported after it is released in kubernetes 1.12.
12+
13+
## Admission Webhook
14+
15+
Admission webhooks are HTTP callbacks that receive admission requests, process them and return admission responses.
16+
There are two types of admission webhooks: mutating admission webhook and validating admission webhook.
17+
With mutating admission webhooks, you may change the request object before it is stored (e.g. for implementing defaulting of fields)
18+
With validating admission webhooks, you may not change the request, but you can reject it (e.g. for implementing validation of the request).
19+
20+
#### Why Admission Webhooks are Important
21+
22+
Admission webhooks are the mechanism to enable kubernetes extensibility through CRD.
23+
- Mutating admission webhook is the only way to do defaulting for CRDs.
24+
- Validating admission webhook allows for more complex validation than pure schema-based validation.
25+
e.g. cross-field validation or cross-object validation.
26+
27+
It can also be used to add custom logic in the core kubernetes API.
28+
29+
#### Mutating Admission Webhook
30+
31+
A mutating admission webhook receives an admission request which contains an object.
32+
The webhook can either decline the request directly or returning JSON patches for modifying the original object.
33+
- If admitting the request, the webhook is responsible for generating JSON patches and send them back in the
34+
admission response.
35+
- If declining the request, a reason message should be returned in the admission response.
36+
37+
#### Validating Admission Webhook
38+
39+
A validating admission webhook receives an admission request which contains an object.
40+
The webhook can either admit or decline the request.
41+
A reason message should be returned in the admission response if declining the request.
42+
43+
#### Authentication
44+
45+
The apiserver by default doesn't authenticate itself to the webhooks.
46+
That means the webhooks don't authenticate the identities of the clients.
47+
48+
But if you want to authenticate the clients, you need to configure the apiserver to use basic auth, bearer token,
49+
or a cert to authenticate itself to the webhooks. You can find detailed steps
50+
[here](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#authenticate-apiservers).
51+
52+
#### Configure Admission Webhooks Dynamically
53+
54+
{% method %}
55+
56+
Admission webhooks can be configured dynamically via the `admissionregistration.k8s.io/v1beta1` API.
57+
So your cluster must be 1.9 or later and has enabled the API.
58+
59+
You can do CRUD operations on WebhookConfiguration objects as on other k8s objects.
60+
61+
{% sample lang="yaml" %}
62+
63+
```yaml
64+
apiVersion: admissionregistration.k8s.io/v1beta1
65+
kind: ValidatingWebhookConfiguration
66+
metadata:
67+
name: <name of itself>
68+
webhooks:
69+
- name: <webhook name, e.g. validate-deployment.example.com>
70+
rules:
71+
- apiGroups:
72+
- apps
73+
apiVersions:
74+
- v1
75+
operations:
76+
- CREATE
77+
resources:
78+
- deployments
79+
clientConfig:
80+
service:
81+
namespace: <namespace of the service>
82+
name: <name of the service>
83+
caBundle: <pem encoded ca cert that signs the server cert used by the webhook>
84+
```
85+
86+
{% endmethod %}
87+

0 commit comments

Comments
 (0)