[ Language select: 日本語 ]

1. Summary

The officially provided Guestbook application did not automatically display messages when multiple users connected, requiring reloading.

In this article, I will describe an application and its internal structure that uses socket.io to immediately reflect written content.

A sample application using socket.io is available at the following URL.

Based on the above getting started tutorial, the container image is available at the following URL.

Sample code for the final application with user authentication enabled is available at the following URL.

2. Starting the server

Before running in a K8s environment, test it using docker on the thinkpad.

If you using your laptop pc, please prepare the docker system referencing the docker official documents.

$ podman run -it --rm -d -p 8080:8080 --name guestbook docker.io/yasuhiroabe/socket-guestbook:1.0.4

You should see the following message on your screen: If port 8080 is in use, stop that docker container and run the above command again.

Unable to find image 'yasuhiroabe/socket-guestbook:1.0.4' locally
1.0.4: Pulling from yasuhiroabe/socket-guestbook
59bf1c3509f3: Pull complete
a507ac90b360: Pull complete
3cd4690eda9d: Pull complete
3dc4384c619b: Pull complete
4be4baab24f4: Pull complete
d28798c5257e: Pull complete
1f3791011175: Pull complete
1546acf6e893: Pull complete
Digest: sha256:d6833cfbcdb046d10a18188dcc107c6b3a72e2a4c7c681820916552d5609594f
Status: Downloaded newer image for yasuhiroabe/socket-guestbook:1.0.4
Socket.IO server running at http://localhost:8080/

After confirming the last message, access http://localhost:8080/ with a web browser.

2.1. Stop the Docker container

$ podman stop guestbook

3. Starting with Kubernetes(k8s)

To run in a K8s environment, do the followings,

  1. Create and apply Deployment YAML file

  2. Create and apply the Service YAML file

  3. Modify and apply the configuration file for Reverse Proxy (reverse-proxy work must be completed)

To create the YAML file, copy the file from a similar process and change the name: and image: lines. In this example, we use reverse-proxy and modify cm/nginx-conf.

3.1. Creation and reflection of Deployment YAML file

Create the following YAML file with any name and reflect it by kubectl -n $(id -un) apply -f command.

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: guestbook
  labels:
    app: my-guestbook
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-guestbook
  template:
    metadata:
      labels:
       app: my-guestbook
    spec:
      containers:
      - name: guestbook
        image: docker.io/yasuhiroabe/socket-guestbook:1.0.4
        imagePullPolicy: "Always"
        ports:
        - containerPort: 8080
        env:
        - name: PORT
          value: "8080"

3.2. Service YAML file creation and reflection

Create the following YAML file and reflect it with the kubectl command.

---
apiVersion: v1
kind: Service
metadata:
  name: my-guestbook
  labels:
    app: my-guestbook
spec:
  type: ClusterIP
  ports:
     -  port: 80
        protocol: TCP
        targetPort: 8080
  selector:
    app: my-guestbook

3.3. Confirmation of procedures

The command line to reflect the above two YAML files is as follows

$ kubectl -n $(id -un) apply -f https://web-int.u-aizu.ac.jp/~yasu-abe/en/sccp/manual/tutorial-stateless-gustbook-socket.deploy-guestbook.yaml
$ kubectl -n $(id -un) apply -f https://web-int.u-aizu.ac.jp/~yasu-abe/en/sccp/manual/tutorial-stateless-gustbook-socket.svc-guestbook.yaml

3.4. Modify and reflect the configuration file for Reverse Proxy

In the tutorial (reverse-proxy) used configmap/nginx-conf (abbreviated name: cm/nginx-conf) as a configuration file.

Specify an appropriate Editor, such as emacs, and edit cm/nginx-conf.

$ env EDITOR=emacs kubectl -n $(id -un) edit cm/nginx-conf

Now add the following location section in parallel with the other location sections,

(Replace "<your namespace"> with your namespace name.)

  location /<your namespace>/guestbook/ {
         proxy_http_version  1.1;
         proxy_set_header    Upgrade $http_upgrade;
         proxy_set_header    Connection "upgrade";
         proxy_set_header    X-Nginx-Proxy true;
         proxy_pass    http://my-guestbook/;
  }

Alternatively, you can use the following command to add the location section.

$ curl "https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/tutorial-stateless-gustbook-socket.configmap-proxy-guestbook.yaml" | sed -e "s/s12xxxxx/$(id -un)/" | kubectl -n $(id -un) apply -f -

Once the changes are complete, stop all Proxy Pods as follows,

$ kubectl -n $(id -un) delete pod -l app=my-proxy

After restarting proxy pods, access your my-guestbook application via your URL. Open multiple tabs or windows in your browser and see how the content you wrote in one side of the page is being broadcast to the other side of the page.

4. Future enhancements

To develop a real-world application, following functions need to be considered.

  • Actual user authentication to identify who posted the message

  • To use some of database servers to store posted messages

    • e.g., MySQL, PostgreSQL, MongoDB, etc.

4.1. Execute A Enhanced Application using Docker

Under the K8s environment, various configuration changes are required to enable these features, so we have prepared an easy way to check this on Thinkpad using the docker command.

Please follow these steps to git clone your project and get your Guestbook app up and running with your changes.

$ git clone https://inovtst9.u-aizu.ac.jp/gitbucket/git/yasu-abe/docker-sccp-guestbook.git

Then, start the server as follows

$ cd docker-sccp-guestbook/
$ make DOCKER_CMD=podman docker-redis-run
$ make run

Once the system is up and running, go to http://localhost:8080/, not the URL displayed on the screen.

Please use multiple tabs, or use your browser’s private window function to access the application.

4.1.1. Example of errors

You might face the following error message if make docker-redis-run command is not executed successfully before make run command.

$ make run
...
Error: connect ECONNREFUSED 192.168.100.29:6379
...
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '192.168.100.29',
  port: 6379
}
...
make: *** [Makefile:26: run] Error 1

This message shows that the 6379 port used by the Redis server is not accessible.

In this case, please execute docker stop redis || make docker-redis-run again and then execute make run again.

4.2. Running the application after the extension on Kubernetes

Applications running on K8s can be accessed at the following URL. (It may not be running depending on the timing.)

You cannot run the application by executing the YAML listed here, as it requires additional configuration. You will also need to run Redis (NoSQL server).

At the previous section, you git clone the directory, and there are templates of YAML files required to run the application on Kubernetes.

When using these YAML files, you need to change some of the s12xxxxx strings to your AINS ID.

Please edit the files with your favorite editor, or use the following command to replace the strings.

$ sed -i -e "s/s12xxxxx/$(id -un)/" kubernetes/00.deployment-guestbook.yaml

To apply these files, execute the following commands.

$ kubectl -n $(id -un) apply -f kubernetes/00.deployment-guestbook.yaml
$ kubectl -n $(id -un) apply -f kubernetes/01.service-redis.yaml
$ kubectl -n $(id -un) apply -f kubernetes/02.deployment-redis.yaml

As the results, you can access the application with authentication and the example chat application has been deployed.

This application also use the service/my-guestbook you have already prepared, so you can access the application from your URL.

4.2.1. URL List

4.3. Modify the application and create/register the container for Kubernetes use

Let’s create your own application using the code downloaded by git clone.

After using the application, you might find some issues. For example, the following improvements are considered.

  • There is an "logout" link even though you are not logged in (there is a "login" link even though you are logged in) → Implement exclusive display of "login/logout" links

  • New messages are displayed at the top, not at the bottom → Change the growth rate of messages

Other improvements include changing the background color and removing buttons might be thought.

To run the modified application on Kubernetes, the following steps are required.

  1. Create new container image on WS/PC using docker/podman

  2. Transfer the container image to the Docker-compatible registry on the network (Harbor or DockerHub)

  3. Specify your own image in the image: on Kubernetes

Original container images need to be transferred via a container registry, so it takes a little time.

The following tasks are performed in the docker-sccp-guestbook/ directory created by git clone.

4.3.1. Saving local changes before creating a container image

If you made changes to files, such as index.html, you should first save these changes in the local git repository.

$ git add .
$ git commit -m 'Saved any local changes.'
$ git checkout master

4.3.2. Creating your container image

First, create a container without any changes.

Under the docker-sccp-guestbook/ directory, there are Dockerfile and Makefile required to create the container image.

$ make DOCKER_CMD=podman docker-build

To run the created container image, execute the following command.

$ make DOCKER_CMD=podman docker-redis-run
$ make DOCKER_CMD=podman run

You can access to the application running here at the following URL.

4.3.3. Transfer the container image to the registry

To use the tag and push commands, you need to specify the destination registry server and the storage location of the image on the server with a special name.

First, log in to the destination registry server and register a project with the same name as your ID. Details are described in the following section.

After prepared the destination, execute the following commands to tag the container image you created earlier and transfer it to the server.

$ make DOCKER_CMD=podman docker-build-prod
$ make DOCKER_CMD=podman docker-tag
$ make DOCKER_CMD=podman docker-push

As the result, the image is tagged with the following name.

$ podman images
REPOSITORY                                       TAG         IMAGE ID      CREATED         SIZE
localhost/socket-guestbook                       latest      0b9837ee863a  22 minutes ago  225 MB
inovtst9.u-aizu.ac.jp/yasu-abe/socket-guestbook  latest      0b9837ee863a  22 minutes ago  225 MB

In this sample, there are two images with the same IMAGE ID.

The inovtst9.u-aizu.ac.jp/yasu-abe/socket-guestbook string indicates that the destination server is inovtst9.u-aizu.ac.jp, the project name is yasu-abe, and the name of the container to be saved is socket-guestbook.

4.3.4. Running on Kubernetes

The inovtst9.u-aizu.ac.jp/yasu-abe/socket-guestbook:1.0.14 container image has been running on Kubernetes, so edit the YAML file and apply it again with the kubectl command.

To open the YAML file with your favorite editor, edit the image: line.

$ emacs kubernetes/00.deployment-guestbook.yaml
## Before editing
        image: docker.io/yasuhiroabe/socket-guestbook:1.0.14

## After editing
        image: inovtst9.u-aizu.ac.jp/<Your AINS ID>/socket-guestbook:latest

After saving your editor, exit the editor and apply the changes.

$ kubectl -n $(id -un) apply -f kubernetes/00.deployment-guestbook.yaml

4.3.5. Example: Change the background color

For example, the background color can be changed by modifying the index.pug file as follows.

diff --git a/index.pug b/index.pug
index d9cf07c..325c0a4 100644
--- a/index.pug
+++ b/index.pug
@@ -5,7 +5,7 @@ html
     title Socket.IO chat
     link(href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css", rel="stylesheet", integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC", crossorigin="anonymous")
     script(src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js", integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM", crossorigin="anonymous")
-  body(class="container")
+  body(class="container",style="background: #efefef;")
     div(class="row mt-2")
       div(class="col-sm-2")
         a(href="auth") Login

The line beginning with a minus ('-') is the original code, which is deleted and replaced by a line beginning with a plus ('+').

Similarly, if you check the index.pug file, you will see that the value of #{email} is "N/A" if the user is not logged in.

We use the email variable to add some conditions to show login and logout links.

diff --git a/index.pug b/index.pug
index 325c0a4..911b844 100644
--- a/index.pug
+++ b/index.pug
@@ -8,8 +8,9 @@ html
   body(class="container",style="background: #efefef;")
     div(class="row mt-2")
       div(class="col-sm-2")
+      if email === "N/A"
         a(href="auth") Login
-        span  |
+      if email !== "N/A"
         a(href="logout") Logout
       div(class="col-sm-10")
         span User: #{username} (E-mail: #{email})

これも’span |'を含む行を削除し、その前後のLogin/Logoutリンクの前に条件文を追加しています。 This also removes(-) the line containing 'span |' and adds(+) a conditional statement before and after the Login/Logout links.

4.3.7. Example 3: Change the addition of a message from the bottom line to the first line

The message management conducts by the pug-client.js file.

When a message is added, it is supposed to add (.appendChild()) the element of li tag inside the ul tag. So, it will be changed to add it at the beginning by the .prepend() method to move new message at first line.

diff --git a/pug-client.js b/pug-client.js
index d978066..74d3184 100644
--- a/pug-client.js
+++ b/pug-client.js
@@ -23,6 +23,5 @@
         const item = document.createElement('li');
         item.className = "list-unstyled list-group-item";
         item.innerHTML = msg;
-        messages.appendChild(item);
-        window.scrollTo(0, document.body.scrollHeight);
+        messages.prepend(item);
       });

4.3.8. Check your operation while editing code

There are two ways to run this application within the work environment.

One is to create a container by make docker-build and run it with the make docker-run command, which provides a confirmation similar to a production environment.

Another easier way is to use the make run command to run it in the operating system of your working environment.

It is highly likely that the working environment will not necessarily be identical, such as different versions of node.js being used, but it is useful when the items to be checked are limited.

4.3.9. Migration to Kubernetes environment after work is completed

Repeat the following tasks performed at the beginning of this section.

You may then change the version of the container at the top of the Makefile from latest to a number such as 2.0.0 to make it unique. You may also use the date. (e.g., 20231113.01)

## Example of editing Makefile
...
DOCKER_IMAGE_VERSION = 2.0.0
...

If the tag is still latest, it can be hard to notice to the kubernetes api-server if you still have an old image running on the k8s cluster.

The latest label should only be used for testing and should be assigned a unique number for actual use.