Skip to content
Browse files

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](, 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
  • Loading branch information...
DirectXMan12 committed May 13, 2019
1 parent e46e06e commit 52ba0c36d023f5cc53646307cbe5a500c171d3b4
Showing with 8,690 additions and 76 deletions.
  1. +2 −2 .gitignore
  2. +0 −17 build/thirdparty/brodocs/Dockerfile
  3. +0 −6 build/thirdparty/brodocs/
  4. +0 −18 docs/book/book.json
  5. +12 −0 docs/book/book.toml
  6. +11 −0 docs/book/
  7. +11 −0 docs/book/
  8. +29 −0 docs/book/src/
  9. +7 −0 docs/book/src/
  10. +36 −0 docs/book/src/
  11. +46 −0 docs/book/src/cronjob-tutorial/
  12. +54 −0 docs/book/src/cronjob-tutorial/
  13. +24 −0 docs/book/src/cronjob-tutorial/
  14. +22 −0 docs/book/src/cronjob-tutorial/
  15. +5 −0 docs/book/src/cronjob-tutorial/
  16. +7 −0 docs/book/src/cronjob-tutorial/
  17. +69 −0 docs/book/src/cronjob-tutorial/
  18. +9 −0 docs/book/src/cronjob-tutorial/
  19. +28 −0 docs/book/src/cronjob-tutorial/
  20. +28 −0 docs/book/src/cronjob-tutorial/
  21. +51 −0 docs/book/src/cronjob-tutorial/
  22. +97 −0 docs/book/src/cronjob-tutorial/testdata/emptyapi.go
  23. +99 −0 docs/book/src/cronjob-tutorial/testdata/emptycontroller.go
  24. +93 −0 docs/book/src/cronjob-tutorial/testdata/emptymain.go
  25. +24 −0 docs/book/src/cronjob-tutorial/testdata/project/.gitignore
  26. +18 −0 docs/book/src/cronjob-tutorial/testdata/project/Dockerfile
  27. +64 −0 docs/book/src/cronjob-tutorial/testdata/project/Makefile
  28. +3 −0 docs/book/src/cronjob-tutorial/testdata/project/PROJECT
  29. +177 −0 docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_types.go
  30. +50 −0 docs/book/src/cronjob-tutorial/testdata/project/api/v1/groupversion_info.go
  31. +125 −0 docs/book/src/cronjob-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go
  32. +24 −0 docs/book/src/cronjob-tutorial/testdata/project/config/certmanager/certificate.yaml
  33. +13 −0 docs/book/src/cronjob-tutorial/testdata/project/config/certmanager/kustomization.yaml
  34. +16 −0 docs/book/src/cronjob-tutorial/testdata/project/config/certmanager/kustomizeconfig.yaml
  35. +5,533 −0 ...rc/cronjob-tutorial/testdata/project/config/crd/bases/batch.tutorial.kubebuilder.io_cronjobs.yaml
  36. +15 −0 docs/book/src/cronjob-tutorial/testdata/project/config/crd/kustomization.yaml
  37. +14 −0 docs/book/src/cronjob-tutorial/testdata/project/config/crd/kustomizeconfig.yaml
  38. +18 −0 docs/book/src/cronjob-tutorial/testdata/project/config/crd/patches/webhook_in_cronjob.yaml
  39. +40 −0 docs/book/src/cronjob-tutorial/testdata/project/config/default/kustomization.yaml
  40. +24 −0 docs/book/src/cronjob-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml
  41. +12 −0 docs/book/src/cronjob-tutorial/testdata/project/config/default/manager_image_patch.yaml
  42. +19 −0 docs/book/src/cronjob-tutorial/testdata/project/config/default/manager_prometheus_metrics_patch.yaml
  43. +24 −0 docs/book/src/cronjob-tutorial/testdata/project/config/default/manager_webhook_patch.yaml
  44. +15 −0 docs/book/src/cronjob-tutorial/testdata/project/config/default/webhookcainjection_patch.yaml
  45. +17 −0 docs/book/src/cronjob-tutorial/testdata/project/config/manager/kustomization.yaml
  46. +72 −0 docs/book/src/cronjob-tutorial/testdata/project/config/manager/manager.yaml
  47. +13 −0 docs/book/src/cronjob-tutorial/testdata/project/config/rbac/auth_proxy_role.yaml
  48. +12 −0 docs/book/src/cronjob-tutorial/testdata/project/config/rbac/auth_proxy_role_binding.yaml
  49. +20 −0 docs/book/src/cronjob-tutorial/testdata/project/config/rbac/auth_proxy_service.yaml
  50. +9 −0 docs/book/src/cronjob-tutorial/testdata/project/config/rbac/kustomization.yaml
  51. +44 −0 docs/book/src/cronjob-tutorial/testdata/project/config/rbac/role.yaml
  52. +12 −0 docs/book/src/cronjob-tutorial/testdata/project/config/rbac/role_binding.yaml
  53. +7 −0 docs/book/src/cronjob-tutorial/testdata/project/config/samples/batch_v1_cronjob.yaml
  54. +5 −0 docs/book/src/cronjob-tutorial/testdata/project/config/webhook/kustomization.yaml
  55. +25 −0 docs/book/src/cronjob-tutorial/testdata/project/config/webhook/kustomizeconfig.yaml
  56. +567 −0 docs/book/src/cronjob-tutorial/testdata/project/controllers/cronjob_controller.go
  57. +13 −0 docs/book/src/cronjob-tutorial/testdata/project/go.mod
  58. +185 −0 docs/book/src/cronjob-tutorial/testdata/project/go.sum
  59. +15 −0 docs/book/src/cronjob-tutorial/testdata/project/hack/boilerplate.go.txt
  60. +97 −0 docs/book/src/cronjob-tutorial/testdata/project/main.go
  61. +46 −0 docs/book/src/
  62. +106 −0 docs/book/src/
  63. +3 −0 docs/book/utils/go.mod
  64. +228 −0 docs/book/utils/literate.go
  65. +164 −0 docs/book/utils/plugin/input.go
  66. +55 −0 docs/book/utils/plugin/plugin.go
  67. +1 −10 go.mod
  68. +2 −23 go.sum
  69. +4 −0 netlify.toml
@@ -1,7 +1,7 @@

# Not check in node_modules to a specific arch and os.
# don't check in the build output of the book

# Editor temp files

This file was deleted.

Oops, something went wrong.

This file was deleted.

Oops, something went wrong.

This file was deleted.

Oops, something went wrong.
@@ -0,0 +1,12 @@
authors = ["The Kubebuilder Maintainers"]
multilingual = false
src = "src"
title = "The Kubebuilder Book"

google-analytics = "UA-119864590-1"
curly-quotes = true

command = "./"
@@ -0,0 +1,11 @@

os=$(go env GOOS)
arch=$(go env GOARCH)

# grab mdbook
# mdbook's deploy got borked by a CI move, so grab our build till that gets
# fixed (
curl -sL -o /tmp/mdbook${os}-${arch}
chmod +x /tmp/mdbook
/tmp/mdbook build
@@ -0,0 +1,11 @@

set -ex

pushd ./utils
go build -o ../../../bin/literate-go ./literate.go
) &>/dev/null

../../bin/literate-go "$@"
@@ -0,0 +1,29 @@
# Summary


[Quick Start](./


- [Tutorial: Building CronJob](./

- [What's in a basic project?](./cronjob-tutorial/
- [Every journey needs a start, every program a main](./cronjob-tutorial/
- [Groups and Versions and Kinds, oh my!](./cronjob-tutorial/
- [Adding a new API](./cronjob-tutorial/
- [Designing an API](./cronjob-tutorial/

- [A Brief Aside: What's the rest of this stuff?](./cronjob-tutorial/

- [What's in a controller?](./cronjob-tutorial/
- [Implementing a controller](./cronjob-tutorial/

- [You said something about main?](./cronjob-tutorial/

- [Running and deploying the controller](./cronjob-tutorial/
- [Epilogue](./cronjob-tutorial/


[Appendix: The TODO Landing Page](./
@@ -0,0 +1,7 @@

If you're seeing this page, it's probably because something's not done in
the book yet. Go [see if anyone else has found
or [bug the
@@ -0,0 +1,36 @@
# Tutorial: Building CronJob

Too many tutorials start out with some really contrived setup, or some toy
application that gets the basics across, and then stalls out on the more
complicated suff. Instead, this tutorial should take you through (almost)
the full gamut of complexity with Kubebuilder, starting off simple and
building up to something pretty full-featured.

Let's pretend (and sure, this is a teensy bit contrived) that we've
finally gotten tired of the maintenance burden of the non-Kubebuilder
implementation of the CronJob controller in Kuberntes, and we'd like to
rewrite it using KubeBuilder.

The job (no pun intended) of the *CronJob* controller is to run one-off
tasks on the Kubernetes cluster at regular intervals. It does the by
bulding on top of the *Job* controller, whose task is to run one-off tasks
once, seeing them to completion.

Instead of trying to tackle rewriting the Job controller as well, we'll
use this as an opportunity to see how to interact with external types.

## Scaffolding Out Our Project

As covered in the [quick start](./, we'll need to scaffold
out a new project. Make sure you've [installed
Kubebuilder](./, then scaffold out a new

# we'll use a domain of,
# so all API groups will be <group>
kubebuilder init --domain

Now that we've got've a project in place, let's take a look at what
Kubebuilder has scaffolded for us so far...
@@ -0,0 +1,46 @@
# Designing an API

In Kubernetes, we have a few rules for how we design APIs. Namely, all
serialized fields *must* be `camelCase`, so we use JSON struct tags to
specify this. We can also use the `omitempty` struct tag to mark that
a field should be omitted from serialization when empty.

Fields may use most of the primitive types. Numbers are the exception:
for API compatibility purposes, we accept two forms of numbers: `int32`
for integers, and `resource.Quantity` for decimals.

<details><summary>Hold up, what's a Quantity?</summary>

Quantities are a special notation for decimal numbers that have an
explicitly fixed representation that makes them more portable across
machines. You've probably noticed them when specifying resources requests
and limits on pods in Kubernetes.

They conceptually work similar to floating point numbers: they have
a significand, base, and exponent. Their serialize, human readable for
uses whole numbers and suffixes to specify values much the way we describe
computer storage.

For instance, the value `2m` means `0.002` in decimal notation. `2Ki`
means `2048` in decimal, while `2K` means `2000` in decimal. If we want
to specify fractions, we switch to a suffix that lets us use a whole
number: `2.5` is `2500m`.

There are two supported bases: 10 and 2 (called decimal and binary,
respectively). Decimal base is indicated with "normal" SI suffixes (e.g.
`M` and `K`), while Binary base is specified in "mebi" notation (e.g. `Mi`
and `Ki`). Think [megabytes vs mebibytes](../


There's one other special type that we use: `metav1.Time`. This functions
identically to `time.Time`, except that it has a fixed, portable
serialization format.

With that out of the way, let's take a look at what our CronJob object
looks like!

{{#literatego ./testdata/project/api/v1/cronjob_types.go}}

Now that we have an API, we'll need to write a controller to actually
implement the functionality.
@@ -0,0 +1,54 @@
# What's in a basic project?

When scaffolding out a new project, Kubebuilder provides us with a few
basic pieces of boilerplate.

## Build Infrastructure

First up, basic infrastructure for building you project:

<details><summary>`go.mod`: A new Go module matching our project, with basic dependencies</summary>

{{#include ./testdata/project/go.mod}}

<details><summary>`Makefile`: Make targets for building and deploying your controller</summary>
{{#include ./testdata/project/Makefile}}

<details><summary>`PROJECT`: Kubebuilder metadata for scaffolding new components</summary>
{{#include ./testdata/project/PROJECT}}

## Launch Configuration

We also get launch configuration under the
directory. Right now, it just contains
[Kustomize]( YAML definitions required to
launch our controller on a cluster, but once we get started writing our
controller, it'll also hold our CustomResourceDefinitions, RBAC
configuration, and WebhookConfigurations.

[`config/default`](../ contains a [Kustomize base](../ for launching
the controller in a standard configuration.

Each other directory contains a different piece of configuration,
refactored out into its own base:

- [`config/manager`](../ launch your controllers as pods in the

- [`config/rbac`](../ permissions required to run your
controllers under their own service account

## The Entrypoint

Last, but certainly not least, Kubebuilder scaffolds out the basic
entrypoint of our project: `main.go`. Let's take a look at that next...
@@ -0,0 +1,24 @@
# Implementing a controller

The basic logic of our CronJob controller is this:

1. Load the named CronJob

2. List all active jobs, and update the status

3. Clean up old jobs according to the history limits

4. Check if we're supsended (and don't do anything else if we are)

5. Get the next scheduled run

6. Run a new job if it's on schedule, not past the deadline, and not
blocked by our concurrency policy

7. Requeue when we either see a running job (done automatically) or it's
time for the next scheduled run.

{{#literatego ./testdata/project/controllers/cronjob_controller.go}}

That was a doozy, but now we've got a working controller. Let's test
against the cluster, then, if we don't have any issues, deploy it!
@@ -0,0 +1,22 @@
# What's in a controller?

Controllers are the core of Kubernetes, and of any operator.

It's a controller's job to ensure that, for any given object, the actual
state of the world (both the cluster state, and potentially external state
like running containers for Kubelet or loadbalancers for a cloud provider)
matches the desired state in the object. Each controller focuses on one
*root* Kind, but may interact with other Kinds.

We call this process *reconciling*.

In controller-runtime, the logic that implements the reconciling for
a specific kind is called a [*Reconciler*](../ A reconciler
takes the name of an object, and returns whether or not we need to try
again (e.g. in case of errors or periodic controllers, like the

{{#literatego ./testdata/emptycontroller.go}}

Now that we've seen the basic structure of a reconciler, let's fill out
the logic for `CronJob`s.
@@ -0,0 +1,5 @@
# Every journey needs a start, every program a main

{{#literatego ./testdata/emptymain.go}}

With that out of the way, we can get on to scaffolding our API!
@@ -0,0 +1,7 @@
# Epilogue

Things left to do:

- Write custom printer columns
- Discuss webhooks
- Use different watches
@@ -0,0 +1,69 @@
# Groups and Versions and Kinds, oh my!

Actually, before we get started with our API, we should talk terminology
a bit.

When we talk about APIs in Kubernetes, we often use 4 terms: *groups*,
*versions*, *kinds*, and *resources*.

## Groups and Versions

An *API Group* in Kubernetes is simply a collection of related
functionality. Each group has one or more *versions*, which, as the name
suggests, allow us to change how an API works over time.

## Kinds and Resources

Each API group-version contains one or more API types, which we call
*Kinds*. While a Kind may change forms between versions, each form must
be able to store all the data of the other forms, somehow (we can store
the data in fields, or in annotations). This means that using an older
API version won't cause newer data to be lost or corrupted. See the
[Kubernetes API guidelines](../ for more information.

You'll also hear mention of *resources* on occaison. A resource is simply
a use of a Kind in the API. Often, there's a one-to-one mapping between
Kinds and resources. For instance, the `pods` resource corresponds to the
`Pod` Kind. However, sometimes, the same Kind may be returned by multiple
resources. For instance, the `Scale` Kind is returned by all scale
subresources, like `deployments/scale` or `replicasets/scale`. This is
what allows the Kubernetes HorizontalPodAutoscaler to interact with
different resources. With CRDs, however, each Kind will correspond to
a single resource.

Notice that resources are always lowercase, and by convention are the
lowercase form of the Kind.

## So, how does that correspond to Go?

When we refer to a kind in a particular group-version, we'll call it
a *GroupVersionKind*, or GVK for short. Same with resources and GVR. As
we'll see shortly, each GVK corresponds to a given root Go type in
a package.

Now that we have our terminology straight, we can *actually* create our

## Err, but what's that Scheme thing?

The `Scheme` we saw before is simply a way to keep track of what Go type
corresponds to a given GVK.

For instance, suppose we mark that the
`"".CronJob{}` type as being in the
`` API group (implicitly saying it has the
Kind `CronJob`).

Then, we can later construct a new `&CronJob{}` given some JSON from the
API server that says

"kind": "CronJob",
"apiVersion": "",

or properly look up the group-version when we go to submit a `&CronJob{}`
in an update.
Oops, something went wrong.

0 comments on commit 52ba0c3

Please sign in to comment.
You can’t perform that action at this time.