diff --git a/README.md b/README.md index cd732ff..98e52f3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ The DOKS team provides this image for use as-is and for transparency as the imag # Usage +Prefer the **DaemonSet** when you need debug pods on many nodes, or the ephemeral **`debug-node`** helper when you need access to a single node. Avoid leaving a long-lived `doks-debug` Deployment in the cluster. + +## DaemonSet + ```bash kubectl apply -f k8s/daemonset.yaml ``` @@ -18,6 +22,7 @@ This DaemonSet manifest will: 2. Use `hostPID`, `hostIPC`, and `hostNetwork`. 3. Mount the entire host filesystem to `/host` in the containers. 4. Mount the `containerd` socket at `/run/containerd/containerd.sock` from the host into the container. + 5. Tolerate all taints (`operator: Exists`) so pods can land on cordoned or specially tainted nodes. In order to make use of these workloads, you can exec into a pod of choice by name: @@ -33,6 +38,28 @@ POD_NAME=$(kubectl -n kube-system get pods --field-selector spec.nodeName=${NODE kubectl -n kube-system exec -it ${POD_NAME} bash ``` +Clean up when finished: + +```bash +kubectl delete -f k8s/daemonset.yaml +``` + +## Ephemeral single-node access (`debug-node`) + +For short-lived access to one node, use `script/debug-node`. It creates a `doks-debug` Deployment pinned with a `nodeSelector`, execs into the host via `chroot /host`, and deletes the Deployment when you exit. + +```bash +./script/debug-node +``` + +The Deployment manifest does **not** include a default catch-all toleration. A long-lived Deployment with `tolerations: [{operator: Exists}]` can reschedule onto draining nodes and block scale-down or upgrades. If you need to reach a tainted or cordoned node for a brief session, pass `--tolerate-all`: + +```bash +./script/debug-node --tolerate-all +``` + +Requires `kubectl`, `curl` (if the local manifest is unavailable), and [`yq`](https://github.com/mikefarah/yq). + Once you're in, you have access to the set of tools listed in the `Dockerfile`. This includes: - [`vim`](https://github.com/vim/vim) - is a greatly improved version of the good old UNIX editor Vi. diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 18d0aca..d6315f1 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -22,8 +22,6 @@ spec: hostPID: true hostIPC: true hostNetwork: true - tolerations: - - operator: Exists containers: - name: doks-debug securityContext: diff --git a/script/debug-node b/script/debug-node new file mode 100755 index 0000000..8db2ba4 --- /dev/null +++ b/script/debug-node @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# Ephemeral node access via a doks-debug Deployment. +# Pins to one node, execs in, and deletes the Deployment on exit. +# +# Usage: debug-node [--tolerate-all] +# +# --tolerate-all Inject tolerations: operator: Exists so the pod can land on +# tainted/cordoned nodes. Use only for short-lived sessions; +# long-lived Deployments with this toleration can block drains +# and upgrades. + +set -euo pipefail + +tolerate_all=false +node_name="" + +usage() { + echo "Usage: $(basename "$0") [--tolerate-all] " + echo " connect and chroot into the node using an ephemeral doks-debug Deployment" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + --tolerate-all) + tolerate_all=true + shift + ;; + -*) + echo "Error: unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + *) + if [[ -n "$node_name" ]]; then + echo "Error: unexpected argument: $1" >&2 + usage >&2 + exit 1 + fi + node_name="$1" + shift + ;; + esac +done + +if [[ -z "$node_name" ]]; then + usage >&2 + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +local_manifest="${script_dir}/../k8s/deployment.yaml" +remote_manifest_url="https://raw.githubusercontent.com/digitalocean/doks-debug/refs/heads/master/k8s/deployment.yaml" + +deployment_name="doks-debug" +deployment_namespace="kube-system" + +cleanup() { + kubectl delete deployment "${deployment_name}" \ + --namespace "${deployment_namespace}" \ + --ignore-not-found=true +} +trap cleanup EXIT + +if [[ -f "$local_manifest" ]]; then + doks_debug_deployment_yaml="$(cat "$local_manifest")" +else + doks_debug_deployment_yaml="$(curl --silent --fail --location "$remote_manifest_url")" +fi + +if [[ -z "$doks_debug_deployment_yaml" ]]; then + echo "Error: failed to load the deployment manifest." >&2 + exit 1 +fi + +if ! command -v yq >/dev/null 2>&1; then + echo "Error: yq is required (https://github.com/mikefarah/yq)." >&2 + exit 1 +fi + +modified_yaml="$( + echo "$doks_debug_deployment_yaml" | \ + nodeName="$node_name" yq '.spec.template.spec.nodeSelector["kubernetes.io/hostname"] = env(nodeName)' +)" + +if [[ "$tolerate_all" == true ]]; then + modified_yaml="$( + echo "$modified_yaml" | \ + yq '.spec.template.spec.tolerations = [{"operator": "Exists"}]' + )" +fi + +echo "$modified_yaml" | kubectl apply -f - + +kubectl wait deployment "${deployment_name}" \ + --namespace "${deployment_namespace}" \ + --for=condition=Available \ + --timeout=120s \ + >/dev/null + +pod_name="$( + kubectl get pods \ + --namespace "${deployment_namespace}" \ + -l name=doks-debug \ + -o jsonpath='{.items[0].metadata.name}' +)" + +if [[ -z "$pod_name" ]]; then + echo "Error: could not find a running pod for the deployment." >&2 + exit 1 +fi + +kubectl wait pod "${pod_name}" \ + --namespace "${deployment_namespace}" \ + --for=condition=Ready \ + --timeout=60s \ + >/dev/null + +kubectl exec -it "${pod_name}" \ + --namespace "${deployment_namespace}" \ + -- chroot /host bash