dockerコンテナ内でrootlessによるコンテナ実行をしたい

## 前提

* K8s でなんらかのサービスを提供しているときに、そのサービス内でユーザ権限によるコンテナ実行をしたい
* ユーザは winbind により ActiveDirectory で認証

手段としては以下のいずれかが考えられる。

* DinD:Docker-inside-Docker による rootless コンテナ実行
* DooD: Docker-outside-of-Docker による rootless コンテナ実行
* podman によるユーザ権限でのコンテナ実行

## DinD による rootless コンテナ実行の試行

結論から言うとできない。

DinD を行う場合は docker 公式イメージの `docker:dind` タグなどを用いることが多いが、これは主に GitHub Actions におけるコンテナ内でイメージビルドを行う際に使うもの。

独自サービス内でコンテナ起動をしたいため、ベースイメージは好きなものを使いたい。

[CentOSイメージを使用したDinD](https://qiita.com/t_katsumura/items/d5be6afadd6ec6545a9d#32-centos%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9Fdind)
を参考に、dockerインストールを試みる。

### docker インストール

https://docs.docker.com/engine/install/rhel/
に従えば、インストールはすんなりできた。

```sh
[root@xxxxxxxx /]# dnf -y install dnf-plugins-core
[root@xxxxxxxx /]# dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
[root@xxxxxxxx /]# dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

が、systemd が入っていないため systemctl による docker サービス起動ができない。

```sh
[root@xxxxxxxx /]# systemctl enable --now docker
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service.
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
```

dockerd 直接起動を実施したが、エラーになる。

```sh
[root@xxxxxxxx /]# dockerd
INFO[******************] Starting up                                  
INFO[******************] containerd not running, starting managed containerd 
INFO[******************] started new containerd process                address=/var/run/docker/containerd/containerd.sock module=libcontainerd pid=205

...

INFO[******************] unable to detect if iptables supports xlock: 'iptables --wait -L -n': `iptables v1.8.5 (nf_tables): Could not fetch rule set generation id: Permission denied (you must be root)`  error="exit status 4"
INFO[******************] stopping event stream following graceful shutdown  error="<nil>" module=libcontainerd namespace=moby
INFO[******************] stopping healthcheck following graceful shutdown  module=libcontainerd
INFO[******************] stopping event stream following graceful shutdown  error="context canceled" module=libcontainerd namespace=plugins.moby
failed to start daemon: Error initializing network controller: error obtaining controller instance: failed to register "bridge" driver: failed to create NAT chain DOCKER: iptables failed: iptables -t nat -N DOCKER: iptables v1.8.5 (nf_tables): Could not fetch rule set generation id: Permission denied (you must be root)

 (exit status 4)
```

ということで rootless 設定以前にdockerサービス実行できない。


## DooD による rootless コンテナ実行の試行

DinD では docker サービス起動時に iptables によるブリッジインタフェースの生成に失敗している様子だったため、 DooD で回避できるかどうかを試行。

```sh
$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock ...  --rm [service-container] bash

## docker インストール用リポジトリ追加
[root@xxxxxxxx /]# dnf -y install dnf-plugins-core
[root@xxxxxxxx /]# dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo

## docker-cli のみインストール
[root@xxxxxxxx /]# dnf install -y docker-ce-cli
```

確かに `docker ps` コマンドで実行ホストのコンテナ群が見えた (見えてしまった)。

```
[root@xxxxxxxx /]# docker ps
CONTAINER ID   IMAGE                               COMMAND                  CREATED          STATUS                 PORTS                    NAMES
***********   busybox                             "sleep 3600"             41 minutes ago   Up 41 minutes                                   k8s_pod-01a_pod-01a_default_************
***********   busybox                             "sleep 3600"             41 minutes ago   Up 41 minutes                                   k8s_pod-01_pod-01_default_**********
...

```

### rootless docker インストール

この状態で rootless docker ができるかを試したが、失敗。

ローカルユーザ追加
```sh
[root@xxxxxxxx /]# adduser testuser
```

この状態で testuser 指定でコンテナに入り rootless を設定。

```sh
$ docker exec -it -u testuser xxxxxxxx bash

[testuser@xxxxxxxx /]$ curl -fsSL https://get.docker.com/rootless | sh
# Installing stable version 27.3.1
# Executing docker rootless install script, commit: 6b775c4
# Existing rootless Docker detected at /home/testuser/bin/dockerd

# To reinstall or upgrade rootless Docker, run the following commands and then rerun the installation script:
systemctl --user stop docker
rm -f /home/testuser/bin/dockerd

# Alternatively, install the docker-ce-rootless-extras RPM/deb package for ease of package management (requires root).
# See https://docs.docker.com/go/rootless/ for details.
```

https://docs.docker.com/engine/security/rootless/#errors-when-starting-the-docker-daemon
に "could not get XDG_RUNTIME_DIR" という項目があり、環境変数 XDG_RUNTIME_DIR がセットされていなかったため強引にセットして起動してみたが、権限エラーになる。

```sh
[testuser@xxxxxxxx /]$ export XDG_RUNTIME_DIR=$HOME/.docker/xrd
[testuser@xxxxxxxx /]$ rm -rf $XDG_RUNTIME_DIR
[testuser@xxxxxxxx /]$ mkdir -p $XDG_RUNTIME_DIR
[testuser@xxxxxxxx /]$ dockerd-rootless.sh

...

+ exec rootlesskit --state-dir=/home/testuser/.docker/xrd/dockerd-rootless --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run --propagation=rslave /home/testuser/bin/dockerd-rootless.sh
WARN[0000] [rootlesskit:parent] The host root filesystem is mounted as "master:1575". Setting child propagation to "rslave" is not supported. 
[rootlesskit:parent] error: failed to start the child: fork/exec /proc/self/exe: operation not permitted
```

rootless docker は DooD でもできなさそうな感じ。

## podman によるユーザ権限でのコンテナ実行

コンテナに入り podmanを dnf インストールする。

```sh
$ docker run -it --rm [service-container] bash
[root@xxxxxxx /]# dnf install -y podman
```

だが podmanで適当なコンテナを実行しようとするとエラーになる。

```sh
[root@xxxxxxx /]# podman run hello-world
WARN[0000] "/" is not a shared mount, this could cause issues or missing mounts with rootless containers 
cannot clone: Operation not permitted
Error: cannot re-exec process
```

https://github.com/containers/podman/issues/22606

によれば、 docker 実行時に `--security-opt seccomp=unconfined` を付けると良いらしい。

```sh
$ docker run -it --security-opt seccomp=unconfined --rm [service-container] bash
[root@xxxxxxxxx /]# dnf install -y podman
[root@xxxxxxxxx /]# podman run hello-world
WARN[0000] "/" is not a shared mount, this could cause issues or missing mounts with rootless containers 
Resolved "hello-world" as an alias (/etc/containers/registries.conf.d/000-shortnames.conf)
Trying to pull quay.io/podman/hello:latest...
Getting image source signatures
Copying blob 81df7ff16254 done   | 
Copying config 5dd467fce5 done   | 
Writing manifest to image destination
ERRO[0003] Unmounting /var/lib/containers/storage/overlay/885a8ae8337b760ac777ab466e2a72974d258477107a5594ebb927a716e89b54/merged: invalid argument 
WARN[0003] Failed to load cached network config: network podman not found in CNI cache, falling back to loading network podman from disk 
ERRO[0003] Preparing container d6b42ea4963f7264ab229bab8b4c201639cfca2330c840308b7c90ce9dfc9169: plugin type="bridge" failed (add): cni plugin bridge failed: failed to create bridge "cni-podman0": could not add "cni-podman0": operation not permitted 
Error: mounting storage for container d6b42ea4963f7264ab229bab8b4c201639cfca2330c840308b7c90ce9dfc9169: creating overlay mount to /var/lib/containers/storage/overlay/885a8ae8337b760ac777ab466e2a72974d258477107a5594ebb927a716e89b54/merged, mount_data="lowerdir=/var/lib/containers/storage/overlay/l/SBCUSNZOXDGNIQ6SDMWKBGA7VK,upperdir=/var/lib/containers/storage/overlay/885a8ae8337b760ac777ab466e2a72974d258477107a5594ebb927a716e89b54/diff,workdir=/var/lib/containers/storage/overlay/885a8ae8337b760ac777ab466e2a72974d258477107a5594ebb927a716e89b54/work,nodev": using mount program /usr/bin/fuse-overlayfs: unknown argument ignored: lazytime
fuse: device not found, try 'modprobe fuse' first
fuse-overlayfs: cannot mount: No such file or directory
: exit status 1
```

podman コマンドは実行できたが、"/" が shared でないという警告や、cni-podman0 インタフェースを作れないなどのエラーでコンテナが動かない。

### priviledged モードによるコンテナ実行

https://developers.redhat.com/e-books/podman-action
https://www.redhat.com/en/blog/podman-inside-container

によれば、 security-opt ではなくて `--privileged` モードで動かすと良いとある。

```sh
$ docker run -it --privileged --rm [service-container] bash
[root@xxxxxxxx /]# dnf install -y podman
[root@xxxxxxxx /]# podman run hello-world
Resolved "hello-world" as an alias (/etc/containers/registries.conf.d/000-shortnames.conf)
Trying to pull quay.io/podman/hello:latest...
Getting image source signatures
Copying blob 81df7ff16254 done   | 
Copying config 5dd467fce5 done   | 
Writing manifest to image destination
!... Hello Podman World ...!

         .--"--.           
       / -     - \         
      / (O)   (O) \        
   ~~~| -=(,Y,)=- |         
    .---. /`  \   |~~      
 ~/  o  o \~~~~.----. ~~   
  | =(X)= |~  / (O (O) \   
   ~~~~~~~  ~| =(Y_)=-  |   
  ~~~~    ~~~|   U      |~~ 

Project:   https://github.com/containers/podman
Website:   https://podman.io
Desktop:   https://podman-desktop.io
Documents: https://docs.podman.io
YouTube:   https://youtube.com/@Podman
X/Twitter: @Podman_io
Mastodon:  @Podman_io@fosstodon.org
```

"/" のマウント警告も出ず、実行できた。   アザラシかわゆす。   (´ω`っ )3

### ユーザ権限での実行確認

ユーザ権限でも実行できるか確認。

```sh
[root@xxxxxxx /]# adduser testuser
```
としてユーザ追加し、ユーザ名指定でコンテナに入り、コンテナ実行。

```sh
[testuser@xxxxxxx /]$ podman run hello-world
!... Hello Podman World ...!

         .--"--.           
       / -     - \         
      / (O)   (O) \        
   ~~~| -=(,Y,)=- |         
    .---. /`  \   |~~      
 ~/  o  o \~~~~.----. ~~   
  | =(X)= |~  / (O (O) \   
   ~~~~~~~  ~| =(Y_)=-  |   
  ~~~~    ~~~|   U      |~~ 

Project:   https://github.com/containers/podman
Website:   https://podman.io
Desktop:   https://podman-desktop.io
Documents: https://docs.podman.io
YouTube:   https://youtube.com/@Podman
X/Twitter: @Podman_io
Mastodon:  @Podman_io@fosstodon.org
```

警告も出ず実行できた。

### イメージビルド

ADユーザのホームディレクトリ領域が NFSマウントされている場合のために、コンテナ保存先をホームディレクトリ以外の場所に設定する。

`storage.conf`:
```
[storage]

rootless_storage_path = "/var/tmp/$USER"
```

ADユーザのuid, gid に対して subuid, subgid 設定をコンテナにも反映。
subuid,subgidについては [rootless 用プロセス設定](
https://qiita.com/mah_orz/items/6a6834206e0634291bc0#rootless-%E7%94%A8%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E8%A8%AD%E5%AE%9A) を参照。

ADユーザで実行できるようwinbindを入れておく。


```Dockerfile
...
RUN dnf update -y && \
    dnf install -y \
        ...
        authselect samba-winbind-clients \
        podman 

## AD設定
ENV SECRET_AD_USER=""
ENV SECRET_AD_PASSWD=""

COPY smb.conf /etc/samba
RUN authselect select winbind --force

## podman設定
COPY ./storage.conf /etc/containers/storage.conf
COPY ./subuid /etc/subuid
COPY ./subgid /etc/subgid

## サービス実行用スクリプト
COPY ./run.sh /usr/local/bin/
RUN chmod  +x /usr/local/bin/run.sh

CMD ["/usr/local/bin/run.sh"]
```

最後のサービス実行用スクリプトには AD認証できるよう winbindd 実行する。

`run.sh`:

```sh
#!/bin/sh

### AD参加
net ads join -U "${SECRET_AD_USER}%${SECRET_AD_PASSWD}"
winbindd -D

### サービスプログラム実行
xxxx
```



### K8s サービスを privileged モードで動かす

privileged モードで pod を動かすには spec.containers.securityContext.privileged=true にすればよい。

https://stackoverflow.com/questions/72542109/how-can-i-start-a-pod-in-privileged-mode-from-a-manifest

マニフェストに以下を追記して適用。


```yaml
---
apiVersion: v1
kind: Secret
metadata:
  name: my-service-sercret
type: Opaque
data:
  SECRET_AD_USER: *************
  SECRET_AD_PASSWD: ***********
---
apiVersion: apps/v1
kind: StatefulSet
spec:
  template:
    spec:
      containers:
      - name: my-service
        securityContext:
          privileged: true

        env:
        - name: SECRET_AD_USER
          valueFrom:
            secretKeyRef:
              name: my-service-sercret
              key: SECRET_AD_USER
        - name: SECRET_AD_PASSWD
          valueFrom:
            secretKeyRef:
              name: my-service-sercret
              key: SECRET_AD_PASSWD
...
```

AD参加時のユーザ・パスワードを Secret リソースとして登録し、環境変数 SECRET_AD_USER, SECRET_AD_PASSWD でコンテナに渡すようにしている。


上記イメージに更新してpod起動しなおすことで、ADユーザでコンテナ実行ができる。

```sh
### 更新したイメージで起動しなおすよう既存 pod を停止 → StatefulSet により自動再起動
$ kubectl delete pods [service-continer]
```