How to experiment locally on Kubernetes with minikube and your local Dockerfiles
Introduction
In this entry-level article, I present a simple way to build, expose and test your dockerized application in local development environment with Minikube, essentially Kubernetes on your computer. Sometimes the developers struggle with handling the experimental images, because for every image change, they push it to the registry and then pull it on the test K8s cluster. Below, we will use only local environment — this is going to work even if you don’t have a network connection.
We will gather necessary tools, install Minikube and build and test our own sample application.
TL;DR version
1. You can easily run simple Kubernetes environment with Minikube on your own laptop without creating VMs. Just use `docker driver`.
2. You can plug in to the Docker deamon of Minikube, you just need to run `minikube docker-env` and follow the instructions.
3. You can use this connection to build your Docker images so that Minikube K8s cluster sees it automatically.
4. You can then create Kubernetes deployment based on the built image and expose it as a service without having to push it to some external registry.
Gathering a toolset
Getting Docker
Since you are reading this article, I assume you already have the Docker installed on your machine. If not, the best place to visit is the Docker documentation page, with its sub-pages dedicated to popular distros, such as Ubuntu Linux.
Getting Kubectl
In order to check if the `kubectl` tool is installed, just run:
$ kubectl version --client
The result should look a bit like this:
Client Version: version.Info{Major:”1", Minor:”19", GitVersion:”v1.19.2", GitCommit:”f5743093fd1c663cb0cbc89748f730662345d44d”, GitTreeState:”clean”, BuildDate:”2020–09–16T13:41:02Z”, GoVersion:”go1.15", Compiler:”gc”, Platform:”linux/amd64"}
If there is no `kubectl` tool on your system, the popular way to get the latest `kubectl` tool is to get it directly from kubernetes.io site. I suggest you put into your `/usr/local/bin/` folder like this:
$ curl -Lo kubectl “https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" && chmod +x kubectl$ sudo mv kubectl /usr/local/bin/
Of course you would need to alter this method if you are using Windows or MacOS. You can visit the Kubernetes docs for instructions.
Getting Minikube
Since `minikube` is the standalone application pretty similar to the way `kubectl` is, the installation is also quite similar:
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube$ sudo mv minikube /usr/local/bin
Please bear in mind that moving to `/usr/local/bin` is just a suggestion — this is the way I am used to follow in order to have all the useful tools in my path; you can alter it however you want. Just make sure that the `minikube` command works:
$ minikube versionminikube version: v1.13.0
commit: 0c5e9de4ca6f9c55147ae7f90af97eff5befef5f-dirty
Running Minikube the easy way
Minikube supports many options regarding the environment which will run the Kubernetes components, such as KVM or VirtualBox. However, none of those are necessary and will clutter your development box when installed.
Minikube also provides the `none` and `podman` drivers but those require privileged access and may in some cases overwrite some sensitive system configuration.
I would advise that you simply use the `docker` driver as it does not require root privileges and poses no threat to the configuration of the system you are running Minikube on.
The first run of the Minikube is then a simple command line, depicted below with exemplary result:
$ minikube start — driver=docker
😄 minikube v1.13.0 on Linuxmint 19.3
✨ Using the docker driver based on user configuration
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image …
💾 Downloading Kubernetes v1.19.0 preload …
> preloaded-images-k8s-v6-v1.19.0-docker-overlay2-amd64.tar.lz4: 486.28 MiB
🔥 Creating docker container (CPUs=2, Memory=2200MB) …
🐳 Preparing Kubernetes v1.19.0 on Docker 19.03.8…
🔎 Verifying Kubernetes components…
🌟 Enabled addons: default-storageclass, storage-provisioner
🏄 Done! kubectl is now configured to use “minikube” by default
You can check the minikube deployment status after the above operation has finished:
$ minikube statusminikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
Re-using the Docker daemon
As the Minikube documentation states, “When using a single VM for Kubernetes, it’s useful to reuse Minikube’s built-in Docker daemon. Reusing the built-in daemon means you don’t have to build a Docker registry on your host machine and push the image into it”.
I have quoted the above sentence in full, because there are no better words to describe the idea. But this note is as easy to miss in the quickstart page, as it is vital to quick experiments on the local machine!
We are going to “plug-in” to the Minikube’s Docker environment, so any image we will build, is going to be instantly visible in our minikube cluster just as if it was pushed to DockerHub or some other repository.
The command to run in the terminal is:
$ eval $(minikube -p minikube docker-env)
Remember, this is going to set the environment for this particular terminal session! From now on, anything you will do using `docker` command, will be interpreted by the Docker inside Minikube. If you destroy this terminal session or start another, you need to repeat the `eval` command as shown above.
Preparing and building the image
Now, we will take on the `Dev` part of our quick DevOps recipe. We need to create two files — one will be an application to run in the container, the other will contain the instructions on how to build a Docker container running the application.
Crude WWW application
For our simple app, we will create go http server, using the idea from Arundel & Dominguis’ “Cloud Native DevOps with Kubernetes”. If you haven’t got your hands in this book yet, I heavily recommend it!
There is a specific reason for this choice — the golang app can be compiled and built into very small, optimized container, that also leaves very small _attack surface_ when built into `scratch` container. Basically, we would be creating an app within Docker with nothing more inside.
Create a `hello.go` file and edit it in your editor of choice to achieve the following:
package hello
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "It works!")
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Docker image for WWW application
Next, create a `Dockerfile` in the same directory with the following content:
FROM golang:1.15-alpine AS base
WORKDIR /src/
COPY hello.go /src/
ENV CGO_ENABLED=0
RUN go build -o /bin/hello
FROM scratch
COPY --from=base /bin/hello /bin/hello
ENTRYPOINT ["/bin/hello"]
Right now, if you build the container in the terminal that is connected to the Minikube’s Docker deamon, it is going to be available in K8s cluster immediately.
To use the connection, run the command below. Remember to stay in the directory where the Dockerfile is placed! As you can see, we are using the `1.0` tag. Take note of the single dot at the end.
$ docker build -t go-hello:1.0 .
IMPORTANT NOTE: **Always** tag the image. Do not use `latest` tag with the local image. Otherwise, you may encounter `ErrPullBackoff` errors within Minikube’s K8s.
Deploying and exposing
In production, you should always follow IaC and DevOps principles. In terms of Kubernetes, you should have at least the deployment and service YAML files, or even the Helm charts to re-deploy the application.
On your local environment, as you experiment, you can just create the deployment and expose it with two simple commands:
$ kubectl create deployment go-hello --image=go-hello:1.0$ kubectl expose deployment go-hello --type=NodePort --port=8080
Please remember to set the tag to whatever you have used in the previous subsection.
After the deployment and if the service has been created successfully, you can verify the application behaviour in K8s cluster, as well as use Minikube’s built-in feature to verify the service in browser window:
$ kubectl get svc go-hello$ kubectl get deploy go-hello -o wide$ kubectl get pods -l=app=go-hello$ minikube service go-hello
The last command should result in opening your browser to your service, as well as showing the connection values, just like in the example below.
| default | go-hello | 8080 | http://172.17.0.3:32061 |🎉 Opening service default/go-hello in default browser…
You can of course use the URL with curl, Postman or whathever tools you use to test your service.
Whenever you change your application and want to test in Kubernetes, you can:
- Build an image with a new tag, for example `2.0`
$ docker build -t go-hello:2.0 .
- Edit the deployment in Minikube, using the command:
$ kubectl set image deployment/go-hello go-hello=go-hello:2.0
The updated image will cause the pod to be recreated and the change will be immediately visible in your browser.
That’s it!
You have successfully deployed a minikube Kubernetes cluster, built an optimized Docker application, run in on the cluster — and did all of it on your local enviroment. Congratulations!
Let me know in the comments if there were some parts that caused some problems. There are many configurations and OSes and I hope my tutorial covers most, but I am certain that not all of them :)