Projects STRLCPY cdebug Commits ecf38890
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    README.md
    1 1  # cdebug - a swiss army knife of container debugging (WIP)
    2 2   
    3  -TODO: Add table command (exec, port-forward, export, explore) x runtime (docker, containerd, k8s, etc).
     3 +With this tool you can:
     4 + 
     5 +- Troubleshoot containers lacking shell and/or debugging tools
     6 +- Forward unpublished or even localhost ports to your host system
     7 +- Expose endpoints from the host system to containers & Kubernetes networks
     8 +- Handily export image's and/or container's filesystem to local folders
     9 +- and more :)
    4 10   
    5  -A handy way to troubleshoot containers lacking a shell and/or debugging tools
    6  -(e.g, scratch, slim, or distroless):
     11 +The following _commands_ x _runtimes_ are supported:
     12 + 
     13 +| | Docker | Containerd | Kubernetes | Kubernetes CRI | runc |
     14 +| :--- | :---: | :---: | :---: | :---: | :---: |
     15 +| `exec` | ✅ | 🛠️ | - | - | - |
     16 +| `port-forward` local | ✅ | - | - | - | - |
     17 +| `port-forward` remote | 🛠️ | - | 🛠️ | - | - |
     18 +| `export` | - | - | - | - | - |
     19 + 
     20 +## Installation
     21 + 
     22 +It's a statically linked Go binary, so you know the drill:
    7 23   
    8 24  ```sh
    9  -# Simple (busybox)
    10  -$ cdebug exec -it <target-container>
     25 +GOOS=linux
     26 +GOARCH=amd64
     27 + 
     28 +curl -Ls https://github.com/iximiuz/cdebug/releases/latest/download/cdebug_${GOOS}_${GOARCH}.tar.gz | tar xvz
     29 + 
     30 +sudo mv cdebug /usr/local/bin
     31 +```
     32 + 
     33 +At the moment, the following systems are (kinda sorta) supported:
     34 + 
     35 +- linux/amd64
     36 +- darwin/amd64
     37 +- darwin/arm64
     38 + 
     39 +## Commands
     40 + 
     41 +### cdebug exec
    11 42   
    12  -# Advanced (shell + ps + vim)
    13  -$ cdebug exec -it --image nixery.dev/shell/ps/vim <target-container>
     43 +Run an interactive shell in a scratch, slim, or distroless container, with ease:
     44 + 
     45 +```sh
     46 +cdebug exec -it <target-container>
    14 47  ```
    15 48   
    16  -The `cdebug exec` command is some sort of crossbreeding of `docker exec` and `kubectl debug` commands.
     49 +The `cdebug exec` command is a crossbreeding of `docker exec` and `kubectl debug` commands.
    17 50  You point the tool at a running container, say what toolkit image to use, and it starts
    18  -a debugging "sidecar" container that _feels_ like a `docker exec` session into the target container:
     51 +a debugging "sidecar" container that _feels_ like a `docker exec` session to the target container:
    19 52   
    20 53  - The root filesystem of the debugger **_is_** the root filesystem of the target container.
    21 54  - The target container isn't recreated and/or restarted.
    22 55  - No extra volumes or copying of debugging tools is needed.
    23 56  - The debugging tools **_are_** available in the target container.
    24 57   
    25  -Currently supported toolkit images:
     58 +By default, the `busybox:latest` image is used for the debugger sidecar, but you can override it
     59 +with the `--image` flag. Combining this with the superpower of Nix and [Nixery](https://nixery.dev/),
     60 +you can get all your favorite tools by simply listing them in the image name:
     61 + 
     62 +```
     63 +cdebug exec -it --image nixery.dev/shell/ps/vim/tshark <target-container>
     64 +```
     65 + 
     66 +<details>
     67 +<summary>How it works</summary>
     68 + 
     69 +The technique is based on the ideas from this [blog post](https://iximiuz.com/en/posts/docker-debug-slim-containers).
     70 +Oversimplifying, the debugger container is started like:
     71 + 
     72 +```sh
     73 +docker run [-it] \
     74 + --network container:<target> \
     75 + --pid container:<target> \
     76 + --uts container:<target> \
     77 + <toolkit-image>
     78 + sh -c <<EOF
     79 +ln -s /proc/$$/root/bin/ /proc/1/root/.cdebug
     80 + 
     81 +export PATH=$PATH:/.cdebug
     82 +chroot /proc/1/root sh
     83 +EOF
     84 +```
     85 + 
     86 +The secret sauce is the symlink + PATH modification + chroot-ing.
     87 + 
     88 +</details>
     89 + 
     90 +### cdebug port-forward
     91 + 
     92 +Forward local ports to containers and vice versa. This command is another crossbreeding -
     93 +this time it's `kubectl port-forward` and `ssh -L|-R`.
     94 + 
     95 +Currently, only local port forwarding (`cdebug port-forward -L`) is supported,
     96 +but remote port forwarding is under active development.
    26 97   
    27  -- 🧰 `busybox` - a good default choice
    28  -- 🧙 `nixery.dev/shell/...` - [a very powerful way to assemble images on the fly](https://nixery.dev/).
     98 +Local port forwarding use cases (works for Docker Desktop too!):
    29 99   
    30  -Supported runtimes:
     100 +- Publish "unpublished" port 80 to a random port on the host: `cdebug port-forward <target> -L 80`
     101 +- Expose container's localhost to the host system: `cdebug port-forward <target> -L 127.0.0.1:5432`
     102 +- Proxy local traffic to a remote host via the target: `cdebug port-forward <target> -L <LOCAL_HOST>:<LOCAL_PORT>:<REMOTE_HOST>:<REMOTE_PORT>`
     103 +- 🛠️ Expose a Kubernetes service to the host system: `cdebug port-forward <target> -L 8888:my.svc.cluster.local:443`
     104 + 
     105 +Remote port forwarding use cases:
     106 + 
     107 +- Start a container/Pod forwarding traffic destined to its `<IP>:<port>` to a non-cluster endpoint reachable from the host system.
     108 +- ...
    31 109   
    32  -- Docker (via the socket file)
    33  -- containerd (via the socket file) - coming soon
    34  -- Kubernetes CRI (via the CRI gRPC API) - coming later
    35  -- Kubernetes (via the API server) - coming later
    36  -- runc or alike (via directly invoking the CLI) - coming later.
     110 +<details>
     111 +<summary>How it works</summary>
    37 112   
    38  -## Installation
     113 +**Local port forwarding** is implemented by starting an extra forwarder container in the
     114 +target's network and publishing its ports to the host using the standard means (e.g.,
     115 +`docker run --publish`). The forwarder container itself runs something like:
    39 116   
    40  -It's a statically linked Go binary, so you know the drill:
     117 +`socat TCP-LISTEN:<REMOTE_PORT>,fork TCP-CONNECT:<REMOTE_HOST>:<REMOTE_PORT>`
    41 118   
    42  -```sh
    43  -GOOS=linux
    44  -GOARCH=amd64
     119 +If the _REMOTE_HOST_ doesn't belong to the target or it's the target's localhost,
     120 +an extra sidecar container is started in the target's network namespace with another
     121 +socat forwarding traffic from the target public interface to `REMOTE_HOST:REMOTE_PORT`.
    45 122   
    46  -curl -Ls https://github.com/iximiuz/cdebug/releases/latest/download/cdebug_${GOOS}_${GOARCH}.tar.gz | tar xvz
     123 +**Remote port forwarding** will use similar tricks but combined with more advanced
     124 +reverse tunneling.
    47 125   
    48  -sudo mv cdebug /usr/local/bin
    49  -```
     126 +</details>
    50 127   
    51  -At the moment, the following targets are (kinda sorta) supported:
     128 +## Demos
    52 129   
    53  -- linux/amd64
    54  -- darwin/amd64
    55  -- darwin/arm64
     130 +Below are a few popular scenarios formatted as reproducible demos.
    56 131   
    57  -## Demo 1: An interactive shell with busybox
     132 +### A simple interactive shell to a distroless container
    58 133   
    59 134  First, a target container is needed. Let's use a distroless nodejs image for that:
    60 135   
    61 136  ```sh
    62  -docker run -d --rm \
     137 +$ docker run -d --rm \
    63 138   --name my-distroless gcr.io/distroless/nodejs \
    64 139   -e 'setTimeout(() => console.log("Done"), 99999999)'
    65 140  ```
    skipped 1 lines
    67 142  Now, let's start an interactive shell (using busybox) into the above container:
    68 143   
    69 144  ```sh
    70  -cdebug exec -it my-distroless
     145 +$ cdebug exec -it my-distroless
    71 146  ```
    72 147   
    73 148  Exploring the filesystem shows that it's a rootfs of the nodejs container:
    skipped 26 lines
    100 175  The process tree of the debugger container is the process tree of the target:
    101 176   
    102 177  ```sh
    103  -/ # ps auxf
     178 +/ $# ps auxf
    104 179  PID USER TIME COMMAND
    105 180   1 root 0:00 /nodejs/bin/node -e setTimeout(() => console.log("Done"),
    106 181   13 root 0:00 sh -c set -euo pipefail sleep 999999999 & SANDBOX_PID=$!
    skipped 4 lines
    111 186   45 root 0:00 ps auxf
    112 187  ```
    113 188   
    114  -## Demo 2: An interactive shell with code editor
     189 +### An interactive shell with code editor (vim)
    115 190   
    116 191  If the tools provided by busybox aren't enough, you can bring your own tools with
    117 192  a ~~little~~ huge help of the [nixery](https://nixery.dev/) project:
    118 193   
    119 194  ```sh
    120  -cdebug exec -it --image nixery.dev/shell/vim my-distroless
     195 +$ cdebug exec -it --image nixery.dev/shell/vim my-distroless
    121 196  ```
    122 197   
    123  -## Demo 3: An interactive shell with tshark and other advanced tools
     198 +### An interactive shell with tshark and other advanced tools
    124 199   
    125 200  Even more powerful exammple:
    126 201   
    127 202  ```sh
    128  -cdebug exec -it --image nixery.dev/shell/ps/findutils/tshark my-distroless
     203 +$ cdebug exec -it --image nixery.dev/shell/ps/findutils/tshark my-distroless
    129 204  ```
    130 205   
    131  -## How it works
     206 +### Publish "forgotten" port
     207 + 
     208 +Start an nginx container but don't expose its port 80:
     209 + 
     210 +```sh
     211 +$ docker run -d --name nginx-1 nginx:1.23
     212 +```
    132 213   
    133  -The technique is based on the ideas from this [blog post](https://iximiuz.com/en/posts/docker-debug-slim-containers).
    134  -Oversimplifying, the debugger container is started like:
     214 +Forward local port 8080 to the nginx's 80:
    135 215   
    136 216  ```sh
    137  -docker run [-it] \
    138  - --network container:<target> \
    139  - --pid container:<target> \
    140  - --uts container:<target> \
    141  - <toolkit-image>
    142  - sh -c <<EOF
    143  -ln -s /proc/$$/root/bin/ /proc/1/root/.cdebug
     217 +$ cdebug port-forward nginx-1 -L 8080:80
     218 +$ curl localhost:8080
     219 +```
     220 + 
     221 +### Expose localhost's ports
    144 222   
    145  -export PATH=$PATH:/.cdebug
    146  -chroot /proc/1/root sh
    147  -EOF
     223 +Start a containerized service that listens only on its localhost:
     224 + 
     225 +```sh
     226 +$ docker run -d --name svc-1 python:3-alpine python3 -m 'http.server' -b 127.0.0.1 8888
    148 227  ```
    149 228   
    150  -The secret sauce is the symlink + PATH modification + chroot-ing.
     229 +Tap into the above service:
     230 + 
     231 +```sh
     232 +$ cdebug port-forward svc-1 -L 127.0.0.1:8888
     233 +Pulling forwarder image...
     234 +latest: Pulling from shell/socat
     235 +Digest: sha256:b43b6cf8d22615616b13c744b8ff525f5f6c0ca6c11b37fa3832a951ebb3c20c
     236 +Status: Image is up to date for nixery.dev/shell/socat:latest
     237 +Forwarding 127.0.0.1:49176 to 127.0.0.1:8888 through 172.17.0.4:34128
     238 + 
     239 +$ curl localhost:49176
     240 +<!DOCTYPE HTML>
     241 +<html lang="en">
     242 +<head>
     243 +...
     244 +```
    151 245   
    152 246  ## F.A.Q
    153 247   
    skipped 12 lines
    166 260   
    167 261  ## TODO:
    168 262   
    169  -- Make exec accept (partial) container IDs (only names are supported at the moment)
    170  -- Terminal resizing ([example](https://github.com/docker/cli/blob/110c4d92b883357c9fb3edc344c4fbec5f77896f/cli/command/container/tty.go#L71))
    171 263  - More `exec` flags (like in `docker run`): `--cap-add`, `--cap-drop`, `--env`, `--volume`, etc.
    172 264  - Helper command(s) suggesting nix(ery) packages
     265 +- Non-docker runtimes (containerd, runc, k8s)
    173 266  - E2E Tests
    174  -- Cross-platform builds + goreleaser
    175  -- Non-docker runtimes (containerd, runc, k8s)
    176 267   
    177 268  ## Contributions
    178 269   
    skipped 2 lines
Please wait...
Page is in error, reload to recover