Skip to content

Commit 959815d

Browse files
committed
📖 multiple version tutorial
1 parent 94c206b commit 959815d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2341
-2
lines changed

docs/book/src/SUMMARY.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
- [Deploying webhooks](./cronjob-tutorial/running-webhook.md)
2929

3030
- [Epilogue](./cronjob-tutorial/epilogue.md)
31+
- [Tutorial: Multi-version API](./multiversion-tutorial/tutorial.md)
32+
- [Concepts](./multiversion-tutorial/concepts.md)
33+
- [Implement APIs](./multiversion-tutorial/api-implementation.md)
34+
- [Deployment and Testing](./multiversion-tutorial/deployment.md)
3135

3236
---
3337

@@ -37,9 +41,7 @@
3741
- [Using Finalizers](./reference/using-finalizers.md)
3842
- [Kind cluster](reference/kind.md)
3943
- [What's a webhook?](reference/webhook-overview.md)
40-
4144
- [Admission webhook](reference/admission-webhook.md)
42-
4345
---
4446

4547
[Appendix: The TODO Landing Page](./TODO.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
## Prerequisites
2+
CRD conversion webhook support was introduced as alpha feature in Kubernetes 1.13
3+
release and has gone beta in Kubernetes 1.15 release. So ensure that you have a
4+
Kubernetes cluster that supports CRD conversion feature enabled.
5+
Refer to [instructions](../TODO.txt) to enable CRD conversion feature in your
6+
cluster.
7+
Refer to [instructions](../reference/kind.md) to setup a local cluster with
8+
Kind.
9+
10+
### What are we building ?
11+
In this tutorial, we will implement a simple Disk API. Disk API has a field
12+
called price that represents price per GB. We will go through three
13+
iterations to evolve the price field specification.
14+
15+
- In v1 version of Disk API, price field is string with "AMOUNT CURRENCY" format.
16+
Example values could be "10 USD", "100 USD".
17+
- In v2 version of Disk, price field is represented by a structure `Price`
18+
that has `amount` and `currency` as separate fields.
19+
- In v3 version of Disk, we rename the price field to `pricePerGB` to make it
20+
more explicit.
21+
22+
Here are some sample manifests of the three versions representing same Disk
23+
object.
24+
```yaml
25+
26+
apiVersion: infra.kubebuilder.io/v1
27+
kind: Disk
28+
metadata:
29+
name: disk-sample
30+
spec:
31+
price: 10 USD <--- price as string
32+
----------------------------------------
33+
34+
apiVersion: infra.kubebuilder.io/v2
35+
kind: Disk
36+
metadata:
37+
name: disk-sample
38+
spec:
39+
price: <---- price as structured object
40+
amount: 10
41+
currency: USD
42+
----------------------------------------
43+
44+
apiVersion: infra.kubebuilder.io/v3
45+
kind: Disk
46+
metadata:
47+
name: disk-sample
48+
spec:
49+
pricePerGB: <--- price is renamed to pricePerGB
50+
amount: 10
51+
currency: USD
52+
```
53+
54+
## Tutorial
55+
Now that we have covered the basics and the goal, we are all set to begin this
56+
tutorial. We will go through the following steps:
57+
58+
- Project Setup
59+
- Adding API with versions v1, v2, v3 of Disk API
60+
- Setting up Webhooks
61+
- CRD Generation
62+
- Configuring Kustomization
63+
- Deploying and testing
64+
65+
66+
### Project Setup
67+
Assuming you have created a new directory and cd in to it. Let's initialize the project.
68+
```bash
69+
70+
# Initialize Go module
71+
go mod init infra.kubebuilder.io
72+
73+
# Initilize Kubebuilder project
74+
kubebuilder init --domain kubebuilder.io
75+
76+
```
77+
78+
79+
### Version v1
80+
81+
Let's create version `v1` of our `Disk` API.
82+
83+
```bash
84+
85+
# create v1 version with resource and controller
86+
kubebuilder create api --group infra --kind Disk --version v1
87+
Create Resource [y/n]
88+
y
89+
Create Controller [y/n]
90+
y
91+
```
92+
93+
Let's take a look at file `api/v1/disk_types.go`.
94+
95+
{{#literatego ./testdata/project/api/v1/disk_types.go}}
96+
97+
### Version v2
98+
99+
Let's add version `v2` to the `Disk` API. We will not add any controller this
100+
time because we already have a controller for `Disk` API.
101+
102+
```bash
103+
# create v2 version without controller
104+
kubebuilder create api --group infra --kind Disk --version v2
105+
Create Resource [y/n]
106+
y
107+
Create Controller [y/n]
108+
n
109+
```
110+
111+
Now, let's take a look at file `api/v2/disk_types.go`.
112+
113+
{{#literatego ./testdata/project/api/v2/disk_types.go}}
114+
115+
### Version v3
116+
117+
Let's add version `v3` to the `Disk` API and once again, we will not add any
118+
controller since we already have controller for the `Disk` API.
119+
120+
```bash
121+
# create v3 version without controller
122+
kubebuilder create api --group infra --kind Disk --version v3
123+
Create Resource [y/n]
124+
y
125+
Create Controller [y/n]
126+
n
127+
128+
```
129+
130+
{{#literatego ./testdata/project/api/v3/disk_types.go}}
131+
132+
Now that we have all the API implementations in place, let's take a look at what
133+
is required to setup conversion webhook for our `Disk` API.
134+
135+
### Setting up Webhooks
136+
In `2.0.0+` release, Kubebuilder introduced new command `create webhook` to make
137+
it easy to setup admission and conversion webhooks. Run the following command to
138+
setup conversion webhook. Note that we can specify any version from v1, v2 or v3
139+
in this command because there is single conversion webhook for a Kind.
140+
141+
```bash
142+
kubebuilder create webhook --group infra --kind Disk --version v1 --conversion
143+
144+
Writing scaffold for you to edit...
145+
api/v1/disk_webhook.go
146+
```
147+
Above commands does the following:
148+
- Scaffolds a new file `api/v1/disk_webhook.go` to implement webhook setup method.
149+
- Updates `main.go` to setup webhooks with the manager instance.
150+
151+
Let's take a quick look at the `api/v1/disk_webhook.go` file.
152+
153+
{{#literatego ./testdata/project/api/v1/disk_webhook.go}}
154+
155+
If you look at `main.go`, you will notice the following snippet that invokes the
156+
SetupWebhook method.
157+
```Go
158+
.....
159+
160+
if err = (&infrav1.Disk{}).SetupWebhookWithManager(mgr); err != nil {
161+
setupLog.Error(err, "unable to create webhook", "webhook", "Disk")
162+
os.Exit(1)
163+
}
164+
165+
....
166+
```
167+
168+
### CRD Generation
169+
170+
The `controller-gen` tool that generates the CRD manifest takes a parameter to indicate if our API has multiple versions. We need to specify `trivialVersions=false` in CRD_OPTIONS in your project's Makefile to enable multi-version.
171+
172+
``` bash
173+
...
174+
CRD_OPTIONS ?= "crd:trivialVersions=false"
175+
...
176+
```
177+
178+
Run `make manifests` to ensure that CRD manifests gets generated under `config/crd/bases/` directory.
179+
[TODO](../TODO.md) embed a compressed form of the generated CRD `testdata/project/config/crd`
180+
181+
### Manifests Generation
182+
183+
Kubebuilder generates Kubernetes manifests under 'config' directory with webhook
184+
bits disabled. Follow the steps below to enable conversion webhook in manifests
185+
generation.
186+
187+
- Ensure that `patches/webhook_in_<kind>.yaml` and `patches/cainjection_in_<kind>.yaml` are enabled in `config/crds/kustomization.yaml` file.
188+
- Ensure that `../certmanager` and `../webhook` directories are enabled under `bases` section in `config/default/kustomization.yaml` file.
189+
- Ensure that `manager_webhook_patch.yaml` is enabled under `patches` section in `config/default/kustomization.yaml` file.
190+
- Enable all the vars under section `CERTMANAGER` in `config/default/kustomization.yaml` file.
191+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
## Concepts
3+
Let's take a quick look at some of the basic concepts related to multiple
4+
versions in Kubernetes.
5+
6+
### Hub/Spoke/Storage Versions
7+
All versions of a Kubernetes object share the same underlying storage. So if you
8+
have versions v1, v2 and v3 of a kind, kubernetes will use one of the versions to
9+
persist the object of that Kind in Etcd. You can specify the version to be used
10+
for storage in the Custom Resource definition for that API.
11+
12+
One can think of storage version as the hub and other versions as spokes to visualize the
13+
relationship between storage and other versions (as shown below in the diagram).
14+
The important thing to note is that conversion between storage and other version
15+
should be lossless (round trippable). As shown in the diagram below, v1 is the
16+
storage/hub version and v2 and v3 spoke version. This tutorial uses
17+
the terms `storage version` and `hub` interchangeably.
18+
19+
![conversion-image](./conversion-diagram.png)
20+
21+
So if each spoke version implements conversion functions to convert
22+
to/from a `hub`, then conversions betweek spokes can be derived. In the example
23+
shown in the above diagram, a v2 object can be converted to v3 object by first
24+
converting `v2` to `v1` and then converting `v2` to `v3`. And same is true for
25+
converting `v3` object to `v2`.
26+
27+
### Conversion Webhook
28+
API clients such as kubectl, controllers can request different versions of your
29+
API. So when a client requests for a version other than the storage version of
30+
your API, Kubernetes API server calls out to an HTTP endpoint to perform the
31+
conversion between the requested version and storage version. This HTTP endpoint
32+
is called Conversion Webhook and its discovery/connection parameters are
33+
configured in the CRD definition. With kubebuilder, you just need to implement
34+
conversion functions between different versions and it takes care of the rest of
35+
the work associated with running a webhook server, generating and plumbing the
36+
conversion webhook handler.
37+
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
### Deployment
2+
3+
Now we have all our code changes and manifests in place, so let's deploy it to
4+
the cluster and test it out.
5+
6+
Ensure that you have installed [cert-manager](../cronjob-tutorial/cert-manager.md) `0.9.0+` version in your cluster. We have
7+
tested the instructions in this tutorial with [0.9.0-alpha.0](https://github.com/jetstack/cert-manager/releases/tag/v0.9.0-alpha.0) release.
8+
9+
Running `make deploy` will deploy the controller-manager in the cluster.
10+
11+
### Testing
12+
13+
Now that we have deployed the controller-manager with conversion webhook enabled, let's test out the version conversion feature.
14+
We will do the following to perform a simple version conversion test:
15+
16+
- Create disk object named `disk-sample` using v1 specification
17+
- Get disk object `disk-sample` using v2 version
18+
- Get disk object `disk-sample` using v3 version
19+
20+
#### 1. Create v1 disk object
21+
22+
```yaml
23+
{{#include ./testdata/project/config/samples/infra_v1_disk.yaml}}
24+
```
25+
26+
```bash
27+
kubectl apply -f config/samples/infra_v1_disk.yaml
28+
```
29+
30+
#### 2. Get disk object using v2 version
31+
32+
```bash
33+
kubectl get disks.v2.infra.kubebuilder.io/disk-sample -o yaml
34+
```
35+
36+
```yaml
37+
apiVersion: infra.kubebuilder.io/v2 <-- note the v2 version
38+
kind: Disk
39+
metadata:
40+
name: disk-sample
41+
selfLink: /apis/infra.kubebuilder.io/v2/namespaces/default/disks/disk-sample
42+
uid: 0e9be0fd-a284-11e9-bbbe-42010a8001af
43+
spec:
44+
price: <-- note the structured price object
45+
amount: 10
46+
currency: USD
47+
status: {}
48+
```
49+
50+
51+
#### 3. Get disk object using v3 version
52+
53+
```bash
54+
kubectl get disks.v3.infra.kubebuilder.io/disk-sample -o yaml
55+
```
56+
```yaml
57+
apiVersion: infra.kubebuilder.io/v3 <-- note the v3 version
58+
kind: Disk
59+
metadata:
60+
name: disk-sample
61+
selfLink: /apis/infra.kubebuilder.io/v3/namespaces/default/disks/disk-sample
62+
uid: 0e9be0fd-a284-11e9-bbbe-42010a8001af <-- note the same uid as v2
63+
....
64+
spec:
65+
pricePerGB: <-- note the pricePerGB name of the field
66+
amount: 10
67+
currency: USD
68+
status: {}
69+
```
70+
71+
72+
### Troubleshooting
73+
TODO(../TODO.md) steps for troubleshoting
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
# Binaries for programs and plugins
3+
*.exe
4+
*.exe~
5+
*.dll
6+
*.so
7+
*.dylib
8+
bin
9+
10+
# Test binary, build with `go test -c`
11+
*.test
12+
13+
# Output of the go coverage tool, specifically when used with LiteIDE
14+
*.out
15+
16+
# Kubernetes Generated files - skip generated files, except for vendored files
17+
18+
!vendor/**/zz_generated.*
19+
20+
# editor and IDE paraphernalia
21+
.idea
22+
*.swp
23+
*.swo
24+
*~
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Build the manager binary
2+
FROM golang:1.12.5 as builder
3+
4+
WORKDIR /workspace
5+
# Copy the Go Modules manifests
6+
COPY go.mod go.mod
7+
COPY go.sum go.sum
8+
# cache deps before building and copying source so that we don't need to re-download as much
9+
# and so that source changes don't invalidate our downloaded layer
10+
RUN go mod download
11+
12+
# Copy the go source
13+
COPY main.go main.go
14+
COPY api/ api/
15+
COPY controllers/ controllers/
16+
17+
# Build
18+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go
19+
20+
# Use distroless as minimal base image to package the manager binary
21+
# Refer to https://github.com/GoogleContainerTools/distroless for more details
22+
FROM gcr.io/distroless/static:latest
23+
WORKDIR /
24+
COPY --from=builder /workspace/manager .
25+
ENTRYPOINT ["/manager"]

0 commit comments

Comments
 (0)