Kubernetes: Custom App Deployment (Part 1)
👣

Kubernetes: Custom App Deployment (Part 1)

So far the deployments have been for existing applications. That doesn’t do much good when developing an application.

👨‍🎓
All commands are executed from inside the 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.

Sources