Skip to content

nilaydown/tinylet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tinylet

A small reimplementation of kubelet's static-pod path, in Go.

tinylet watches a directory of YAML pod specs and reconciles them against a local containerd via the Container Runtime Interface (CRI). About 400 lines of logic. No apiserver, no scheduler, no etcd.

I wrote this to figure out what kubelet actually does. Don't run it in production.

Demo

make run
make logs

Drop a YAML into manifests/:

name: hello
namespace: default
containers:
  - name: web
    image: docker.io/library/nginx:1.27

On the next 2-second tick, tinylet creates the pod sandbox and starts the container. Delete the file and the pod is torn down.

Inspect what's running with crictl:

make crictl ARGS="pods"
make crictl ARGS="ps"

What it does

  1. Reads pod YAMLs from manifests/
  2. Talks CRI (gRPC over a Unix socket) to a local containerd
  3. Pulls images, creates pod sandboxes, creates and starts containers
  4. Tears down pods whose YAMLs were deleted
  5. Reconciles every 2 seconds, idempotently

What it does not do

Most of what a real kubelet does:

  • No apiserver, no node registration, no leases
  • No scheduling
  • No volumes, probes, resources, env vars, secrets, configmaps
  • No networking beyond whatever containerd + CNI bridge gives you
  • No log collection, no metrics

tinylet labels every sandbox it creates with tinylet=true and ignores everything else, so it won't touch pods owned by a real kubelet on the same containerd.

The Pod spec is deliberately small:

type Pod struct {
    Name       string
    Namespace  string
    Containers []Container
}

type Container struct {
    Name    string
    Image   string
    Command []string
    Args    []string
}

Architecture

   manifests/*.yaml                  containerd
        |                                ^
        v                                |
   [manifest.LoadFromDir]                |
        |                                |
        v                                |
   [reconcile.Run] --diff--> [cri.Client] --gRPC--+
                                  ^
                                  |
                            /run/containerd/containerd.sock

Three packages, each around 100 lines:

  • internal/manifest reads YAML from disk into Pod structs.
  • internal/cri wraps containerd's two CRI gRPC services. RuntimeService handles pods and containers, ImageService handles image pulls.
  • internal/reconcile is the loop. Each tick it loads desired state from disk, lists actual state from containerd, and converges the two by calling EnsurePod and StopPod on the CRI client.

Same pattern as real kubelet, but driven by a directory scan instead of an apiserver watch.

Demo stack

make run brings up two containers via docker-compose:

  • tl-containerd: privileged containerd with a CNI bridge plugin
  • tl-kubelet: the tinylet binary, sharing containerd's socket via a named volume and mounting ./manifests read-only

Tested on macOS via Docker Desktop. Should work the same on Linux.

Running outside Docker

If you have containerd installed locally:

go build -o bin/tinylet ./cmd/tinylet
sudo ./bin/tinylet \
  --socket=/run/containerd/containerd.sock \
  --manifests=/etc/tinylet/manifests \
  --interval=2s

You'll usually need root for the containerd socket.

Tests

go test ./...

The CRI client is tested against an in-process fake gRPC server, so the tests run with no Docker and no real containerd.

Layout

cmd/tinylet/          # main, flag parsing, signal handling
internal/manifest/    # YAML loader + Pod types
internal/cri/         # CRI gRPC client wrapper
internal/reconcile/   # the loop
docker/               # containerd + tinylet images
manifests/            # example pod YAMLs

Reading the code

A reasonable order:

  1. internal/manifest/types.go for the Pod struct.
  2. internal/cri/client.go for how kubelet talks to containerd. The four methods (Dial, ListPods, EnsurePod, StopPod) are the entire runtime surface kubelet uses for static pods.
  3. internal/reconcile/loop.go for the diff-and-converge body.
  4. cmd/tinylet/main.go for wiring.

About

A small reimplementation of the kubelet's static-pod path, in Go. Watches YAML pods and reconciles against containerd via CRI.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors