0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Summary

ChatGPT Image 2026年6月21日 23_51_17.png

  • hostPath利用時のPod移動不可問題を解決するため、rsynclsyncdを組み合わせたディレクトリ同期を導入した
  • これにより、片ノード障害時でもPodが別ノードへ移動し、継続稼働が可能になった
  • 本構成は単一Pod稼働を前提とし、複数Pod同時稼働時のデータ整合性問題に注意が必要である

本記事はUbuntu 24.04 および 26.04環境で実施した内容である。

やらないこと

  • Ceph等の分散ストレージの導入
  • 完全なリアルタイムでのデータ整合性の確保
  • CSIドライバーによるストレージ抽象化

本記事における課題

Kubernetes環境において、永続ストレージの選択は重要な課題である。

  • 分散ストレージ(例: Ceph, GlusterFS)は高可用性を提供するが、構築と運用に多くのリソース(CPU, メモリ, ストレージI/O)を必要とする。今回の環境では導入が困難であると判断した。
  • NFSは比較的容易に導入可能だが、ネットワーク越しでのI/O性能が低く、一部のアプリケーションで動作が著しく遅延した。特にデータベースや頻繁なファイル操作を伴うアプリケーションで顕著であった。
  • 各KubernetesノードにはRAID1構成のHDDがあり、十分なI/O性能を持つ。このローカルストレージを最大限活用したい。
  • hostPathボリュームはノードローカルストレージへの高速アクセスを提供する。しかし、Podが別のノードに再スケジュールされた際、データが存在しないため継続稼働が不可能となる課題があった。

よって、高速なノードローカルストレージの性能を活かしつつ、ノード障害時にPodが移動可能となる高可用性を実現する必要があった。

やったこと

本課題を解決するため、Kubernetesノードローカルストレージ(RAID1 HDD)をhostPathで使用し、そのデータをlsyncdrsyncを用いて別ノードへ同期する構成を導入した。これにより、ローカルストレージの性能とノード障害時のデータ可用性を両立した。

基本アーキテクチャ

  1. ノードローカルストレージの利用: 各Kubernetesノードは/{base-path}/配下にRAID1構成のHDDをマウントする。PodはhostPathでこの領域を利用する。
  2. lsyncdとrsyncによる同期: 各ノード上の特定ディレクトリ(例: /{base-path}/app-data/)の変更をlsyncdが監視し、変更を検知次第rsyncを用いてもう一方のノードへ自動で同期する。
  3. 高可用性の実現: 片方のノードに障害が発生した場合でも、Podは別の健全なノードに再スケジュールされ、同期済みのデータにアクセスして処理を継続できる。
  4. Podの制約: この構成では、データ整合性を確保するため、対象となるPodは常に1つのノードでのみ稼働するように制御する。具体的には、StatefulSetのreplicas: 1を設定する。

lsyncdの選定理由

lsyncdは、指定されたディレクトリの変更イベントを監視し、イベント発生時にrsyncなどの同期ツールを呼び出す。これにより、リアルタイムに近い形で効率的な差分同期を実現できるため、このユースケースに最適であった。

SSH鍵認証の設定

lsyncdrsync経由でリモートノードへSSH接続するため、各ノードでrootユーザーのSSH鍵認証を設定する必要がある。セキュリティを考慮し、パスワード認証は無効化する。

  1. SSH鍵ペアの生成: 各ノードのrootユーザーで鍵ペアを生成する。既に存在するならばスキップ可能である。

    sudo ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""
    
  2. 公開鍵の交換: 各ノードのroot公開鍵(/root/.ssh/id_ed25519.pub)を、相手ノードのrootユーザーの~/.ssh/authorized_keysに追加する。

    # 同期元ノードから同期先ノードへ公開鍵をコピー
    ssh-copy-id -i /root/.ssh/id_ed25519.pub root@<リモートノードのIPアドレス>
    

    これを相互に行い、各ノードからrootユーザーでパスワードなしにSSH接続できることを確認する。

  3. SSHD設定の変更: PermitRootLogin prohibit-passwordを設定し、rootユーザーのパスワード認証を禁止する。これにより鍵認証のみが許可される。

    # /etc/ssh/sshd_config を編集
    sudo sed -i 's/^#PermitRootLogin prohibit-password/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
    sudo sed -i 's/^PermitRootLogin yes/#PermitRootLogin yes/' /etc/ssh/sshd_config
    sudo sed -i 's/^PermitRootLogin without-password/#PermitRootLogin without-password/' /etc/ssh/sshd_config
    
    sudo systemctl restart sshd
    

    設定変更後、systemctl restart sshdでSSHDサービスを再起動する。

lsyncdのインストール

今回使用したOSはUbuntu 24.04/26.04である。以下のコマンドでlsyncdをインストールする。

sudo apt update
sudo apt install -y lsyncd

インストール後、systemctl status lsyncdでサービスが起動していることを確認する。

lsyncd設定例

/etc/lsyncd/lsyncd.conf.luaは以下の通りに設定した。設定では、複数アプリケーションのデータディレクトリをそれぞれ同期対象としている。
注釈: <ベースパス><アプリケーション名><リモートノードのIPアドレス>は環境に合わせて適切に置き換える必要がある。

settings{
    logfile = "/var/log/lsyncd.log",
    statusFile = "/tmp/lsyncd.stat",
    statusInterval = 1,
    insist         = 1,
}

sync{
  default.rsync,
  source = "/<ベースパス>/app-data-1/",
  target = "root@<リモートノードのIPアドレス>:/<ベースパス>/app-data-1/",
  delete = "running",
  init = false,
  exclude = {"**.log", "**.log.*"},
  rsync = {
    archive = true,
    _extra = {"--exclude=*.log", "--exclude=*.log.*"},
    rsh = "/usr/bin/ssh -i /root/.ssh/id_ed25519 -o StrictHostKeyChecking=no"
  }
}

sync{
  default.rsync,
  source = "/<ベースパス>/app-data-2/",
  target = "root@<リモートノードのIPアドレス>:/<ベースパス>/app-data-2/",
  delete = "running",
  init = false,
  rsync = {
    archive = true,
    rsh = "/usr/bin/ssh -i /root/.ssh/id_ed25519 -o StrictHostKeyChecking=no"
  }
}
  • settings: ロギングとステータスファイルのパス、ステータス更新間隔、エラー時の再試行(insist = 1)を設定する。
  • syncブロック: 各同期対象ディレクトリごとに定義する。
    • source: 同期元のローカルパス。
    • target: 同期先のリモートパス。root@<リモートノードのIPアドレス>形式でSSH接続先を指定する。
    • delete = "running": 同期元でファイルが削除された場合、同期先でも削除する設定。runningはlsyncdが起動中に削除されたファイルを追跡し、同期先でも削除することを意味する。
    • init = false: 重要な設定である。lsyncdサービス起動時に初回同期を実行しない。これにより、サービス起動時の大規模なI/O負荷や、既に同期されているデータに対する不要な処理を避ける。事前にrsync -av <source> root@<target-ip>:<target>コマンドを手動で実行し、初期同期を完了させておくことが推奨される。
    • exclude: 同期対象から除外するファイルを指定する。特にアプリケーションが頻繁に書き込むログファイルなどを除外することで、同期のオーバーヘッドを減らし、同期の無限ループを防ぐ。
  • rsync.rsh: rsyncがSSH接続に使用するコマンドを指定する。秘密鍵のパスと、StrictHostKeyChecking=noを設定することで、初回接続時のKnown Hosts警告を回避し、自動化を容易にする。ただし、StrictHostKeyChecking=noはセキュリティリスクを伴うため、本番環境ではより厳格なSSHホストキー管理を検討すべきである。
  • rsync._extra: 同期対象から除外するファイルを指定する。特にアプリケーションが頻繁に書き込むログファイルなどを除外することで、同期のオーバーヘッドを減らし、同期の無限ループを防ぐ。

一部のアプリケーションでは、_extraオプションでログファイルを除外しないと、既存のログローテーション機構と競合し、ファイルが大量に生成される事象が発生した。excludeと合わせて_extraも用いることで、rsyncコマンドラインレベルでの確実な除外が期待できる。

Kubernetes StatefulSet設定例

Podが単一ノードで稼働し、hostPathを利用するStatefulSetの例を示す。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: my-app-statefulset
  labels:
    app: my-app
spec:
  replicas: 1 # Podは常に1つのみ稼働させる
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                - key: kubernetes.io/hostname
                  operator: In
                  values:
                    - node-01
                    - node-02
      volumes:
        - name: app-data-volume
          hostPath:
            path: /<ベースパス>/app-data-1/ # lsyncdで同期しているローカルパス
            type: DirectoryOrCreate
      containers:
        - name: my-app-container
          image: my-app-image:latest
          ports:
            - containerPort: 80
          volumeMounts:
            - name: app-data-volume
              mountPath: /data/app-data # コンテナ内のマウントパス
  • replicas: 1: Podが1つしか起動しないことを保証する。これにより、複数のPodが同時に同じデータに書き込むことを防ぎ、データ整合性問題を回避する。
  • hostPath: /data/app-data-1/をPod内の/data/app-dataにマウントする。これにより、コンテナはノードのローカルストレージに高速にアクセスできる。
  • affinity: Podがデプロイされるノードを、lsyncdをセットアップしたノードに限定する。

この構成は、あくまで片方のノードが書き込みを行い、もう片方がその変更を追随する形式である。両方のノードで同時に同じデータに対して書き込みが発生した場合、データの整合性が破綻する可能性が非常に高い。 このため、今回のユースケースではPodが常に単一のノードでしか稼働しないように厳しく制御している。

まとめ・所感

本記事で紹介したrsynclsyncdを組み合わせたノードローカルストレージ同期構成は、リソースが限られた環境でKubernetesの永続ストレージ問題に対処する有効な手段である。
分散ストレージの導入コストやNFSの性能課題を回避し、既存のノードローカルストレージの性能を最大限に活用しつつ、ノード障害時の高可用性も確保できた。

ただし、この方法はデータ整合性の保証が限定的であり、単一PodによるReadWriteOnce相当の利用に限定される。複数Podによる同時書き込みが必要な場合には、CephFSやRookのような分散ファイルシステムや、CSIドライバーを活用したより高度なストレージソリューションを検討すべきである。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?