How I met KinD

for my development environment


CNCF Meetup 

Turin, 28/11/2019, 

uname -a

Dario Tranchitella (prometnerion)


Part-time Cloud DevOps Engineer,

Full-Time dad of two.


I love distributed systems and whole stuff around it:
Kubernetes, Go, you know...


I'm mad at food.



up 30 years,  90 weeks, 21 days, 18:45

1 user,  load average: 2.41, 2.62, 2.45

what's kind?


kind is a tool for running local Kubernetes clusters using Docker container "nodes". kind is primarily designed for testing Kubernetes 1.11+, initially targeting the conformance tests.


ben disapproves this talk!





  • minikube
  • docker compose
  • develop and test
    in production



  • runs in a hypervisor
  • battle-tested
  • widely adopted
  • static IP
  • load balancers
  • node ports
  • persistence


  • you need a hypervisor
  • slow file mounts
  • lots of resources
  • hard provisioning between different OSs
  • it's not declarative

minikube: provisioning

if [[ "$detected_OS" == "Ubuntu" ]]; then
	sudo apt-get update
	sudo apt-get -y install \
		nfs-kernel-server \
	sudo systemctl enable nfs-server

if [[ "$detected_OS" == "Fedora" ]]; then
	# Installing KVM Docker Machine
	curl -LO
	chmod +x docker-machine-driver-kvm2
	sudo mv docker-machine-driver-kvm2 /usr/local/bin/
	# Installing system-wide dependencies
	sudo dnf update
	sudo dnf install -y \
	sudo systemctl enable rpcbind nfs-server
	sudo systemctl start rpcbind nfs-server

minikube: NFS

sudo touch /etc/exports

if [[ "$detected_OS" == "Ubuntu" ]]; then
  SOURCES_DIR=$(dirname $(realpath .))
  EXPORT="$SOURCES_DIR $(minikube ip)(rw,subtree_check,all_squash,anonuid=$(id -u),anongid=$(id -g))"
  if ! $(grep -q "$EXPORT" /etc/exports); then
    echo "$EXPORT" | sudo tee -a /etc/exports > /dev/null && sudo systemctl restart nfs-server
  minikube ssh "sudo mkdir -p /data && sudo mount -t nfs -o nfsvers=3,tcp$SOURCES_DIR /data"

if [[ "$detected_OS" == "Fedora" ]]; then
  SOURCES_DIR=$(dirname $(realpath .))
  EXPORT="$SOURCES_DIR $(minikube ip)(rw,subtree_check,all_squash,anonuid=$(id -u),anongid=$(id -g))"
  if ! $(grep -q "$EXPORT" /etc/exports); then
    echo "$EXPORT" | sudo tee -a /etc/exports > /dev/null && sudo systemctl restart nfs-server
  # firewalld permissions
  sudo firewall-cmd --permanent --zone public --add-service mountd && \
  sudo firewall-cmd --permanent --zone public --add-service rpc-bind && \
  sudo firewall-cmd --permanent --zone public --add-service nfs && \
  sudo firewall-cmd --reload
  # SELinux flag
  sudo setsebool -P nfs_export_all_rw 1
  sudo exportfs -a
  # Minikube provisioning
  minikube ssh "sudo umount /data || true"
  minikube ssh "sudo mkdir -p /data && sudo mount -t nfs -o nfsvers=3,tcp$SOURCES_DIR /data"



  • KISS
  • Docker runs anywhere
  • no provisioning needed


  • ...seriously?
  • keep aligned Helm charts and Compose
  • orchestrate services and networking
  • it's not Kubernetes!

KIND's so cute!  ❤️

kind: declarative

type Cluster struct {
  TypeMeta `yaml:",inline"`
  Nodes []Node                             `yaml:"nodes,omitempty"`
  Networking Networking                    `yaml:"networking,omitempty"`
  KubeadmConfigPatches []string            `yaml:"kubeadmConfigPatches,omitempty"`
  KubeadmConfigPatchesJSON6902 []PatchJSON6902 `yaml:"kubeadmConfigPatchesJSON6902,omitempty"`
  ContainerdConfigPatches []string         `yaml:"containerdConfigPatches,omitempty"`
  ContainerdConfigPatchesJSON6902 []string `yaml:"containerdConfigPatchesJSON6902,omitempty"`

type Node struct {
  Role NodeRole                                `yaml:"role,omitempty"`
  Image string                                 `yaml:"image,omitempty"`
  ExtraMounts []Mount                          `yaml:"extraMounts,omitempty"`
  ExtraPortMappings []PortMapping              `yaml:"extraPortMappings,omitempty"`
  KubeadmConfigPatches []string                `yaml:"kubeadmConfigPatches,omitempty"`
  KubeadmConfigPatchesJSON6902 []PatchJSON6902 `yaml:"kubeadmConfigPatchesJSON6902,omitempty"`

kind: declarative

type PortMapping struct {
	ContainerPort int32          `yaml:"containerPort,omitempty"`
	HostPort int32               `yaml:"hostPort,omitempty"`
	ListenAddress string         `yaml:"listenAddress,omitempty"`
	Protocol PortMappingProtocol `yaml:"protocol,omitempty"`

type Mount struct {
	ContainerPath string         `yaml:"containerPath,omitempty"`
	HostPath string              `yaml:"hostPath,omitempty"`
	Readonly bool                `yaml:"readOnly,omitempty"`
	SelinuxRelabel bool          `yaml:"selinuxRelabel,omitempty"`
	Propagation MountPropagation `yaml:"propagation,omitempty"`

kind: EASY CLI

kind creates and manages local Kubernetes clusters using Docker container 'nodes'

  kind [command]

Available Commands:
  build       Build one of [base-image, node-image]
  completion  Output shell completion code for the specified shell (bash or zsh)
  create      Creates one of [cluster]
  delete      Deletes one of [cluster]
  export      exports one of [kubeconfig, logs]
  get         Gets one of [clusters, nodes, kubeconfig]
  help        Help about any command
  load        Loads images into nodes
  version     prints the kind CLI version

  -h, --help              help for kind
      --loglevel string   DEPRECATED: see -v instead
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity
      --version           version for kind

Use "kind [command] --help" for more information about a command.

kind: create

Creates a local Kubernetes cluster using Docker container 'nodes'

  kind create cluster [flags]

      --config string       path to a kind config file
  -h, --help                help for cluster
      --image string        node docker image to use for booting the cluster
      --kubeconfig string   sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config
      --name string         cluster context name (default "kind")
      --retain              retain nodes for debugging when cluster creation fails
      --wait duration       Wait for control plane node to be ready (default 0s)

Global Flags:
      --loglevel string   DEPRECATED: see -v instead
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity


kind: Cluster
- role: control-plane
  - containerPath: /mnt/data
    hostPath: /path/to/your/codebase
    readOnly: false
    propagation: Bidirectional
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP

how does it work...?

...jokes apart:

  • systemd
    responsible of taking care of units
  • runc
    avoiding Docker in Docker, let's use syscalls
  • kubeadm
    it's the standard for cluster bootstrapping

...but we got also cons!

cons: persistence

kubectl get
NAME                 PROVISIONER               AGE
standard (default)   161m

// Create for hostPath simply creates a local /tmp/%/%s directory as a new PersistentVolume, default /tmp/hostpath_pv/%s.
// This Provisioner is meant for development and testing only and WILL NOT WORK in a multi-node cluster.
func (r *hostPathProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
	if util.CheckPersistentVolumeClaimModeBlock(r.options.PVC) {
		return nil, fmt.Errorf("%s does not support block volume provisioning", r.plugin.GetPluginName())

	fullpath := fmt.Sprintf("/tmp/%s/%s", r.basePath, uuid.NewUUID())

cons: restarting

with v0.6.0 still issues while restarting

cons: kubeconfig

deleting a cluster doesn't cleanup kubeconfig

cons: docker opts

cannot customize docker commands

cons: docker images

no docker...

but kind load docker-image FTW!

cons: docs

still a wip

but #kind on Kubernetes Slack is very much alive!

...however chuck approves!

that's all folks!

Made with