From b33c4bd66a90154401ae70771fb79d460b36b585 Mon Sep 17 00:00:00 2001 From: Alessio Biancalana Date: Fri, 16 Oct 2026 11:22:35 +0200 Subject: [PATCH] feat(site): refresh design with scheduler shipment artwork --- docs/concepts/architecture.md | 8 +- docs/intro.md | 20 +- docs/sked/architecture.md | 14 +- docs/sked/crds.md | 8 +- docs/sked/development.md | 16 +- docs/sked/installation.md | 8 +- docs/sked/overview.md | 8 +- docs/sked/trust-policy.md | 8 +- docs/sked/usage.md | 10 +- src/css/custom.css | 113 +++---- src/pages/index.module.css | 483 +++++++++++++++++------------- src/pages/index.tsx | 393 +++++++++++------------- static/img/README.md | 16 +- static/img/scheduler-shipment.svg | 101 +++++++ 14 files changed, 665 insertions(+), 541 deletions(-) create mode 100644 static/img/scheduler-shipment.svg diff --git a/docs/concepts/architecture.md b/docs/concepts/architecture.md index 0d38f7f..2d38126 100644 --- a/docs/concepts/architecture.md +++ b/docs/concepts/architecture.md @@ -6,7 +6,7 @@ sidebar_label: Architecture # Architecture overview -schedkit has two tools for running OCI-packaged `sched_ext` CPU schedulers: schedctl on a host, and sked in Kubernetes. Both run scheduler images, but they manage them independently. +schedkit runs OCI-packaged `sched_ext` CPU schedulers with two tools: schedctl on a host and sked in Kubernetes. ## On one host: schedctl @@ -16,11 +16,11 @@ schedctl talks to a local Podman or containerd runtime. It verifies scheduler im user -> schedctl -> Podman/containerd -> scheduler binary -> sched_ext ``` -The CLI does not require Kubernetes. Use it for a workstation, a standalone server, or host management driven by scripts. See [schedctl](../schedctl/overview). +Use [schedctl](../schedctl/overview) on a workstation or server, directly or from scripts. It does not require Kubernetes. ## In Kubernetes: sked -sked watches cluster-scoped `Scheduler` resources. Each names a scheduler image and can select a node pool, tolerate taints, and configure container arguments, environment, pull secrets, and resources. +sked watches cluster-scoped `Scheduler` resources that specify an image, node selection, and container settings. ```text cluster administrator @@ -49,7 +49,7 @@ See [sked architecture](../sked/architecture) for the reconcile loop and failure ## What the tools share -The common input is an OCI-packaged scheduler, not a controller-to-agent protocol. sked neither imports schedctl nor shells out to it. Both implement signature verification, but their trust-policy formats and defaults differ. Configuring one tool does not configure the other. +Both tools accept OCI scheduler images. sked does not import or invoke schedctl; each tool has its own signature verifier, trust-policy format, and defaults. Do not let both manage a scheduler on the same node. sked's conflict handling only sees Scheduler resources; it cannot account for a scheduler started by schedctl or another host process. diff --git a/docs/intro.md b/docs/intro.md index 7723eda..95f5a28 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -6,26 +6,20 @@ slug: /intro # Introduction -schedkit is a small ecosystem of tools for distributing, running, and orchestrating Linux CPU schedulers built on top of `sched_ext`. +schedkit distributes and runs Linux `sched_ext` CPU schedulers as OCI images. -The kernel side of pluggable schedulers is solved: recent Linux kernels let you attach a BPF-based scheduler at runtime without rebooting, recompiling, or shipping a custom kernel module. The pieces that aren't solved by the kernel are the boring-but-important ones around it: how do you ship a scheduler binary, how do you decide which one is running on a given machine, how do you roll back when something misbehaves, and how do you do any of this across more than a single host. That's the problem space schedkit lives in. +On a kernel with `sched_ext` support, a BPF-based scheduler can attach at runtime without a reboot or a custom kernel module. schedkit provides image verification, container lifecycle management, and tools to check scheduler status. ## What's in this site -The documentation is split into three sections. - -**Concepts** is for readers who want to understand the design before touching anything. It covers what `sched_ext` actually is and isn't, why we settled on OCI as the distribution format for schedulers, and how the various components fit together. - -**schedctl** documents the host-side command-line tool. If you have a single machine and you want to try out a different scheduler for the duration of a build, a game session, or a test run, this is what you're looking for. - -**sked** documents the Kubernetes operator. It uses cluster-scoped Scheduler resources to select node pools, verifies scheduler images, and reports DaemonSet rollout status. Start with the [overview](/docs/sked/overview) or [install it with Helm](./sked/installation). - -You can read the sections in order or jump straight to whichever tool you came here for. The Concepts pages are prerequisite-free; the project pages assume you've at least skimmed Concepts but try not to lean on it too hard. +- **[Concepts](./concepts/sched-ext)** explains `sched_ext`, the OCI image format, and how the tools fit together. +- **[schedctl](./schedctl/overview)** is the host CLI. Use it to check a Linux host, run a scheduler image with Podman or containerd, and inspect or stop the scheduler. +- **[sked](./sked/overview)** is the Kubernetes operator. Use cluster-scoped Scheduler resources to select node pools, configure scheduler containers, and track DaemonSet rollouts. [Install it with Helm](./sked/installation). ## Audience -We assume you're comfortable on a Linux command line, you've used containers (any runtime is fine), and for the sked sections that you've operated a Kubernetes cluster before. We don't assume you've written a scheduler. We don't even assume you know what `sched_ext` is — that's the very next page. +The guides assume familiarity with Linux and containers. The sked guides also assume experience with Kubernetes. You do not need to have written a scheduler. ## Where to file things -Documentation issues belong in the [docs repo](https://github.com/schedkit/docs/issues). Tool issues belong in the relevant project repo: [schedctl](https://github.com/schedkit/schedctl/issues) for the CLI, [sked](https://github.com/schedkit/sked/issues) for the operator. If you're not sure which it is, open it against schedctl and we'll move it if needed. +Report documentation problems in the [docs repository](https://github.com/schedkit/docs/issues). Report tool bugs in [schedctl](https://github.com/schedkit/schedctl/issues) or [sked](https://github.com/schedkit/sked/issues). diff --git a/docs/sked/architecture.md b/docs/sked/architecture.md index 7bfc188..436b590 100644 --- a/docs/sked/architecture.md +++ b/docs/sked/architecture.md @@ -5,7 +5,7 @@ title: Architecture # Architecture -sked is one manager binary built with controller-runtime. The process hosts a Scheduler reconciler, a validating admission webhook, a trust-policy refresher, metrics, and health endpoints. There is no node agent or controller-to-node protocol. +sked runs a single manager binary built with controller-runtime. It contains the Scheduler reconciler, validating webhook, trust-policy refresher, metrics, and health endpoints. It communicates with nodes through Kubernetes, without a separate node agent. ```text kubectl create/update Scheduler @@ -32,9 +32,9 @@ The kubelet runs the scheduler image directly. sked does not install or invoke s ## Processes and availability -Helm and Kustomize deploy two manager replicas by default. The lease `b4c95cb3.schedkit.io` elects one reconciler; both replicas serve admission requests and refresh their trust policy. Extra replicas provide failover and webhook capacity, not parallel controller leaders. +Helm and Kustomize deploy two manager replicas by default. They use the lease `b4c95cb3.schedkit.io` to elect a leader. Only the leader reconciles; both replicas serve admission requests and refresh their trust policy. -The default PodDisruptionBudget keeps one controller available during voluntary evictions. A soft topology-spread rule prefers different nodes but permits co-location. Neither setting protects against all replicas sharing a failed node. +The default PodDisruptionBudget requires one controller to remain available during voluntary evictions. A soft topology-spread rule prefers different nodes but allows replicas to share a node. If that node fails, both replicas can be lost. If all controllers stop, existing DaemonSets and scheduler pods keep running. Reconciliation and status updates stop, and Scheduler creates and updates fail because the validating webhook has `failurePolicy: Fail`. Deletion is not handled by the webhook; Kubernetes owner-reference garbage collection does not require sked to be running. @@ -61,7 +61,7 @@ For each Scheduler, the reconciler: Conflict arbitration does not consider taints, tolerations, image validity, pod health, or schedulers started outside sked. A blocked older Scheduler does not automatically yield to a healthy newer one. See [overlapping selectors](./crds#overlapping-selectors). -The controller replaces the generated DaemonSet spec on reconciliation. Manual changes to its pod template are not a supported configuration path. Owner references connect Scheduler to DaemonSet to pods; there is no custom cleanup finalizer. +Reconciliation replaces the DaemonSet spec, overwriting manual changes to its pod template. Owner references connect Scheduler to DaemonSet to pods; there is no custom cleanup finalizer. ### Rollouts and status @@ -69,7 +69,7 @@ A change to the generated pod template triggers Kubernetes' default DaemonSet ro `status.nodes` and the `Ready`, `Progressing`, and `Degraded` conditions come from DaemonSet status. `Active` records whether the Scheduler is fully blocked by conflicts. Partial conflicts can produce both `Ready=True` and `Degraded=True`: the remaining workload is ready, but some requested nodes belong to another Scheduler. -There is no direct kernel observation. A container that stays alive after failing to attach can appear ready. Conversely, image-pull failures and crashes appear through pod availability, without a kernel-specific diagnosis. Use pod logs and node kernel state to establish the cause. +sked does not inspect the kernel. A container that stays alive after failing to attach can appear ready. Image-pull failures and crashes affect pod availability, but status does not identify kernel errors. Check pod logs and node kernel state for the cause. Events for cluster-scoped Schedulers are stored in `default`, not the workload namespace. Normal reasons include `RolloutInProgress`, `DaemonSetReady`, and `NoNodesScheduled`; warnings include conflicts, verification and reconcile failures, unavailable nodes, and `StatusUpdateFailed`. State-change events are not repeated just because a pod count changes. Events expire; conditions describe the last recorded state. @@ -88,7 +88,7 @@ The installed defaults differ from some standalone binary defaults: | HTTP/2 | Disabled | Disabled | | Logging | Slog JSON at info on stderr | Same | -Both health endpoints use a ping check; they do not certify registry access, image verification, or node scheduler health. +Both health endpoints use a ping check. They do not test registry access, image verification, or node scheduler health. Reconcile controls are exposed as flags and corresponding `controller` chart values: @@ -101,7 +101,7 @@ Reconcile controls are exposed as flags and corresponding `controller` chart val | `--reconcile-retry-base-delay` | `1s` | | `--reconcile-retry-max-delay` | `5m` | -The timeout bounds a reconcile attempt. Failed reconciles use exponential backoff plus a shared retry rate limit. These QPS and burst settings limit failed-reconcile requeues, not all Kubernetes API traffic. +The timeout limits the duration of one reconcile attempt. Failed reconciles use exponential backoff plus a shared retry rate limit. These QPS and burst settings limit failed-reconcile requeues, not all Kubernetes API traffic. ## Failure handling diff --git a/docs/sked/crds.md b/docs/sked/crds.md index db81423..21a5df3 100644 --- a/docs/sked/crds.md +++ b/docs/sked/crds.md @@ -46,7 +46,7 @@ This creates a DaemonSet named `scx-rusty` in the workload namespace, targeting | `spec.imagePullSecrets` | `LocalObjectReference[]` | No | Named pull secrets in the **workload namespace**. Used by the kubelet, not by the controller's signature verifier. | | `spec.resources` | Kubernetes `ResourceRequirements` | No | Compute resources for the scheduler container, including requests and limits. These do not set CPU policy for other workloads. | -No other scheduler configuration fields are defined. In particular, there is no `priority`, `imagePullPolicy`, `command`, `affinity`, `volumes`, `serviceAccountName`, probe configuration, or update strategy. Fields inherited from Kubernetes types may depend on cluster feature gates; sked does not provide a general-purpose pod template. +The CRD does not expose `priority`, `imagePullPolicy`, `command`, `affinity`, `volumes`, `serviceAccountName`, probes, or an update strategy. Fields inherited from Kubernetes types may depend on cluster feature gates. There is no general-purpose pod template. ### Container configuration example @@ -75,7 +75,7 @@ spec: memory: 128Mi ``` -The resource request is an example, not a sizing recommendation. Check the scheduler's own flags and memory requirements. Treat this as an alternative to the first example, not a second Scheduler for the same pool. +Choose CPU and memory requests for your scheduler, and check its supported flags. Use this manifest instead of the first example; applying both targets the same nodes. ### Validation @@ -100,7 +100,7 @@ The webhook handles creates and updates with `failurePolicy: Fail`. If it cannot | `status.nodes.unavailable` | DaemonSet `numberUnavailable`. | | `status.conditions` | Conditions keyed by `type`, with `status`, `reason`, `message`, `observedGeneration`, and `lastTransitionTime`. | -There is no phase or per-node status list. Counts come from the DaemonSet, not a count of kernels with an attached scheduler. After a reconcile failure, image and node counts may still describe the last successful reconciliation; read the conditions too. +There is no phase or per-node status list. The counts describe DaemonSet pods, not kernel attachment. After a reconcile failure, the image and counts may still reflect the last successful reconciliation. Read the conditions too. ### Conditions @@ -140,7 +140,7 @@ For each node, the oldest matching Scheduler wins. Equal creation timestamps are - **Partial overlap:** the losing Scheduler excludes conflict nodes through required node affinity on `kubernetes.io/hostname`, and runs on the remaining nodes. - **Complete overlap:** the losing Scheduler's DaemonSet is deleted. Its node counts become zero, `resolvedImage` is cleared, and `Active=False`, `Ready=False`, and `Degraded=True` report `SchedulerConflict`. -Arbitration compares node labels only. It does not consider tolerations, kernel support, image validity, or pod health. An older Scheduler that cannot run can still block a newer one. Use non-overlapping selectors rather than relying on age as a priority mechanism. Label changes trigger reassignment, but pod termination and startup are asynchronous; this is not an atomic handoff. +Arbitration compares node labels only. It does not consider tolerations, kernel support, image validity, or pod health. An older Scheduler that cannot run can still block a newer one. Use non-overlapping selectors. Label changes trigger reassignment, but pods stop and start asynchronously; a scheduler handoff is not atomic. ## Migrating from `SchedExt` diff --git a/docs/sked/development.md b/docs/sked/development.md index c84b2fa..a08241e 100644 --- a/docs/sked/development.md +++ b/docs/sked/development.md @@ -7,7 +7,7 @@ title: Development Run these commands from the [sked repository](https://github.com/schedkit/sked), not this documentation repository. -The module is `github.com/schedkit/sked`. The current checkout uses Go 1.27 and controller-runtime v0.25.2, with Kubebuilder v4 layout. Check `go.mod` and the Makefile for the versions required by the revision you are building. +The module is `github.com/schedkit/sked`, built with Go 1.27 and controller-runtime v0.25.2. Check `go.mod` and the Makefile when building a different revision. ## Build @@ -42,9 +42,15 @@ helm install sked ./charts/sked \ Alternatively, use `make deploy IMG=ghcr.io/your-org/sked:dev` for Kustomize. Do not run both installations in one cluster. -For host-side debugging, `make install` installs only the CRD, and `make run` starts the manager with your kubeconfig. This is not a complete installation: the always-on webhook server needs `tls.crt` and `tls.key` under `/tmp/k8s-webhook-server/serving-certs`, and admission requests need a webhook endpoint reachable by the API server with the corresponding CA configuration. Set the workload and trust-policy namespaces explicitly when running outside a pod. Avoid running an unelected local reconciler alongside an installed controller. +For host-side debugging, `make install` installs only the CRD, and `make run` starts the manager with your kubeconfig. You must also: -Kind is useful for API, reconciliation, and chart tests. Its nodes share the host kernel; Kind does not supply `sched_ext` support. Tests that establish actual attachment need a compatible host kernel, scheduler image, and runtime permissions. A passing chart test alone does not establish kernel compatibility. +- Provide `tls.crt` and `tls.key` under `/tmp/k8s-webhook-server/serving-certs`. +- Configure an admission webhook endpoint the API server can reach, with the matching CA. +- Set the workload and trust-policy namespaces explicitly. + +Do not run an unelected local reconciler alongside an installed controller. + +Kind nodes share the host kernel. API and chart tests can run without `sched_ext`, but attachment tests need a compatible host kernel, scheduler image, and runtime permissions. ## Generate manifests @@ -81,7 +87,7 @@ make test-helm - `make lint` runs the pinned golangci-lint version; `make lint-fix` applies supported automatic fixes. - `make test-helm` checks generated chart files, lints the chart, and tests rendered resources against Kustomize. CI tests Helm 3 and 4. -Cluster tests have separate entrypoints: +Run cluster tests separately: ```bash make test-e2e @@ -113,6 +119,6 @@ make build-installer IMG=ghcr.io/your-org/sked:dev bash hack/package-helm.sh v1.2.3 /tmp/sked-chart ``` -The first command renders `dist/install.yaml`. The second illustrates chart packaging for a release tag: `v1.2.3` produces chart version `1.2.3` and `appVersion: v1.2.3` without changing the checked-in `Chart.yaml`. +The first command writes `dist/install.yaml`. The second packages chart version `1.2.3` with `appVersion: v1.2.3`, without changing the checked-in `Chart.yaml`. Replace the example tag with your release tag. The release workflow publishes and signs the controller image, attaches the chart to the GitHub Release, and publishes it to `https://charts.schedkit.io`. Chart and controller versions are released together. Follow the [release instructions](https://github.com/schedkit/sked#releases-and-helm-repository) for tag rules and repository setup. diff --git a/docs/sked/installation.md b/docs/sked/installation.md index cba77e5..1f393c7 100644 --- a/docs/sked/installation.md +++ b/docs/sked/installation.md @@ -50,7 +50,7 @@ controller: workloadNamespace: schedulers ``` -Create `schedulers` first and configure its admission policy to permit privileged workloads. Helm does not create a custom workload namespace. Secrets and ConfigMaps referenced by Scheduler pods must exist there too. Choose the namespace before creating Schedulers; changing it is not a workload migration mechanism. +Create `schedulers` and configure it to permit privileged pods before installing the release. Helm does not create a custom workload namespace. Put the pods' referenced Secrets and ConfigMaps there too. Changing `controller.workloadNamespace` later does not move existing DaemonSets. Common values: @@ -73,7 +73,7 @@ Common values: The [chart values](https://github.com/schedkit/sked/blob/main/charts/sked/values.yaml) list all settings. -Two schedulable nodes are needed for protection against a node failure. The default spreading rule allows both replicas on one node. The PDB protects against voluntary evictions, not node failures or direct deletion. More replicas add webhook capacity and failover options, not reconciliation throughput. For a single-replica development install, set `replicaCount: 1` and consider disabling the PDB to allow drains. +Use at least two schedulable nodes so the replicas can run on separate hosts. The default spreading rule still allows both on one node. The PDB covers voluntary evictions, not node failures or direct deletion. Extra replicas do not increase reconciliation throughput. For a single-replica development install, set `replicaCount: 1`; disable the PDB if you need to drain that node. The published `latest` and `main` image tags follow the main branch, not the latest release. Release charts select the matching release image. Prefer a release or digest over a moving tag. @@ -122,7 +122,7 @@ kubectl get crd schedulers.sked.schedkit.io kubectl get certificate -n sked-system ``` -The certificate command applies to cert-manager installs. If pods cannot start, inspect pod events for image-pull or missing certificate Secret errors. If Scheduler admission fails, check the webhook Service, certificate readiness, and controller logs. See [Usage](./usage) to create the first Scheduler and verify admission as well as reconciliation. +The certificate command applies to cert-manager installs. If pods cannot start, inspect pod events for image-pull or missing certificate Secret errors. If Scheduler admission fails, check the webhook Service, certificate readiness, and controller logs. Next, [create a Scheduler](./usage) to test admission and workload creation. ## Install with Kustomize @@ -179,6 +179,6 @@ kubectl wait --for=delete daemonset/scx-rusty -n sked-system --timeout=2m helm uninstall sked --namespace sked-system ``` -Repeat the deletion for each Scheduler you intend to stop, using the configured workload namespace. Check that the pods have terminated before treating the nodes as returned to their default CPU scheduler. +Repeat the deletion for each Scheduler you intend to stop, using the configured workload namespace. Wait for the pods to terminate, then check the nodes' kernel scheduler state. For a Kustomize installation, `make undeploy` deletes the default overlay, **including the CRD and namespace**. Unlike Helm uninstall, it removes all Scheduler objects and their owned workloads. Deleting `schedulers.sked.schedkit.io` manually has the same cluster-wide effect on Scheduler objects. Export anything you need to retain before deleting the CRD. diff --git a/docs/sked/overview.md b/docs/sked/overview.md index acef6df..ad11864 100644 --- a/docs/sked/overview.md +++ b/docs/sked/overview.md @@ -7,7 +7,7 @@ slug: /sked/overview # sked -sked runs OCI-packaged `sched_ext` CPU schedulers on Kubernetes nodes. You declare an image and a set of node labels in a cluster-scoped `Scheduler` resource. The operator creates a privileged DaemonSet for it, verifies the image, and reports rollout status. +sked runs OCI-packaged `sched_ext` CPU schedulers on Kubernetes nodes. A cluster-scoped `Scheduler` resource specifies the image and node labels to target. The operator verifies the image, creates a privileged DaemonSet, and reports rollout status. sked does not replace the Kubernetes scheduler. Kubernetes places pods on nodes; a `sched_ext` scheduler controls how a node's Linux kernel allocates CPU time to threads. @@ -21,11 +21,11 @@ sked does not replace the Kubernetes scheduler. Kubernetes places pods on nodes; The [Helm chart](./installation) installs two controller replicas, leader election, admission webhooks, and a PodDisruptionBudget by default. Only the elected leader reconciles; both replicas serve webhooks. -## Boundaries +## Limitations There is no per-node sked agent, and sked does not invoke [schedctl](../schedctl/overview). The kubelet runs the scheduler image directly. schedctl and sked implement their own signature verification and use different trust-policy formats. -sked does not inspect node kernels or confirm that a scheduler has attached. `Ready=True` means the DaemonSet rollout is complete, not that `/sys/kernel/sched_ext/state` is `enabled`. Check the kernel when validating a deployment. +sked does not inspect node kernels or confirm that a scheduler has attached. `Ready=True` means the DaemonSet rollout is complete, not that `/sys/kernel/sched_ext/state` is `enabled`. [Check kernel state](./usage#verify-the-kernel-state) on each target node. Rollouts use Kubernetes' default DaemonSet update strategy. The CRD does not expose rollout tuning, scheduler priority, custom probes, or automatic rollback. Conflict handling covers Scheduler objects, not schedulers started outside sked. @@ -35,6 +35,6 @@ Scheduler containers run privileged and change node-wide kernel behaviour. Restr 1. [Install sked](./installation) with Helm or Kustomize. 2. [Run a scheduler](./usage) on a labelled node pool. -3. Consult the [CRD reference](./crds) and [trust policy](./trust-policy) for configuration. +3. Configure it using the [CRD reference](./crds) and [trust policy](./trust-policy). [Architecture](./architecture) explains reconciliation and failure handling. [Development](./development) covers builds, tests, and generated manifests. diff --git a/docs/sked/trust-policy.md b/docs/sked/trust-policy.md index 5fcd7e3..56c84e9 100644 --- a/docs/sked/trust-policy.md +++ b/docs/sked/trust-policy.md @@ -18,7 +18,7 @@ Signatures must use: - Issuer: `https://token.actions.githubusercontent.com` - Identity: `https://github.com/schedkit/plumbing/.github/workflows/oci-build-publish.yml@refs/heads/main` -There is no extra caller restriction by default; Kubernetes RBAC still applies. Restrict Scheduler writes to administrators trusted to change node-wide kernel behaviour. Admission restrictions are additional checks, not replacements for RBAC. +The default policy adds no caller restrictions beyond Kubernetes RBAC. Grant Scheduler write access only to administrators trusted to change node-wide kernel behaviour. Custom identity restrictions apply in addition to RBAC. ## Custom policy @@ -69,11 +69,11 @@ The verifier uses Cosign v3 signature bundles through OCI referrers. It resolves ## Policy updates and failure behaviour -Each controller replica reloads the ConfigMap every 30 seconds. Allowlist, caller, and verification-enable changes are read from the refreshed policy. The Cosign verifier's issuer, identity, and trusted-root path are configured at process startup: **restart every controller replica after changing `cosign` settings**. Helm changes to a chart-managed policy trigger that restart through a pod-template checksum. Changes to an existing external ConfigMap do not. +Each replica reloads the ConfigMap every 30 seconds. Changes to `allowedImages`, `allowedIdentities`, and `verifySignatures` take effect on reload. The Cosign verifier is configured at startup, so **restart every replica after changing `cosign` settings**. Helm restarts pods when a chart-managed policy changes, using a pod-template checksum. Editing an external ConfigMap does not trigger a restart. A malformed or unreadable policy leaves the last successfully loaded policy in place. At startup, load failures fall back to built-in defaults. A missing ConfigMap or an empty policy document also selects built-in defaults; it is not a deny-all configuration. Check controller logs after policy changes. -Policy refresh does not itself enqueue every Scheduler. Caller and allowlist checks run at admission, not as continuous enforcement against existing resources. If verification fails during reconciliation, sked reports `ImageVerificationFailed` and retries without replacing an existing DaemonSet. Tightening policy does not automatically stop workloads already running; delete their Scheduler objects if they must be removed. +Policy reloads do not trigger Scheduler reconciliation. Caller and allowlist checks apply only at admission. If signature verification fails during reconciliation, sked reports `ImageVerificationFailed` and retries, leaving any existing DaemonSet unchanged. To stop a running workload after tightening the policy, delete its Scheduler. ## Private and mirrored registries @@ -85,4 +85,4 @@ There are three separate credential paths: For a mirror, copy the scheduler image and its signature/referrer artifacts, allow the mirrored repository in the policy, and keep the expected signing identity checks. Nodes and controllers both need access to the mirror. -For an offline environment, also provision a trusted-root file and set `cosign.trustedRootPath`. sked caches trusted material for 24 hours. Mirroring only the image layers is insufficient for signature verification; test admission and reconciliation with external network access blocked before relying on an offline installation. +For offline use, also mount a trusted-root file and set `cosign.trustedRootPath`. sked caches trusted material for 24 hours. Image layers alone are not enough for signature verification. Test admission and reconciliation with external network access blocked. diff --git a/docs/sked/usage.md b/docs/sked/usage.md index 6a6e437..7a908c3 100644 --- a/docs/sked/usage.md +++ b/docs/sked/usage.md @@ -5,7 +5,7 @@ title: Usage # Usage -This walkthrough assumes sked is [installed](./installation) as release `sked` in `sked-system`, using the default trust policy and workload namespace. Adjust namespace arguments if your scheduler DaemonSets run elsewhere. Scheduler resources themselves are cluster-scoped. +These commands assume sked is [installed](./installation) as release `sked` in `sked-system`, with the default trust policy and workload namespace. Change the namespace arguments if your DaemonSets run elsewhere. Scheduler resources are cluster-scoped. ## Check the controller and nodes @@ -24,7 +24,7 @@ ls /sys/kernel/sched_ext cat /sys/kernel/sched_ext/state ``` -The directory's presence is a first check, not a guarantee that a particular scheduler is compatible. Stop schedulers managed outside sked before targeting those nodes. sked cannot arbitrate against schedctl or a manually started scheduler. +Check the scheduler's kernel requirements as well; the directory alone does not establish compatibility. Stop schedulers managed outside sked before targeting those nodes. sked cannot detect conflicts with schedctl or manually started schedulers. Label the nodes you have checked, replacing the example node name: @@ -32,7 +32,7 @@ Label the nodes you have checked, replacing the example node name: kubectl label node worker-1 schedkit.io/pool=batch ``` -Repeat for the other nodes in the pool. A label does not prove kernel support; it is your declaration that those nodes are suitable. +Repeat for the other nodes you have checked. ## Create a Scheduler @@ -82,7 +82,7 @@ kubectl exec -n sked-system -c scx -- cat /sys/kernel/sched_ext/state kubectl exec -n sked-system -c scx -- cat /sys/kernel/sched_ext/root/ops ``` -If not, use a node shell. A privileged container is not a promise that the image includes debugging tools. For attachment failures, check the scheduler logs and node kernel logs: +Otherwise, run the commands from a node shell. For attachment failures, check the scheduler logs and node kernel logs: ```bash kubectl logs -n sked-system -c scx @@ -100,7 +100,7 @@ kubectl edit scheduler scx-rusty Changing the image, arguments, environment, or other pod-template fields causes a DaemonSet rolling update. sked does not expose rollout tuning or automatic rollback. To revert, restore the previous Scheduler spec, preferably with its recorded image digest. -After an update, compare `status.observedGeneration` and each condition's `observedGeneration` with `metadata.generation`. A condition from an earlier generation may still be visible immediately after applying a change. +After an update, compare `status.observedGeneration` and each condition's `observedGeneration` with `metadata.generation`. Until the controller processes the change, conditions may describe the previous spec. ### Tainted nodes diff --git a/src/css/custom.css b/src/css/custom.css index ff297f6..eb59213 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -1,18 +1,6 @@ /** - * schedkit docs — monochrome with one earned accent. - * - * The page is grey. There is exactly one colour, and it has exactly one job: - * to mark the product, a pointer, the current position, or keyboard focus. - * - * --sk-accent is allowed on: the logo, links, the active sidebar/TOC - * entry, and the focus ring. Nothing else. - * - * The test for adding more colour is not "would this look nicer" but "does - * this encode something a reader could act on". Decoration loses. - * - * Structure is carried by type scale and 1px hairlines; mono is used only - * for literal code, commands and check IDs. Dark and light are the same - * design inverted, not two designs. + * schedkit: warm paper and charcoal, ink rules, and a blue accent. + * System fonts only. The homepage artwork shares these theme tokens. */ /* ------------------------------------------------------------------ * @@ -28,7 +16,7 @@ 'Liberation Mono', monospace; --ifm-font-size-base: 16.5px; - --ifm-line-height-base: 1.72; + --ifm-line-height-base: 1.65; --ifm-code-font-size: 87.5%; --ifm-heading-font-weight: 700; --ifm-heading-line-height: 1.12; @@ -42,28 +30,26 @@ } /* ------------------------------------------------------------------ * - * Dark (default). True black, off-white text, achromatic hairlines. + * Dark (default). Charcoal surfaces and warm off-white text. * ------------------------------------------------------------------ */ /* * Infima ships its whole dark palette on `html[data-theme=dark]`, which is - * specificity (0,1,1) — it beats a bare `:root` or `[data-theme='light']` - * selector. That silently overrode the page background (landing on #1b1b1d - * slate instead of true black) plus ~30 other tokens. `:root[data-theme=...]` - * is (0,2,0) and wins, so both theme blocks are scoped this way. + * specificity (0,1,1). Use `:root[data-theme=...]` at (0,2,0) so the custom + * palette wins in both themes. */ :root, :root[data-theme='dark'] { - --sk-bg: #000000; - --sk-surface: #0e0e0e; - --sk-surface-2: #171717; - --sk-fg: #ededed; - --sk-fg-muted: #a8a8a8; - --sk-fg-subtle: #8a8a8a; - --sk-line: #2b2b2b; - --sk-line-strong: #4a4a4a; - /* The one colour. Logo, links, current position, focus. Nothing else. */ - --sk-accent: #4c9be8; + --sk-bg: #161614; + --sk-surface: #1e1e1b; + --sk-surface-2: #262620; + --sk-fg: #eceae3; + --sk-fg-muted: #b7b4aa; + --sk-fg-subtle: #969287; + --sk-line: #393932; + --sk-line-strong: #69665e; + --sk-accent: #70acd6; + --sk-accent-tint: #203545; --ifm-background-color: var(--sk-bg); --ifm-background-surface-color: var(--sk-surface); @@ -113,9 +99,9 @@ --ifm-hover-overlay: rgba(255, 255, 255, 0.06); --ifm-breadcrumb-item-background-active: transparent; - --ifm-navbar-background-color: rgba(0, 0, 0, 0.88); + --ifm-navbar-background-color: var(--sk-bg); --ifm-navbar-shadow: none; - --ifm-footer-background-color: #000000; + --ifm-footer-background-color: var(--sk-bg); } /* ------------------------------------------------------------------ * @@ -123,21 +109,21 @@ * ------------------------------------------------------------------ */ :root[data-theme='light'] { - --sk-bg: #ffffff; - --sk-surface: #ffffff; - --sk-surface-2: #f4f4f4; - --sk-fg: #111111; - --sk-fg-muted: #5c5c5c; - --sk-fg-subtle: #6e6e6e; - --sk-line: #e2e2e2; - --sk-line-strong: #c2c2c2; - /* Exactly the logo blue — 4.8:1 on white, so it clears AA for link text. */ - --sk-accent: #2376b8; + --sk-bg: #faf9f5; + --sk-surface: #f1efe7; + --sk-surface-2: #eae7df; + --sk-fg: #20221f; + --sk-fg-muted: #55584f; + --sk-fg-subtle: #6a6e63; + --sk-line: #d5d3c9; + --sk-line-strong: #a29f93; + --sk-accent: #246b9a; + --sk-accent-tint: #e2edf3; --ifm-background-color: var(--sk-bg); --ifm-background-surface-color: var(--sk-surface); --ifm-font-color-base: var(--sk-fg); - --ifm-heading-color: #000000; + --ifm-heading-color: var(--sk-fg); --ifm-font-color-light: var(--sk-fg-muted); --ifm-font-color-lighter: var(--sk-fg-muted); --ifm-font-color-lightest: var(--sk-fg-subtle); @@ -172,7 +158,7 @@ --ifm-hover-overlay: rgba(0, 0, 0, 0.05); --ifm-breadcrumb-item-background-active: transparent; - --ifm-navbar-background-color: rgba(255, 255, 255, 0.9); + --ifm-navbar-background-color: var(--sk-bg); --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.07); } @@ -196,11 +182,7 @@ body { color: var(--sk-bg); } -/* - * Focus is blue. A keyboard user must be able to tell focus apart from a - * decorative state at a glance, and blue is the only thing on this page that - * already means "actionable". - */ +/* Visible outline for keyboard focus. */ :focus-visible { outline: 2px solid var(--sk-accent); outline-offset: 3px; @@ -236,14 +218,12 @@ body { * ------------------------------------------------------------------ */ .navbar { - border-bottom: 1px solid var(--sk-line); + border-bottom: 2px solid var(--sk-fg); padding-inline: 0.75rem; - backdrop-filter: saturate(120%) blur(10px); - -webkit-backdrop-filter: saturate(120%) blur(10px); } .navbar__inner { - max-width: 1440px; + max-width: 72rem; margin-inline: auto; } @@ -254,7 +234,6 @@ body { .navbar__logo { height: 22px; width: auto; - /* Full saturation: the logo is the one surface that earns the accent. */ } .navbar__title { @@ -365,11 +344,7 @@ body { text-decoration: none; } -/* - * Active sidebar entry: blue, no fill. "You are here" is a pointer, so it - * gets the pointer colour rather than a chip — a background fill would be the - * one place on the page where the accent stops meaning anything. - */ +/* Mark the current page with an accent border and text, without a fill. */ .menu__link--active:not(.menu__link--sublist) { background: transparent; color: var(--sk-accent); @@ -407,15 +382,15 @@ body { /* Docusaurus wraps the title in

, so cover both shapes. */ .theme-doc-markdown header > h1, .theme-doc-markdown > h1 { - font-size: 2.4rem; - font-weight: 700; - letter-spacing: -0.04em; - line-height: 1.08; + font-size: clamp(2.25rem, 4vw, 3.25rem); + font-weight: 750; + letter-spacing: -0.045em; + line-height: 1.05; margin-bottom: 1.75rem; } .theme-doc-markdown > h2 { - font-size: 1.55rem; + font-size: 1.75rem; letter-spacing: -0.028em; margin-top: 3rem; padding-bottom: 0.5rem; @@ -765,6 +740,14 @@ code { background: var(--sk-surface-2); } +@media screen and (max-width: 996px) { + .theme-doc-markdown table { + display: block; + max-width: 100%; + overflow-x: auto; + } +} + /* ------------------------------------------------------------------ * * Admonitions — monochrome, never colour-only * ------------------------------------------------------------------ */ @@ -976,7 +959,7 @@ code { * ------------------------------------------------------------------ */ .footer { - border-top: 1px solid var(--sk-line); + border-top: 2px solid var(--sk-fg); margin-top: 4rem; background: var(--sk-bg); } diff --git a/src/pages/index.module.css b/src/pages/index.module.css index ec9fae5..22eb053 100644 --- a/src/pages/index.module.css +++ b/src/pages/index.module.css @@ -1,216 +1,217 @@ -/** - * Homepage styles. Scoped to src/pages/index.tsx. - * - * Structure comes from type scale and 1px rules. No grids, no glow, no pills, - * no rounded cards, no hover lift, no colour. - */ +/* Homepage layout and illustration styles. */ + +.home { + width: min(100% - 3rem, 72rem); + margin-inline: auto; + padding-bottom: 2rem; +} .hero { - padding: 4.5rem 0 4rem; - border-bottom: 1px solid var(--sk-line); + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr); + align-items: center; + gap: 2.5rem; + padding-block: clamp(3rem, 6vw, 5.5rem) 3.5rem; } -/* - * The type scale is the whole design: this is the biggest thing on the site. - * It stays grey — the navbar logo is the only place the accent is allowed to - * sit beside the product name, and two blues competing for the same wordmark - * would undo the rule. - */ -.wordmark { +.eyebrow { margin: 0 0 1.25rem; - font-size: clamp(3.4rem, 11vw, 7.5rem); - font-weight: 700; + font-family: var(--ifm-font-family-monospace); + font-size: 0.7rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--sk-fg-subtle); +} + +.wordmark { + margin: 0 0 1.6rem -0.045em; + font-size: clamp(4.5rem, 9.5vw, 7.25rem); + font-weight: 800; line-height: 0.9; - letter-spacing: -0.055em; - color: var(--sk-fg); + letter-spacing: -0.065em; } .tagline { - margin: 0 0 3rem; - max-width: 28ch; - font-size: clamp(1.25rem, 2.6vw, 1.75rem); + margin: 0 0 1.5rem; + font-size: clamp(1.85rem, 3.2vw, 2.6rem); font-weight: 500; - line-height: 1.25; - letter-spacing: -0.03em; - color: var(--sk-fg); - text-wrap: balance; + line-height: 1.15; + letter-spacing: -0.035em; } -.heroLower { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); - gap: 4rem; - align-items: start; +.tagline span { + color: var(--sk-accent); } -.heroProse p { - margin: 0 0 1rem; - max-width: 54ch; +.heroDescription { + max-width: 38ch; + margin: 0; + font-size: 1.1rem; + line-height: 1.6; color: var(--sk-fg-muted); } .heroActions { display: flex; + align-items: center; flex-wrap: wrap; - gap: 0.75rem; - margin-top: 1.75rem; + gap: 1.25rem 1.75rem; + margin-top: 2rem; } -/* ---- Transcript ---- */ +.heroActions :global(.button) { + display: inline-flex; + align-items: center; + gap: 1.5rem; + padding: 0.9rem 1.3rem; + font-size: 0.95rem; +} -.transcript { +.artwork { margin: 0; - border: 1px solid var(--sk-line); - background: var(--sk-surface); + min-width: 0; } -.transcriptBar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; - padding: 0.3rem 0.4rem 0.3rem 0.8rem; - border-bottom: 1px solid var(--sk-line); +.schematic { + display: block; + width: 100%; + height: auto; } -.transcriptTitle { - font-family: var(--ifm-font-family-monospace); - font-size: 0.7rem; - letter-spacing: 0.04em; +.artwork figcaption { + max-width: 52ch; + margin: 1rem auto 0; + padding-top: 0.75rem; + border-top: 1px solid var(--sk-line); + font-size: 0.75rem; + line-height: 1.5; color: var(--sk-fg-subtle); } -.transcriptButton { - flex: 0 0 auto; - width: 15px; - height: 15px; - border: 1px solid var(--sk-line-strong); +.artwork figcaption code { + padding: 0; + background: transparent; + font-size: inherit; + color: inherit; } -.transcriptBody { - margin: 0; - padding: 1rem 1.1rem; - overflow-x: auto; - font-family: var(--ifm-font-family-monospace); - font-size: 0.78rem; - line-height: 1.7; +.section { + padding-block: 2rem 0; + margin-top: 4.5rem; + border-top: 2px solid var(--sk-fg); } -.transcriptBody code { - display: block; - background: transparent; - border: 0; - padding: 0; - font-size: inherit; - white-space: pre; - color: var(--sk-fg); +.hero + .section { + margin-top: 0; } -/* ---- Sections ---- */ - -.section { - padding: 4rem 0; - border-bottom: 1px solid var(--sk-line); +.sectionHead { + margin-bottom: 2.25rem; } -.section:last-child { - border-bottom: 0; +.sectionHead .eyebrow { + margin-bottom: 0.75rem; } -.sectionTitle { - margin: 0 0 2rem; - font-size: clamp(1.6rem, 3.4vw, 2.25rem); +.sectionHead h2 { + margin: 0; + font-size: clamp(1.9rem, 3.5vw, 2.85rem); font-weight: 700; - letter-spacing: -0.04em; - line-height: 1.05; - color: var(--sk-fg); + line-height: 1.1; + letter-spacing: -0.045em; } -.sectionLede { - margin: -1.25rem 0 2rem; - max-width: 56ch; - color: var(--sk-fg-muted); +.textLink { + color: var(--sk-fg); + font-size: 0.9rem; + font-weight: 600; + text-decoration: underline; + text-decoration-color: var(--sk-line-strong); + text-underline-offset: 0.3em; + text-decoration-thickness: 1px; } -/* ---- Tools ---- */ +.textLink:hover { + color: var(--sk-accent); + text-decoration-color: currentColor; +} .tools { - margin: 0; - border-top: 1px solid var(--sk-line-strong); + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 3rem; } .tool { - display: grid; - grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); - gap: 2rem; - padding-block: 2rem; - border-bottom: 1px solid var(--sk-line); + min-width: 0; + padding-top: 1rem; + border-top: 1px solid var(--sk-line-strong); } -.toolHead { - display: flex; - flex-direction: column; - gap: 0.2rem; - margin: 0; +.toolScope { + margin: 0 0 0.3rem; + font-size: 0.8rem; + color: var(--sk-fg-subtle); } .toolNameRow { display: flex; align-items: center; - gap: 0.55rem; + gap: 0.85rem; + margin-bottom: 1rem; } .toolName { - font-family: var(--ifm-font-family-monospace); - font-size: 1.2rem; - font-weight: 600; - letter-spacing: -0.02em; - color: var(--sk-fg); + margin: 0; + font-size: 1.9rem; + font-weight: 700; + letter-spacing: -0.04em; } -/* Monochrome status pill: encodes maturity, adds no hue. */ .toolBadge { + padding: 0.1rem 0.35rem; + border: 1px solid var(--sk-line-strong); font-family: var(--ifm-font-family-monospace); font-size: 0.6rem; - font-weight: 600; - letter-spacing: 0.14em; - text-transform: uppercase; + letter-spacing: 0.06em; + line-height: 1.4; + color: var(--sk-fg-subtle); +} + +.toolDescription { + margin: 0 0 1.5rem; color: var(--sk-fg-muted); - border: 1px solid var(--sk-line-strong); - background: var(--sk-surface); - padding: 0.1rem 0.4rem; - line-height: 1.5; - user-select: none; + line-height: 1.6; } -.toolLine { - font-size: 0.92rem; +.installNote { + margin: 0 0 0.6rem; + font-size: 0.8rem; + line-height: 1.5; color: var(--sk-fg-subtle); } -.toolBody { - margin: 0; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 1.1rem; +.installNote a { + color: var(--sk-fg-muted); + text-decoration: underline; + text-underline-offset: 0.2em; } -.toolBody p { - margin: 0; - max-width: 58ch; - color: var(--sk-fg-muted); +.installNote a:hover { + color: var(--sk-accent); } .install { - margin: 0; max-width: 100%; - padding: 0.6rem 0.85rem; + margin: 0 0 1.25rem; + padding: 1rem 1.1rem; border: 1px solid var(--sk-line); background: var(--sk-surface); overflow-x: auto; font-family: var(--ifm-font-family-monospace); - font-size: 0.82rem; + font-size: 0.76rem; + line-height: 1.8; } .install code { @@ -227,148 +228,222 @@ user-select: none; } -/* Same rule as the doc pages: blue marks a pointer, never a decoration. */ -.toolLink { - font-size: 0.9rem; - font-weight: 600; - color: var(--sk-accent); - text-decoration: underline; - text-decoration-color: var(--sk-line-strong); - text-underline-offset: 0.22em; - text-decoration-thickness: 1px; - transition: text-decoration-color 120ms ease; +.steps { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 2.5rem; + margin: 0; + padding: 0; + list-style: none; +} + +.steps li { + padding-top: 0.8rem; + border-top: 1px solid var(--sk-line-strong); } -.toolLink:hover { +.steps .stepLabel { + margin: 0 0 1rem; + font-family: var(--ifm-font-family-monospace); + font-size: 0.7rem; + color: var(--sk-fg-subtle); +} + +.stepLabel span { + margin-right: 0.5rem; color: var(--sk-accent); - text-decoration-color: var(--sk-accent); } -/* ---- Requirements table ---- */ +.steps h3 { + margin: 0 0 0.75rem; + font-size: 1.4rem; + letter-spacing: -0.03em; +} + +.steps p { + margin: 0 0 1.1rem; + font-size: 0.95rem; + line-height: 1.6; + color: var(--sk-fg-muted); +} + +.requirements { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 3rem; + align-items: start; +} + +.requirements .sectionHead { + margin: 0; +} + +.sectionLede { + max-width: 46ch; + margin: 1.25rem 0 1rem; + color: var(--sk-fg-muted); +} + +.sectionNote { + max-width: 52ch; + margin-bottom: 1.25rem; + font-size: 0.85rem; + color: var(--sk-fg-subtle); +} .reqTable { display: table; width: 100%; margin: 0; - font-size: 0.92rem; border-collapse: collapse; - border-top: 1px solid var(--sk-line-strong); + font-size: 0.9rem; + line-height: 1.6; } -.reqTable thead, -.reqTable tbody tr:nth-child(2n) { +.reqTable tr, +.reqTable tr:nth-child(2n) { background: transparent; + border: 0; } -.reqTable th { - font-family: var(--ifm-font-family-monospace); - font-size: 0.68rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.12em; - text-align: left; - color: var(--sk-fg-subtle); - padding: 0.7rem 0.8rem; - border-bottom: 1px solid var(--sk-line); -} - +.reqTable th, .reqTable td { - padding: 0.85rem 0.8rem; - vertical-align: top; + padding: 1rem 0; + border: 0; border-bottom: 1px solid var(--sk-line); + vertical-align: top; + text-align: left; } -.reqTable tbody tr:last-child td { - border-bottom: 0; +.reqTable tr:first-child > * { + border-top: 1px solid var(--sk-line-strong); } -.reqTable td code { - background: transparent; - border: 0; - padding: 0; - font-size: 0.85rem; - color: var(--sk-fg); +.reqTable th { + width: 7.5rem; + padding-right: 1rem; + font-weight: 600; } -.reqNote { +.reqTable td { color: var(--sk-fg-muted); + overflow-wrap: anywhere; } -/* ---- Routes ---- */ - .routes { - list-style: none; margin: 0; padding: 0; + list-style: none; border-top: 1px solid var(--sk-line-strong); } .route { display: grid; - grid-template-columns: minmax(0, 18rem) minmax(0, 1fr); - gap: 2rem; - padding-block: 1.4rem; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto; + align-items: baseline; + gap: 1.5rem; + padding-block: 1.2rem; border-bottom: 1px solid var(--sk-line); - color: inherit; - transition: color 120ms ease; + color: var(--sk-fg); } .route:hover { - color: inherit; + color: var(--sk-accent); text-decoration: none; } -.routeWhen { +.routeTitle { font-weight: 600; - color: var(--sk-fg); + letter-spacing: -0.015em; } -.route:hover .routeWhen { +.route:hover .routeTitle { text-decoration: underline; - text-underline-offset: 0.22em; - text-decoration-color: var(--sk-accent); + text-underline-offset: 0.25em; } -.routeWhat { +.routeDetail { + font-size: 0.9rem; color: var(--sk-fg-muted); } -/* ------------------------------------------------------------------ * - * Responsive - * ------------------------------------------------------------------ */ +.routeArrow { + color: var(--sk-accent); +} -@media screen and (max-width: 996px) { +@media (max-width: 996px) { .hero { - padding: 3rem 0; + gap: 1rem; + } + + .heroActions { + gap: 1rem; + } + + .tools, + .requirements { + gap: 2rem; + } + + .steps { + gap: 1.5rem; + } +} + +@media (max-width: 760px) { + .hero { + grid-template-columns: minmax(0, 1fr); + gap: 2rem; + padding-block: 3rem 2.5rem; + } + + .wordmark { + font-size: clamp(4.5rem, 17vw, 7.25rem); } .tagline { - margin-bottom: 2rem; + font-size: clamp(1.9rem, 6vw, 2.6rem); + } + + .artwork { + width: min(100%, 30rem); + justify-self: center; } - .heroLower { + .tools, + .requirements, + .steps { grid-template-columns: minmax(0, 1fr); - gap: 2.5rem; + gap: 2rem; } .section { - padding: 3rem 0; + margin-top: 3rem; + padding-top: 1.5rem; } - .tool, .route { - grid-template-columns: minmax(0, 1fr); - gap: 0.75rem; + grid-template-columns: minmax(0, 1fr) auto; + gap: 0.2rem 1rem; + } + + .routeDetail { + grid-column: 1; + } + + .routeArrow { + grid-column: 2; + grid-row: 1; } } -@media screen and (max-width: 576px) { - .heroActions { - flex-direction: column; - align-items: stretch; +@media (max-width: 400px) { + .home { + width: calc(100% - 2rem); } - .heroActions :global(.button) { - width: 100%; + .reqTable th { + width: 6rem; + padding-right: 0.75rem; } } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 46b43f5..758c108 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,220 +1,203 @@ import type {ReactNode} from 'react'; -import clsx from 'clsx'; import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Layout from '@theme/Layout'; import Heading from '@theme/Heading'; +import SchedulerShipment from '@site/static/img/scheduler-shipment.svg'; import styles from './index.module.css'; -/* ------------------------------------------------------------------ * - * Hero - * ------------------------------------------------------------------ */ - -const DOCTOR_TRANSCRIPT: string[] = [ - '$ sudo schedctl doctor', - '', - 'schedctl doctor — host readiness check', - '', - '[PASS] kernel.version 7.1.8-1-default', - '[PASS] kernel.sched_ext /sys/kernel/sched_ext is present', - '[PASS] kernel.btf /sys/kernel/btf/vmlinux (5993874 bytes)', - '[PASS] kernel.config all required CONFIG_* flags enabled', - '[PASS] caps.cap_bpf CapEff bit 39 set', - '[PASS] caps.cap_sys_admin CapEff bit 21 set', - '[PASS] caps.cap_perfmon CapEff bit 38 set', - '[PASS] runtime.podman_socket /run/podman/podman.sock', - '[WARN] runtime.containerd_socket none reachable: /run/containerd/containerd.sock', - '[PASS] runtime.any /run/podman/podman.sock', - '', - 'Remediation:', - ' - runtime.containerd_socket: start containerd (e.g. `systemctl enable --now containerd`) or rely on Podman', - '', - 'Summary: 9 passed, 1 failed, 0 skipped (0 blocking)', -]; - -/** Single right-aligned button, old-school Linux titlebar. Not macOS dots. */ -function Transcript() { - return ( -
-
-
- schedctl doctor -
-
-
-        {DOCTOR_TRANSCRIPT.join('\n')}
-      
-
- ); -} - function Hero() { const {siteConfig} = useDocusaurusContext(); return (
-
+
+

Linux sched_ext · OCI images

{siteConfig.title} -

{siteConfig.tagline}

- -
-
-

- Linux 6.12 made the CPU scheduler a BPF program the kernel loads, - verifies and unloads on demand. No custom kernel, no module, no - maintenance window. -

-

- schedkit handles everything around that: shipping a scheduler as - an OCI image, checking whether a host can run one, reporting what - is actually attached, and doing the same across a cluster. -

-
- - Introduction - - - What is sched_ext? - -
-
-
- -
+

+ CPU schedulers,
+ packaged as OCI images. +

+

+ Use schedctl on a Linux host or sked in Kubernetes to verify + and run sched_ext scheduler images. +

+
+ + Read the docs + + + View the source +
+
+ +
+ The scheduler arrives as an OCI image and attaches to the host + kernel through sched_ext. +
+
); } -/* ------------------------------------------------------------------ * - * Tools - * ------------------------------------------------------------------ */ - -const TOOLS = [ +type Tool = { + name: string; + scope: string; + alpha?: boolean; + body: string; + prerequisite: ReactNode; + commands: string[]; + href: string; +}; + +const TOOLS: Tool[] = [ { name: 'schedctl', - line: 'The host-side CLI.', - alpha: false, - body: 'Verifies, pulls and runs an OCI-packaged sched_ext scheduler on a single machine, with a kernel preflight built in. Reaches for one scheduler for the length of a build, a game session, or a test run.', - install: ['sudo zypper in schedctl'], - href: '/docs/schedctl/overview', + scope: 'On a host', + body: 'Run scheduler images with Podman or containerd. schedctl verifies signatures, checks host compatibility, reports which scheduler is attached, and starts and stops scheduler containers.', + prerequisite: <>Install on openSUSE, or see the other installation options., + commands: ['sudo zypper in schedctl', 'sudo schedctl doctor'], + href: '/docs/schedctl/usage', }, { name: 'sked', - line: 'The Kubernetes operator.', + scope: 'In Kubernetes', alpha: true, - body: 'A cluster-scoped Scheduler resource selects an OCI scheduler image and a node pool. sked verifies the image and manages a privileged DaemonSet on those nodes. No schedctl installation is needed on the nodes.', - prerequisite: ( - <> - Install cert-manager first, - or supply a webhook certificate. - - ), - install: [ + body: 'Create a Scheduler resource for a node pool. sked verifies the image, creates a privileged DaemonSet, and reports rollout status. It does not require schedctl on the nodes.', + prerequisite: <>Install cert-manager first, or supply a webhook certificate., + commands: [ 'helm repo add schedkit https://charts.schedkit.io', 'helm repo update', 'helm install sked schedkit/sked \\\n --namespace sked-system --create-namespace \\\n --wait --timeout 5m', ], - href: '/docs/sked/overview', + href: '/docs/sked/usage', }, ]; function Tools() { return ( -
-
- - Two tools - -
- {TOOLS.map((tool) => ( -
-
- - {tool.name} - {tool.alpha && ( - - alpha - - )} +
+
+

01 / Deployment

+ Run on a host or in Kubernetes +
+
+ {TOOLS.map((tool) => ( +
+

{tool.scope}

+
+ {tool.name} + {tool.alpha && ( + + alpha - {tool.line} -
-
-

{tool.body}

- {tool.prerequisite &&

{tool.prerequisite}

} -
-                  
-                    {tool.install.map((command, index) => (
-                      
-                        {index > 0 && '\n'}
-                        
-                        {command}
-                      
-                    ))}
-                  
-                
- - {tool.name} documentation - -
+ )}
- ))} -
+

{tool.body}

+

{tool.prerequisite}

+
+              
+                {tool.commands.map((command, index) => (
+                  
+                    {index > 0 && '\n'}
+                    
+                    {command}
+                  
+                ))}
+              
+            
+ + Use {tool.name} + + + ))}
); } -/* ------------------------------------------------------------------ * - * Requirements - * ------------------------------------------------------------------ */ +const STEPS = [ + { + name: 'Package', + detail: 'An OCI image contains the scheduler binary and its dependencies. Publish it to a container registry.', + label: 'OCI image', + href: '/docs/concepts/oci-schedulers', + }, + { + name: 'Run', + detail: 'schedctl or sked verifies the image signature by default. The container runtime pulls the image and starts the scheduler.', + label: 'Container runtime', + href: '/docs/concepts/architecture', + }, + { + name: 'Schedule', + detail: 'The scheduler attaches through sched_ext and allocates CPU time to threads. When it detaches, the kernel returns to its default policy.', + label: 'Linux kernel', + href: '/docs/concepts/sched-ext', + }, +]; + +function Delivery() { + return ( +
+
+

02 / How it works

+ From registry to kernel +
+
    + {STEPS.map((step, index) => ( +
  1. +

    + {String(index + 1).padStart(2, '0')} {step.label} +

    + {step.name} +

    {step.detail}

    + + {step.label} + +
  2. + ))} +
+
+ ); +} -/* Check IDs are the real ones from `schedctl doctor` (see commands.md). */ -const REQUIREMENTS: Array<[string, string, string]> = [ - ['kernel.version', 'Linux 6.12 or later', 'The release that shipped sched_ext upstream.'], - ['kernel.config', 'CONFIG_SCHED_CLASS_EXT=y', 'With the CONFIG_BPF* flags and CONFIG_DEBUG_INFO_BTF for CO-RE programs.'], - ['caps.*', 'CAP_BPF, CAP_SYS_ADMIN, CAP_PERFMON', 'Verified by schedctl doctor. Under sked each scheduler runs in a privileged DaemonSet instead.'], - ['runtime.any', 'A reachable runtime socket', 'Podman or containerd, so the scheduler can be pulled as an OCI image.'], +const REQUIREMENTS = [ + ['Kernel', 'Linux 6.12 or later with CONFIG_SCHED_CLASS_EXT=y, BPF support, and BTF.'], + ['Runtime', 'Podman or containerd on a host; the node’s container runtime in Kubernetes.'], + ['Permissions', 'Access to load BPF programs. sked runs scheduler containers privileged.'], + ['Compatibility', 'A scheduler image built for the host’s architecture and kernel.'], ]; function Requirements() { return ( -
-
- - What a host needs - -

- Each of these is a check ID that schedctl doctor reports - by name, and exits non-zero on when one fails. -

- - - - - - - - +
+
+
+

03 / Before you run

+ Check the host +

+ Run schedctl doctor to check the kernel, capabilities, + and container runtime. Check the scheduler’s own requirements too. +

+

+ sked reports DaemonSet readiness but does not check kernel + attachment. Verify the running scheduler on each node. +

+ + Check host compatibility + +
+
CheckRequiresNotes
- {REQUIREMENTS.map(([id, needs, note]) => ( - - - - + {REQUIREMENTS.map(([name, detail]) => ( + + + ))} @@ -224,69 +207,45 @@ function Requirements() { ); } -/* ------------------------------------------------------------------ * - * Where to start - * ------------------------------------------------------------------ */ - -const ROUTES: Array<[string, string, string]> = [ - [ - 'Understand the design first', - 'What sched_ext is and is not, and why OCI ended up as the distribution format.', - '/docs/concepts/sched-ext', - ], - [ - 'One machine, right now', - 'Install schedctl, run the preflight, attach a scheduler.', - '/docs/schedctl/installation', - ], - [ - 'Nodes already in Kubernetes', - 'Install sked with Helm and declare a Scheduler resource.', - '/docs/sked/installation', - ], - [ - 'Package your own scheduler', - 'The image layout that schedctl and sked both expect.', - '/docs/schedctl/packaging-a-scheduler', - ], +const ROUTES = [ + ['Understand sched_ext', 'How BPF-based CPU scheduling works in Linux.', '/docs/concepts/sched-ext'], + ['Run on one machine', 'Install schedctl and try a scheduler.', '/docs/schedctl/installation'], + ['Deploy to a node pool', 'Install sked with Helm and create a Scheduler.', '/docs/sked/installation'], + ['Package a scheduler', 'Build an image that both tools can run.', '/docs/schedctl/packaging-a-scheduler'], ]; function Routes() { return ( -
-
- - Where to start - -
    - {ROUTES.map(([when, what, href]) => ( -
  • - - {when} - {what} - -
  • - ))} -
+
+
+

04 / Documentation

+ Where to start
+
    + {ROUTES.map(([title, detail, href]) => ( +
  • + + {title} + {detail} + + +
  • + ))} +
); } -/* ------------------------------------------------------------------ * - * Page - * ------------------------------------------------------------------ */ - export default function Home(): ReactNode { - const {siteConfig} = useDocusaurusContext(); return ( - -
+
+ +
diff --git a/static/img/README.md b/static/img/README.md index d7c1913..200878f 100644 --- a/static/img/README.md +++ b/static/img/README.md @@ -1,9 +1,15 @@ # Static images -This directory holds static assets served at the site root. The site config currently expects: +These assets are served under `/img/`. -- `logo.svg` — used in the navbar. A placeholder is committed; replace it with a real logo when one exists. -- `favicon.ico` — favicon. **Not yet committed.** Generate one (e.g. with [realfavicongenerator.net](https://realfavicongenerator.net/)) and drop it here, or remove the `favicon` reference from `docusaurus.config.ts` until you have one. -- `social-card.png` — used as the OpenGraph/Twitter card image. **Not yet committed.** A 1200×630 PNG is the standard size. +## Homepage artwork -Until `favicon.ico` and `social-card.png` exist, the build will succeed but the social/favicon experience will be empty. That's fine for an internal preview but worth fixing before the site goes wide. +`scheduler-shipment.svg` shows an OCI scheduler package arriving at a Linux host. The illustrated socket represents attachment through `sched_ext`; it is not a hardware component. + +The homepage imports the SVG inline so its `--sk-*` colours follow the site theme. Colour fallbacks support standalone use. The file includes a title and description for screen readers and uses system fonts, without scripts or external images. + +## Site assets + +- `logo.svg` is used in the navbar. +- `social-card.png` is the OpenGraph/Twitter preview image; `social-card.svg` is its editable source. +- `favicon.ico` is referenced by `docusaurus.config.ts` but is missing. Add the file or remove the reference. diff --git a/static/img/scheduler-shipment.svg b/static/img/scheduler-shipment.svg new file mode 100644 index 0000000..d532fcb --- /dev/null +++ b/static/img/scheduler-shipment.svg @@ -0,0 +1,101 @@ + + OCI scheduler package arriving at a Linux host + An isometric parcel labelled OCI sits above a host board. Dashed lines lead to a socket representing sched_ext, with traces connecting it to four CPU cores. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OCI + schedkit + + + + + + + + + + + + + + + + OCI scheduler + sched_ext + CPU cores + Linux host + verify + start + + +
- {id} - {needs}{note}
{name}{detail}