[ 言語選択: English ]

1. 概要

公式が提供するGuestbookアプリケーションは、複数ユーザーが接続しても書き込み内容は自動的に表示されず、リロードする必要がありました。

今回は、socket.ioを利用して書き込み内容が即時に反映されるアプリケーションとその内部構造について説明していきます。

利用するsocket.ioのサンプルアプリケーションは以下のURLで公開されています。

利用するアプリケーションのコンテナイメージは以下のURLで公開されています。

最終的にユーザー認証を有効にしたアプリケーションのサンプルコードは以下のURLで公開しています。(ログインが必要です)

2. サーバーの起動

K8s環境で動作させる前に、dockerを利用してテストします。

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

画面には次のようなメッセージが表示されるはずです。8080ポートが使用されている場合には、そのdockerコンテナを停止して、再度上記のコマンドを実行してください。

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/

最後のメッセージを確認してから、Webブラウザで、http://localhost:8080/ にアクセスします。

2.1. Dockerコンテナの停止

$ podman stop guestbook

3. Kubernetes(k8s)での起動

K8s環境で稼動させる場合には、次のような作業を行ないます。

  1. Deployment YAMLファイルの作成と反映

  2. Service YAMLファイルの作成と反映

  3. Reverse Proxy用設定ファイルの修正と反映 (リバースプロキシーの作業が完了していること)

YAMLファイルの作成は、類似の作業を行なった際のファイルをコピーし、name: や image: 行を変更します。 ここでは リバースプロキシー での内容をコピーし、修正しています。

3.1. Deployment YAMLファイルの作成と反映

次のようなYAMLファイルを任意の名前で作成し、kubectl -n $(id -un) apply -f コマンドで反映させます。

---
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ファイルの作成と反映

次のようなYAMLファイルを作成し、kubectlコマンドで反映させます。

---
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. 手順の確認

上記の2つのYAMLファイルを反映させるためのコマンドラインは次のようになります。

$ 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. Reverse Proxy用設定ファイルの修正と反映

チュートリアル(リバースプロキシー)の中で、設定ファイルとして、configmap/nginx-conf (省略名: cm/nginx-conf) を利用していました。

emacsなどの適当なEditorを指定して、cm/nginx-conf を編集します。

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

ここで次のようなlocationセクションを、他のlocationセクションと並列に追加します。

"<your namespace>"の部分は、各自のnamespace名(user名)に変更してください。

  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/;
  }

この編集が難しければ次のようなコマンドを入力し、このguestbookだけにアクセスするよう service/<ID>-svc オブジェクトを作成します。

$ 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 -

変更が完了したら、次の要領で、Proxy Podを全て停止します。(自動的に再起動します。)

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

再起動が終ったら、自分のURLから "/$(id -un)/guestbook/" にアクセスします。 ブラウザ上で、複数のタブやウィンドウを開いて、片方で書き込んだ内容が他に電波する様子を確認してください。

4. 今後の機能拡張について

実用的なチャット・アプリにするにはユーザー認証や、投稿されたメッセージの保存などについて検討する必要があります。

  • ユーザー認証機構の追加 → OpenID Connect (OpenID Connect Japan) の利用

  • 投稿されたメッセージの保存 → Database Serverの利用

K8s環境下では、これらの機能を有効にするための設定変更が必要になります。

4.1. Docker環境下での改造済みアプリケーションの実行

まず、Docker/Podmanを利用してThinkpad上で簡単に確認できる方法を準備しました。 次の手順に従って、プロジェクトをgit cloneし、変更を加えたGuestbookアプリを稼動させてみてください。

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

続いて、次の要領でサーバーを起動してください。

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

システムが稼動してから、画面に表示されたURLではなくhttp://localhost:8080/にアクセスしてください。

複数のタブを利用したり、ブラウザのプライベートウィンド機能なども利用して複数人を想定したアクセスを行ってください。

4.1.1. エラーの例

make docker-redis-runコマンドが正常に実行されていない場合には、make runコマンドを実行した後に次のようなメッセージが表示されます。

$ 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

このメッセージは、Redisサーバーが利用している6379ポートに接続できないことを意味しています。 この場合には再度 docker stop redis || make docker-redis-run と実行して再度make runを実行してください。

4.2. Kubernetes上でのアプリの稼動

K8s上で動いているアプリケーションは下記のURLでアクセスできます。(タイミングによって稼動していない場合もあります)

前節でgit cloneしたディレクトリの中にKubernetesで稼動させるために必要なYAMLファイルのテンプレートがあります。

このファイルは一部のs12xxxxxの文字列を自分のAINS IDに変更する必要があります。

エディタで編集するか、次のコマンドで書き換えてください。

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

3つのファイルを順番に適用します。

$ 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

これで認証可能で、過去のメッセージを保存できるチャット・アプリケーションがデプロイされました。

これは最初に準備した service/my-guestbook を利用しますので、既に自分のURLからアクセスできます。

4.2.1. URLリスト

4.3. アプリケーションの改造と、コンテナの作成・登録

git cloneでダウンロードしたコードを利用して自分のアプリケーションを作成しましょう。

使ってみると様々な不満があると思います。例えば、次のような改善が考えられるでしょう。

  • ログインしていないのに"logout"リンクが表示されている (ログインしているのに"login"リンクが表示されている) → "login/logout"リンクの排他的表示の実装

  • 新しいメッセージを最下行ではなく、先頭に表示する → メッセージの成長速度の変更

他にも背景色の変更や、ボタンの削除などが考えられます。

変更したアプリケーションをKubernetes上で稼動させるためには次のようなステップが必要です。

  1. WS/PC上でdocker/podmanを利用し、コンテナ・イメージを作成する

  2. ネットワーク上のDocker互換レジストリ(HarborやDockerHub)に転送する

  3. Kubernetes上のimage:に自作のイメージを指定する

自作のコンテナイメージはコンテナ・レジストリを経由する必要があるため、少し手間がかかります。

以下の作業は git cloneで作成した docker-sccp-guestbook/ ディレクトリで実行します。

4.3.1. コンテナ・イメージの作成

まずは無改造のまま、コンテナを作成します。

docker-sccp-guestbook/ には作業に必要な DockerfileMakefile が配置済みで、これらを使用します。

$ make DOCKER_CMD=podman docker-build

作成したコンテナをローカル環境で実行するために、次の要領で実行します。

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

ここで実行されたアプリケーションには次のURLでアクセスできます。

4.3.2. レジストリへのコンテナ・イメージの転送

転送先のレジストリ・サーバーと、そのサーバー上でのイメージの格納先を指定した特別な名前を付けるために、tagコマンドとpushコマンドを利用します。

まず転送先のレジストリ・サーバーにログインし、自分のIDと同名のプロジェクトを登録してください。 詳細はHarborの説明ページに記載しています。

転送先が準備できたら次のコマンドで先ほど作成したコンテナイメージに別名を付け、サーバーに転送します。

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

例えば次のような名前が付けられています。

$ 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

IMAGE IDが同一の2つのイメージが登録されています。

このinovtst9.u-aizu.ac.jp/yasu-abe/socket-guestbookの文字列から転送先のサーバーがinovtst9.u-aizu.ac.jpであること、転送先のプロジェクト名が yasu-abe であること、保存するコンテナの名前が socket-guestbook であることが示されています。

4.3.3. Kubernetesでの実行

既に inovtst9.u-aizu.ac.jp/yasu-abe/socket-guestbook:1.0.14 が動作しているはずですので、YAMLファイルを編集し、再度kubectlコマンドでapplyします。

適当なエディタでYAMLファイルを開き、image:行を編集してください。

$ emacs kubernetes/00.deployment-guestbook.yaml
## 編集前
        image: docker.io/yasuhiroabe/socket-guestbook:1.0.14

## 編集後
        image: inovtst9.u-aizu.ac.jp/<Your AINS ID>/socket-guestbook:latest

保存したらemacsを終了し、内容を反映させます。

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

ここまでで、改造したアプリケーションのコンテナイメージを作成し、Harborを経由してKubernetes上で動作させることができるようになりました。

4.3.4. 改造例: 背景の変更

例えば次のように index.pug ファイルを変更することで、背景色を変更することができるようになります。

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

マイナス('-')で始まる行が元のコードでこれを削除し、プラス('+')で始まる行で置き換えます。

4.3.5. 改造例2: login/logoutリンクを必要な場合だけ表示する

同様にindex.pugファイルを確認すると未ログイン状態であれば #{email} の値が"N/A"になっています。

これを条件にリンクの表示、非表示を変更します。

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リンクの前に条件文を追加しています。

4.3.6. 改造例3: メッセージの追加を最下行から先頭行に変更する

メッセージの管理は pug-client.js ファイルで行っています。

メッセージが追加されると、ulタグの内部にliタグの要素を追加する(.appendChild())するようになっているので、この部分を先頭に追加するように変更します。

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.7. 変更作業中の成果確認

このアプリケーションを作業環境内で実行する方法は2つあります。

1つはmake docker-build によってコンテナを作成し、make docker-runコマンドで実行する方法で、これは本番環境に近い確認ができます。

より簡単な方法はmake runコマンドで、作業環境のOS内で稼動させる方法です。 利用するnode.jsのバージョンが異なるなど必ずしも同一の作業環境にはならない可能性が高いですが、確認項目が限定されている場合には有用です。

4.3.8. 作業完了後のKubernetes環境への移行作業

このセクションの先頭で行った次の作業を繰り返し実行してください。

その際にMakefileの先頭にあるコンテナのバージョンを、ユニークになるようlatestから2.0.0のような数字に変更してください。日付を利用しても良いでしょう。(e.g., 20231113.01)

## Makefileを変更した例
...
DOCKER_IMAGE_VERSION = 2.0.0
...

バージョンがlatestのままだと、Kubernetes上で古いイメージを動作させたままでも気がつきにくい場合があります。

latestはテストなどでの利用に留め、実際に使用する場合にはユニークな番号を振りましょう。