Kubernetes のコンテナ起動時に起こる pvc のトラブル

# この記事はなに？
Kubernetes のコンテナ起動時に pvc の扱いでつまづくケースと、その解決方法を記載しています。
また、初期状態の pvc の性質についても簡単に説明します。

※pvc の詳細な説明については割愛します

## 対象とする読者
* Kubernetes のコンテナ起動時の pvc の扱いに困っている方
  * 例： pvc を PostgreSQL のデータディレクトリにしたいけれど、エラーが出てしまう
  * 例： pvc を apache ユーザでファイル保存に使いたいけれど、デフォルトだと書き込み権限が無く、ファイルが作れない

## 本記事の検証環境
+ マウント先コンテナのOS： `CentOS7`
+ pvc のストレージクラス： `drbd`


# pvc とは？
正式名称は PersistentVolumeClaim （永続ボリューム）。

Kubernetes では通常、コンテナ上でデータを保存すると、
コンテナを削除、再作成した際にデータが消えてしまいます。

これを解消する仕組みが pvc。
pvc を作成し、コンテナにマウントすると、
コンテナを削除、再作成しても pvc のデータは消えません。
※例えが古いですが、外付けハードディスクのようなものです


# pvc の初期状態
マウントしたての pvc には以下の性質があります。
* パーミッションとオーナー
  * デフォルトで `755` の `root:root` で作成されます
* 初期配置されるディレクトリ
  * `lost+found` が初期ディレクトリとして配置されます
* 既存ディレクトリにマウントした場合、上書きされます
    例： 
    以下のようなディレクトリをもつコンテナがあるとき
    ```
    # ls -1 /var/tmp/test
    file1 file2 test_dir
    ```
    pvc を /var/tmp にマウントすると
    ```
    # ls -1 /var/tmp
    lost+found
    ```
    このように、上書きされてしまいます。

※pvc の初期状態については、コンテナの OS や pvc のストレージクラスによっては異なる可能性があります


# pvc の初期化で困ること
* pvc に root 以外のユーザで書き込みを加えようとすると、
書き込みのパーミッションが無いためファイルなどを作成できません。

* pvc をアプリケーションの初期ディレクトリにする場合、
空ではないためエラーになることがあります。

今回は、後者に焦点を当てて説明していきます。
※パーミッションの解決方法は一番最後に記載します（解決方法はほぼ同じ）


# 検証
## PostgreSQL のデータディレクトリとして使う場合
※PostgreSQL version 15 を使います

以下のようなマニフェストファイルを書いてみます（一部抜粋）。
```
    spec:
      containers:
        - name: postgres
          image: postgres:15
          env:
            ～省略～
            - name: PGDATA
              value: /var/lib/postgresql/data
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data
      volumes:
        - name: postgres-data
          persistentVolumeClaim:
            claimName: pvc-test
```

PVCのマウントポイントを、そのまま PostgreSQL のデータディレクトリに指定しています。

この状態でコンテナを作ると、
```
* 作成
# kubectl apply -f postgres.yml
deployment.apps/postgres created

* 確認（エラーになってしまった）
# kubectl get pods
NAME                READY   STATUS    RESTARTS     AGE
postgres-XXX-XXX    0/1     Error     1 (2s ago)   109s
```
エラーになってしまいました。

logs コマンドでエラーの内容を確認してみます。
```
* エラー内容を確認
# kubectl logs postgres-XXX-XXX
～省略～
initdb: error: directory "/var/lib/postgresql/data" exists but is not empty
initdb: detail: It contains a lost+found directory, perhaps due to it being a mount point.
initdb: hint: Using a mount point directly as the data directory is not recommended.
Create a subdirectory under the mount point.
```

エラーメッセージを読んでみると、
*「error: データディレクトリが空でないためエラーになった」*
*「hint: マウントポイントをそのままデータディレクトリにするのは非推奨」*
*「hint: マウントポイントの下にデータディレクトリを作成してね」*
となります。

どうやら、pvc がデフォルトで `lost+found` ディレクトリを保持しているため、エラーになったようです。


# 解決方法
先ほどの PostgreSQL の検証で、logs の hint に出ていた通り、
**マウントポイントの下にデータディレクトリを作成する**
というのが最適解のようです。

ではどうすれば良いのか？
答えは、`initContainers` を使うこと。

## initContainers を使う
`initContainers`（Initコンテナ） とは、
文字通りアプリケーションコンテナの初期化をサポートするコンテナです。
　※アプリケーションコンテナ： `spec.containers` で作成するコンテナ

主な用途としては

* pvc のように、コンテナ間で共通して使えるリソースの初期化
* アプリケーションコンテナのサービス起動を待機し、完了後に終了する

など。

今回は前者の「リソースの初期化」として使います。


## pvc に初期ディレクトリを作る
では、先ほどの PostgreSQL 15 のマニフェストを修正していきます。

1. `spec.containers` と同階層に `initContainers` を追加します
    ```
        spec:
          containers:
            - name: postgres
              image: postgres:15
              env:
                ～省略～
                - name: PGDATA
                  # マウント先にある data ディレクトリ を PostgreSQL のデータディレクトリとする
                  value: /var/lib/postgresql/data
              volumeMounts:
                - name: postgres-data
                  # マウント先には、データディレクトリの1個上の階層を指定する
                  mountPath: /var/lib/postgresql
          # ここで初期化する
          initContainers:
            - name: initialize
              image: alpine:latest
              # マウント先となるディレクトリの下に data ディレクトリを作る
              command: ['sh', '-c', 'mkdir -p /pgsql/data']
              volumeMounts:
                - name: postgres-data
                  mountPath: "/pgsql"
          volumes:
            - name: postgres-data
              persistentVolumeClaim:
                claimName: pvc-test
    ```
    * `initContainers` にて、pvc に data ディレクトリを作ります
    * pvc をアプリケーションコンテナにマウントし、マウントポイントの下に作成した data ディレクトリを PostgreSQL のデータディレクトリ として指定します
1. この定義でコンテナを作成します
    ```
    * 再作成
    # kubectl apply -f postgres.yml
    deployment.apps/postgres configured
    
    * 確認（起動成功！）
    # kubectl get pods
    NAME                READY   STATUS    RESTARTS   AGE
    postgres-XXX-XXX    1/1     Running   0          99s
    ```
    * コンテナの起動には成功しました。
1. データディレクトリも確認してみます
    ```
    * コンテナにログイン
    # kubectl exec -it postgres-XXX-XXX -c postgres -- /bin/bash
    
    * マウントポイントを確認
    # ls -l /var/lib/postgresql
    total 20
    drwx------ 19 postgres postgres  4096 Oct XX 05:27 data
    drwx------  2 postgres root      xxxx Oct XX 04:09 lost+found
    
    * data 配下を確認（一部抜粋）
    # ls -l /var/lib/postgresql/data/
    -rw------- 1 postgres postgres  xxxx Oct XX 05:27 pg_hba.conf
    -rw------- 1 postgres postgres  xxxx Oct XX 05:27 postgresql.conf
    ```
    * マウントポイントの下にデータディレクトリが配置され、
    そこに PostgreSQL のファイルが配置されました。
    初期化成功です！


## （参考）pvc のパーミッションを変える
パーミッションを変える場合も `initContainers` で解決できます。

マニフェストファイル（一部抜粋）。
```
    spec:
      containers:
        ～省略～
      initContainers:
        - name: initalize
          image: alpine:latest
          command: ['sh', '-c', 'chmod 777 /test']
          volumeMounts:
            - name: pvc-test
              mountPath: "/test"
```

このように、command 部分で権限を変更すればOKです。

## あとがき
本記事は、2023/12 のアドベントカレンダーに投稿した記事の再掲です。
過去履歴削除のため、同じ内容で新規作成しました。