Red Hatでサポートをしている石川と申します。OpenShift Advent Calendar 2024のエントリです。
近頃すっかり寒くなり、街中がクリスマスムードですね。毎年この時期になると娘のクリスマスプレゼントが悩みの種です。
昨今のプレゼント事情を探るため、娘にお友達はサンタさんに何をお願いしたのか尋ねてみました。すると、「サンタさん」との答え。
なるほど、ランプの精に願い事を増やしてもらう的な...子供の発想は柔軟ですね。
というわけで(?)、今回はそんな柔軟性を提供するネストされたコンテナ (Nested Container)を試してみます。
Linux User Namespaceを使用してNested Containerを起動する
ローカルマシンでコンテナアプリケーションの開発を行う場合、podmanやdockerでいつでもイメージをビルドしてコンテナを実行することができます。しかし、コンテナ上でコンテナを実行する場合、権限の昇格を行うとホストにも同様の権限でアクセスできてしまう問題がありました。これはOpenShift Dev Spacesを使用してコンテナ上で開発を行う際、セキリティの観点から望ましくありません。
また、追加でコンテナを起動する必要があるLocalStackやTestContainersなどのツールを利用するチームではこれはより大きな問題となります。
Linux User Namespaceの使用はそのような問題の解決策となります。
OpenShift 4.17で、Technology preview (TP)機能としてLinux User NamespaceでPodが実行可能になりました。
Linux User Namespaceを使用することで、コンテナはホストとは別のユーザーとグループのマッピングを持つことができるため、コンテナ内ではroot権限でプロセスを実行できますが、ホスト上ではnon-rootユーザーとしてプロセスを実行できます。
Nested Containerのサンプルを試してみる
OpenShift Dev SpacesでNested Containerを使用するサンプルが用意されているので、さっそく試していきます。
※Linux User NamespaceはTP機能ですので、お試しする場合は検証用のクラスターをお使いください。
今回の記事では以下の環境を使用しております。
Red Hat OpenShift Container Platform: 4.17.0 (vSphere IPI)
Red Hat OpenShift Dev Spaces: 3.17.0
DevWorkspace Operator: 0.31.2
ContainerRuntimeConfig
でcrunをデフォルトのコンテナランタイムに設定します。今回はMulti Node Clusterのため、workerノードでのみ有効化します。
$ cat << EOF | oc apply -f -
apiVersion: machineconfiguration.openshift.io/v1
kind: ContainerRuntimeConfig
metadata:
name: enable-crun-worker
spec:
machineConfigPoolSelector:
matchLabels:
pools.operator.machineconfiguration.openshift.io/worker: ""
containerRuntimeConfig:
defaultRuntime: crun
EOF
FeatureGate
でProcMountTypeにUserNamespacesSupportを設定します。これにより、クラスターはアップデートを適用できなくなりますのでご注意ください。
$ oc patch FeatureGate cluster --type merge --patch '{"spec":{"featureSet":"CustomNoUpgrade","customNoUpgrade":{"enabled":["ProcMountType","UserNamespacesSupport"]}}}'
OpenShift Dev SpacesでNested Containerを使用するためのSecurityContextConstraint (SCC)を作成します。
$ cat << EOF | oc apply -f -
apiVersion: security.openshift.io/v1
kind: SecurityContextConstraints
metadata:
name: nested-podman-scc
priority: null
allowPrivilegeEscalation: true
allowedCapabilities:
- SETUID
- SETGID
fsGroup:
type: MustRunAs
ranges:
- min: 1000
max: 65534
runAsUser:
type: MustRunAs
uid: 1000
seLinuxContext:
type: MustRunAs
seLinuxOptions:
type: container_engine_t
supplementalGroups:
type: MustRunAs
ranges:
- min: 1000
max: 65534
EOF
OpenShift Dev Spaces Operatorをインストールします。既にインストール済みの場合はスキップしてください。
cat << EOF | oc apply -f -
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: devspaces
namespace: openshift-operators
spec:
channel: stable
installPlanApproval: Automatic
name: devspaces
source: redhat-operators
sourceNamespace: openshift-marketplace
EOF
CheCluster
を作成し、nested-podman-scc SCCを使用するように設定します。
cat << EOF | oc apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: devspaces
---
apiVersion: org.eclipse.che/v2
kind: CheCluster
metadata:
name: devspaces
namespace: devspaces
spec:
components:
cheServer:
debug: false
logLevel: INFO
metrics:
enable: true
pluginRegistry:
openVSXURL: https://open-vsx.org
containerRegistry: {}
devEnvironments:
startTimeoutSeconds: 600
secondsOfRunBeforeIdling: -1
maxNumberOfWorkspacesPerUser: -1
maxNumberOfRunningWorkspacesPerUser: 5
containerBuildConfiguration:
openShiftSecurityContextConstraint: nested-podman-scc
disableContainerBuildCapabilities: false
defaultComponents:
- name: dev-tools
container:
image: quay.io/cgruver0/che/dev-tools:latest
memoryLimit: 6Gi
mountSources: true
defaultEditor: che-incubator/che-code/latest
defaultNamespace:
autoProvision: true
template: <username>-devspaces
secondsOfInactivityBeforeIdling: 1800
storage:
pvcStrategy: per-workspace
gitServices: {}
networking: {}
EOF
OpenShift Dev Spacesのダッシュボードにログインし、https://github.com/cgruver/ocp-4-17-nested-container-tech-preview.git
リポジトリから新しいワークスペースを作成します。
podmanを使ってnginxをNested Containerとして実行します。ワークスペース上でターミナルを開き、以下を実行します。
podman run -d --rm --name webserver -p 8080:80 quay.io/libpod/banner
curl http://localhost:8080
ワークスペース上でコンテナを実行することができました!
もう少し詳しく調べてみる
ワークスペースのPodがどのように実行されているか調べてみます。今回はkubeadminユーザーを使ったので、Podは che-kube-admin-devspaces-sai8k3
というNamespaceに作成されていました。
$ oc project che-kube-admin-devspaces-sai8k3
Now using project "che-kube-admin-devspaces-sai8k3" on server "https://api.shishika.example.com:6443".
$ oc get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
workspace63b0516a11e94e96-6d8f9d65f-wtch5 2/2 Running 0 100s 10.131.1.175 shishika-fmhq4-worker-0-vtcng <none> <none>
Podのマニフェストを確認すると、hostUsersにfalseが設定されています。この設定により、Pod内のコンテナプロセスはUser Namespaceで動作します。
$ oc get pod workspace63b0516a11e94e96-6d8f9d65f-wtch5 -o yaml | grep hostUser
hostUsers: false
こちらの記事を参考に、User NamespaceにおけるUIDのマッピングを確認してみます。
/proc/self/uid_map
を見ると、コンテナ内のUID 0以降65536個のUIDが、ホスト上のUID 4147052544以降の65536個にマッピングされていることがわかります。
$ oc exec pod/workspace63b0516a11e94e96-6d8f9d65f-wtch5 -- cat /proc/self/uid_map
Defaulted container "dev-tools" out of: dev-tools, che-gateway, che-code-injector (init), project-clone (init)
0 4147052544 65536
該当のworkerノード(shishika-fmhq4-worker-0-vtcng
)でPIDを調べます。
$ oc debug node/shishika-fmhq4-worker-0-vtcng -- chroot /host systemd-cgls -u kubepods-burstable-pod$(oc get pod workspace63b0516a11e94e96-6d8f9d65f-wtch5 -n che-kube-admin-devspaces-sai8k3 -o json | jq -r .metadata.uid | tr '-' '_').slice
Starting pod/shishika-fmhq4-worker-0-vtcng-debug-lbxrq ...
To use host binaries, run `chroot /host`
Unit kubepods-burstable-pod18233744_03cc_41b3_b4ad_1b6fafb2683f.slice (/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod18233744_03cc_41b3_b4ad_1b6fafb2683f.slice):
├─crio-conmon-56486d4e7b520bda967cd1b8174f1f1765def93e3229a3883759f27f9e684f62.scope … (#756473)
│ → user.delegate: 1
│ → trusted.delegate: 1
│ └─3689199 /usr/bin/conmon -b /run/containers/storage/overlay-containers/56486…
├─crio-conmon-4ae52e245b24e61f20a957b596e4f105ae0ea2e0019d3ce66d663347033e7708.scope … (#756681)
│ → user.delegate: 1
│ → trusted.delegate: 1
│ └─3689333 /usr/bin/conmon -b /run/containers/storage/overlay-containers/4ae52…
├─crio-56486d4e7b520bda967cd1b8174f1f1765def93e3229a3883759f27f9e684f62.scope … (#756544)
│ → user.delegate: 1
│ → trusted.delegate: 1
│ └─container (#756615)
│ ├─3689202 bash /entrypoint.sh tail -f /dev/null
│ ├─3689211 /bin/sh /checode/entrypoint-volume.sh
│ ├─3689222 /checode/bin/machine-exec --url 0.0.0.0:3333
│ ├─3689230 /usr/libexec/podman/catatonit -- tail -f /dev/null
│ ├─3689237 /usr/bin/coreutils --coreutils-prog-shebang=tail /usr/bin/tail -f…
│ ├─3689257 /checode/checode-linux-libc/ubi9/node ./launcher/entrypoint.js
│ ├─3689317 /checode/checode-linux-libc/ubi9/node out/server-main.js --host 1…
│ ├─3689766 /checode/checode-linux-libc/ubi9/node --dns-result-order=ipv4firs…
│ ├─3689836 /checode/checode-linux-libc/ubi9/node /checode/checode-linux-libc…
│ ├─3689930 /checode/checode-linux-libc/ubi9/node /checode/checode-linux-libc…
│ ├─3690025 /usr/bin/bash --init-file /checode/checode-linux-libc/ubi9/out/vs…
│ ├─3690498 catatonit -P
│ ├─3690708 /usr/bin/fuse-overlayfs -o lowerdir=/home/user/.local/share/conta…
│ ├─3690709 /usr/bin/slirp4netns --disable-host-loopback --mtu=65520 --enable…
│ ├─3690713 rootlessport
│ ├─3690719 rootlessport-child
│ ├─3690728 /usr/bin/conmon --api-version 1 -c 084108646a267f3cb83ca51716cf52…
│ ├─3690730 nginx: master process nginx -g daemon off;
│ ├─3690733 nginx: worker process
│ ├─3690734 nginx: worker process
│ ├─3690735 nginx: worker process
│ ├─3690736 nginx: worker process
│ ├─3690737 nginx: worker process
│ ├─3690738 nginx: worker process
│ ├─3690739 nginx: worker process
│ └─3690740 nginx: worker process
└─crio-4ae52e245b24e61f20a957b596e4f105ae0ea2e0019d3ce66d663347033e7708.scope … (#756752)
→ user.delegate: 1
→ trusted.delegate: 1
└─container (#756823)
└─3689336 /traefik
ワークスペースのコンテナはPID 3689202以降、nginxはworker processとしてPID 3690733以降で実行されているようです。
最後にノード上でそれぞれのプロセスを実行しているUIDを確認します。
# ps -o user= -p 3689202
4147053544
# ps -o user= -p 3690733
4147053644
どちらも /proc/self/uid_map
で確認したUIDの範囲内で動いていることが確認できました。
今回はここまで!