はじめに
Kubernetes 1.25でAlpha機能ではありますが、PodでのUser Namespaceがサポートされました!
- User Namespaceの概要について書いた記事は以下の通りです。
[Kubernetes 1.25] Added alpha support for user namespaces in pods phase 1
今までは、Podは.spec.securityContext.runAsUserで非rootで動作させることが可能でしたが、その場合だと、起動にroot権限が必要なnginxなどのコンテナは起動できず、PodがCrashLoopBackOffとなっていました。そこで、Kubernetes 1.25で実装されたPodでのUser Namespaceを利用し、ノードのある範囲のUIDを、PodのUID 0-65535にマッピングすることで、見せかけのroot(UID 0)で起動することが可能となります。
またPodはrootで動作していないので、プロセスが奪取されてもUser Namespace外の操作はできず、リスクも軽減されます。例えば、コンテナ内にノードのディレクトリがマウントされているとして、マッピングされているノードUID範囲外のUIDで所有されているファイルがそのディレクトリに配置されていても、コンテナのプロセスは権限不足で読むことができません。
なお、現在Alphaでは以下のvolumeのみがサポートされており、今後Alpha→Beta→GAと昇格する際に他のvolumeもサポートされる予定です。
・configmap
・secret
・downwardAPI
・emptyDir
・projected
検証環境
今回の環境は以下の通りです。
- Kubernetes v1.25.0
- CRI: CRI-O v.1.25.0
前提条件
- FeatureGate:UserNamespacesStatelessPodsSupportが有効であること
- CRIがCRI-O v1.25以上であること
※containerdはv1.7からuser namespaceをサポート予定です。
FeatureGate:UserNamespacesStatelessPodsSupport有効化対象
Kubernetesのドキュメントを確認すると、以下の通りkube-scheduler、kube-proxy、kube-apiserver、kube-controller-managerがヒットします。
しかし、実際には全部にFeatureGateを有効にしても、User Namespaceは使えません。
そこで、User NamespaceのPR時のコードを確認すると、kube-scheduler、kube-proxy関連のコードに変更はないことがわかります。
また、kubeletのコードに変更が加えられており、User NamespaceのManager作成時やUIDのマッピングの際に、FeatureGate:UserNamespacesStatelessPodsSupportが有効か、確認しているようでした。
func MakeUserNsManager(kl userNsPodsManager) (*usernsManager, error) {
m := usernsManager{
// Create a bitArray for all the UID space (2^32).
// As a by product of that, no index param to bitArray can be out of bounds (index is uint32).
used: makeBitArray((math.MaxUint32 + 1) / userNsLength),
usedBy: make(map[types.UID]uint32),
kl: kl,
}
// First block is reserved for the host.
m.used.set(0)
// Second block will be used for phase II. Don't assign that range for now.
m.used.set(1)
// do not bother reading the list of pods if user namespaces are not enabled.
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
return &m, nil
}
:
// GetOrCreateUserNamespaceMappings returns the configuration for the sandbox user namespace
func (m *usernsManager) GetOrCreateUserNamespaceMappings(pod *v1.Pod) (*runtimeapi.UserNamespace, error) {
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
return nil, nil
}
// getHostIDsForPod if the pod uses user namespaces, takes the uid and gid
// inside the container and returns the host UID and GID those are mapped to on
// the host. If containerUID/containerGID is nil, then it returns the host
// UID/GID for ID 0 inside the container.
// If the pod is not using user namespaces, as there is no mapping needed, the
// same containerUID and containerGID params are returned.
func (m *usernsManager) getHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) {
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
return containerUID, containerGID, nil
}
:
ということで、最終的に以下のコンポーネントにFeatureGateを有効化することでUser Namespaceが利用できるようになりました。
- kube-apiserver
- kube-controller-manager
- WokerNodeのkubelet
試してみる
1.CRI-Oのバージョン確認
前提条件を満たすため、CRI-Oはv1.25.0を利用します。
$ sudo crio version
INFO[2022-08-31 01:09:11.421365971Z] Starting CRI-O, version: 1.25.0, git: unknown(clean)
Version: 1.25.0
GitCommit: unknown
GitCommitDate: unknown
GitTreeState: clean
BuildDate: 2022-08-29T19:48:32Z
GoVersion: go1.19
Compiler: gc
Platform: linux/amd64
Linkmode: dynamic
BuildTags:
apparmor
seccomp
containers_image_ostree_stub
exclude_graphdriver_btrfs
exclude_graphdriver_devicemapper
LDFlags: -s -w -X github.com/cri-o/cri-o/internal/pkg/criocli.DefaultsPath="" -X github.com/cri-o/cri-o/internal/version.buildDate=2022-08-29T19:48:32Z
SeccompEnabled: true
AppArmorEnabled: true
Dependencies:
2.Kubernetes Cluster確認
$ k get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
v125-master Ready control-plane 21h v1.25.0 10.146.0.8 <none> Ubuntu 20.04.4 LTS 5.15.0-1016-gcp cri-o://1.25.0
v125-worker Ready <none> 21h v1.25.0 10.146.0.9 <none> Ubuntu 20.04.4 LTS 5.15.0-1016-gcp cri-o://1.25.0
3.FeatureGate有効化
以下のファイルにfeature-gatesパラメータを追記し、有効化します。
- kube-apiserver
/etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.146.0.8:6443
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
:
- --feature-gates=UserNamespacesStatelessPodsSupport=true
- kube-controller-manager
/etc/kubernetes/manifests/kube-controller-manager.yaml
kind: Pod
metadata:
creationTimestamp: null
labels:
component: kube-controller-manager
tier: control-plane
name: kube-controller-manager
namespace: kube-system
spec:
containers:
- command:
- kube-controller-manager
:
- --feature-gates=UserNamespacesStatelessPodsSupport=true
- WokerNodeのkubelet
/var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--container-runtime=remote --container-runtime-endpoint=unix:///var/run/crio/crio.sock --pod-infra-container-image=registry.k8s.io/pause:3.8 --feature-gates=UserNamespacesStatelessPodsSupport=true"
→ systemctl restart kubelet
以上で全ての前提条件が満たせました。
いよいよUser Namespaceを利用してみます。
4.User Namespaceを利用するPod作成
.spec.hostUsersフィールドをfalseにすることでUser Namespaceが利用可能です。
$ cat << EOT | kubectl apply -f -
> apiVersion: v1
> kind: Pod
> metadata:
> name: userns
> spec:
> hostUsers: false
> containers:
> - name: shell
> command: ["sleep", "infinity"]
> image: debian
> EOT
pod/userns created
5.WorkerNode上でUID確認
PodにマッピングされているUIDをWorkerNode上のファイルから確認可能です。
/var/lib/kubelet/pods/\<Podの.metadata.uid\>/userns
ファイルを確認することで、マッピング状況が確認できます。
以下の場合、UIDの範囲が65536なので、WorkerNode上のUID 131072〜196607が、PodのUID 0〜65535にマッピングされていることが分かります。
$ sudo cat /var/lib/kubelet/pods/$(k get po userns -o jsonpath='{.metadata.uid}')/userns
{"uidMappings":[{"hostId":131072,"containerId":0,"length":65536}],"gidMappings":[{"hostId":131072,"containerId":0,"length":65536}]}
6.Pod内でのUID確認
/proc/self/uid_map
ファイルを確認することで、マッピング状況がPodからも確認できます。
WorkerNode上で確認したマッピング状況と同じになってます。
これで、WorkerNodeとPodのUIDが分離できていることが分かりました。
$ k exec userns -it -- bash
root@userns:/#
root@userns:/# id
uid=0(root) gid=0(root) groups=0(root)
root@userns:/#
root@userns:/# cat /proc/self/uid_map
0 131072 65536
7.root権限が必要なコンテナを持つPodを作成
以下の通り、User Namespaceを利用し、見せかけのrootとして起動しているので、RUNNINGとなります。
$ cat << EOT | kubectl apply -f -
> apiVersion: v1
> kind: Pod
> metadata:
> labels:
> run: nginx
> name: nginx
> spec:
> hostUsers: false
> containers:
> - image: nginx
> name: nginx
> EOT
pod/nginx unchanged
$
$ k get po nginx
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 2 27h
$
$ sudo cat /var/lib/kubelet/pods/$(k get po nginx -o jsonpath='{.metadata.uid}')/userns
{"uidMappings":[{"hostId":196608,"containerId":0,"length":65536}],"gidMappings":[{"hostId":196608,"containerId":0,"length":65536}]}
さいごに
いかがでしたでしょうか?
まだAlphaの機能ですが、我慢できずに試してみました。
本番運用レベルGAになるにはまだ先ですが、今後の開発状況を監視しつつ楽しみに待ちましょう。