【初心者向けハンズオン】GKE(Google Kuberenetes Engine)にJavaアプリデプロイ ~Step2:DBコンテナ作成~

1. はじめに

本記事では、Google cloudのkubernetes上にSpring Bootで構築したアプリ(Quizアプリ)を動作させるハンズオンをご紹介します。以前、オンプレのKubernetesにてSpring Bootアプリをデプロイする方法を紹介しましたが、実際はクラウドサービスを使うケースが多いかと思います。オンプレとクラウドでやることは大きく変わらないですが、微妙に違う点がありますので紹介させていただきます。今回はStep2としてDBコンテナの作成方法をご説明します。
まだGoogle Cloudにてkubernetesクラスタを作成していないようでしたら、Step1を実施してください。
【初心者向けハンズオン】Google CloudのKuberenetesにJava(Spring Boot)アプリデプロイ ~Step1:Kubernetesクラスタ作成~

2. ハンズオン環境

作業端末:Windows11
Kubernetesクラスタ:GKE Autopilot
仮想サーバ:GCE(CentOS9)

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

3. システム構成

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

4. DBコンテナの作成

4.1 NFSサーバの作成
データを永続化するためのボリュームをNFSで作成していきます。NFSを構築することで、すべてのノードから同一ボリュームを参照・更新することができるようになります。
まず、GCEでインスタンスを作成してください。OSの種類は何でもよいですが、ここではCentOS9を選択しています。
作成方法がわからない方は、下記のサイトを参考にして作成してみて下さい。
Compute Engine インスタンスを作成して起動する  |  Compute Engine Documentation  |  Google Cloud

GCEインスタンスが作成できたら接続してください。sudo su – でルートユーザにスイッチします。

Linux quizapp-nfs-server 6.1.0-31-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.128-1 (2025-02-07) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
username@quizapp-nfs-server:~$ 
username@quizapp-nfs-server:~$
username@quizapp-nfs-server:~$ sudo su - 
root@quizapp-nfs-server:~#

NFS関連パッケージをすべての仮想サーバにインストールします。

[root@quizapp-nfs-server ~]# dnf -y install nfs-utils
CentOS Stream 9 - BaseOS                                       9.4 MB/s | 8.7 MB     00:00    
CentOS Stream 9 - AppStream                                    8.4 MB/s |  23 MB     00:02    
CentOS Stream 9 - Extras packages                               60 kB/s |  20 kB     00:00    
Google Compute Engine                                           22 kB/s | 7.2 kB     00:00    
Google Cloud SDK                                                45 MB/s | 139 MB     00:03    

Dependencies resolved.
===============================================================================================
 Package                   Architecture      Version                   Repository         Size
===============================================================================================
Installing:
 nfs-utils                 x86_64            1:2.5.4-34.el9            baseos            458 k
Installing dependencies:

~~Omission~~

  Verifying        : rpcbind-1.2.6-7.el9.x86_64                                          10/11 
  Verifying        : sssd-nfs-idmap-2.9.6-4.el9.x86_64                                   11/11 

Installed:
  gssproxy-0.8.4-7.el9.x86_64                     libev-4.33-6.el9.x86_64                      
  libnfsidmap-1:2.5.4-34.el9.x86_64               libtirpc-1.3.3-9.el9.x86_64                  
  libverto-libev-0.3.2-3.el9.x86_64               nfs-utils-1:2.5.4-34.el9.x86_64              
  python3-pyyaml-5.4.1-6.el9.x86_64               quota-1:4.09-4.el9.x86_64                    
  quota-nls-1:4.09-4.el9.noarch                   rpcbind-1.2.6-7.el9.x86_64                   
  sssd-nfs-idmap-2.9.6-4.el9.x86_64              

Complete!
[root@quizapp-nfs-server ~]#

続いて、idmpd.confファイルのDomainに仮想サーバのホスト名を設定します。

[root@quizapp-nfs-server ~]# cp -p /etc/idmapd.conf /etc/idmapd.conf_bk
[root@quizapp-nfs-server ~]# vi /etc/idmapd.conf
[root@quizapp-nfs-server ~]# diff /etc/idmapd.conf /etc/idmapd.conf_bk 
6d5
< Domain = quizapp-nfs-server
[root@quizapp-nfs-server ~]#

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

[root@quizapp-nfs-server ~]# mkdir -p /nfs/share/postgresql
[root@quizapp-nfs-server ~]# ls -ld /nfs/share/postgresql
drwxr-xr-x. 2 root root 6 Mar 12 01:21 /nfs/share/postgresql
[root@quizapp-nfs-server ~]#

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

[root@quizapp-nfs-server ~]# vi /etc/exports
[root@quizapp-nfs-server ~]# cat /etc/exports
/nfs/share/postgresql 10.128.0.0/20(rw,no_root_squash)
[root@quizapp-nfs-server ~]#

続いて必要なサービスを有効化・起動していきます。まず、NFSサーバにてnfs-serverを有効化・起動してください。

[root@quizapp-nfs-server ~]# 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@quizapp-nfs-server ~]# 
[root@quizapp-nfs-server ~]# systemctl status nfs-server
● nfs-server.service - NFS server and services
     Loaded: loaded (/usr/lib/systemd/system/nfs-server.service; enabled; preset: disabled)
    Drop-In: /run/systemd/generator/nfs-server.service.d
             └─order-with-mounts.conf
     Active: active (exited) since Wed 2025-03-12 01:40:30 UTC; 9s ago
       Docs: man:rpc.nfsd(8)
             man:exportfs(8)
    Process: 78556 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
    Process: 78557 ExecStart=/usr/sbin/rpc.nfsd (code=exited, status=0/SUCCESS)
    Process: 78577 ExecStart=/bin/sh -c if systemctl -q is-active gssproxy; then systemctl rel>
   Main PID: 78577 (code=exited, status=0/SUCCESS)
        CPU: 36ms

Mar 12 01:40:30 quizapp-nfs-server systemd[1]: Starting NFS server and services...
Mar 12 01:40:30 quizapp-nfs-server systemd[1]: Finished NFS server and services.
[root@quizapp-nfs-server ~]#

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

[root@quizapp-nfs-server ~]# systemctl enable --now rpcbind
[root@quizapp-nfs-server ~]# 
[root@quizapp-nfs-server ~]# systemctl status rpcbind
● rpcbind.service - RPC Bind
     Loaded: loaded (/usr/lib/systemd/system/rpcbind.service; enabled; preset: enabled)
     Active: active (running) since Wed 2025-03-12 01:40:29 UTC; 43s ago
TriggeredBy: ● rpcbind.socket
       Docs: man:rpcbind(8)
   Main PID: 78549 (rpcbind)
      Tasks: 1 (limit: 23097)
     Memory: 1.5M
        CPU: 39ms
     CGroup: /system.slice/rpcbind.service
             └─78549 /usr/bin/rpcbind -w -f

Mar 12 01:40:29 quizapp-nfs-server systemd[1]: Starting RPC Bind...
Mar 12 01:40:29 quizapp-nfs-server systemd[1]: Started RPC Bind.
[root@quizapp-nfs-server ~]#

4.2 Namespaceの作成
まず、Quizアプリ用にNamespaceを作成します。他の用途に使うときにNamespaceで区別できるようにしておくと便利ですので、少し手間ですがNamespaceを作っておきましょう。Powershellを起動して下記のとおりNamespaceを作成してください。

PS C:\Windows\system32> kubectl create namespace quizapp
namespace/quizapp created
PS C:\Windows\system32> kubectl get ns
NAME                          STATUS   AGE
default                       Active   33h
gke-gmp-system                Active   33h
gke-managed-cim               Active   33h
gke-managed-filestorecsi      Active   33h
gke-managed-system            Active   33h
gke-managed-volumepopulator   Active   33h
gmp-public                    Active   33h
kube-node-lease               Active   33h
kube-public                   Active   33h
kube-system                   Active   33h
quizapp                       Active   4s
PS C:\Windows\system32>

4.3 Persistent Volume (PV)作成
アプリケーションで参照・更新するデータをpod上に格納してしまうと、pod再作成時にデータが削除されてしまいます。データが削除されないようにPVというストレージリソース作成します。下記の通りマニフェストを作成して適用してください。PVのSTATUSがAvailableになっていれば成功です。

PS C:\Program Files\gke\manifest> cat .\psql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-volume
  labels:
    type: local
    app: postgres
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: standard-rwo
  nfs:
    server: 10.128.0.22
    path: /nfs/share/postgresql
PS C:\Program Files\gke\manifest>
PS C:\Program Files\gke\manifest> kubectl apply -f .\psql-pv.yaml
persistentvolume/postgres-volume created
PS C:\Program Files\gke\manifest>
PS C:\Program Files\gke\manifest> kubectl get pv
NAME              CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
postgres-volume   5Gi        RWX            Retain           Available           standard-rwo   <unset>                          8s
PS C:\Program Files\gke\manifest>

4.4 Persistent Volume Claim(PVC)の作成
続いて、Podがストレージを要求するためのリソースであるPVCを作成します。

PS C:\Program Files\gke\manifest> cat .\psql-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-volume-claim
  namespace: quizapp
  labels:
    app: postgres
spec:
  storageClassName: standard-rwo
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
PS C:\Program Files\gke\manifest>
PS C:\Program Files\gke\manifest> kubectl apply -f .\psql-claim.yaml
persistentvolumeclaim/postgres-volume-claim created
PS C:\Program Files\gke\manifest>

下記の通りPVCリソースが作成できていればOKです。この時点ではSTATUSはpendingで問題ありません。

PS C:\Program Files\gke\manifest> kubectl get pvc -n quizapp
NAME                    STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
postgres-volume-claim   Pending                                      standard-rwo   <unset>                 12s
PS C:\Program Files\gke\manifest>

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

PS C:\Program Files\gke\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
PS C:\Program Files\gke\manifest>

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

PS C:\Program Files\gke\manifest> kubectl apply -f .\postgres-configmap.yaml
configmap/postgres-secret created
PS C:\Program Files\gke\manifest>
PS C:\Program Files\gke\manifest> kubectl get configmap -n quizapp
NAME               DATA   AGE
kube-root-ca.crt   1      17m
postgres-secret    3      14s
PS C:\Program Files\gke\manifest>

postgresをデプロイするためのマニフェスト例を記載しているので参考にしてみてください。
下記あたりの設定はご自身の要件に応じて適宜変更ください。
・レプリカセット⇒replicas: 1 ※無料枠のKubernetesクラスタだと多くのpodを起動するだけのリソースがないので、最低限にしています。
・Postgresバージョン⇒image: ‘postgres:15’
・マウントパス⇒mountPath: /var/lib/postgresql/data

PS C:\Program Files\gke\manifest> cat .\postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: quizapp
spec:
  replicas: 1
  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
          env:
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
      volumes:
        - name: postgresdata
          persistentVolumeClaim:
            claimName: postgres-volume-claim
PS C:\Program Files\gke\manifest>

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

PS C:\Program Files\gke\manifest> kubectl apply -f .\postgres-deployment.yaml
Warning: autopilot-default-resources-mutator:Autopilot updated Deployment quizapp/postgres: defaulted unspecified 'cpu' resource for containers [postgres] (see http://g.co/gke/autopilot-defaults).
deployment.apps/postgres created
PS C:\Program Files\gke\manifest>
PS C:\Program Files\gke\manifest> kubectl get pod -o wide -n quizapp
NAME                        READY   STATUS    RESTARTS   AGE   IP            NODE                                       NOMINATED NODE   READINESS GATES
postgres-7667d96d7c-g2k5d   1/1     Running   0          8s    10.117.0.10   gk3-quizapp-cluster-pool-2-7538a23d-q5ph   <none>           <none>
PS C:\Program Files\gke\manifest>

後続で作成するアプリケーションの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でご紹介します。

PS C:\Program Files\gke\manifest> kubectl apply -f .\postgres-service.yaml
service/postgres created
PS C:\Program Files\gke\manifest>
PS C:\Program Files\gke\manifest> kubectl get svc -n quizapp
NAME       TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
postgres   LoadBalancer   34.118.235.39   <pending>     5432:30530/TCP   15s
PS C:\Program Files\gke\manifest>

Postgresデータベースに接続してみましょう。ひとつPodを選択して、postgresデータベースに接続してください。

PS C:\Program Files\gke\manifest> kubectl exec -it postgres-7667d96d7c-g2k5d -n quizapp -- psql -h localhost -U postgres --password -p 5432 postgres
Password:
psql (15.12 (Debian 15.12-1.pgdg120+1))
Type "help" for help.

postgres=#

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

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=#

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

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)
);
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. 参考文献

コメント