diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..b2fe052 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,53 @@ +name: Deploy docs to Pages + +on: + push: + branches: [main] + paths: + - 'docs/**' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install mdBook + run: | + mkdir -p mdbook-bin + curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.5.4/mdbook-v0.5.4-x86_64-unknown-linux-gnu.tar.gz \ + | tar -xz -C mdbook-bin + echo "$(pwd)/mdbook-bin" >> "$GITHUB_PATH" + + - name: Build docs + run: mdbook build docs + + - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + + - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: docs/book + + deploy: + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.gitignore b/.gitignore index 092058e..6b7ef89 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ _output/ bin/ *.out kubeconfig-* +docs/book/ +user-docs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 998d9a1..6e4ee6a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,7 +50,7 @@ systemctl --user start podman.socket Before diving into the code, familiarize yourself with the project from a user perspective by reading the [README](README.md) and the -[Architecture](docs/ARCHITECTURE.md) document. We also recommend getting +[Architecture](docs/design/ARCHITECTURE.md) document. We also recommend getting familiar with [bink](https://github.com/bootc-dev/bink), which is used to create lightweight Kubernetes clusters backed by bootc nodes for development and testing. diff --git a/Makefile b/Makefile index 8ceb1ed..fd50934 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,16 @@ print-var-%: help: ## Display this help. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) +##@ Documentation + +.PHONY: docs +docs: ## Build documentation locally (requires mdBook). + mdbook build docs + +.PHONY: docs-serve +docs-serve: ## Serve documentation locally with live reload. + mdbook serve docs --open + ##@ Development .PHONY: manifests diff --git a/README.md b/README.md index ca2f8f0..20c09df 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ The Bootc Operator then exposes this bootc API to the Kubernetes control plane in pure operator declarative fashion: you define the desired state your hosts should be in, and the operator handles the reconciliation. This includes image updates, but also future bootc enhancements like dynamic config overlays, -sysexts, etc. See the [PRD](docs/PRD.md) for more details and the +sysexts, etc. See the [PRD](docs/design/PRD.md) for more details and the [Roadmap](ROADMAP.md) for what's ahead. ## Highlights diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 0000000..c92deb8 --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,8 @@ +[book] +title = "Bootc Operator" +language = "en" +src = "src" + +[output.html] +git-repository-url = "https://github.com/bootc-dev/bootc-operator" +edit-url-template = "https://github.com/bootc-dev/bootc-operator/edit/main/docs/{path}" diff --git a/docs/ARCHITECTURE.md b/docs/design/ARCHITECTURE.md similarity index 100% rename from docs/ARCHITECTURE.md rename to docs/design/ARCHITECTURE.md diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/design/IMPLEMENTATION_PLAN.md similarity index 100% rename from docs/IMPLEMENTATION_PLAN.md rename to docs/design/IMPLEMENTATION_PLAN.md diff --git a/docs/PRD.md b/docs/design/PRD.md similarity index 100% rename from docs/PRD.md rename to docs/design/PRD.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 0000000..cf70574 --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,9 @@ +# Summary + +[Introduction](introduction.md) + +# User Guide + +- [Concepts](concepts.md) +- [Operations](operations/index.md) + - [Managing a pool](operations/pool.md) diff --git a/docs/src/concepts.md b/docs/src/concepts.md new file mode 100644 index 0000000..15756a8 --- /dev/null +++ b/docs/src/concepts.md @@ -0,0 +1,27 @@ +# Concepts + +## BootcNodePool + +A `BootcNodePool` is the only resource users need to create. It defines a +group of nodes by label selector and the OS image those nodes should be running. +The operator creates one `BootcNode` per matching node and drives each toward +the desired image. + +## BootcNode + +A `BootcNode` represents a single managed node. It is created automatically by +the controller (one per node that matches a pool's selector) and named after +the Kubernetes `Node` it represents. Users do not create or modify `BootcNode` +objects directly. + +The status of a `BootcNode` reflects the information reported by `bootc status` +on that node: the currently booted image, any staged image, and the rollback +entry if one is available. + +## Image references + +A pool's `spec.image.ref` can be either a digest reference +(`quay.io/example/myos@sha256:abc123`) or a tag reference +(`quay.io/example/myos:latest`). With a digest ref, the target is pinned and +immutable. With a tag ref, the controller periodically resolves the tag to a +digest and begins a new rollout whenever the digest changes. diff --git a/docs/src/introduction.md b/docs/src/introduction.md new file mode 100644 index 0000000..418f0d4 --- /dev/null +++ b/docs/src/introduction.md @@ -0,0 +1,19 @@ +# Bootc Operator + +A Kubernetes operator for managing [bootc] nodes. + +[bootc] lets you define a complete Linux operating system as an OCI/Docker +container image and deploy it transactionally on physical or virtual machines. +It is a natural fit for managing Kubernetes cluster nodes: a node OS becomes +a container image, updated and rolled back with the same registry tooling +already in use for workloads. + +The Bootc Operator exposes the bootc API to the Kubernetes control plane in +declarative fashion. You declare the desired OS image for a group of nodes in a +`BootcNodePool` resource; the operator resolves image tags, coordinates staged +rollouts, drains nodes, and orchestrates reboots, all without any per-node +configuration. Node OS upgrades become a standard Kubernetes operation, managed +with the same `kubectl` workflows used for any other resource and requiring no +additional tooling. + +[bootc]: https://github.com/bootc-dev/bootc diff --git a/docs/src/operations/index.md b/docs/src/operations/index.md new file mode 100644 index 0000000..106cc27 --- /dev/null +++ b/docs/src/operations/index.md @@ -0,0 +1,4 @@ +# Operations + +This chapter covers the day-to-day operations for managing nodes with the +Bootc Operator. diff --git a/docs/src/operations/pool.md b/docs/src/operations/pool.md new file mode 100644 index 0000000..ecc4b0f --- /dev/null +++ b/docs/src/operations/pool.md @@ -0,0 +1,107 @@ +# Managing a pool + +## Creating a pool + +A `BootcNodePool` tells the operator which nodes to manage and what OS image +they should be running. Create one by applying a manifest to your cluster: + +```yaml +apiVersion: node.bootc.dev/v1alpha1 +kind: BootcNodePool +metadata: + name: workers +spec: + nodeSelector: + matchLabels: + node-role.kubernetes.io/worker: "" + image: + ref: ghcr.io/bootc-dev/bink/node:latest +``` + +The `nodeSelector` field follows the standard Kubernetes label selector format. +Each node can belong to at most one pool, if a node matches multiple selectors, +the affected pools are marked `Degraded` with reason `NodeConflict`. + +The image in ref can be specified by digest or by tag. If it is specified by tag +the bootc-operator controller will periodically checks for update. If a new image +version under that tag is detected, then a rollout is started by patching the new +digest in the bootc node spec. + +Once the pool exists, the operator creates a `BootcNode` for each matching node +and begins staging the image. + +## Updating the OS image + +To roll out a new OS image across the pool, update `spec.image.ref` to a new +digest: + +```shell +kubectl patch bootcnodepool workers --type merge -p \ + '{"spec":{"image":{"ref":"ghcr.io/bootc-dev/bink/node@sha256:newdigest..."}}}' +``` + +The operator stages the new image on each node, drains workloads, and reboots +nodes according to the rollout settings. Nodes that are already running the +target digest are left untouched. + +## Monitoring a rollout + +The `BootcNodePool` status shows the overall rollout progress: + +```shell +kubectl get bootcnodepool workers +``` + +The columns `Nodes`, `Updated`, `Updating`, and `Degraded` give a quick +summary. For more detail: + +```shell +kubectl get bootcnodepool workers -o yaml +``` + +```yaml +status: + targetDigest: sha256:9ce7d6d15b8558c226b4c41f3b27bf1722897b0c8a66c7a84a2877bca8d049f7 + nodeCount: 10 + updatedCount: 7 + updatingCount: 2 + degradedCount: 1 + conditions: + - type: UpToDate + status: "False" + reason: RolloutInProgress + message: "7/10 updated; 2 staging, 1 rebooting" +``` + +The `UpToDate` condition is `True` when all nodes in the pool are running +the target digest. + +Individual node progress is available through `BootcNode` resources: + +```shell +kubectl get bootcnodes +``` + +Each `BootcNode` status reflects the output of `bootc status` on that node: +the booted image, any staged image, and the rollback entry. + +## Pausing and resuming + +To pause a rollout (nodes already staging will complete, but no new reboots +start): + +```shell +kubectl patch bootcnodepool workers --type merge -p '{"spec":{"rollout":{"paused":true}}}' +``` + +To resume: + +```shell +kubectl patch bootcnodepool workers --type merge -p '{"spec":{"rollout":{"paused":false}}}' +``` + +## Rolling back + +To roll back, change `spec.image.ref` to the previous digest. Nodes already +running that image are left alone. Nodes that were updated go through the +normal staging and reboot cycle to return to the previous image.