fsGroupとは?
KubernetesでPodやDeployment等のワークロード系のリソースを作成する場合に、マニフェストの securityContext
で fsGroup
というパラメータを指定できます。
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を作って確かめてみましょう。
以下のマニフェストを用意します。
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をデプロイします。
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
ドキュメントを読むと、fsGroupPolicy
が File
の場合は「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
についてしっかり調べてみようと思い本記事を書きました。