【初心者向けハンズオン】KubernetesでJava(Spring Boot)アプリデプロイ ~Step1:DBコンテナ作成~

1. はじめに

本記事では、Kubernetes上にSpring Bootで構築したアプリを動作させるハンズオンをご紹介します。ひととおり実施して基本を理解すれば、ご自身で開発したアプリもKubernetes上で運用できるようになると思いますので、ぜひトライしてみて下さい。今回はStep1としてDBコンテナの作成方法をご説明します。

2. ハンズオン環境

Kubernetesがインストールされている環境を準備してください。今回はWindowsマシンのVirtualBox上に構築した仮想マシン上でKubernetesを構成しています。
・ホストマシン:Windows11
・仮想化ソフトウェア:VirtualBox 7.0
・仮想マシン:RHEL 8.5(Master Node:1台、Worker Node:2台)
・コンテナ・オーケストレーション:Kubernetes v1.29.12

オンプレでKuberntesインストールからやってみたいという方がいれば、下記を参考にして構築してもらえればと思います。
【初心者向け】Kubernetes学習向けのハンズオン(Linux(CentOS))

3. システム構成

Kubernetes上に構築するシステム構成を記載します。Web/AP、DBのシンプルなシステム構成を構築しています。DBについてはデータを永続化したいので、仮想サーバの1台をNFSサーバとして構築しています。

4. DBコンテナの作成

4.1 NFSサーバの作成
データを永続化するためのボリュームをNFSで作成していきます。NFSを構築することで、すべてのノードから同一ボリュームを参照・更新することができるようになります。まず、NFS関連パッケージをすべての仮想サーバにインストールします。私の環境ではすでにインストール済みだったので下記のような出力となりました。

[root@workernode1 ~]# dnf -y install nfs-utils
CentOS Linux 8 - AppStream                                                                         14 kB/s | 4.3 kB     00:00
CentOS Linux 8 - BaseOS                                                                            36 kB/s | 3.9 kB     00:00
CentOS Linux 8 - Extras                                                                            13 kB/s | 1.5 kB     00:00
Docker CE Stable - x86_64                                                                          47 kB/s | 3.5 kB     00:00
Kubernetes                                                                                        4.6 kB/s | 1.7 kB     00:00
Package nfs-utils-1:2.3.3-46.el8.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!
[root@workernode1 ~]#

続いて、NFSサーバとしたい仮想サーバを1台選択して、idmpd.confファイルのDomainに仮想サーバのホスト名を設定します。DNSが設定されていない場合は/etc/hostsも変更しておきましょう。

[root@workernode1 ~]# vi /etc/idmapd.conf
[root@workernode1 ~]# cat /etc/idmapd.conf |grep Domain
#Domain = local.domain.edu
Domain = workernode1
# the old method (comparing the domain in the string to the Domain value,
[root@workernode1 ~]#
[root@workernode1 ~]# cat /etc/hosts |grep workernode1
192.168.56.102 workernode1
[root@workernode1 ~]#

データを格納するディレクトリを作成します。

[root@workernode1 ~]# mkdir -p /nfs/share/postgresql
[root@workernode1 ~]# ls -ld /nfs/share/postgresql
drwxr-xr-x 2 root root 6 Jan 10 06:11 /nfs/share/postgresql
[root@workernode1 ~]#

/etc/exportsファイルに対して、上記で作成したディレクトリを他の仮想サーバから参照・更新できるように設定を追加します。
・ディレクトリ:/nfs/share/postgresql
・IPアドレス帯:192.168.56.0/24
NFSサーバにアクセスする仮想サーバのIPアドレス帯を指定して下さい。
・権限:rw,no_root_squash
rwとすることで読み書き可能とします。no_root_squashを設定することで、他の仮想サーバからルート権限で操作できるようにします。

[root@workernode1 ~]# vi /etc/exports
[root@workernode1 ~]# cat /etc/exports
/nfs/share/postgresql 192.168.56.0/24(rw,no_root_squash)
[root@workernode1 ~]#

続いて必要なサービスを有効化・起動していきます。

まず、NFSサーバにてnfs-serverを有効化・起動してください。

[root@workernode1 ~]# systemctl enable --now nfs-server
Created symlink /etc/systemd/system/multi-user.target.wants/nfs-server.service → /usr/lib/systemd/system/nfs-server.service.
[root@workernode1 ~]#
[root@workernode1 ~]# systemctl status nfs-server
● nfs-server.service - NFS server and services
   Loaded: loaded (/usr/lib/systemd/system/nfs-server.service; enabled; vendor preset: disabled)
  Drop-In: /run/systemd/generator/nfs-server.service.d
           └─order-with-mounts.conf
   Active: active (exited) since Mon 2025-02-10 08:40:55 JST; 11s ago
  Process: 4112 ExecStart=/bin/sh -c if systemctl -q is-active gssproxy; then systemctl reload gssproxy ; fi (code=exited, status=0/SUCCESS)
  Process: 4101 ExecStart=/usr/sbin/rpc.nfsd (code=exited, status=0/SUCCESS)
  Process: 4099 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
 Main PID: 4112 (code=exited, status=0/SUCCESS)

Feb 10 08:40:55 workernode1 systemd[1]: Starting NFS server and services...
Feb 10 08:40:55 workernode1 systemd[1]: Started NFS server and services.
[root@workernode1 ~]#

続いて、rpcbindサービスを有効化・起動してください。NFSはRPC(Remote Procedure Calls)の仕組みを利用してリモートサーバにおける操作を実現しています。nfs-serverサービスを起動すると、rpcbindにプログラム番号と使用するポート番号が登録されます。リモートサーバがNFSサーバに接続する際、まずrpcbindに問い合わせてポート番号を取得することで接続を確立しています。

[root@workernode1 ~]# systemctl enable --now rpcbind
Created symlink /etc/systemd/system/multi-user.target.wants/rpcbind.service → /usr/lib/systemd/system/rpcbind.service.
[root@workernode1 ~]#
[root@workernode1 ~]# systemctl status rpcbind
● rpcbind.service - RPC Bind
   Loaded: loaded (/usr/lib/systemd/system/rpcbind.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2025-02-10 08:40:55 JST; 41s ago
     Docs: man:rpcbind(8)
 Main PID: 4088 (rpcbind)
    Tasks: 1 (limit: 11272)
   Memory: 1.7M
   CGroup: /system.slice/rpcbind.service
           └─4088 /usr/bin/rpcbind -w -f

Feb 10 08:40:55 workernode1 systemd[1]: Starting RPC Bind...
Feb 10 08:40:55 workernode1 systemd[1]: Started RPC Bind.
[root@workernode1 ~]#

以上でNFSサーバの設定は完了です。NFSサーバ以外のサーバからマウントできることを確認しましょう。下記の通りdfコマンドでNFSサーバのディレクトリが表示されていれば成功です。Kubernetesのマニフェスト適用時にマウントされるようにするため、現時点ではumountしておきましょう。

[root@masternode ~]# mount -t nfs workernode1:/nfs/share/postgresql /mnt
[root@masternode ~]# df -h |grep /mnt
workernode1:/nfs/share/postgresql   37G  6.9G   31G  19% /mnt
[root@masternode ~]# umount /mnt
[root@masternode ~]# df -h |grep /mnt
[root@masternode ~]#

4.2 Persistent Volumeの作成
アプリケーションで参照・更新するデータをpod上に格納してしまうと、pod再作成時にデータが削除されてしまいます。データが削除されないように、PV(Persistent Volume)というリソースを作成してデータを永続化しましょう。
まず、マニフェストを配置するためのディレクトリを作成します。

[root@masternode manifest]# mkdir -p /app/quizapp/manifest
[root@masternode manifest]# ls -ld /app/quizapp/manifest
drwxr-xr-x 2 root root 6 Feb 23 08:12 /app/quizapp/manifest
[root@masternode manifest]# mkdir -p /app/quizapp/manifest
[root@masternode manifest]#
[root@masternode manifest]# ls -ld /app/quizapp/manifest
drwxr-xr-x 2 root root 6 Feb 23 08:12 /app/quizapp/manifest
[root@masternode manifest]#
[root@masternode manifest]# cd /app/quizapp/manifest
[root@masternode manifest]#
[root@masternode manifest]# pwd
/app/quizapp/manifest
[root@masternode manifest]#

続いて、Podをデプロイするためのnamespaceを作成します。

[root@masternode manifest]# kubectl create namespace quizapp
namespace/quizapp created
[root@masternode manifest]# kubectl get ns |grep quizapp
quizapp            Active   14m
[root@masternode manifest]#

PV作成のためのマニフェストを下記の通り作成します。nfsの項目に自身のNFSサーバの情報を設定します。

[root@masternode manifest]# vi psql-pv.yaml
[root@masternode manifest]# cat psql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-volume
  labels:
    type: local
    app: postgres
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  nfs:
    server: masternode
    path: /nfs/share/postgresql
[root@masternode manifest]#

作成したマニフェストを適用して状態を確認します。下記の通りSTATUSがAvailableになっていればOKです。

[root@masternode manifest]# kubectl apply -f psql-pv.yaml
persistentvolume/postgres-volume created
[root@masternode manifest]# kubectl get pv
NAME              CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
postgres-volume   20Gi       RWX            Retain           Available           manual         <unset>                          10s
[root@masternode manifest]#

続いてPVC(Persistent Volume Claim)というリソースを作成します。PVだけ作成してもダメで、PVCによりKubernetesのPodによる要求とPVとを紐づけすることによってPodから使用することができます。下記の通りマニフェストを作成して適用します。

[root@masternode manifest]# vi psql-claim.yaml
[root@masternode manifest]# cat psql-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-volume-claim
  namespace: quizapp
  labels:
    app: postgres
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 20Gi
[root@masternode manifest]#
[root@masternode manifest]# kubectl apply -f psql-claim.yaml
persistentvolumeclaim/postgres-volume-claim created
[root@masternode manifest]#
[root@masternode manifest]# kubectl get pvc -n quizapp
NAME                    STATUS   VOLUME            CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
postgres-volume-claim   Bound    postgres-volume   20Gi       RWX            manual         <unset>                 28s
[root@masternode manifest]#
root@masternode manifest]# kubectl get pv
NAME              CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                           STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
postgres-volume   20Gi       RWX            Retain           Bound    quizapp/postgres-volume-claim   manual         <unset>                          29s
[root@masternode manifest]#

上記の通り、PVのSTATUSがBOUNDに代わっていれば、KubernetesのコンテナとPVが紐づけられた状態になっています。

4.3 DBコンテナ作成
さて、いよいよDBコンテナの作成に着手しましょう。configmapを作成してデータベース接続に関する設定を記載していきます。ご自身の作成したデータベースに合わせて、DB名、ユーザ名、パスワードを設定してください。

[root@masternode manifest]# vi postgres-configmap.yaml
[root@masternode manifest]# cat postgres-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-secret
  namespace: quizapp
  labels:
    app: postgres
data:
  POSTGRES_DB: quiz
  POSTGRES_USER: postgres
  POSTGRES_PASSWORD: password
[root@masternode manifest]#

作成したconfigmapのマニフェストを適用下記のします。下記の通りpostgres-secretのリソースが取得できれば正常です。

[root@masternode manifest]# kubectl apply -f postgres-configmap.yaml
configmap/postgres-secret created
[root@masternode manifest]# kubectl get configmap -n quizapp |grep postgres-secret
postgres-secret      3      57s
[root@masternode manifest]#

postgresをデプロイするためのマニフェスト例を記載しているので参考にしてみてください。
下記あたりの設定はご自身の要件に応じて適宜変更ください。
・レプリカセット⇒replicas: 3
・Postgresバージョン⇒image: ‘postgres:15’
・マウントパス⇒mountPath: /var/lib/postgresql/data

[root@masternode manifest]# vi postgres-deployment.yaml
[root@masternode manifest]# cat postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: quizapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
        sidecar.istio.io/inject: "false"
    spec:
      containers:
        - name: postgres
          image: 'postgres:15'
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 5432
          envFrom:
            - configMapRef:
                name: postgres-secret
          volumeMounts:
            - mountPath: /var/lib/postgresql/data
              name: postgresdata
      volumes:
        - name: postgresdata
          persistentVolumeClaim:
            claimName: postgres-volume-claim
[root@masternode manifest]#

Postgresのマニフェストをデプロイしていきます。デプロイの状態がREADYになっていること、PodのSTATUSがRunningになっていたら成功です。少し時間がかかるので、正常になっていない場合は時間をおいてから再度コマンド実行してみて下さい。

[root@masternode ~]# kubectl apply -f postgres-deployment.yaml
deployment.apps/postgres created
[root@masternode ~]#
[root@masternode ~]# kubectl get deployments -n quizapp
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
postgres   3/3     3            3           2m38s
[root@masternode ~]#
[root@masternode ~]# kubectl get pods -n quizapp
NAME                       READY   STATUS    RESTARTS      AGE
postgres-9885f64c8-lhqwm   1/1     Running   3 (86s ago)   3m23s
postgres-9885f64c8-vrjss   1/1     Running   3 (82s ago)   3m23s
postgres-9885f64c8-z8r77   1/1     Running   2 (81s ago)   3m23s
[root@masternode ~]#

後続で作成するアプリケーションのPodからPostgresのPodにアクセスするために、Serviceを作成していきます。Serviceを作成することで、アプリケーションPodから複数作成したPostgresのPodに自動で振り分けが行われるようになります。内部アクセスに使用するServiceとなりますので、ここではtypeにLoadBalancerを指定します。

[root@masternode ~]# vi ps-service.yaml
[root@masternode ~]# cat ps-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres
  labels:
    app: postgres
spec:
  type: LoadBalancer
  ports:
    - port: 5432
  selector:
    app: postgres
[root@masternode ~]# 

Postgresサービスのマニフェストを適用してください。下記の通りpostgresのサービスが表示されていれば成功です。外部アクセスは不要ですので、EXTERNAL-IPがpendingとなってますが問題ありません。アプリケーションのデータベース接続定義にサービス名(postgres)を指定することでPostgres Podにアクセスすることができます。アプリケーションの定義についてはStep2でご紹介します。

[root@masternode manifest]# kubectl apply -f postgres-service.yaml
service/postgres created
[root@masternode manifest]#
[root@masternode manifest]# kubectl get svc -n quizapp
NAME       TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)          AGE
postgres   LoadBalancer   10.106.194.35   192.168.100.200   5432:30168/TCP   13s
[root@masternode manifest]#

Postgresデータベースに接続してみましょう。下記コマンドを実行してPostgresのPod Nameを取得してください。

[root@masternode manifest]# kubectl get pod -o wide -n quizapp
NAME                        READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
postgres-58c98dcd44-5hf7z   1/1     Running   0          62m   192.168.100.42   masternode   <none>           <none>
postgres-58c98dcd44-cbspd   1/1     Running   0          62m   192.168.100.43   masternode   <none>           <none>
postgres-58c98dcd44-rq9d9   1/1     Running   0          62m   192.168.100.24   masternode   <none>           <none>
[root@masternode manifest]#

どれかひとつPodを選択して、postgresデータベースに接続してください。

[root@masternode manifest]# kubectl exec -it postgres-58c98dcd44-5hf7z -n quizapp -- psql -h localhost -U postgres --password -p 5432 postgres
Password:
psql (15.10 (Debian 15.10-1.pgdg120+1))
Type "help" for help.

postgres=#

postgres-configmap.yamlにて指定して作成したデータベースにスイッチしてください。

postgres=# CREATE DATABASE quiz;
CREATE DATABASE
postgres=# \l
                                                List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    | ICU Locale | Locale Provider |   Access privileges
-----------+----------+----------+------------+------------+------------+-----------------+-----------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            |
 quiz      | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | =c/postgres          +
           |          |          |            |            |            |                 | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | =c/postgres          +
           |          |          |            |            |            |                 | postgres=CTc/postgres
(4 rows)

postgres=# \c quiz
Password:
You are now connected to database "quiz" as user "postgres".
quiz=#

アプリ動作に必要な各種テーブルを作成してください。

quiz=# create TABLE users_roles (
    user_id integer NOT NULL,
    role_id integer NOT NULL
);

create TABLE users (
    userid SERIAL NOT NULL,
    username varchar(255) NOT NULL UNIQUE,
    password varchar(255) NOT NULL,
    primary key (userid)
);

CREATE TABLE quiz (
    quizid SERIAL NOT NULL,
    userid integer NOT NULL,
    quiz varchar(255),
    category varchar(255),
    option1 varchar(255),
    option2 varchar(255),
    option3 varchar(255),
    option4 varchar(255),
    answer integer,
    explain varchar(1000),
    status varchar(255),
    PRIMARY KEY (quizid)
);

create TABLE roles (
    roleid SERIAL NOT NULL,
    rolename varchar(255) NOT NULL,
    primary key (roleid)
);
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
postgres=#
postgres=# \dt
            List of relations
 Schema |    Name     | Type  |  Owner
--------+-------------+-------+----------
 public | quiz        | table | postgres
 public | roles       | table | postgres
 public | users       | table | postgres
 public | users_roles | table | postgres
(4 rows)

quiz=#

サンプルデータをINSERTしておきます。

quiz=# delete from quiz;

select setval('quiz_quizid_seq', 1, false);

DO $$
BEGIN
    FOR i IN 1..1000 LOOP
        INSERT INTO quiz (quizid,userid,quiz,category,option1,option2,option3,option4,answer,explain,status) VALUES(default,i,'Linuxのシステムに関する一般的なログファイル名は?','Linux','/var/log/logs','/var/log/messages','/var/log/maillog','/var/log/spooler','1','/var/log/messagesは、UnixおよびUnix系オペレーティングシステム(例えばLinux)で使用される標準的なログファイルの一つです。このファイルはシステムやアプリケーションからの重要なメッセージや イベントを記録するために使用されます。','未完了');
        INSERT INTO quiz (quizid,userid,quiz,category,option1,option2,option3,option4,answer,explain,status)  VALUES(default,i,'ファイルの末尾から100 行を表示させるコマンドは?','Linux','head -n 100','head -l 100','tail -n 100','tail -l 100','3','tailコマンドは指定したファイルの末尾の内容を表示する ための Linux コマンドです。オプション-nで表示する行数を指定します。デフォルトでは最後の 10 行を表示します。','未完了');
        INSERT INTO quiz (quizid,userid,quiz,category,option1,option2,option3,option4,answer,explain,status)  VALUES(default,i,'新規作成するファイルのパーミッションが全て644になるようにする方法は?','Linux','umask 644','umask 022','chmod 644','chmod 022','2','umaskに022を指定するとデフォルトの権限は644となります。一方でディレクトリは755になりますので注意してください。','未完了');
    END LOOP;
END;
$$;

select * from quiz;
DELETE 0
 setval
--------
      1
(1 row)

DO
quiz=#

publicユーザを登録しておきます。

quiz=# INSERT INTO users (username, password) VALUES('public', 'password');
INSERT 0 1
quiz=# select * from users;
 userid | username | password
--------+----------+----------
      1 | public   | password
(1 row)

quiz=#

5. まとめ

DBコンテナ作成のハンズオンは以上となります。Persistent Volumeを利用することで、データベースをコンテナで動かす方法が理解できたかと思います。次のステップではAPコンテナの作成方法をご紹介しますので、引き続きトライしてみて下さい。

6. 参考文献

コメント