Skip to content

Commit 52ba0c3

Browse files
committed
New Book
This introduces a new version of the book. The initial portion is built around a tutorial building the cronjob controller. It now uses [mdbook](https://crates.io/crates/mdbook), which is like gitbook but maintained and written in rust. We've got a custom "plugin" (executable) that slurps Go files into book pages for parts of the tutorial (look for `{{#literatego ./path/to/thing}}`).
1 parent e46e06e commit 52ba0c3

Some content is hidden

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

69 files changed

+8690
-76
lines changed

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.idea/
22

3-
# Not check in node_modules to a specific arch and os.
4-
docs/book/node_modules/
3+
# don't check in the build output of the book
4+
docs/book/book/
55

66
# Editor temp files
77
*~

build/thirdparty/brodocs/Dockerfile

-17
This file was deleted.

build/thirdparty/brodocs/runbrodocs.sh

-6
This file was deleted.

docs/book/book.json

-18
This file was deleted.

docs/book/book.toml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[book]
2+
authors = ["The Kubebuilder Maintainers"]
3+
multilingual = false
4+
src = "src"
5+
title = "The Kubebuilder Book"
6+
7+
[output.html]
8+
google-analytics = "UA-119864590-1"
9+
curly-quotes = true
10+
11+
[preprocessor.literatego]
12+
command = "./litgo.sh"

docs/book/install-and-build.sh

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
os=$(go env GOOS)
4+
arch=$(go env GOARCH)
5+
6+
# grab mdbook
7+
# mdbook's deploy got borked by a CI move, so grab our build till that gets
8+
# fixed (https://github.com/rust-lang-nursery/mdBook/issues/904).
9+
curl -sL -o /tmp/mdbook https://storage.googleapis.com/kubebuilder-build-tools/mdbook-0.2.3-${os}-${arch}
10+
chmod +x /tmp/mdbook
11+
/tmp/mdbook build

docs/book/litgo.sh

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
set -ex
4+
5+
(
6+
pushd ./utils
7+
go build -o ../../../bin/literate-go ./literate.go
8+
popd
9+
) &>/dev/null
10+
11+
../../bin/literate-go "$@"

docs/book/src/SUMMARY.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Summary
2+
3+
[Introduction](./introduction.md)
4+
5+
[Quick Start](./quick-start.md)
6+
7+
---
8+
9+
- [Tutorial: Building CronJob](./cronjob-tutorial.md)
10+
11+
- [What's in a basic project?](./cronjob-tutorial/basic-project.md)
12+
- [Every journey needs a start, every program a main](./cronjob-tutorial/empty-main.md)
13+
- [Groups and Versions and Kinds, oh my!](./cronjob-tutorial/gvks.md)
14+
- [Adding a new API](./cronjob-tutorial/new-api.md)
15+
- [Designing an API](./cronjob-tutorial/api-design.md)
16+
17+
- [A Brief Aside: What's the rest of this stuff?](./cronjob-tutorial/other-api-files.md)
18+
19+
- [What's in a controller?](./cronjob-tutorial/controller-overview.md)
20+
- [Implementing a controller](./cronjob-tutorial/controller-implementation.md)
21+
22+
- [You said something about main?](./cronjob-tutorial/main-revisited.md)
23+
24+
- [Running and deploying the controller](./cronjob-tutorial/running.md)
25+
- [Epilogue](./cronjob-tutorial/epilogue.md)
26+
27+
---
28+
29+
[Appendix: The TODO Landing Page](./TODO.md)

docs/book/src/TODO.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# TODO
2+
3+
If you're seeing this page, it's probably because something's not done in
4+
the book yet. Go [see if anyone else has found
5+
this](https://github.com/kubernetes-sigs/kubebuilder/issues?q=is%3Aopen+is%3Aissue+label%3Akind%2Fdocumentation)
6+
or [bug the
7+
maintainers](https://github.com/kubernetes-sigs/kubebuilder/issues/new?assignees=&labels=kind%2Fdocumentation).

docs/book/src/cronjob-tutorial.md

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Tutorial: Building CronJob
2+
3+
Too many tutorials start out with some really contrived setup, or some toy
4+
application that gets the basics across, and then stalls out on the more
5+
complicated suff. Instead, this tutorial should take you through (almost)
6+
the full gamut of complexity with Kubebuilder, starting off simple and
7+
building up to something pretty full-featured.
8+
9+
Let's pretend (and sure, this is a teensy bit contrived) that we've
10+
finally gotten tired of the maintenance burden of the non-Kubebuilder
11+
implementation of the CronJob controller in Kuberntes, and we'd like to
12+
rewrite it using KubeBuilder.
13+
14+
The job (no pun intended) of the *CronJob* controller is to run one-off
15+
tasks on the Kubernetes cluster at regular intervals. It does the by
16+
bulding on top of the *Job* controller, whose task is to run one-off tasks
17+
once, seeing them to completion.
18+
19+
Instead of trying to tackle rewriting the Job controller as well, we'll
20+
use this as an opportunity to see how to interact with external types.
21+
22+
## Scaffolding Out Our Project
23+
24+
As covered in the [quick start](./quick-start.md), we'll need to scaffold
25+
out a new project. Make sure you've [installed
26+
Kubebuilder](./quick-start.md#installation), then scaffold out a new
27+
project:
28+
29+
```bash
30+
# we'll use a domain of tutorial.kubebuilder.io,
31+
# so all API groups will be <group>.tutorial.kubebuilder.io.
32+
kubebuilder init --domain tutorial.kubebuilder.io
33+
```
34+
35+
Now that we've got've a project in place, let's take a look at what
36+
Kubebuilder has scaffolded for us so far...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Designing an API
2+
3+
In Kubernetes, we have a few rules for how we design APIs. Namely, all
4+
serialized fields *must* be `camelCase`, so we use JSON struct tags to
5+
specify this. We can also use the `omitempty` struct tag to mark that
6+
a field should be omitted from serialization when empty.
7+
8+
Fields may use most of the primitive types. Numbers are the exception:
9+
for API compatibility purposes, we accept two forms of numbers: `int32`
10+
for integers, and `resource.Quantity` for decimals.
11+
12+
<details><summary>Hold up, what's a Quantity?</summary>
13+
14+
Quantities are a special notation for decimal numbers that have an
15+
explicitly fixed representation that makes them more portable across
16+
machines. You've probably noticed them when specifying resources requests
17+
and limits on pods in Kubernetes.
18+
19+
They conceptually work similar to floating point numbers: they have
20+
a significand, base, and exponent. Their serialize, human readable for
21+
uses whole numbers and suffixes to specify values much the way we describe
22+
computer storage.
23+
24+
For instance, the value `2m` means `0.002` in decimal notation. `2Ki`
25+
means `2048` in decimal, while `2K` means `2000` in decimal. If we want
26+
to specify fractions, we switch to a suffix that lets us use a whole
27+
number: `2.5` is `2500m`.
28+
29+
There are two supported bases: 10 and 2 (called decimal and binary,
30+
respectively). Decimal base is indicated with "normal" SI suffixes (e.g.
31+
`M` and `K`), while Binary base is specified in "mebi" notation (e.g. `Mi`
32+
and `Ki`). Think [megabytes vs mebibytes](../TODO.md).
33+
34+
</details>
35+
36+
There's one other special type that we use: `metav1.Time`. This functions
37+
identically to `time.Time`, except that it has a fixed, portable
38+
serialization format.
39+
40+
With that out of the way, let's take a look at what our CronJob object
41+
looks like!
42+
43+
{{#literatego ./testdata/project/api/v1/cronjob_types.go}}
44+
45+
Now that we have an API, we'll need to write a controller to actually
46+
implement the functionality.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# What's in a basic project?
2+
3+
When scaffolding out a new project, Kubebuilder provides us with a few
4+
basic pieces of boilerplate.
5+
6+
## Build Infrastructure
7+
8+
First up, basic infrastructure for building you project:
9+
10+
<details><summary>`go.mod`: A new Go module matching our project, with basic dependencies</summary>
11+
12+
```go
13+
{{#include ./testdata/project/go.mod}}
14+
```
15+
</details>
16+
17+
<details><summary>`Makefile`: Make targets for building and deploying your controller</summary>
18+
```makefile
19+
{{#include ./testdata/project/Makefile}}
20+
```
21+
</details>
22+
23+
<details><summary>`PROJECT`: Kubebuilder metadata for scaffolding new components</summary>
24+
```yaml
25+
{{#include ./testdata/project/PROJECT}}
26+
```
27+
</details>
28+
29+
## Launch Configuration
30+
31+
We also get launch configuration under the
32+
[`config/`](https://sigs.k8s.io/kubebuilder.io/docs/book/cronjob-tutorial/testdata/project/config)
33+
directory. Right now, it just contains
34+
[Kustomize](https://sigs.k8s.io/kustomize) YAML definitions required to
35+
launch our controller on a cluster, but once we get started writing our
36+
controller, it'll also hold our CustomResourceDefinitions, RBAC
37+
configuration, and WebhookConfigurations.
38+
39+
[`config/default`](../TODO.md) contains a [Kustomize base](../TODO.md) for launching
40+
the controller in a standard configuration.
41+
42+
Each other directory contains a different piece of configuration,
43+
refactored out into its own base:
44+
45+
- [`config/manager`](../TODO.md): launch your controllers as pods in the
46+
cluster
47+
48+
- [`config/rbac`](../TODO.md): permissions required to run your
49+
controllers under their own service account
50+
51+
## The Entrypoint
52+
53+
Last, but certainly not least, Kubebuilder scaffolds out the basic
54+
entrypoint of our project: `main.go`. Let's take a look at that next...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Implementing a controller
2+
3+
The basic logic of our CronJob controller is this:
4+
5+
1. Load the named CronJob
6+
7+
2. List all active jobs, and update the status
8+
9+
3. Clean up old jobs according to the history limits
10+
11+
4. Check if we're supsended (and don't do anything else if we are)
12+
13+
5. Get the next scheduled run
14+
15+
6. Run a new job if it's on schedule, not past the deadline, and not
16+
blocked by our concurrency policy
17+
18+
7. Requeue when we either see a running job (done automatically) or it's
19+
time for the next scheduled run.
20+
21+
{{#literatego ./testdata/project/controllers/cronjob_controller.go}}
22+
23+
That was a doozy, but now we've got a working controller. Let's test
24+
against the cluster, then, if we don't have any issues, deploy it!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# What's in a controller?
2+
3+
Controllers are the core of Kubernetes, and of any operator.
4+
5+
It's a controller's job to ensure that, for any given object, the actual
6+
state of the world (both the cluster state, and potentially external state
7+
like running containers for Kubelet or loadbalancers for a cloud provider)
8+
matches the desired state in the object. Each controller focuses on one
9+
*root* Kind, but may interact with other Kinds.
10+
11+
We call this process *reconciling*.
12+
13+
In controller-runtime, the logic that implements the reconciling for
14+
a specific kind is called a [*Reconciler*](../TODO.md). A reconciler
15+
takes the name of an object, and returns whether or not we need to try
16+
again (e.g. in case of errors or periodic controllers, like the
17+
HorizontalPodAutoscaler).
18+
19+
{{#literatego ./testdata/emptycontroller.go}}
20+
21+
Now that we've seen the basic structure of a reconciler, let's fill out
22+
the logic for `CronJob`s.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Every journey needs a start, every program a main
2+
3+
{{#literatego ./testdata/emptymain.go}}
4+
5+
With that out of the way, we can get on to scaffolding our API!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Epilogue
2+
3+
Things left to do:
4+
5+
- Write custom printer columns
6+
- Discuss webhooks
7+
- Use different watches
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Groups and Versions and Kinds, oh my!
2+
3+
Actually, before we get started with our API, we should talk terminology
4+
a bit.
5+
6+
When we talk about APIs in Kubernetes, we often use 4 terms: *groups*,
7+
*versions*, *kinds*, and *resources*.
8+
9+
## Groups and Versions
10+
11+
An *API Group* in Kubernetes is simply a collection of related
12+
functionality. Each group has one or more *versions*, which, as the name
13+
suggests, allow us to change how an API works over time.
14+
15+
## Kinds and Resources
16+
17+
Each API group-version contains one or more API types, which we call
18+
*Kinds*. While a Kind may change forms between versions, each form must
19+
be able to store all the data of the other forms, somehow (we can store
20+
the data in fields, or in annotations). This means that using an older
21+
API version won't cause newer data to be lost or corrupted. See the
22+
[Kubernetes API guidelines](../TODO.md) for more information.
23+
24+
You'll also hear mention of *resources* on occaison. A resource is simply
25+
a use of a Kind in the API. Often, there's a one-to-one mapping between
26+
Kinds and resources. For instance, the `pods` resource corresponds to the
27+
`Pod` Kind. However, sometimes, the same Kind may be returned by multiple
28+
resources. For instance, the `Scale` Kind is returned by all scale
29+
subresources, like `deployments/scale` or `replicasets/scale`. This is
30+
what allows the Kubernetes HorizontalPodAutoscaler to interact with
31+
different resources. With CRDs, however, each Kind will correspond to
32+
a single resource.
33+
34+
Notice that resources are always lowercase, and by convention are the
35+
lowercase form of the Kind.
36+
37+
## So, how does that correspond to Go?
38+
39+
When we refer to a kind in a particular group-version, we'll call it
40+
a *GroupVersionKind*, or GVK for short. Same with resources and GVR. As
41+
we'll see shortly, each GVK corresponds to a given root Go type in
42+
a package.
43+
44+
Now that we have our terminology straight, we can *actually* create our
45+
API!
46+
47+
## Err, but what's that Scheme thing?
48+
49+
The `Scheme` we saw before is simply a way to keep track of what Go type
50+
corresponds to a given GVK.
51+
52+
For instance, suppose we mark that the
53+
`"tutorial.kubebuilder.io/api/v1".CronJob{}` type as being in the
54+
`batch.tutorial.kubebuilder.io/v1` API group (implicitly saying it has the
55+
Kind `CronJob`).
56+
57+
Then, we can later construct a new `&CronJob{}` given some JSON from the
58+
API server that says
59+
60+
```json
61+
{
62+
"kind": "CronJob",
63+
"apiVersion": "batch.tutorial.kubebuilder.io/v1",
64+
...
65+
}
66+
```
67+
68+
or properly look up the group-version when we go to submit a `&CronJob{}`
69+
in an update.

0 commit comments

Comments
 (0)