1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KubernetesのfsGroupを徹底調査 — 仕組みと注意点

Last updated at Posted at 2024-11-23

fsGroupとは?

KubernetesでPodやDeployment等のワークロード系のリソースを作成する場合に、マニフェストの securityContextfsGroup というパラメータを指定できます。

fsGroupは、Podが使用するボリュームにおいて、ファイルやディレクトリのグループID (GID)を指定する パラメータです。

簡単な例

マニフェスト内だと、例えば以下のように記載します。

apiVersion: v1
kind: Pod
metadata:
  name: fsgroup-pod
spec:
  securityContext:
    fsGroup: 2000
  containers:
  - name: fsgroup-pod
    image: busybox
    command: ["tail", "-f", "/dev/null"]
    volumeMounts:
    - name: shared
      mountPath: /shared
  volumes:
  - name: shared
    emptyDir: {}

このマニフェストで、/shared というディレクトリがボリュームのマウントポイントになっていますが、kubectl applyしてPodを作成すると、以下のように /shared のGIDが fsGroup で指定した2000になっているのがわかります。

$ kubectl exec -n default pods/fsgroup-pod -- ls -ld /shared
drwxrwsrwx    2 root     2000          4096 Nov 17 22:06 /shared

活用できるシーン

fsGroup のパラメータは、例えば次のようなケースで役立ちます。

  • Pod内の複数のコンテナが同じボリュームを共有し、それらのコンテナが異なるユーザーとして動作している場合
  • コンテナ内のアプリケーションが、特定のGIDでのアクセス権を前提として動作する場合
  • セキュリティポリシーにより、特定のID以外でアクセスを許可しない要件がある場合

なぜfsGroupを使うのか?

ファイルやディレクトリのGIDの変更は、initContainer等を使ってコンテナ起動時にchownコマンドで行うこともできますが、管理が複雑になり設定ミスを起こしてしまう危険性が増えます。例えば、マウントするボリュームが追加されたりマウントポイントが変更になったりした場合に、chownコマンドで指定する対象もその都度変更する必要が出てきます。
一方でfsGroupを使うと、マウントするほぼ全てのボリュームに指定したGIDを自動的に付与するので、個別に権限を管理する必要がありません。

余談:UIDを指定するパラメータはないのか?

fsGroup でボリュームのGIDを指定できるパラメータがあるのならば、同様にユーザID(UID)を指定できるパラメータがあっても良さそうです。ところが現状は存在しません。
理由については明確に特定できていませんが、fsGroup を使う目的の一つが「複数コンテナ間で、ユーザーが異なる場合でもボリュームを共有できるようにすること」なので、 fsGroup でGIDのみが指定できれば十分だから、という理由が考えられます。

利用上の注意点

fsGroup を使う際には、以下のような点に注意する必要があります。

  • (1) fsGroup の設定が有効にならない場合がある
  • (2) fsGroup の設定により、Podの起動が遅くなる場合がある

各注意点について解説します。

(1) fsGroup の設定が有効にならない場合がある

すべてのボリュームで fsGroup が有効になるわけではなく、利用するボリュームの種類によって挙動が異なります。Podのマニフェストでは、spec.volumes 配下でボリュームタイプを指定しますが、その内容に応じて以下のような動作に分かれます。

  • 有効
    • emptyDir
    • configMap
    • secret
  • 状況による(後述)
    • persistentVolumeClaim
  • 無効
    • hostPath

ボリュームに persistentVolumeClaim を指定した場合の挙動

persistentVolumeClaimでボリュームを指定する場合は、PVCがどのように作られたかで fsGroup が反映されるかどうかが変わります。一言での説明が難しいですが、概ね以下のように理解しておくといいです。

(ケース1) CSIドライバーを経由してPVCを作る場合、多くのケースで fsGroup は有効

(←クリック)例えばこちらのようにPVCが作られるケースです。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc-from-storageclass
  namespace: default
spec:
  storageClassName: nfs-csi
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

CSIドライバーを介して「動的プロビジョニング」によりPVが作られて、fsGroup の設定が有効になる例です。

(ケース2) 自分で作ったPVを参照してPVCを作る場合、多くのケースで fsGroup は無効

(←クリック)例えばこちらのようにPVCが作られるケースです。
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /srv/nfs_share
    server: 10.0.0.5
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc-from-pv
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  volumeName: nfs-pv

マウントするボリュームのパスをPVで明示的に指定して、fsGroup の設定が無効になる例です。

実際は、上の2つケースで fsGroup の有効/無効がきっぱりと分かれるわけではありません。PVを自分で作る場合でもCSIドライバーを使うと fsGroup を有効に出来たり、逆に動的プロビジョニングを行う場合でもCSIドライバーの設定等によって fsGroup を無効に出来たりします。

検証準備

では、上に紹介したケースで実際にボリュームをマウントするPodを作って確かめてみましょう。
以下のマニフェストを用意します。

pod-with-fsgroup.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-fsgroup
  namespace: default
spec:
  securityContext:
    fsGroup: 2000
  containers:
  - name: mnt-nfs-pod
    image: busybox
    command: ["tail", "-f", "/dev/null"]
    volumeMounts:
    # fsGroup有効
    - name: emptydir
      mountPath: /mnt/emptydir
    - name: config-volume
      mountPath: /etc/configmap
    - name: secret-volume
      mountPath: /etc/secret
    # 状況による
    - name: nfs-volume-from-storageclass
      mountPath: /mnt/nfs-volume-from-storageclass
    - name: nfs-volume-from-pv
      mountPath: /mnt/nfs-volume-from-pv
    # fsGroup無効
    - name: hostpath
      mountPath: /mnt/hostpath
  volumes:
  # fsGroup有効
  - name: emptydir
    emptyDir: {}
  - name: config-volume
    configMap:
      name: my-configmap
  - name: secret-volume
    secret:
      secretName: my-secret
  # 状況による
  - name: nfs-volume-from-storageclass
    persistentVolumeClaim:
      claimName: nfs-pvc-from-storageclass
  - name: nfs-volume-from-pv
    persistentVolumeClaim:
      claimName: nfs-pvc-from-pv
  # fsGroup無効
  - name: hostpath 
    hostPath:
      path: /hostpath
      type: DirectoryOrCreate

マニフェストで参照しているConfigMap、Secret、PVCは既に以下のように作られている状態です。

$ kubectl get cm -n default | grep -e ^NAME -e ^my-configmap
NAME               DATA   AGE
my-configmap       1      4d13h

$ kubectl get secret -n default
NAME        TYPE     DATA   AGE
my-secret   Opaque   2      4d13h

$ kubectl get pvc -n default
NAME                        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
nfs-pvc-from-pv             Bound    nfs-pv                                     1Gi        RWX                           <unset>                 6s
nfs-pvc-from-storageclass   Bound    pvc-353f4ce4-fbd3-48a0-9ac0-a7b2f1a74e76   1Gi        RWX            nfs-csi        <unset>                 2m42s

検証

Podを作成してみます。

kubectl apply -f pod-with-fsgroup.yaml

マウントポイントのGIDを確認すると、hostPath、およびPV経由で作られたpersistentVolumeClaimのディレクトリに関しては、fsGroup の設定が反映されていない(つまり2000になっていない)のがわかります。

$ kubectl exec -it pod-with-fsgroup -- ls -l /mnt/
total 16
drwxrwsrwx    2 root     2000          4096 Nov 21 20:54 emptydir
drwxr-xr-x    2 root     root          4096 Nov 21 20:54 hostpath
drwxr-xr-x    2 root     root          4096 Nov 21 20:21 nfs-volume-from-pv
drwxrwsr-x    2 root     2000          4096 Nov 21 20:38 nfs-volume-from-storageclass

また、ConfigMapとSecretからマウントされたディレクトリとファイルは、GIDが fsGroup で指定した2000になっています。

$ kubectl exec -it pod-with-fsgroup -- ls -ld /etc/configmap /etc/secret
drwxrwsrwx    3 root     2000          4096 Nov 21 20:58 /etc/configmap
drwxrwsrwt    3 root     2000           120 Nov 21 20:58 /etc/secret
$ kubectl exec -it pod-with-fsgroup -- ls -l /etc/configmap/
total 0
lrwxrwxrwx    1 root     2000            17 Nov 21 20:58 app.config -> ..data/app.config

$ kubectl exec -it pod-with-fsgroup -- ls -l /etc/secret/
total 0
lrwxrwxrwx    1 root     2000            15 Nov 21 20:58 password -> ..data/password
lrwxrwxrwx    1 root     2000            15 Nov 21 20:58 username -> ..data/username

(2) fsGroup の設定により、Podの起動が遅くなる場合がある

fsGroup を指定すると、対象のボリューム内のファイルやディレクトリの数が多い場合、それら全てのGIDを変更するのに時間がかかります。その結果、Podの起動が遅くなり、正常に起動するまでの時間が増えてしまいます。

この問題を回避するために、特定のボリュームに対して fsGroup を無効にしたり、条件つきで有効にしたりする方法があります。

回避策1 : fsGroupChangePolicy の値を変更する

Podのマニフェストで、securityContext 配下に fsGroupChangePolicy というパラメータを追加します。この値を "OnRootMismatch" にすると、「マウントポイントのGIDが fsGroup のGIDと一致しない場合のみ、ボリューム内のファイルとディレクトリのGIDを変更する」挙動になります。

実際に挙動を確認してみましょう。

先ほど「(1) fsGroup の設定が有効にならない場合がある」でデプロイしたPodで、nfs-pvc-from-storageclass のPVC経由でマウントしたディレクトリに、次のようにGIDが fsGroup のGIDとは異なるファイルを作ります。

$ kubectl exec -it pod-with-fsgroup -- sh
(以下はPod内でのコマンド)
/ # touch /mnt/nfs-volume-from-storageclass/tmp.txt
/ # chown root:1000 /mnt/nfs-volume-from-storageclass/tmp.txt
/ # ls -l /mnt/nfs-volume-from-storageclass/
total 0
-rw-r--r--    1 root     1000             0 Nov 23 00:43 tmp.txt

ちなみに、マウントポイントのルートディレクトリのGIDは fsGroup で指定した 2000 になっています。

(Pod内でのコマンド)
/ # ls -ld /mnt/nfs-volume-from-storageclass/
drwxrwsr-x    2 root     2000          4096 Nov 23 00:43 /mnt/nfs-volume-from-storageclass/

この後、一旦Podを削除します。

kubectl delete pod pod-with-fsgroup

続いて、fsGroupChangePolicy: "OnRootMismatch" を追加した以下のマニフェストを使ってPodをデプロイします。

pod-with-fsgroup-change-policy
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-fsgroup-change-policy
  namespace: default
spec:
  securityContext:
    fsGroup: 2000
    fsGroupChangePolicy: "OnRootMismatch" # 追加
  containers:
  - name: mnt-nfs-pod
    image: busybox
    command: ["tail", "-f", "/dev/null"]
    volumeMounts:
    - name: nfs-volume-from-storageclass
      mountPath: /mnt/nfs-volume-from-storageclass
  volumes:
  - name: nfs-volume-from-storageclass
    persistentVolumeClaim:
      claimName: nfs-pvc-from-storageclass

デプロイするコマンドは以下です。

kubectl apply -f pod-with-fsgroup-change-policy.yaml

fsGroupChangePolicy: "OnRootMismatch" の設定が効いて、先ほど作成した tmp.txt のGIDが fsGroup で指定した 2000 にはならず、1000のままであることを確認できます。

$ kubectl exec -it pod-with-fsgroup-change-policy -- sh
(Pod内でのコマンド)
/ # ls -l /mnt/nfs-volume-from-storageclass/
total 0
-rw-r--r--    1 root     1000             0 Nov 23 00:43 tmp.txt

マウントポイントの GID が fsGroup の値を一致しない場合の挙動を確認

上で試したのは、「マウントポイントと fsGroup のGIDが一致する場合」の動作でした。
そこで次はマウントポイントのGIDを変更して、fsGroup のGIDと異なる場合にどうなるかを試してみます。

(Pod内でのコマンド)
/ # chown root:root /mnt/nfs-volume-from-storageclass/
/ # ls -l /mnt/
total 4
drwxrwsr-x    2 root     root          4096 Nov 23 00:43 nfs-volume-from-storageclass

一旦Podを削除して再デプロイします。

kubectl delete pod pod-with-fsgroup-change-policy && \
kubectl apply -f pod-with-fsgroup-change-policy.yaml

すると、マウントポイントだけではなく、ファイルのGIDも fsGroup で指定した値の2000になっているのがわかります。

$ kubectl exec -it pod-with-fsgroup-change-policy -- sh
(Pod内でのコマンド)
/ # ls -ld /mnt/nfs-volume-from-storageclass/
drwxrwsr-x    2 root     2000          4096 Nov 23 00:43 /mnt/nfs-volume-from-storageclass/
/ # ls -l /mnt/nfs-volume-from-storageclass/
total 0
-rw-rw-r--    1 root     2000             0 Nov 23 00:43 tmp.txt

回避策2 : CSIDriver の fsGroupPolicy を変更する

ボリュームがCSIドライバーによって作られている場合、CSIDriver リソースのパラメータ fsGroupPolicy の値を変更することによって、 fsGroup の有効/無効を制御できます。

CSIDriver の設定を変更する場合、これを使う StorageClass や PVC 全てに影響が及んでしまうので注意が必要です。すべてに影響を与えても問題がないことを確認した場合にのみ、設定変更を適用してください。

CSIDriver リソースの設定確認

Podのマウントに使っているPVCに対応する CSIDriver リソースを特定します。

以下より、StorageClass は nfs-csi であることがわかります。

$ kubectl describe pvc nfs-pvc-from-storageclass | grep ^StorageClass
StorageClass:  nfs-csi

この StorageClass は、以下より nfs.csi.k8s.io の Provisioner を使っていることがわかります。

$ kubectl describe storageclasses.storage.k8s.io nfs-csi  | grep ^Provisioner
Provisioner:           nfs.csi.k8s.io

Provisioner名として出力された nfs.csi.k8s.io が、以下のようにそのまま CSIDriver リソースの名前になります。

$ kubectl get csidrivers
NAME             ATTACHREQUIRED   PODINFOONMOUNT   STORAGECAPACITY   TOKENREQUESTS   REQUIRESREPUBLISH   MODES        AGE
nfs.csi.k8s.io   false            false            false             <unset>         false               Persistent   6d14h

nfs.csi.k8s.io のパラメータを確認すると、 fsGroupPolicy の値が File になっているのがわかります。

$ kubectl get csidrivers nfs.csi.k8s.io -oyaml | grep -A 3 ^spec
spec:
  attachRequired: false
  fsGroupPolicy: File    # この部分
  podInfoOnMount: false

ドキュメントを読むと、fsGroupPolicyFile の場合は「Kubernetesが fsGroup を使ってボリュームの権限と所有権を変更可能」と記載があります。これまで fsGroup を指定した際にマウントポイントのGIDが変更されていたのは、この設定が有効だったためです。

この値を Noneにすると、fsGroup の設定を完全に無効化できます。
(ちなみに ReadWriteOnceWithFSType にすると、 「ReadWriteOnce 以外の場合は fsGroup が無効」になります)

実際に試してみましょう。

事前準備

※ 回避策1で作ったPodが残っている前提で書きます。
該当ボリュームのファイルを削除して、GIDをrootにしておきます。

$ kubectl exec -it pod-with-fsgroup-change-policy -- sh
(Pod内でのコマンド)
/ # rm /mnt/nfs-volume-from-storageclass/*
/ # chown root:root /mnt/nfs-volume-from-storageclass/
/ # exit

一旦Podを削除します。

kubectl delete pod pod-with-fsgroup-change-policy

fsGroupPolicy を None に変更して、fsGroup が反映されないことを確認

次のコマンドで、CSIDriver リソースの fsGroupPolicy を None に変更します。

kubectl patch csidriver nfs.csi.k8s.io \
--type='merge' -p '{"spec":{"fsGroupPolicy":"None"}}'

値が変わっていることを確認します。

$ kubectl get csidrivers nfs.csi.k8s.io -oyaml | grep fsGroup
  fsGroupPolicy: None

この状態で、再び「回避策1」で使ったPodをデプロイします。

kubectl apply -f pod-with-fsgroup-change-policy.yaml

すると、マウントポイントのGIDはrootのままで、 fsGroup が無効になっているのを確認できます。

$ kubectl exec -it pod-with-fsgroup-change-policy -- ls -ld /mnt/nfs-volume-from-storageclass/
drwxrwsr-x    2 root     root          4096 Nov 23 21:22 /mnt/nfs-volume-from-storageclass/

おわりに

本記事を書いた背景を最後に書いておきます。

業務で動かしているシステムでDeploymentリソースから作られたPodがあり、起動に20分くらい近く掛かる事象が頻繁に起きていました。そのDeploymentリソースにReadWriteManyで複数のPodが同時にマウントしているボリュームがあり、ボリュームには大量のファイルが格納されていました。

この条件下で、Deploymentリソースには今回説明した fsGroup が設定されていました。そのため、Podが一つ起動する度に全ファイルのGIDを変更する処理が走り、Pod の起動がやたらと遅くなっていたのです。

Podの起動が遅くなっている原因が fsGroup であることを特定するのに、結構なエネルギーを要しました。問題解決のヒントになりそうなログやメトリクスがほぼ何も出てこなかったので、Deploymentリソースのマニフェストのパラメータを一つ一つ変更したり消したりして地道な調査をしました。作業当時のドキュメントを確認しところ、パラメータ変更の色々試し22回目くらいで、ようやく fsGroup が原因だと判明していました。

このような背景から、fsGroup についてしっかり調べてみようと思い本記事を書きました。

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?