So far the deployments have been for existing applications. That doesn’t do much good when developing an application.
multipass
Ubuntu VM created in Kubernetes: Installation and Test Deployment. Enter the VM shell using multipass shell k3s
. sudo
may be required to run the commands below.Create A Local Private Docker Registry
A Kubernetes application deploys by pulling images from container registries. How to get the custom application into a container registry for frequent updates? It is possible to upload the image to a public repository, but for security and privacy reasons, pushing to a public repository is not the best option.
Instead, a custom application can use a local container registry.
Luckily, a private registry (as a local registry is called) is easy to create. It’s just a container running a particular image, provided by Docker. The name is, predictably, registry
, where the version used is 2
.
This command creates a local private docker registry:
$ docker run -d -p 5000:5000 --restart=always --name registry registry:2
Adding a volume will keep the registry images durable across container restarts.
$ docker run -d -p 5000:5000 --restart=always --name registry -v `pwd`/registry:/var/lib/registry registry:2
The Custom App to Deploy
To demonstrate, the example app to deploy for this post is a simple node.js
web server.
The file index.js
contains a small web server that just responds with the JSON message { message: 'ping' }
.
# index.js
const http = require('http')
const ping = (_, res) => {
res.writeHead(200)
res.end(JSON.stringify({
message: 'ping'
}))
}
const server = http.createServer(ping)
const port = process.env.PORT || 5000
const host = process.env.HOST || 'localhost'
server.listen(port, host)
console.log(`started server at ${host}:${port}`)
And the Dockerfile
to build the image for the app.
FROM node:lts-alpine
WORKDIR /app
COPY index.js ./
CMD node index.js
Put both of these file into a directory together.
Build the Image and Test the App Locally
Build the image and call it ping-node-server
with the following command.
$ docker build -t ping-node-server .
Run the container to test the application to see that it does what we want it to.
$ docker run -p 5000:5000 -e HOST=0.0.0.0 --rm --name ping-node-server ping-node-server
The HOST=0.0.0.0
environment variable is set since the index.js
script will look for localhost by default, which is local to the container, not the VM. Setting the host to all interfaces means it can be reached from outside the container.
Start another shell and test the container. Then stop the container (which will auto-delete itself due to --rm
used on the run
command).
$ curl http://localhost:5000
{"message": "ping"}
$ docker stop ping-node-server
Import App Image Into the Registry
To make the image available to Kubernetes, the image must be loaded into the private registry created earlier.
Docker employs an interesting convention to be able to push images to registries. The image is tagged with the URL of the registry prepended as part of the tag. To push an image to our new, local registry, prepend it as such:
$ docker tag ping-node-server localhost:5000/ping-node-server
The push command sees this name and uses the URL in the tag name to know where to push the image.
$ docker push localhost:5000/ping-node-server
To test the push, remove the local docker image, pull it from the registry, and run it.
$ docker image rm ping-node-server
...
$ docker pull localhost:5000/ping-node-server
...
$ docker run -p 5000:5000 -e HOST=0.0.0.0 --rm --name ping-node-server localhost:5000/ping-node-server
In another shell test the running container. And again stop the container in the first shell. The container will again delete itself.
$ curl http://locahost:5000
{ "message": "ping" }
$ docker stop ping-node-server
Deploy to Kubernetes Cluster
The deployment into the Kubernetes cluster is easy; changing the URL of the image in the yaml file to the private registry retrieves the image just created (bolded below).
Also imagePullPolicy
needs to be set to “Always”, so that updates to the image will be deployed when creating the deployment.
Save the following to the file ping-node-server.yaml
and apply it with k3s kubectl apply -f ping-node-server.yaml
.
# ping-node-server.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: ping-node-server
labels:
app: ping-node-server
spec:
selector:
matchLabels:
app: ping-node-server
template:
metadata:
labels:
app: ping-node-server
spec:
containers:
- name: ping-node-server
image: localhost:5000/ping-node-server:latest
imagePullPolicy: Always
ports:
- name: web
containerPort: 8080
resources:
limits:
memory: 250Mi
cpu: 0.25m
env:
- name: PORT
value: "8080"
- name: HOST
value: "0.0.0.0"
---
apiVersion: v1
kind: Service
metadata:
name: ping-node-server
spec:
selector:
app: ping-node-server
type: LoadBalancer
ports:
- port: 31000
targetPort: 8080
The application returns the message { message: 'ping' }
from http://localhost:31000
. (It may take a few seconds for the app to be provisioned, so it may take a few tries to finally appear.)
Redeployment after Changes
To see changes to the application requires rebuilding the image and redeploying it to the cluster.
This sets up a cycle of build/push/redeploy for the app into the cluster for testing.
Change the message in index.js
to something else (e.g., from “ping” to ‘hello, world”), and then follow the commands below to redeploy the application.
$ docker build -t ping-node-server .
...
$ docker push localhost:5000/ping-node-server
...
$ k3s kubectl delete pod -l app=ping-node-server
...
$ curl http://localhost:31000
{ message: 'hello, world' }
The Docker image is rebuilt with the new script, and pushed to the registry.
Deleting the pod “redeploys” the application into the cluster by forcing the cluster to recreate the pod with the new image, since the imagePullPolicy
is set to “Always”.
The new message is returned (after a suitable time starting the new pod).
This isn’t the ideal, and later articles will explore rolling updates and other means of developing for and within the cluster.