Skip to content

Commit

Permalink
docs.yml
Browse files Browse the repository at this point in the history
  • Loading branch information
docs.yml committed Feb 20, 2024
1 parent f00045c commit d8cb731
Showing 1 changed file with 19 additions and 120 deletions.
139 changes: 19 additions & 120 deletions site/content/en/main/pepr-tutorials/create-pepr-operator.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This tutorial will walk you through the process of building a Kubernetes Operato
## Background


The WebApp Operator deploys the `CustomResourceDefinition` for WebApp, then watches and reconciles against instances of WebApps to ensure the desired state meets the actual cluster state.
The WebApp Operator deploys the WebApp `CustomResourceDefinition`, then watches and reconciles against instances of WebApps to ensure the desired state meets the actual cluster state.

The WebApp instance represents a `Deployment` object with configurable replicas, a `Service`, and a `ConfigMap` that has a `index.html` file that can be configured to a specific language, and theme. The resources the Operator deploys contain `ownerReferences`, causing a cascading delete effect when the WebApp instance is deleted.

Expand All @@ -22,7 +22,7 @@ If any object deployed by the Operator is deleted for any reason, the Operator w
- [Create a new Pepr Module](#create-a-new-pepr-module)
- [Create CRD](#create-crd)
- [Create Helpers](#create-helpers)
- [Create Queue and Reconciler](#create-queue-and-reconciler)
- [Create Reconciler](#create-reconciler)
- [Build and Deploy](#build-and-deploy)

## Create a new Pepr Module
Expand All @@ -43,9 +43,7 @@ npx pepr init

## Create CRD

Now, it is time to structure the CRD. The CRD is the definition of the WebApp resource. It is a Kubernetes object that defines the WebApp resource and its schema.

A CRD is created and has the following attributes: `theme`, `language`, and `replicas`. The `status` is also defined in the CRD, which is used to track the status of the WebApp resource.
The WebApp CRD has the following properties: `theme`, `language`, and `replicas` with a `status` section used to track the status of the WebApp resource.

```yaml
apiVersion: apiextensions.k8s.io/v1
Expand Down Expand Up @@ -112,24 +110,15 @@ spec:
- wa
```
Make sure the CRD has a `status` listed under `subresources` and it is a good idea to provide descriptions under the properties to help users understand what the property is used for. Enums are useful to limit the values that can be used for a property.


Status should also be listed under `subresources` to make it writable. We provide descriptions under the properties for clarity around what the property is used for. Enums are useful to limit the values that can be used for a property.

Go to the `capabilities` directory, create a new directory called `crd` with two child folders, generated and source.

```bash
mkdir -p capabilities/crd/generated capabilities/crd/source
```

Lets go ahead and install the node modules for the project.

```bash
npm ci
```


Generate a typescript class from the using `kubernetes-fluent-client` in the `generated` directory.
Generate a class based on the WebApp CRD using `kubernetes-fluent-client` and store it in the generated directory.

```bash
npx kubernetes-fluent-client crd https://gist.githubusercontent.com/cmwylie19/69b765af5ab25af62696f3337df13687/raw/72f53db7ddc06fc8891dc81136a7c190bc70f41b/WebApp.yaml .
Expand Down Expand Up @@ -300,7 +289,7 @@ export async function validator(req: PeprValidateRequest<WebApp>) {
}
```

In this section we generated scaffolded the CRD class, created a function to add ownerReferences to the manifests that will be deployed by the Operator, registered the CRD, and added a validator to the CRD.
In this section we generated the CRD class for WebApp, created a function to add `ownerReferences` to the manifests that will be deployed by the Operator to handle deletion of Kubernetes objects, registered the CRD, and added a validator to validate that instances of WebApp are in valid namespaces.

## Create Helpers

Expand Down Expand Up @@ -643,9 +632,9 @@ function configmap(instance: WebApp) {
}
```

In this section we created a `generators.ts` file that contains functions that generate the manifests that will be deployed by the Operator with the ownerReferences added to them. We decide which `ConfigMap` to deploy based on the language and theme specified in the WebApp resource and how many replicas to deploy based on the replicas specified in the WebApp resource.
We decide which `ConfigMap` to deploy based on the language and theme specified in the WebApp resource and how many replicas to deploy based on the replicas specified in the WebApp resource.

## Create Queue and Reconciler
## Create Reconciler

In the base of the `capabilities` folder, create a `reconciler.ts` file and add the following:

Expand Down Expand Up @@ -716,89 +705,15 @@ async function updateStatus(instance: WebApp, status: Status) {
}
```

Create another file in the `capabilities` folder called `enqueue.ts` and add the following:

```typescript
import { Log } from "pepr";

import { WebApp } from "./crd";
import { reconciler } from "./reconciler";

type QueueItem = {
instance: WebApp;
resolve: (value: void | PromiseLike<void>) => void;
reject: (reason?: string) => void;
};

/**
* Queue is a FIFO queue for reconciling webapps
*/
export class Queue {
#queue: QueueItem[] = [];
#pendingPromise = false;

/**
* Enqueue adds a webapp to the queue and returns a promise that resolves when the webapp is
* reconciled.
*
* @param pkg The webapp to reconcile
* @returns A promise that resolves when the instance is reconciled
*/
enqueue(instance: WebApp) {
Log.debug(
`Enqueueing ${instance.metadata!.namespace}/${instance.metadata!.name}`,
);
return new Promise<void>((resolve, reject) => {
this.#queue.push({ instance, resolve, reject });
return this.#dequeue();
});
}

/**
* Dequeue reconciles the next webapp in the queue
*
* @returns A promise that resolves when the webapp is reconciled
*/
async #dequeue() {
// If there is a pending promise, do nothing
if (this.#pendingPromise) return false;

// Take the next item from the queue
const item = this.#queue.shift();

// If there is no item, do nothing
if (!item) return false;

try {
// Set the pending promise flag to avoid concurrent reconciliations
this.#pendingPromise = true;

// Reconcile the webapp
await reconciler(item.instance);

item.resolve();
} catch (e) {
item.reject(e);
} finally {
// Reset the pending promise flag
this.#pendingPromise = false;

// After the webapp is reconciled, dequeue the next webapp
await this.#dequeue();
}
}
}
```

Finally create the `index.ts` file in the `capabilities` folder and add the following:

```typescript
import { Capability, a, Log } from "pepr";
import { WebApp } from "./crd";
import { validator } from "./crd/validator";
import { Queue } from "./enqueue";
import { WebAppCRD } from "./crd/source/webapp.crd";
import { RegisterCRD } from "./crd/register";
import { reconciler } from "./reconciler";
import "./crd/register";
import Deploy from "./controller/generators";

Expand All @@ -810,18 +725,16 @@ export const WebAppController = new Capability({

const { When, Store } = WebAppController;

const queue = new Queue();

// When instance is created or updated, validate it and enqueue it for processing
When(WebApp)
.IsCreatedOrUpdated()
.Validate(validator)
.Watch(async instance => {
.Reconcile(async instance => {
try {
Store.setItem(instance.metadata.name, JSON.stringify(instance));
await queue.enqueue(instance);
await reconciler(instance);
} catch (error) {
Log.info(`Error enqueing instance of WebApp`);
Log.info(`Error reconciling instance of WebApp`);
}
});

Expand Down Expand Up @@ -870,32 +783,18 @@ When(a.ConfigMap)

```

In this section we created a `reconciler.ts` file that contains the reconciler function that is called from the queue and is responsible for reconciling the state of the instance with the cluster and updating the status of the instance. We also created a `enqueue.ts` file that contains the queue class that is used to enqueue instances of the WebApp resource. Finally, we created the `index.ts` file that contains the WebAppController capability and the functions that are used to watch for changes to the WebApp resource and the resources that are deployed by the Operator.


## Build and Deploy

# WebApp Operator

The WebApp Operator deploys the `CustomResourceDefinition` for WebApp, then watches and reconciles against instances of WebApps to ensure the desired state meets the actual cluster state.

The WebApp instance represents a `Deployment` object with confirgurable replicas, a `Service`, and a `ConfigMap` that has a `index.html` file that can be configured to a specific language, and theme. The resources the Operator deploys contain `ownerReferences`, causing a cascading delete effect when the WebApp instance is deleted.

If any object deployed by the Operator is deleted for any reason, other than through the `ownerReference` mechanism, the Operator will abruptly redeploy the object.
In this section we created a `reconciler.ts` file that contains the function that is responsible for reconciling the state of the instance with the cluster based on CustomResource and updating the status of the instance. The `index.ts` file that contains the WebAppController capability and the functions that are used to watch for changes to the WebApp resource and corresponding Kubernetes resources. The `Reconcile` action processes the callback in a queue guaranteeing ordered and synchronous processing of events

## Demo

# WebApp Operator

The WebApp Operator deploys the `CustomResourceDefinition` for WebApp, then watches and reconciles against instances of WebApps to ensure the desired state meets the actual cluster state.

The WebApp instance represents a `Deployment` object with confirgurable replicas, a `Service`, and a `ConfigMap` that has a `index.html` file that can be configured to a specific language, and theme. The resources the Operator deploys contain `ownerReferences`, causing a cascading delete effect when the WebApp instance is deleted.

If any object deployed by the Operator is deleted for any reason, other than through the `ownerReference` mechanism, the Operator will abruptly redeploy the object.
_Create an ephemeral cluster. (Kind or k3d will work)_

## Demo
Clone the Operator

_Create an ephemeral cluster. (Kind or k3d will work)_
```bash
git clone https://github.com/defenseunicorns/pepr-excellent-examples.git
cd pepr-operator
```

Make sure Pepr is update to date

Expand Down

0 comments on commit d8cb731

Please sign in to comment.