How To Run A Kubernetes Cluster Locally With Kind

0
895
Kubernete Cluster

Kind is a tool used for running Kubernetes (k8s) locally using Docker container ‘nodes’. Its primary purpose is to test k8s, but it can also be used in your CI system or for local development.

Kind serves various purposes in the Kubernetes ecosystem depending upon your use case. It can be used to learn Kubernetes, to make sure that your Kubernetes application manifests are correct, or to test your deployments locally. In this article, we will see how to use Kind to create a Kubernetes cluster locally and run a Web application using it.

Let’s invoke the Kind executable to see what are the available options:

> kind
kind creates and manages local Kubernetes clusters using Docker container ‘nodes’

Usage:
  kind [command]

Available Commands:
  build       Build one of [node-image]
  completion  Output shell completion code for the specified shell (bash, zsh or fish)
  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

Flags:
  -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.

Cluster creation and interaction

Now let’s create a Kubernetes cluster locally and interact with it:

> kind create cluster
Creating cluster “kind” ...
 > Ensuring node image (kindest/node:v1.21.1) >
 > Preparing nodes >
 > Writing configuration >
 > Starting control-plane >
 > Installing CNI >
 > Installing StorageClass >
Set kubectl context to “kind-kind”

You can now use your cluster with:

kubectl cluster-info --context kind-kind

Not sure what to do next? Check out https://kind.sigs.k8s.io/docs/user/quick-start/

The above command will bootstrap a k8s cluster using a pre-built node image. In fact, once the above command completes, you can use Docker to verify that a new image has indeed been downloaded:

> docker images | rg kind
kindest/node                                      <none>                                         32b8b755dee8   8 months ago    1.12GB

The default cluster name is kind. And you can confirm it using the following command:

> kind get clusters
kind

You can also get it’s kubeconfig via the following command:

> kind get kubeconfig | head
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FUR S0tLS0tCk1JSUM1ekNDQWMrZ0F3 SUJBZ0lCQURBTkJna3Foa2l HOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcm RXSmwKY201bGRHVnpNQjRYRF RJeU1ESXdOREUxTlRNeU1sb1hEVE15TURJd01qRTFOV<Snipped>
OG9QRDNoYnRraFJ4MQotLS0tL UVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    server: https://127.0.0.1:38471
  name: kind-kind
contexts:
- context:
    cluster: kind-kind
    user: kind-kind

You can use the –wait flag if you want to wait till the control plane is in a reachable state. Since Kind is mostly used for testing, you cannot actually update it. Let’s say we want to have a declarative setup with this configuration for our Kind cluster:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: “ingress-ready=true”
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP

The above configuration creates a Kind cluster with extraPortMappings and node-labels. The port mappings are required to allow localhost to make requests to the Ingress controller. Let’s try to apply the above manifest (but note that it’s going to fail):

> kind create cluster --config ./cluster.yaml
ERROR: failed to create cluster: node(s) already exist for a cluster with the name “kind”

So we have to delete and create a new one. Let’s delete it first:

❯ kind delete cluster
Deleting cluster “kind” ...

Let’s create the cluster using our declarative setup now:

> kind create cluster --config ./cluster.yaml
Creating cluster “kind” ...
 > Ensuring node image (kindest/node:v1.21.1) >
 > Preparing nodes >
 > Writing configuration >
 > Starting control-plane >
 > Installing CNI >
 > Installing StorageClass >
Set kubectl context to “kind-kind”

You can now use your cluster with:

kubectl cluster-info --context kind-kind

You can also specify your k8s version in the above manifest by specifying the proper Docker image, which you can find in its GitHub release page:

- role: control-plane
  image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6

Cluster interaction

We need to interact with the cluster to get the actual job done. This could be deploying your application or doing maintenance operations on your node. You can use the usual kubectl to interact with your cluster:

> kubectl get nodes
NAME             STATUS   ROLES          AGE   	VERSION
kind-control-plane  Ready   control-plane,master 95s  v1.21.1

Now let’s check the various name spaces present:

> kubectl get namespaces -A
NAME                 STATUS   AGE
default              Active   3m1s
kube-node-lease      Active   3m2s
kube-public          Active   3m2s
kube-system          Active   3m2s
local-path-storage   Active   2m58s

And let’s check the various logs that are running:

> kubectl get pods -A
NAMESPACE            NAME                                         READY   STATUS    RESTARTS   AGE
kube-system          coredns-558bd4d5db-7pzbj                     1/1     Running   0          3m25s
kube-system          coredns-558bd4d5db-vgf9l                     1/1     Running   0          3m25s
kube-system          etcd-kind-control-plane                      1/1     Running   0          3m28s
kube-system          kindnet-jq54g                                1/1     Running   0          3m25s
kube-system          kube-apiserver-kind-control-plane            1/1     Running   0          3m28s
kube-system          kube-controller-manager-kind-control-plane   1/1     Running   0          3m28s
kube-system          kube-proxy-bkxql                             1/1     Running   0          3m25s
kube-system          kube-scheduler-kind-control-plane            1/1     Running   0          3m28s
local-path-storage   local-path-provisioner-547f784dff-2vkf4      1/1     Running   0          3m25s

Loading a Docker image: To load a Docker image, give the following command:

kind load docker-image image_1 image_2

Note that once you load the image, you still have to create the Deployment k8s resource, which will typically reference the loaded image and then do kubectl apply of the resource.

NGINX Ingress controller: Now let’s deploy Ingress NGINX to our cluster:

> kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created

Next, let’s inspect our NGINX pods:

> kubectl get pods -n ingress-nginx
NAME               READY   STATUS              RESTARTS   AGE
ingress-nginx-admission-create-hrmn5        0/1     Completed           0          47s
ingress-nginx-admission-patch-7ds99         0/1     Completed           1          47s
ingress-nginx-controller-674cb6ff57-tmjbc   0/1     ContainerCreating   0          47s

Let’s check the service of the NGINX controller:

❯ kubectl get services -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.96.65.28    <none>        80:32544/TCP,443:30304/TCP   116s
ingress-nginx-controller-admission   ClusterIP   10.96.207.79   <none>        443/TCP                      116s

You can see the NGINX controller is listening on both port 80 and 443. Now let’s check what our Ingress class is:

❯ kubectl get ingressclass -A
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       8m26s

Deploying an application

Let’s deploy the https://httpbin.org/ application inside our Kind cluster. I have the manifest for the application in the app_k8s repository: https://github.com/psibi/app_k8s.

Rendering the resource for the application will result in this:

❯ kustomize build overlays/app_nginx
apiVersion: v1
kind: Namespace
metadata:
  name: base-app
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: httpbin
  name: httpbin
  namespace: base-app
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 80
  selector:
    app: httpbin
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
  namespace: base-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-httpbin
  namespace: base-app
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - backend:
          service:
            name: httpbin
            port:
              number: 8000
        path: /
        pathType: Prefix

Once done, apply the above application:

❯ kubectl apply -f application.yaml
namespace/base-app created
service/httpbin created
deployment.apps/httpbin created
ingress.networking.k8s.io/ingress-httpbin created

Once done, apply the above application:

❯ kubectl apply -f application.yaml
namespace/base-app created
service/httpbin created
deployment.apps/httpbin created
ingress.networking.k8s.io/ingress-httpbin created

Now let’s check the Ingress:

❯ kubectl get ingress -n base-app
NAME              CLASS   HOSTS   ADDRESS     PORTS   AGE
ingress-httpbin   nginx   *       localhost   80      3m17s

Let’s check that the application is indeed working:

❯ curl -s http://localhost | head
<!DOCTYPE html>
<html lang=”en”>

<head>
    <meta charset=”UTF-8”>
    <title>httpbin.org</title>
    <link href=”https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700”
        rel=”stylesheet”>
    <link rel=”stylesheet” type=”text/css” href=”/flasgger_static/swagger-ui.css”>
    <link rel=”icon” type=”image/png” href=”/static/favicon.ico” sizes=”64x64 32x32 16x16” />

This summarises how to use Kind to create a Kubernetes cluster, deploy a NGINX controller, and then use that to deploy a Web application on it.

LEAVE A REPLY

Please enter your comment!
Please enter your name here