Skip to content

Commit 450508e

Browse files
authored
Merge pull request #776 from mengqiy/webhookdoc
📖 add doc for admission webhooks
2 parents dcf0812 + 6aea2c7 commit 450508e

30 files changed

+716
-124
lines changed

docs/book/src/SUMMARY.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
---
88

9-
- [Tutorial: Building CronJob](./cronjob-tutorial.md)
9+
- [Tutorial: Building CronJob](cronjob-tutorial/cronjob-tutorial.md)
1010

1111
- [What's in a basic project?](./cronjob-tutorial/basic-project.md)
1212
- [Every journey needs a start, every program a main](./cronjob-tutorial/empty-main.md)
@@ -19,9 +19,14 @@
1919
- [What's in a controller?](./cronjob-tutorial/controller-overview.md)
2020
- [Implementing a controller](./cronjob-tutorial/controller-implementation.md)
2121

22-
- [You said something about main?](./cronjob-tutorial/main-revisited.md)
22+
- [You said something about main?](./cronjob-tutorial/main-revisited.md)
2323

24+
- [Implementing defaulting/validating webhooks](./cronjob-tutorial/webhook-implementation.md)
2425
- [Running and deploying the controller](./cronjob-tutorial/running.md)
26+
27+
- [Deploying the cert manager](./cronjob-tutorial/cert-manager.md)
28+
- [Deploying webhooks](./cronjob-tutorial/running-webhook.md)
29+
2530
- [Epilogue](./cronjob-tutorial/epilogue.md)
2631

2732
---
@@ -30,6 +35,10 @@
3035

3136
- [Generating CRD](./reference/generating-crd.md)
3237
- [Using Finalizers](./reference/using-finalizers.md)
38+
- [Kind cluster](reference/kind.md)
39+
- [What's a webhook?](reference/webhook-overview.md)
40+
41+
- [Admission webhook](reference/admission-webhook.md)
3342

3443
---
3544

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Deploying the cert manager
2+
3+
We suggest using [cert manager](https://github.com/jetstack/cert-manager) for
4+
provisioning the certificates for the webhook server. Other solutions should
5+
also work as long as they put the certificates in the desired location.
6+
7+
You can follow
8+
[the cert manager document](https://docs.cert-manager.io/en/latest/getting-started/install/kubernetes.html)
9+
to install it.
10+
11+
Cert manager also has a component called CA injector, which is responsible for
12+
injecting the CA bundle into the Mutating|ValidatingWebhookConfiguration.
13+
14+
To accomplish that, you need to use an annotation with key
15+
`certmanager.k8s.io/inject-ca-from`
16+
in the Mutating|ValidatingWebhookConfiguration objects.
17+
The value of the annotation should point to an existing certificate CR instance
18+
in the format of `<certificate-namespace>/<certificate-name>`.
19+
20+
This is the [kustomize](https://github.com/kubernetes-sigs/kustomize) patch we
21+
used for annotating the Mutating|ValidatingWebhookConfiguration objects.
22+
```yaml
23+
{{#include ./testdata/project/config/default/webhookcainjection_patch.yaml}}
24+
```

docs/book/src/cronjob-tutorial.md renamed to docs/book/src/cronjob-tutorial/cronjob-tutorial.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ use this as an opportunity to see how to interact with external types.
2121

2222
## Scaffolding Out Our Project
2323

24-
As covered in the [quick start](./quick-start.md), we'll need to scaffold
24+
As covered in the [quick start](../quick-start.md), we'll need to scaffold
2525
out a new project. Make sure you've [installed
26-
Kubebuilder](./quick-start.md#installation), then scaffold out a new
26+
Kubebuilder](../quick-start.md#installation), then scaffold out a new
2727
project:
2828

2929
```bash
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Deploying Admission Webhooks
2+
3+
## Kind Cluster
4+
5+
It is recommended to develop your webhook with a
6+
[kind](../reference/kind.md) cluster for faster iteration.
7+
Why?
8+
9+
- You can bring up a multi-node cluster locally within 1 minute.
10+
- You can tear it down in seconds.
11+
- You don't need to push your images to remote registry.
12+
13+
## Cert Manager
14+
15+
You need follow [this](./cert-manager.md) to install the cert manager bundle.
16+
17+
## Build your image
18+
19+
Run the following command to build your image locally.
20+
21+
```bash
22+
make docker-build
23+
```
24+
25+
You don't need to push the image to a remote container registry if you are using
26+
a kind cluster. You can directly load your local image to your kind cluster:
27+
28+
```bash
29+
kind load docker-image your-image-namge:your-tag
30+
```
31+
32+
## Deploy Webhooks
33+
34+
You need to enable the webhook and cert manager configuration through kustomize.
35+
`config/default/kustomization.yaml` should now look like the following:
36+
37+
```yaml
38+
{{#include ./testdata/project/config/default/kustomization.yaml}}
39+
```
40+
41+
Now you can deploy it to your cluster by
42+
43+
```bash
44+
make deploy
45+
```
46+
47+
Wait a while til the webhook pod comes up and the certificates are provisioned.
48+
It usually completes within 1 minute.
49+
50+
Now you can create a valid CronJob to test your webhooks. The creation should
51+
successfully go through.
52+
53+
```bash
54+
kubectl create -f config/samples/batch_v1_cronjob.yaml
55+
```
56+
57+
You can also try to create an invalid CronJob (e.g. use an ill-formatted
58+
schedule field). You should see a creation failure with a validation error.
59+
60+
**Note**: If you are
61+

docs/book/src/cronjob-tutorial/running.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Now that we've installed our CRDs, we can run the controller against our
1313
cluster. This will use whatever credentials that we connect to the
1414
cluster with, so we don't need to worry about RBAC just yet.
1515

16+
Note that if you have a webhook and want to deploy it locally, you need to
17+
ensure the certificates are in the right place.
18+
1619
In a separate terminal, run
1720

1821
```bash

docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,13 @@ the fields.
5757

5858
// CronJobSpec defines the desired state of CronJob
5959
type CronJobSpec struct {
60+
// +kubebuilder:validation:Minimum=0
61+
6062
// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
6163
Schedule string `json:"schedule"`
6264

65+
// +kubebuilder:validation:Minimum=0
66+
6367
// Optional deadline in seconds for starting the job if it misses scheduled
6468
// time for any reason. Missed jobs executions will be counted as failed ones.
6569
// +optional
@@ -81,11 +85,15 @@ type CronJobSpec struct {
8185
// Specifies the job that will be created when executing a CronJob.
8286
JobTemplate batchv1beta1.JobTemplateSpec `json:"jobTemplate"`
8387

88+
// +kubebuilder:validation:Minimum=0
89+
8490
// The number of successful finished jobs to retain.
8591
// This is a pointer to distinguish between explicit zero and not specified.
8692
// +optional
8793
SuccessfulJobsHistoryLimit *int32 `json:"successfulJobsHistoryLimit,omitempty"`
8894

95+
// +kubebuilder:validation:Minimum=0
96+
8997
// The number of failed finished jobs to retain.
9098
// This is a pointer to distinguish between explicit zero and not specified.
9199
// +optional
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
*/
15+
// +kubebuilder:docs-gen:collapse=Apache License
16+
17+
package v1
18+
19+
import (
20+
"github.com/robfig/cron"
21+
22+
apierrors "k8s.io/apimachinery/pkg/api/errors"
23+
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/runtime/schema"
25+
validationutils "k8s.io/apimachinery/pkg/util/validation"
26+
"k8s.io/apimachinery/pkg/util/validation/field"
27+
28+
ctrl "sigs.k8s.io/controller-runtime"
29+
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
30+
"sigs.k8s.io/controller-runtime/pkg/webhook"
31+
)
32+
33+
// +kubebuilder:docs-gen:collapse=Go imports
34+
35+
/*
36+
Next, we'll setup a logger for the webhooks.
37+
*/
38+
39+
var cronjoblog = logf.Log.WithName("cronjob-resource")
40+
41+
/*
42+
Then, we set up the webhook with the manager.
43+
*/
44+
45+
func (r *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error {
46+
return ctrl.NewWebhookManagedBy(mgr).
47+
For(r).
48+
Complete()
49+
}
50+
51+
/*
52+
Notice that we use kubebuilder markers to generate webhook manifests.
53+
This markers is responsible for generating a mutating webhook manifest.
54+
55+
The meaning of each marker can be found [here](../TODO.md).
56+
*/
57+
58+
// +kubebuilder:webhook:path=/mutate-batch-tutorial-kubebuilder-io-tutorial-kubebuilder-io-v1-cronjob,mutating=true,failurePolicy=fail,groups=batch.tutorial.kubebuilder.io.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=mcronjob.kb.io
59+
60+
/*
61+
We use the `webhook.Defaulter` interface to set defaults to our CRD.
62+
A webhook will automatically be served that calls this defaulting.
63+
64+
The `Default` method is expected to mutate the receiver, setting the defaults.
65+
*/
66+
67+
var _ webhook.Defaulter = &CronJob{}
68+
69+
// Default implements webhook.Defaulter so a webhook will be registered for the type
70+
func (r *CronJob) Default() {
71+
cronjoblog.Info("default", "name", r.Name)
72+
73+
if r.Spec.ConcurrencyPolicy == "" {
74+
r.Spec.ConcurrencyPolicy = AllowConcurrent
75+
}
76+
if r.Spec.Suspend == nil {
77+
r.Spec.Suspend = new(bool)
78+
}
79+
if r.Spec.SuccessfulJobsHistoryLimit == nil {
80+
r.Spec.SuccessfulJobsHistoryLimit = new(int32)
81+
*r.Spec.SuccessfulJobsHistoryLimit = 3
82+
}
83+
if r.Spec.FailedJobsHistoryLimit == nil {
84+
r.Spec.FailedJobsHistoryLimit = new(int32)
85+
*r.Spec.FailedJobsHistoryLimit = 1
86+
}
87+
}
88+
89+
/*
90+
Notice that we use kubebuilder markers to generate webhook manifests.
91+
This markers is responsible for generating a validating webhook manifest.
92+
93+
The meaning of each marker can be found [here](../TODO.md).
94+
*/
95+
96+
// +kubebuilder:webhook:path=/validate-batch-tutorial-kubebuilder-io-tutorial-kubebuilder-io-v1-cronjob,mutating=false,failurePolicy=fail,groups=batch.tutorial.kubebuilder.io.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=vcronjob.kb.io
97+
98+
/*
99+
To validate our CRD beyond what's possible with declarative validation.
100+
Generally, declarative validation should be sufficient, but sometimes more
101+
advanced use cases call for complex validation.
102+
103+
For instance, we'll see below that we use this to validate a well-formed cron
104+
schedule without making up a long regular expression.
105+
106+
If `webhook.Validator` interface is implemented, a webhook will automatically be
107+
served that calls the validation.
108+
109+
The `ValidateCreate` and `ValidateUpdate` methods are expected to validate that its
110+
receiver upon creation and update respectively. We separate out ValidateCreate
111+
from ValidateUpdate to allow behavior like making certain fields immutable, so
112+
that they can only be set on creation.
113+
Here, however, we just use the same shared validation.
114+
*/
115+
116+
var _ webhook.Validator = &CronJob{}
117+
118+
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
119+
func (r *CronJob) ValidateCreate() error {
120+
cronjoblog.Info("validate create", "name", r.Name)
121+
122+
return r.validateCronJob()
123+
}
124+
125+
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
126+
func (r *CronJob) ValidateUpdate(old runtime.Object) error {
127+
cronjoblog.Info("validate update", "name", r.Name)
128+
129+
return r.validateCronJob()
130+
}
131+
132+
/*
133+
We validate the name and the spec of the CronJob.
134+
*/
135+
136+
func (c *CronJob) validateCronJob() error {
137+
allErrs := c.validateCronJobSpec()
138+
if err := c.validateCronJobName(); err != nil {
139+
allErrs = append(allErrs, err)
140+
}
141+
142+
return apierrors.NewInvalid(
143+
schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"},
144+
c.Name, allErrs)
145+
}
146+
147+
/*
148+
Some fields are declaratively validated by OpenAPI schema.
149+
You can find kubebuilder validation markers (prefixed
150+
with `// +kubebuilder:validation`) in the [API](api-design.md)
151+
You can find all of the kubebuilder supported markers for
152+
declaring validation in [here](../TODO.md).
153+
*/
154+
155+
func (c *CronJob) validateCronJobSpec() field.ErrorList {
156+
// The field helpers from the kubernetes API machinery help us return nicely
157+
// structured validation errors.
158+
allErrs := field.ErrorList{}
159+
allErrs = append(allErrs, validateScheduleFormat(
160+
c.Spec.Schedule,
161+
field.NewPath("spec").Child("schedule"))...)
162+
return allErrs
163+
}
164+
165+
/*
166+
Validating the length of a string field can be done declaratively by
167+
the validation schema.
168+
169+
But the `ObjectMeta.Name` field is defined in a shared package under
170+
the apimachinery repo, so we can't declaratively validate it using
171+
the validation schema.
172+
*/
173+
174+
func (c *CronJob) validateCronJobName() *field.Error {
175+
if len(c.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 {
176+
// The job name length is 63 character like all Kubernetes objects
177+
// (which must fit in a DNS subdomain). The cronjob controller appends
178+
// a 11-character suffix to the cronjob (`-$TIMESTAMP`) when creating
179+
// a job. The job name length limit is 63 characters. Therefore cronjob
180+
// names must have length <= 63-11=52. If we don't validate this here,
181+
// then job creation will fail later.
182+
return field.Invalid(field.NewPath("metadata").Child("name"), c.Name, "must be no more than 52 characters")
183+
}
184+
return nil
185+
}
186+
187+
// +kubebuilder:docs-gen:collapse=Validate object name
188+
189+
/*
190+
We'll need to validate the [cron](https://en.wikipedia.org/wiki/Cron) schedule
191+
is well-formatted.
192+
*/
193+
194+
func validateScheduleFormat(schedule string, fldPath *field.Path) field.ErrorList {
195+
allErrs := field.ErrorList{}
196+
if _, err := cron.ParseStandard(schedule); err != nil {
197+
allErrs = append(allErrs, field.Invalid(fldPath, schedule, err.Error()))
198+
}
199+
200+
return allErrs
201+
}

0 commit comments

Comments
 (0)