spiffe-csiについて
2021/12に最初のバージョンである0.1.0がリリースされたばかりの新しいものであり、利用には注意が必要です。
spiffe-csiはSPIFFE Workload APIのためのUNIX Domain Socketを含むディレクトリをEphemeralな
VolumeとしてPodにマウントすることができるCSI Driverです。
例えばSPIFFEの実装であるSPIREをK8s上で動かしたい場合、Workload APIはDaemonSetで各Nodeに配置されたSPIRE AgentによってUNIX Domain Socketで提供されます。これまで、各Workloadが動作するPodではSPIRE Agentが提供するWorkload APIをhostPathでマウントして利用する必要がありました。
しかし、現在ではセキュリティの観点からhostPathマウントの利用はポリシーで制限されている環境が多く利用にはハードルがありました。spiffe-csiを利用することで、hostPathを利用する必要なくPodからWorkload APIを利用することが可能になります。
※注: 一方でCSI DriverをPodとして動かす場合にはKubeletとの対話のためにhostPathを利用しなければなりません。
イメージ
spiffe-csi DriverはSPIRE Agentのサイドカーとしてデプロイされます。
spiffe-csi DriverとSPIRE AgentはEphemeral VolumeなemptyDirによってWorkload APIを共有して おり、DriverはWorkload APIのディレトリをKubeletからリクエストされたTargetPathにバインドマウントすることで、他のPodから利用できるようにします。
詳細はmount --bindを参照してください。
動作確認
リポジトリで提供されているExampleに従ってkindでk8sクラスタを作成しSPIREとDriver,Workloadなどをデプロイします。
CSIDriverのマニフェスト
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: "csi.spiffe.io"
spec:
# Only ephemeral, inline volumes are supported. There is no need for a
# controller to provision and attach volumes.
attachRequired: false
# Request the pod information which the CSI driver uses to verify that an
# ephemeral mount was requested.
podInfoOnMount: true
# Don't change ownership on the contents of the mount since the Workload API
# Unix Domain Socket is typically open to all (i.e. 0777).
fsGroupPolicy: None
# Declare support for ephemeral volumes only.
volumeLifecycleModes:
- Ephemeral
SPIRE AgentとCSI Driverのマニフェスト
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: spire-agent
namespace: spire
labels:
app: spire-agent
spec:
selector:
matchLabels:
app: spire-agent
updateStrategy:
type: RollingUpdate
template:
metadata:
namespace: spire
labels:
app: spire-agent
spec:
hostPID: true
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
serviceAccountName: spire-agent
containers:
- name: spire-agent
image: ghcr.io/spiffe/spire-agent:1.1.1
imagePullPolicy: IfNotPresent
args: ["-config", "/run/spire/config/agent.conf"]
volumeMounts:
- name: spire-config
mountPath: /run/spire/config
readOnly: true
- name: spire-bundle
mountPath: /run/spire/bundle
readOnly: true
- name: spire-token
mountPath: /var/run/secrets/tokens
- name: spire-agent-socket-dir
mountPath: /run/spire/sockets
# This is the container which runs the SPIFFE CSI driver.
- name: spiffe-csi-driver
image: ghcr.io/spiffe/spiffe-csi-driver:nightly
imagePullPolicy: IfNotPresent
args: [
"-workload-api-socket-dir", "/spire-agent-socket",
"-csi-socket-path", "/spiffe-csi/csi.sock",
]
env:
# The CSI driver needs a unique node ID. The node name can be
# used for this purpose.
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
# The volume containing the SPIRE agent socket. The SPIFFE CSI
# driver will mount this directory into containers.
- mountPath: /spire-agent-socket
name: spire-agent-socket-dir
readOnly: true
# The volume that will contain the CSI driver socket shared
# with the kubelet and the driver registrar.
- mountPath: /spiffe-csi
name: spiffe-csi-socket-dir
# The volume containing mount points for containers.
- mountPath: /var/lib/kubelet/pods
mountPropagation: Bidirectional
name: mountpoint-dir
securityContext:
privileged: true
# This container runs the CSI Node Driver Registrar which takes care
# of all the little details required to register a CSI driver with
# the kubelet.
- name: node-driver-registrar
image: quay.io/k8scsi/csi-node-driver-registrar:v2.0.1
imagePullPolicy: IfNotPresent
args: [
"-csi-address", "/spiffe-csi/csi.sock",
"-kubelet-registration-path", "/var/lib/kubelet/plugins/csi.spiffe.io/csi.sock",
]
volumeMounts:
# The registrar needs access to the SPIFFE CSI driver socket
- mountPath: /spiffe-csi
name: spiffe-csi-socket-dir
# The registrar needs access to the Kubelet plugin registration
# directory
- name: kubelet-plugin-registration-dir
mountPath: /registration
volumes:
- name: spire-config
configMap:
name: spire-agent
- name: spire-bundle
configMap:
name: spire-bundle
- name: spire-token
projected:
sources:
- serviceAccountToken:
path: spire-agent
expirationSeconds: 7200
audience: spire-server
# This volume is used to share the workload api socket between the
# CSI driver and SPIRE agent
- name: spire-agent-socket-dir
emptyDir: {}
# This volume is where the socket for kubelet->driver communication lives
- name: spiffe-csi-socket-dir
hostPath:
path: /var/lib/kubelet/plugins/csi.spiffe.io
type: DirectoryOrCreate
# This volume is where the SPIFFE CSI driver mounts volumes
- name: mountpoint-dir
hostPath:
path: /var/lib/kubelet/pods
type: Directory
# This volume is where the node-driver-registrar registers the plugin
# with kubelet
- name: kubelet-plugin-registration-dir
hostPath:
path: /var/lib/kubelet/plugins_registry
type: Directory
Workloadのマニフェスト
piVersion: v1
kind: Pod
metadata:
name: example-workload
spec:
containers:
- name: example-workload
image: spiffe-csi-driver-example-workload:example
imagePullPolicy: Never
volumeMounts:
- name: spiffe-workload-api
mountPath: /spiffe-workload-api
readOnly: true
env:
- name: SPIFFE_ENDPOINT_SOCKET
value: unix:///spiffe-workload-api/spire-agent.sock
volumes:
- name: spiffe-workload-api
csi:
driver: "csi.spiffe.io"
それでは、READMEに従ってデプロイを実施していきます。
$ kind create cluster
ここでクラスタ名を指定した場合は以降の手順が一部失敗してしまうと思います。
./build-and-load-workload-image.sh などでkind loadコマンドを呼び出している箇所がありますので、--nameでクラスタ名を指定するように修正が必要です。
$ git clone git@github.com:spiffe/spiffe-csi.git
$ cd spiffe-csi/example
$ ./build-and-load-workload-image.sh
$ ./deploy-spire-and-csi-driver.sh
$ ./register-workload.sh
$ kubectl apply -f config/workload.yaml
デプロイが完了するとspire namespaceにServerとAgentがデプロイされているはずです。
$ kubectl get pods -n spire
NAME READY STATUS RESTARTS AGE
spire-agent-9476q 3/3 Running 0 1h29m
spire-server-6f6d88f75c-lsfsc 1/1 Running 0 1h30m
また、default namespaceではWorkloadのPodがデプロイされており、VolumeMountで指定したパスにてWorkload APIのUDSが参照できるようになっており、SVIDの取得・更新が行われていることが確認できます。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
example-workload 1/1 Running 0 1h33m
$ kubectl exec example-workload -- ls -li /spiffe-workload-api
total 0
684260 srwxrwxrwx 1 root root 0 Dec 18 08:58 spire-agent.sock
$ kubectl logs pod/example-workload
2021/12/18 09:01:06 Watching...
2021/12/18 09:01:07 Update:
2021/12/18 09:01:07 SVIDs:
2021/12/18 09:01:07 spiffe://example.org/workload
2021/12/18 09:01:07 Bundles:
2021/12/18 09:01:07 example.org (1 authorities)
2021/12/18 09:30:48 Update:
2021/12/18 09:30:48 SVIDs:
2021/12/18 09:30:48 spiffe://example.org/workload
2021/12/18 09:30:48 Bundles:
2021/12/18 09:30:48 example.org (1 authorities)
ここからは実際にどのような仕組みになっているのか見てみたいと思います。
Agent Podのuidを取得し、Node上の状況について確認します。
$ kubectl get pods -n spire spire-agent-9476q -o json | jq .metadata.uid
"4a686f16-5505-45fd-b3a9-ae175641ce17"
$ docker exec $ID ls -li /var/lib/kubelet/pods/4a686f16-5505-45fd-b3a9-ae175641ce17/volumes/kubernetes.io~empty-dir/spire-agent-socket-dir/
total 0
684260 srwxrwxrwx 1 root root 0 Dec 18 08:58 spire-agent.sock
/var/lib/kublet/pods配下に kubernetes.io~empty-dirが作成されており、spire-agent-socket-dir という名前(manifestで指定した名前)のディレトリ配下にUDSのファイルが作成されています。
Workload Podに対しても同様にNodeの状況を確認してみます。
$ kubectl get pods example-workload -o json | jq .metadata.uid
"db7db9ae-d651-41dc-820f-1c5e5e3d5e28"
$ docker exec 3457f65502d7 ls -li /var/lib/kubelet/pods/db7db9ae-d651-41dc-820f-1c5e5e3d5e28/volumes/kubernetes.io~csi/spiffe-workload-api/mount
total 0
684260 srwxrwxrwx 1 root root 0 Dec 18 08:58 spire-agent.sock
/var/lib/kubelet/pods/$uid/volumes配下にkubernetes.io~csiが作成されており、その配下にはマニフェストで指定した名前のディレトリspiffe-workload-apiが作成されており、同じinode番号のUDSが参照できました。
このディレトリは、Podがデプロイされた際にKubeletからDriverのNodePublishVolume APIが呼び出されますが、その際にtargetPathとして指定されています。DriverがそのパスにWorkload APIのディレトリパスをバインドマウントすることでディレトリが作成されます
WorkloadのPodにはこのディレトリがVolumeとして利用可能になっており、WorkloadはVolumeをマウントしたパスを参照してWorklaod APIを利用します。
課題
SPIRE AgentとCSI DriverはWorkload APIをEphemeral Volumeで共有しているため、Podが何らかの理由で再作成されてしまった場合には、CSIを使って利用しているWorkloadから参照できなくなってしまう問題があるように思いました。
現時点では、Workload側のヘルスチェック等によりWorkload APIが利用できない場合には再作成する等の対応が必要になりそうです。
自前でK8sを構築しているなど、環境によってはSPIRE agentとCSI driverをPodではなくNodeにバインドしてsystemdなどで動作させることもできそうです。
また、この問題についてはすでにissueが作成されていました。
https://github.com/spiffe/spiffe-csi/issues/16
まとめ
CSI DriverのPodが再作成されてしまった場合の課題はありますが、バインドマウントすることでhostPathの利用を回避するという手段について勉強になりました。今後はkubelet側に機能が追加されるなどで課題が解消できることを期待して引き続きアップデートを追いかけていきたいと思います。
