Skip to content

Commit

Permalink
New Book
Browse files Browse the repository at this point in the history
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}}`).
  • Loading branch information
DirectXMan12 committed May 14, 2019
1 parent e46e06e commit 52ba0c3
Show file tree
Hide file tree
Showing 69 changed files with 8,690 additions and 76 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.idea/

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

# Editor temp files
*~
Expand Down
17 changes: 0 additions & 17 deletions build/thirdparty/brodocs/Dockerfile

This file was deleted.

6 changes: 0 additions & 6 deletions build/thirdparty/brodocs/runbrodocs.sh

This file was deleted.

18 changes: 0 additions & 18 deletions docs/book/book.json

This file was deleted.

12 changes: 12 additions & 0 deletions docs/book/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[book]
authors = ["The Kubebuilder Maintainers"]
multilingual = false
src = "src"
title = "The Kubebuilder Book"

[output.html]
google-analytics = "UA-119864590-1"
curly-quotes = true

[preprocessor.literatego]
command = "./litgo.sh"
11 changes: 11 additions & 0 deletions docs/book/install-and-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

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 (https://github.com/rust-lang-nursery/mdBook/issues/904).
curl -sL -o /tmp/mdbook https://storage.googleapis.com/kubebuilder-build-tools/mdbook-0.2.3-${os}-${arch}
chmod +x /tmp/mdbook
/tmp/mdbook build
11 changes: 11 additions & 0 deletions docs/book/litgo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

set -ex

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

../../bin/literate-go "$@"
29 changes: 29 additions & 0 deletions docs/book/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Summary

[Introduction](./introduction.md)

[Quick Start](./quick-start.md)

---

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

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

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

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

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

- [Running and deploying the controller](./cronjob-tutorial/running.md)
- [Epilogue](./cronjob-tutorial/epilogue.md)

---

[Appendix: The TODO Landing Page](./TODO.md)
7 changes: 7 additions & 0 deletions docs/book/src/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# TODO

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
this](https://github.com/kubernetes-sigs/kubebuilder/issues?q=is%3Aopen+is%3Aissue+label%3Akind%2Fdocumentation)
or [bug the
maintainers](https://github.com/kubernetes-sigs/kubebuilder/issues/new?assignees=&labels=kind%2Fdocumentation).
36 changes: 36 additions & 0 deletions docs/book/src/cronjob-tutorial.md
Original file line number Diff line number Diff line change
@@ -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](./quick-start.md), we'll need to scaffold
out a new project. Make sure you've [installed
Kubebuilder](./quick-start.md#installation), then scaffold out a new
project:

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

Now that we've got've a project in place, let's take a look at what
Kubebuilder has scaffolded for us so far...
46 changes: 46 additions & 0 deletions docs/book/src/cronjob-tutorial/api-design.md
Original file line number Diff line number Diff line change
@@ -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](../TODO.md).

</details>

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.
54 changes: 54 additions & 0 deletions docs/book/src/cronjob-tutorial/basic-project.md
Original file line number Diff line number Diff line change
@@ -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>

```go
{{#include ./testdata/project/go.mod}}
```
</details>

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

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

## Launch Configuration

We also get launch configuration under the
[`config/`](https://sigs.k8s.io/kubebuilder.io/docs/book/cronjob-tutorial/testdata/project/config)
directory. Right now, it just contains
[Kustomize](https://sigs.k8s.io/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`](../TODO.md) contains a [Kustomize base](../TODO.md) 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`](../TODO.md): launch your controllers as pods in the
cluster

- [`config/rbac`](../TODO.md): 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...
24 changes: 24 additions & 0 deletions docs/book/src/cronjob-tutorial/controller-implementation.md
Original file line number Diff line number Diff line change
@@ -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!
22 changes: 22 additions & 0 deletions docs/book/src/cronjob-tutorial/controller-overview.md
Original file line number Diff line number Diff line change
@@ -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*](../TODO.md). 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
HorizontalPodAutoscaler).

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

Now that we've seen the basic structure of a reconciler, let's fill out
the logic for `CronJob`s.
5 changes: 5 additions & 0 deletions docs/book/src/cronjob-tutorial/empty-main.md
Original file line number Diff line number Diff line change
@@ -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!
7 changes: 7 additions & 0 deletions docs/book/src/cronjob-tutorial/epilogue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Epilogue

Things left to do:

- Write custom printer columns
- Discuss webhooks
- Use different watches
69 changes: 69 additions & 0 deletions docs/book/src/cronjob-tutorial/gvks.md
Original file line number Diff line number Diff line change
@@ -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](../TODO.md) 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
API!

## 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
`"tutorial.kubebuilder.io/api/v1".CronJob{}` type as being in the
`batch.tutorial.kubebuilder.io/v1` 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

```json
{
"kind": "CronJob",
"apiVersion": "batch.tutorial.kubebuilder.io/v1",
...
}
```

or properly look up the group-version when we go to submit a `&CronJob{}`
in an update.
Loading

0 comments on commit 52ba0c3

Please sign in to comment.