Skip to content

Commit

Permalink
better conciseness and less use of "just" or "simply"
Browse files Browse the repository at this point in the history
trying to take a note from https://justsimply.dev/

Signed-off-by: clux <[email protected]>
  • Loading branch information
clux committed Aug 21, 2023
1 parent 2aef9ea commit f50bdb6
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 34 deletions.
35 changes: 16 additions & 19 deletions docs/controllers/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The **application** starts the [Controller] and links it up with the [[reconcile

## Goal

This document shows the basics of creating a simple controller with a `Pod` as the main [[object]].
This document shows the basics of creating a small controller with a `Pod` as the main [[object]].

## Requirements

Expand Down Expand Up @@ -41,17 +41,13 @@ thiserror = "LATESTTHISERROR"

This will populate some [`[dependencies]`](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html) in your `Cargo.toml` file.

### Main Dependencies
### Dependencies

The [kube] dependency is what we provide. It's used here with its controller `runtime` feature, its Kubernetes `client` and the `derive` macro for custom resources.

The [k8s-openapi] dependency is needed if using core Kubernetes resources.

The [thiserror] dependency is used in this guide as an easy way to do basic error handling, but it is optional.

The [futures] dependency provides helpful abstractions when working with asynchronous rust.

The [tokio] runtime dependency is needed to use async rust features, and is the supported way to use futures created by kube.
- [kube] :: with the controller `runtime`, Kubernetes `client` and a `derive` macro for custom resources
- [k8s-openapi] :: structs for core Kubernetes resources at the `latest` supported [[kubernetes-version]]
- [thiserror] :: typed error handling
- [futures] :: async rust abstractions
- [tokio] :: supported runtime for async rust features

!!! warning "Alternate async runtimes"

Expand All @@ -61,7 +57,7 @@ Additional dependencies are useful, but we will go through these later as we add

### Setting up errors

We will start with the right thing from the start and define a proper `Error` enum:
A full `Error` enum is the most versatile approach:

```rust
#[derive(thiserror::Error, Debug)]
Expand All @@ -72,9 +68,9 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;

### Define the object

Import the [[object]] that you want to control into your `main.rs`.
Create or import the [[object]] that you want to control into your `main.rs`.

For the purposes of this demo we are going to use [Pod] (hence the explicit `k8s-openapi` dependency):
For the purposes of this demo we will import [Pod]:

```rust
use k8s_openapi::api::core::v1::Pod;
Expand All @@ -101,7 +97,7 @@ async fn main() -> Result<(), kube::Error> {

This creates a [Client], a Pod [Api] object (for all namespaces), and a [Controller] for the full list of pods defined by a default [watcher::Config].

We are not using [[relations]] here, so we merely tell the controller to call reconcile when a pod changes.
We are not using [[relations]] here, this only schedules reconciliations when a pod changes.

### Creating the reconciler

Expand All @@ -114,7 +110,7 @@ async fn reconcile(obj: Arc<Pod>, ctx: Arc<()>) -> Result<Action> {
}
```

and a basic error handler (for what to do when `reconcile` returns an `Err`):
and an error handler to decide what to do when `reconcile` returns an `Err`:

```rust
fn error_policy(_object: Arc<Pod>, _err: &Error, _ctx: Arc<()>) -> Action {
Expand Down Expand Up @@ -203,11 +199,11 @@ I.e. you should get a reconcile request for every pod in your cluster (`kubectl

If you now edit a pod (via `kubectl edit pod traefik-xxx` and make a change), or create a new pod, you should immediately get a reconcile request.

**Congratulations**. You have just built your first kube controller. 🎉
**Congratulations**. You have __technically__ built a kube controller.

!!! note "Continuation"
!!! warning "Continuation"

At this point, you have gotten the 3 main components; an [[object]], a [[reconciler]] and an [[application]], but there are many topics we have not touched on. Follow the links to other pages to learn more.
You have created the [[application]] using trivial reconcilers and objects, but you are not controlling anything yet. See the [[object]] and [[reconciler]] chapters for the business logic.

## Deploying

Expand Down Expand Up @@ -258,6 +254,7 @@ These in turn also pull in their own dependencies (and tls features, depending o
[//begin]: # "Autogenerated link references for markdown compatibility"
[reconciler]: reconciler "The Reconciler"
[object]: object "The Object"
[kubernetes-version]: ../kubernetes-version "kubernetes-version"
[relations]: relations "Related Objects"
[testing]: testing "Testing"
[security]: security "Security"
Expand Down
27 changes: 13 additions & 14 deletions docs/controllers/reconciler.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The reconciler is the **user-defined function** in charge of reconciling the **s
async fn reconcile(o: Arc<K>, ctx: Arc<T>) -> Result<Action, Error>
```

It is always **called** with the [[object]] type that you instantiate the [Controller] with, regardless of what auxillary objects you end up watching:
It is always **called** with the [[object]] type that you instantiate the [Controller] with, independent of what auxillary objects you may be watching:

```mermaid
graph TD
Expand All @@ -25,17 +25,16 @@ graph TD
end
```

A [Controller] contains a machinery that will:
A [Controller] is a system that will:

- watch api endpoints in Kubernetes (main object and related objects)
- map changes from those apis (via [[relations]]) into your main [[object]]
- schedule and apply reconciliations
- observe the result of reconciliations to decide when to reschedule
- tolerate a wide class of failures
1. watch resources from the Kubernetes api (main + related objects)
2. `map` returned objects (via [[relations]]) into your main [[object]]
3. schedule and run reconciliations
4. observe the result of reconciliations to decide when to reschedule

We will largerly treat [Controller] as a black-box, but details are explored in [[internals]] and [[architecture]].
> Details about this system is explored in [[internals]] and [[architecture]].
As a user of `kube`, you will just to have to instantiate a [Controller] (see [[application]]) and define your `reconcile` fn.
You must instantiate a [Controller] in your [[application]], and define your `reconcile` + `error_policy` fns.

## The World

Expand Down Expand Up @@ -69,13 +68,13 @@ Notice that the **reason** for why the reconciliation started is **not included*

!!! note "Fault-tolerance against missed messages"

If your controller is down / crashed earlier, you **might have missed messages**. In fact, **no matter how well** you guard against downtime (e.g with multiple replicas, rolling upgrades, pdbs, leases), the Kubernetes watch api is **not sufficiently safe** to guarantee unmissed messages. <!-- TODO; link to desync explanations (watch desyncs can happen and you never know you will have skipped an update) -->
If your controller is down / crashed earlier, you **might have missed messages**. No matter how well you guard against downtime (e.g with multiple replicas, rolling upgrades, pdbs, leases), the Kubernetes watch api is **not sufficiently safe** to guarantee unmissed messages. <!-- TODO; link to desync explanations (watch desyncs can happen and you never know you will have skipped an update) -->

> It is unsafe to give you a reason for why you got a `reconcile` call, because it is sometimes impossible to know.
It is unsafe to give you a reason for why you got a `reconcile` call, because it is sometimes impossible to know.

We therefore **have to hide** this information from you, and you are forced to write a more **defensive reconciler**.
We therefore **omit** this information, so you that you write a more **defensive reconciler**.

We have to:
You should:

- assume nothing about why reconciliation started
- assume the reconciler could have failed at any point during the function
Expand All @@ -100,7 +99,7 @@ Both of these operations can be done in isolation in an idempotent manner (we wi

### Combining Idempotent Operations

A naive approach to the above problem might be to take a shortcut, and simply **check if the work has been done**, and if not, do it:
The naive approach to the above problem would be to **check if the work has been done**, and if not, do it:

```rust
if pod_missing {
Expand Down
2 changes: 1 addition & 1 deletion docs/controllers/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ First, we need a working [Client]. Using `Client::try_default()` inside an async
let patch = Patch::Apply(doc.clone());
docs.patch("test", &ssapply, &patch).await.unwrap();

// reconcile it (as if it was just applied to the cluster like this)
// reconcile it (as if it was applied to the cluster like this)
reconcile(Arc::new(doc), ctx).await.unwrap();

// verify side-effects happened
Expand Down

0 comments on commit f50bdb6

Please sign in to comment.