OpenShift 4.17 + Dev SpacesでNested Containerを試してみる

Red Hatでサポートをしている石川と申します。OpenShift Advent Calendar 2024のエントリです。

近頃すっかり寒くなり、街中がクリスマスムードですね。毎年この時期になると娘のクリスマスプレゼントが悩みの種です。

昨今のプレゼント事情を探るため、娘にお友達はサンタさんに何をお願いしたのか尋ねてみました。すると、「サンタさん」との答え。

なるほど、ランプの精に願い事を増やしてもらう的な...子供の発想は柔軟ですね。

というわけで(?)、今回はそんな柔軟性を提供するネストされたコンテナ (**Nested Container**)を試してみます。

# Linux User Namespaceを使用してNested Containerを起動する

ローカルマシンでコンテナアプリケーションの開発を行う場合、podmanやdockerでいつでもイメージをビルドしてコンテナを実行することができます。しかし、コンテナ上でコンテナを実行する場合、権限の昇格を行うとホストにも同様の権限でアクセスできてしまう問題がありました。これはOpenShift Dev Spacesを使用してコンテナ上で開発を行う際、セキリティの観点から望ましくありません。

また、追加でコンテナを起動する必要がある[LocalStack](https://www.localstack.cloud/)や[TestContainers](https://testcontainers.com/)などのツールを利用するチームではこれはより大きな問題となります。

Linux User Namespaceの使用はそのような問題の解決策となります。

OpenShift 4.17で、Technology preview (TP)機能として[Linux User NamespaceでPodが実行可能](https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/release_notes/ocp-4-17-release-notes#ocp-4-17-nodes-userns_release-notes
)になりました。

Linux User Namespaceを使用することで、コンテナはホストとは別のユーザーとグループのマッピングを持つことができるため、コンテナ内ではroot権限でプロセスを実行できますが、ホスト上ではnon-rootユーザーとしてプロセスを実行できます。

# Nested Containerのサンプルを試してみる

OpenShift Dev SpacesでNested Containerを使用する[サンプル](https://github.com/cgruver/ocp-4-17-nested-container-tech-preview)が用意されているので、さっそく試していきます。
※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`リポジトリから新しいワークスペースを作成します。

![Screenshot from 2024-12-18 03-25-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2959561/3dfd6338-0288-f49a-1373-3b383bcc6485.png)

podmanを使ってnginxをNested Containerとして実行します。ワークスペース上でターミナルを開き、以下を実行します。

~~~
podman run -d --rm --name webserver -p 8080:80 quay.io/libpod/banner
curl http://localhost:8080
~~~

![Screenshot from 2024-12-18 03-39-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2959561/ed3e7925-908f-5ed5-8104-30c13f0a0dcb.png)

ワークスペース上でコンテナを実行することができました！

# もう少し詳しく調べてみる

ワークスペースの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
~~~

[こちらの記事](https://zenn.dev/orimanabu/articles/k8s-v130-user-namespace-poc)を参考に、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の範囲内で動いていることが確認できました。

今回はここまで！