New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webhook gitbook #392

Merged
merged 1 commit into from Sep 14, 2018
Jump to file or symbol
Failed to load files and symbols.
+319 −0
Diff settings

Always

Just for now

View
@@ -35,6 +35,9 @@
* [Controllers For Core Resources](beyond_basics/controllers_for_core_resources.md)
* [Controller Watch Functions](beyond_basics/controller_watches.md)
* [Creating Events](beyond_basics/creating_events.md)
* Webhooks
* [What is a Webhook](beyond_basics/what_is_a_webhook.md)
* [Webhook Example](beyond_basics/sample_webhook.md)
* Deployment Workflow
* [Deploying the manager in Cluster](beyond_basics/deploying_controller.md)
@@ -0,0 +1,229 @@
# Webhook Example
This chapter walks through a simple webhook implementation.
It uses the [controller-runtime](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/webhook) libraries to implement
a Webhook Server and Manager.
Same as controllers, a Webhook Server is a
[`Runable`](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/manager#Runnable) which needs to be registered to a manager.
Arbitrary number of `Runable`s can be registered to a manager,
so a webhook server can run with other controllers in the same manager.
They will share the same dependencies provided by the manager. For example, shared cache, client, scheme, etc.
## Setup
#### Way to Deploy your Webhook Server
There are various ways to deploy the webhook server in terms of
1. Where the serving certificates live.
1. In what environment the webhook server runs, in a pod or directly on a VM, etc.
1. If in a pod, on what type of node, worker nodes or master node.
The recommended way to deploy the webhook server is
1. Run the webhook server as a regular pod on worker nodes through a workload API, e.g. Deployment or StatefulSet.
1. Put the certificate in a k8s secret in the same namespace as the webhook server
1. Mount the secret as a volume in the pod
1. Create a k8s service to front the webhook server.

This comment has been minimized.

@pwittrock

pwittrock Sep 13, 2018

Contributor

Hopefully the config for this is generated for you by the scaffolding tools?

@pwittrock

pwittrock Sep 13, 2018

Contributor

Hopefully the config for this is generated for you by the scaffolding tools?

This comment has been minimized.

@mengqiy

mengqiy Sep 14, 2018

Contributor

The service is created by the webhook server during bootstrapping.
scaffolding tools doesn't need to worry about the service.

@mengqiy

mengqiy Sep 14, 2018

Contributor

The service is created by the webhook server during bootstrapping.
scaffolding tools doesn't need to worry about the service.

#### Creating a Handler
{% method %}
The business logic for a Webhook exists in a Handler.
A Handler implements the `admission.Handler` interface, which contains a single `Handle` method.
If a Handler implements `inject.Client` and `inject.Decoder` interfaces,
the manager will automatically inject the client and the decoder into the Handler.
Note: The `client.Client` provided by the manager reads from a cache which is lazily initialized.
To eagerly initialize the cache, perform a read operation with the client before starting the server.
`podAnnotator` is a Handler, which implements the `admission.Handler`, `inject.Client` and `inject.Decoder` interfaces.
Details about how to implement an admission webhook podAnnotator is covered in a later section.
{% sample lang="go" %}
```go
type podAnnotator struct {
client client.Client
decoder types.Decoder
}
// podAnnotator implements admission.Handler.
var _ admission.Handler = &podAnnotator{}
func (a *podAnnotator) Handle(ctx context.Context, req types.Request) types.Response {
...
}
// podAnnotator implements inject.Client.
var _ inject.Client = &podAnnotator{}
// InjectClient injects the client into the podAnnotator
func (a *podAnnotator) InjectClient(c client.Client) error {
a.client = c
return nil
}
// podAnnotator implements inject.Decoder.
var _ inject.Decoder = &podAnnotator{}
// InjectDecoder injects the decoder into the podAnnotator
func (a *podAnnotator) InjectDecoder(d types.Decoder) error {
a.decoder = d
return nil
}
```
{% endmethod %}
#### Configuring a Webhook and Registering the Handler
{% method %}
A Webhook configures what type of requests the Handler should accept from the apiserver. Options include:
- The type of the Operations (CRUD)
- The type of the Targets (Deployment, Pod, etc)
- The type of the Handler (Mutating, Validating)
When the Server starts, it will register all Webhook Configurations with the apiserver to start accepting and
routing requests to the Handlers.
[controller-runtime](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder) provides a useful package for
building a webhook.
You can incrementally set the configuration of a webhook and then invoke `Build` to complete building a webhook.
If you want to specify the name and(or) path for your webhook instead of using the default, you can invoke
`Name("yourname")` and `Path("/yourpath")` respectively.
{% sample lang="go" %}
```go
wh, err := builder.NewWebhookBuilder().
Mutating().
Operations(admissionregistrationv1beta1.Create).
ForType(&corev1.Pod{}).
Handlers(&podAnnotator{}).
WithManager(mgr).
Build()
if err != nil {
// handle error
}
```
{% endmethod %}
#### Creating a Server
{% method %}
A Server registers Webhook Configuration with the apiserver and creates an HTTP server to route requests to the handlers.
The server is behind a Kubernetes Service and provides a certificate to the apiserver when serving requests.
The Server depends on a Kubernetes Secret containing this certificate to be mounted under `CertDir`.
If the Secret is empty, during bootstrapping the Server will generate a certificate and write it into the Secret.
A new webhook server can be created by invoking `webhook.NewServer`.
The Server will be registered to the provided manager.
You can specify `Port`, `CertDir` and various `BootstrapOptions`.
For the full list of Server options, please see [GoDoc](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/webhook).
{% sample lang="go" %}
```go
svr, err := webhook.NewServer("foo-admission-server", mgr, webhook.ServerOptions{
CertDir: "/tmp/cert",
BootstrapOptions: &webhook.BootstrapOptions{
Secret: &types.NamespacedName{
Namespace: "default",
Name: "foo-admission-server-secret",
},
Service: &webhook.Service{
Namespace: "default",
Name: "foo-admission-server-service",
// Selectors should select the pods that runs this webhook server.
Selectors: map[string]string{
"app": "foo-admission-server",
},
},
},
})
if err != nil {
// handle error
}
```
{% endmethod %}
#### Registering a Webhook with the Server
You can register webhook(s) in the webhook server by invoking `svr.Register(wh)`.
## Implementing Webhook Handler
#### Implementing the Handler Business Logic
{% method %}
`decoder types.Decoder` is a decoder that knows how the decode all core type and your CRD types.
`client client.Client` is a client that knows how to talk to the API server.
The guideline of returning HTTP status code is that:
- If the server decides to admit the request, it should return 200 and set
[`Allowed`](https://github.com/kubernetes/api/blob/f456898a08e4bbc5891694118f3819f324de12ff/admission/v1beta1/types.go#L86-L87)
to `true`.
- If the server rejects the request due to an admission policy reason, it should return 200, set
[`Allowed`](https://github.com/kubernetes/api/blob/f456898a08e4bbc5891694118f3819f324de12ff/admission/v1beta1/types.go#L86-L87)
to `false` and provide an informational message as reason.
- If the request is not well formatted, the server should reject it with 400 (Bad Request) and an error message.
- If the server encounters an unexpected error during processing, it should reject the request with 500 (Internal Error).
`controller-runtime` provides various helper methods for constructing Response.
- `ErrorResponse` for rejecting a request due to an error.
- `PatchResponse` for mutating webook to admit a request with patches.
- `ValidationResponse` for admitting or rejecting a request with a reason message.
{% sample lang="go" %}
```go
type podAnnotator struct {
client client.Client
decoder types.Decoder
}
// podAnnotator Iimplements admission.Handler.
var _ admission.Handler = &podAnnotator{}
// podAnnotator adds an annotation to every incoming pods.
func (a *podAnnotator) Handle(ctx context.Context, req types.Request) types.Response {
pod := &corev1.Pod{}
err := a.decoder.Decode(req, pod)
if err != nil {
return admission.ErrorResponse(http.StatusBadRequest, err)
}
copy := pod.DeepCopy()
err = a.mutatePodsFn(ctx, copy)
if err != nil {
return admission.ErrorResponse(http.StatusInternalServerError, err)
}
// admission.PatchResponse generates a Response containing patches.
return admission.PatchResponse(pod, copy)
}
// mutatePodsFn add an annotation to the given pod
func (a *podAnnotator) mutatePodsFn(ctx context.Context, pod *corev1.Pod) error {
if pod.Annotations == nil {
pod.Annotations = map[string]string{}
}
pod.Annotations["example-mutating-admission-webhook"] = "foo"
return nil
}
```
{% endmethod %}
@@ -0,0 +1,87 @@
# Webhook
Webhooks are HTTP callbacks, providing a way for notifications to be delivered to an external web server.
A web application implementing webhooks will send an HTTP request (typically POST) to other application when certain event happens.
In the kubernetes world, there are 3 kinds of webhooks:
[admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#admission-webhooks),
[authorization webhook](https://kubernetes.io/docs/reference/access-authn-authz/webhook/) and CRD conversion webhook.
In [controller-runtime](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/webhook) libraries,
currently we only support admission webhooks.
CRD conversion webhooks will be supported after it is released in kubernetes 1.12.
## Admission Webhook
Admission webhooks are HTTP callbacks that receive admission requests, process them and return admission responses.
There are two types of admission webhooks: mutating admission webhook and validating admission webhook.
With mutating admission webhooks, you may change the request object before it is stored (e.g. for implementing defaulting of fields)
With validating admission webhooks, you may not change the request, but you can reject it (e.g. for implementing validation of the request).
#### Why Admission Webhooks are Important
Admission webhooks are the mechanism to enable kubernetes extensibility through CRD.
- Mutating admission webhook is the only way to do defaulting for CRDs.
- Validating admission webhook allows for more complex validation than pure schema-based validation.
e.g. cross-field validation or cross-object validation.
It can also be used to add custom logic in the core kubernetes API.
#### Mutating Admission Webhook
A mutating admission webhook receives an admission request which contains an object.
The webhook can either decline the request directly or returning JSON patches for modifying the original object.
- If admitting the request, the webhook is responsible for generating JSON patches and send them back in the
admission response.
- If declining the request, a reason message should be returned in the admission response.
#### Validating Admission Webhook
A validating admission webhook receives an admission request which contains an object.
The webhook can either admit or decline the request.
A reason message should be returned in the admission response if declining the request.
#### Authentication
The apiserver by default doesn't authenticate itself to the webhooks.

This comment has been minimized.

@pwittrock

pwittrock Sep 14, 2018

Contributor

Is this the auth for the requesting user, or the apiserver itself?

@pwittrock

pwittrock Sep 14, 2018

Contributor

Is this the auth for the requesting user, or the apiserver itself?

This comment has been minimized.

@mengqiy

mengqiy Sep 14, 2018

Contributor

The apiserver itself

@mengqiy

mengqiy Sep 14, 2018

Contributor

The apiserver itself

That means the webhooks don't authenticate the identities of the clients.
But if you want to authenticate the clients, you need to configure the apiserver to use basic auth, bearer token,
or a cert to authenticate itself to the webhooks. You can find detailed steps
[here](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#authenticate-apiservers).
#### Configure Admission Webhooks Dynamically
{% method %}
Admission webhooks can be configured dynamically via the `admissionregistration.k8s.io/v1beta1` API.
So your cluster must be 1.9 or later and has enabled the API.
You can do CRUD operations on WebhookConfiguration objects as on other k8s objects.
{% sample lang="yaml" %}
```yaml
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: <name of itself>
webhooks:
- name: <webhook name, e.g. validate-deployment.example.com>
rules:
- apiGroups:
- apps
apiVersions:
- v1
operations:
- CREATE
resources:
- deployments
clientConfig:
service:
namespace: <namespace of the service>
name: <name of the service>
caBundle: <pem encoded ca cert that signs the server cert used by the webhook>
```
{% endmethod %}
ProTip! Use n and p to navigate between commits in a pull request.