Ruan Bekker's Blog

From a Curious mind to Posts on Github

KinD for Local Kubernetes Clusters

kubernetes-kind

In this tutorial we will demonstrate how to use KinD (Kubernetes in Docker) to provision local kubernetes clusters for local development.

Updated at: 2023-12-22

About

KinD uses container images to run as “nodes”, so spinning up and tearing down clusters becomes really easy or running multiple or different versions, is as easy as pointing to a different container image.

Configuration such as node count, ports, volumes, image versions can either be controlled via the command line or via configuration, more information on that can be found on their documentation:

Installation

Follow the docs for more information, but for mac:

1
brew install kind

To verify if kind was installed, you can run:

1
kind version

Create a Cluster

Create the cluster with command line arguments, such as cluster name, the container image:

1
kind create cluster --name cluster-1 --image kindest/node:v1.26.6

And the output will look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
Creating cluster "cluster-1" ...
 ✓ Ensuring node image (kindest/node:v1.26.6) đŸ–ŧ
 ✓ Preparing nodes đŸ“Ļ
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹ī¸
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-cluster-1"
You can now use your cluster with:

kubectl cluster-info --context kind-cluster-1

Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂

Then you can interact with the cluster using:

1
kubectl get nodes --context kind-cluster-1

Then delete the cluster using:

1
kind delete cluster --name kind-cluster-1

I highly recommend installing kubectx, which makes it easy to switch between kubernetes contexts.

Create a Cluster with Config

If you would like to define your cluster configuration as config, you can create a file default-config.yaml with the following as a 2 node cluster, and specifying version 1.24.0:

1
2
3
4
5
6
7
8
---
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  image: kindest/node:v1.26.6@sha256:6e2d8b28a5b601defe327b98bd1c2d1930b49e5d8c512e1895099e4504007adb
- role: worker
  image: kindest/node:v1.26.6@sha256:6e2d8b28a5b601defe327b98bd1c2d1930b49e5d8c512e1895099e4504007adb

Then create the cluster and point the config:

1
kind create cluster --name kind-cluster --config default-config.yaml

Interact with the Cluster

View the cluster info:

1
kubectl cluster-info --context kind-kind-cluster

View cluster contexts:

1
kubectl config get-contexts

Use context:

1
kubectl config use-context kind-kind-cluster

View nodes:

1
2
3
4
5
kubectl get nodes -o wide

NAME                         STATUS   ROLES           AGE     VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE       KERNEL-VERSION      CONTAINER-RUNTIME
kind-cluster-control-plane   Ready    control-plane   2m11s   v1.26.6   172.20.0.5    <none>        Ubuntu 21.10   5.10.104-linuxkit   containerd://1.6.4
kind-cluster-worker          Ready    <none>          108s    v1.26.6   172.20.0.4    <none>        Ubuntu 21.10   5.10.104-linuxkit   containerd://1.6.4

Deploy Sample Application

We will create a deployment, a service and port-forward to our service to access our application. You can also specify port configuration to your cluster so that you don’t need to port-forward, which you can find in their port mappings documentation

I will be using the following commands to generate the manifests, but will also add them to this post:

1
2
kubectl create deployment hostname --namespace default --replicas 2 --image ruanbekker/containers:hostname --port 8080 --dry-run=client -o yaml > hostname-deployment.yaml
kubectl expose deployment hostname --namespace default --port=80 --target-port=8080 --name=hostname-http --dry-run=client -o yaml > hostname-service.yaml

The manifest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
---
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: hostname
  name: hostname
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hostname
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: hostname
    spec:
      containers:
      - image: ruanbekker/containers:hostname
        name: containers
        ports:
        - containerPort: 8080
        resources: {}
status: {}
---
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: hostname
  name: hostname-http
  namespace: default
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: hostname
status:
  loadBalancer: {}

Then apply them with:

1
kubectl apply -f <name-of-manifest>.yaml

Or if you used kubectl to create them:

1
2
kubectl apply -f hostname-deployment.yaml
kubectl apply -f hostname-service.yaml

You can then view your resources with:

1
2
3
4
5
6
7
8
9
10
11
12
kubectl get deployment,pod,service

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hostname   2/2     2            2           9m27s

NAME                            READY   STATUS    RESTARTS   AGE
pod/hostname-7ff58c5644-67vhq   1/1     Running   0          9m27s
pod/hostname-7ff58c5644-wjjbw   1/1     Running   0          9m27s

NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/hostname-http   ClusterIP   10.96.218.58   <none>        80/TCP    5m48s
service/kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP   24m

Port forward to your service:

1
kubectl port-forward svc/hostname-http 8080:80

Then access your application:

1
2
3
curl http://localhost:8080/

Hostname: hostname-7ff58c5644-wjjbw

Delete Kind Cluster

View the clusters:

1
kind get clusters

Delete a cluster:

1
kind delete cluster --name kind-cluster

Additional Configs

If you want more configuration options, you can look at their documentation:

But one more example that I like using, is to define the port mappings:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  image: kindest/node:v1.26.6@sha256:6e2d8b28a5b601defe327b98bd1c2d1930b49e5d8c512e1895099e4504007adb
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
    listenAddress: "0.0.0.0"
  - containerPort: 443
    hostPort: 443
    protocol: TCP
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"

Extras

I highly recommend using kubectx to switch contexts and kubens to set the default namespace, and aliases:

1
2
3
alias k=kubectl
alias kx=kubectx
alias kns=kubens

Thank You

Thanks for reading, feel free to check out my website, feel free to subscribe to my newsletter or follow me at @ruanbekker on Twitter.