Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Saiyam Pathak Ivan Velichko Kyle Quest Building a Tool to Debug Minimal Container Images in Docker, containerd, and Kubernetes https://github.com/mintoolkit/mint (aka DockerSlim / SlimToolkit)

Slide 3

Slide 3 text

Ivan Velichko ● The Iximiuz Labs Creator (the Learning Platform to Master Cloud Native Craft) ● https://labs.iximiuz.com ● https://iximiuz.com/newsletter ● https://twitter.com/iximiuz ● https://github.com/iximiuz

Slide 4

Slide 4 text

Saiyam Pathak ● Field CTO, Civo ● CNCF Ambassador ● Founder Kubesimplify ● https://xcom/saiyampathak ● https://github.com/saiyam1814

Slide 5

Slide 5 text

Kyle Quest ● Creator, DockerSlim (aka SlimToolkit/minToolkit) ● Founder, AutonomousPlane ● Founder/CTO, Slim.AI ● https://twitter.com/kcqon ● https://github.com/kcq

Slide 6

Slide 6 text

How It Started

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Dan Čermák ● Software Engineer, SUSE ● Developer Tools ● https://dancermak.name ● https://twitter.com/DefolosDC ● https://github.com/dcermak

Slide 9

Slide 9 text

The Problem

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

What do you typically do when a container… starts acting up? Docker ● docker exec ● nsenter ● docker debug (beta/paid) containerd ● nerdctl/ctr exec ● nsenter Kubernetes ● kubectl exec ● kubectl debug ● SSH + nsenter*

Slide 12

Slide 12 text

What if it’s a minimal container image… with no shell? ● Slimmed/minified (minToolkit) ● gcr.io/distroless ● Chainguard Images ● Slim and scratch bases

Slide 13

Slide 13 text

Docker ● ❌ docker exec ● ✅ nsenter ● ✅ docker debug (beta/paid) containerd ● ❌ nerdctl/ctr exec ● ✅ nsenter Kubernetes ● ❌ kubectl exec ● ✅ kubectl debug ● ✅ SSH + nsenter* What if it’s a minimal container image… with no shell?

Slide 14

Slide 14 text

🤩 docker exec ● Can run target binaries, see the target rootfs "as is", install extra tools ● …but only works if there is a shell 🙂 kubectl debug / docker run (aka debugging sidecar) ● Can see target processes, access target rootfs (but not “as is”), use custom debugger image ● …but cannot see volumes, run as user, or fine-tune the ephemeral container spec, see target rootfs "as is" 🤯 nsenter ● Can do anything ● …given you know how and have sufficient access (on the node) Developer Experience During Debugging

Slide 15

Slide 15 text

From docker exec ● Run target binaries ● See the target rootfs "as is" From kubectl debug ● Works even with shell-less targets ● Can bring you own debugging toolkit Can We Combine the Best of Two Worlds?

Slide 16

Slide 16 text

Since we’re writing a new tool, we can try providing the same UX for all runtimes supported runtimes! Bonus: Uniform debugging UX across runtimes $ tool debug --runtime docker --target [cmd] $ tool debug --runtime containerd --target [cmd] $ tool debug --runtime k8s --pod --target [cmd]

Slide 17

Slide 17 text

The Tech & Design

Slide 18

Slide 18 text

What actually happens when you docker exec

Slide 19

Slide 19 text

● https://github.com/opencontainers/runtime-spec/issues/345 ● https://github.com/opencontainers/runtime-spec/pull/388 What actually happens when you docker exec 💡 Fun fact - The OCI Runtime Spec doesn't define the exec command. Check out the issues #345 and #388 for an interesting discussion of how the exec functionality is actually redundant and can be reproduced in runtimes implementing only create and start commands.

Slide 20

Slide 20 text

$ docker run \ --net container:app \ --ipc container:app \ --pid container:app \ busybox Reproducing docker exec with docker run

Slide 21

Slide 21 text

Reproducing docker exec with docker run

Slide 22

Slide 22 text

What actually happens when you kubectl debug

Slide 23

Slide 23 text

Unfortunately, mount namespace cannot be shared. Let’s see if we are clever enough to overcome that! Gotcha: But My rootfs Looks Different!

Slide 24

Slide 24 text

Accessing Target rootfs via /proc//root Since pid namespace is shared, we can use the /proc//root (e.g., /proc/1/root) trick to browse target rootfs!

Slide 25

Slide 25 text

Adding chroot, symlinks and $PATH to the mix!

Slide 26

Slide 26 text

The Implementation

Slide 27

Slide 27 text

High Level Algorithm (all runtimes) ● Create the sidecar container (connected to the target container) ● Start the sidecar container ● Attach the terminal to its stdio streams ● Wait for the sidecar container to exit Sounds too easy? Now repeat it for every runtime!

Slide 28

Slide 28 text

Gotcha: Will it Always Work? ● The chroot trick requires the root user and sufficient privileges ○ May need to run the sidecar with --privileged flag ○ If the Pod spec mandates runAsNonRoot, don’t chroot! ● The debugger toolkit image has to be “portable enough” (more later)

Slide 29

Slide 29 text

Docker API

Slide 30

Slide 30 text

Relevant Client Interfaces and Methods ● ContainerAPIClient interface ● ContainerCreate ● ContainerStart ● ContainerAttach ● ContainerWait Notes ● Configuring the namespace modes - key for connecting to target container!

Slide 31

Slide 31 text

API github.com/docker/docker/client

Slide 32

Slide 32 text

pkg/app/master/command/debug/handle_docker_runtime.go

Slide 33

Slide 33 text

ContainerD API

Slide 34

Slide 34 text

Relevant Client Interfaces and Methods ● client.NewContainer ● Container.NewTask ● Task.Start ● Task.HandleConsoleResize ● Task.Wait Notes / Challenges ● Much more lower-level (compared to Docker) ● Doesn’t provide a facade client interface ● Requires wrapping one’s head around it ● Debugger Container Spec Namespaces - key for connecting to target container!

Slide 35

Slide 35 text

API github.com/containerd/containerd

Slide 36

Slide 36 text

pkg/app/master/command/debug/handle_containerd_runtime.go

Slide 37

Slide 37 text

Kubernetes API

Slide 38

Slide 38 text

Relevant Client Interfaces and Methods ● core/v1.PodInterface.UpdateEphemeralContainers() or lower level core/v1.PodInterface.Patch/Put() to add the ephemeral container to the Pod’s spec ● core/v1.PodInterface.Wait() ● rest.Interface.Post() .Resource("pods").SubResource("attach") Notes ● TargetContainerName in EphemeralContainer - key for connecting to target container! ● Learning how to initialize and use K8s client-go library is a challenge on its own!

Slide 39

Slide 39 text

API: github.com/kubernetes/client-go

Slide 40

Slide 40 text

pkg/app/master/command/debug/handle_kubernetes_runtime.go

Slide 41

Slide 41 text

The Debugger Images

Slide 42

Slide 42 text

Gotchas and Considerations With the chroot trick (beware of portability issues) ● busybox:musl - simple and portable ● Nix and nixery.dev ...and we also can use the target image's binaries "as is" Without the chroot+PATH+symlink trick (simpler) ● NetShoot, KoolKit or general ubuntu/debian/etc are good candidates ...but we cannot invoke binaries from the target image easily

Slide 43

Slide 43 text

pkg/app/master/command/debug/debug_images.go

Slide 44

Slide 44 text

The Challenges

Slide 45

Slide 45 text

Reverse Engineering APIs and Client Code ● Not much documentation & examples …but you can find some inspiration in ● github.com/containerd/nerdctl ● github.com/docker/cli ● github.com/kubernetes/kubectl ● github.com/iximiuz/client-go-examples ● https://github.com/iximiuz/cdebug Pro Tip: Use GitHub code search - in highly-specialized areas, it’s still more helpful than ChatGPT.

Slide 46

Slide 46 text

Other Gotchas Cases that are trickier to debug ● Tightened security contexts ● Non-root containers ● Picking the portable debugging toolkit

Slide 47

Slide 47 text

The Demo

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

The Code

Slide 52

Slide 52 text

The Debug Command Code ● https://github.com/mintoolkit/mint - main repo ● https://github.com/mintoolkit/mint/tree/master/pkg/app/master/comman d/debug - the “debug” CLI command code ● https://github.com/mintoolkit/mint/blob/master/pkg/app/master/comma nd/debug/handle_docker_runtime.go - Docker runtime support ● https://github.com/mintoolkit/mint/blob/master/pkg/app/master/comma nd/debug/handle_kubernetes_runtime.go - K8s runtime support ● https://github.com/mintoolkit/mint/blob/master/pkg/app/master/comma nd/debug/handle_containerd_runtime.go - ContainerD runtime support

Slide 53

Slide 53 text

Thank You!

Slide 54

Slide 54 text

Thank You for attending! You can connect with us: @kcqon @SaiyamPathak Share your feedback!