DockerからChromeをホストPCにWaylandで表示する方法

## はじめに

この記事ではDockerコンテナで起動するChromeブラウザをホストPC側のWaylandを使って表示する方法について確認する。

Ubuntu 24.04をはじめとする最近のLinuxディストリビューションでは、X11の代わりにWaylandがディスプレイサーバーとしてデフォルトに設定されています。また、組み込み機器においても、Waylandはその軽量性と効率性から広く利用されている。

DockerコンテナからWaylandアプリケーションをホストPCに直接表示するためには、ホストPCのWaylandディスプレイサーバにコンテナ内のアプリケーションがアクセスできるように設定する必要があります。まずは、シンプルなテスト環境を構築し、WaylandアプリをホストPCで表示させることを確認します。最後に、ChromeブラウザをホストPCで表示させる。

## 動作環境

| 項目                   | ホストPC           | Dockerコンテナ      |
|------------------------|--------------------|---------------------|
| OS                     | Ubuntu 24.04       | Ubuntu 24.04        |
| ディスプレイサーバー   | Wayland            | n/a                 |

Waylandをディスプレイサーバーとして選択し、Ubuntuにログインする方法は、下記が詳しい。

https://kledgeb.blogspot.com/2022/05/ubuntu-2204-95-xorgwayland.html


## WaylandアプリをホストPCで表示する

Waylandがディスプレイサーバーとして動作するUbuntu24.04などの環境をホスト側に用意しているものとして進める。

### ホストPCの準備

#### XDG_RUNTIME_DIRの確認

ホストPCで`XDG_RUNTIME_DIR`が正しく設定されているか確認する。

```bash
echo $XDG_RUNTIME_DIR
```

通常、Ubuntuではこのディレクトリは `/run/user/$(id -u)` だ。


#### Wayland環境の確認
ホストPCがWaylandセッションで動作していることを確認します。Ubuntu24.04であれば、Gnomeを使っている場合、デフォルトでWaylandが使われている。
ホストPCで`XDG_SESSION_TYPE`が`wayland`であることを確認する

```bash
echo $XDG_SESSION_TYPE
```
`wayland` と出力されるか確認する。`x11`と表示された場合にはログイン時にX11が利用されている。

#### GPUの確認 

ホストPCのGPUを確認する

```bash
lspci -v | grep -i --color 'vga\|3d\|2d'
```

GPUとしてIntel Chipが認識されている

```bash: IntelのGPUが確認される
00:02.0 VGA compatible controller: Intel Corporation Raptor Lake-S GT1 [UHD Graphics 770] (rev 04) (prog-if 00 [VGA controller])
```

#### Dockerの用意

ホストPCにDockerがインストールされていることを確認する。インストールされていない場合は下記のコマンドでインストールする。

```bash
sudo apt-get update
sudo apt-get install docker.io
```

#### Waylandソケットの確認
Waylandは`/run/user/$(id -u)/wayland-0`にソケットを作成する。このソケットにコンテナからアクセスできるようにする。

```bash
file /run/user/$(id -u)/wayland-0
```

`/run/user/1000/wayland-0: socket`のようにWaylandソケットの存在が確認できる


### Dockerコンテナの設定

#### Waylandソケットのマウント

コンテナ内からホストのWaylandソケットにアクセスできるように、ソケットをボリュームとしてマウントする。また、Dockerコンテナで `XDG_RUNTIME_DIR` をホストPCと同じ値に設定する。

例えば、`docker run`コマンドに以下のオプションを追加する。

```bash
-v /run/user/$(id -u)/wayland-0:/run/user/$(id -u)/wayland-0:rw \
-e WAYLAND_DISPLAY=wayland-0 \
-e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \
```

:::note info
WaylandのソケットをマウントしてDocker側からも利用するためにコンテナ内ユーザーのUID/GIDをホストPC側のUID/GIDと揃える必要がある
:::


#### Wayland対応アプリケーションのインストール

テスト用のWayland対応アプリケーションをコンテナ内にインストールする。`weston-terminal`をここではインストールする。

下記のような`Dockerfile`を適当なディレクトリに作成する。

```Dockerfile
FROM ubuntu:latest
RUN apt-get update && apt-get install -y \
    weston \
    && rm -rf /var/lib/apt/lists/*
```

#### Dockerイメージのビルド

wayland-testと名前をつけてDockerイメージを作成する

```bash
docker build -t wayland-test .
```

### テスト環境の構築と実行

#### Dockerコンテナの起動

コンテナ内ユーザーのUID/GIDをホストPC側と揃えて、ホストのWaylandソケットをマウントしコンテナを起動する。

```bash
docker run -it \
--user $(id -u):$(id -g) \
-v /run/user/$(id -u)/wayland-0:/run/user/$(id -u)/wayland-0:rw \
-e WAYLAND_DISPLAY=wayland-0 \
-e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \
--name wayland-test-container \
wayland-test /bin/bash
```

#### アプリケーションの実行

コンテナ内で`weston-terminal`を実行する。

```bash:Dockerコンテナで実行する
weston-terminal
```

#### 表示確認
成功の場合には`weston-terminal`がホストPCのWaylandセッションに表示される。

![Screenshot from 2024-08-17 16-34-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/72a764d2-3750-d073-df78-c4787f39f4b6.png)


```bash: ホストPCがX11セッションだった場合などはエラーとなる
failed to connect to Wayland display: Connection refused
failed to create display: Connection refused
```

### トラブルシューティング

#### 表示されない場合
コンテナがホストPCのWaylandソケットに正しくアクセスできているか、ソケットのパスと`WAYLAND_DISPLAY`変数が正しいかを再確認する。

#### 権限の問題
Waylandソケットにアクセスするため、ホストPCのユーザーIDと同じUIDをDockerコンテナに設定する必要がある。これは、`--user $(id -u):$(id -g)`オプションで設定する。

## ChromeブラウザをホストPCに表示する

### ホストPCがX11の場合との違い

#### dbus
X11を使用する場合、dbus-x11パッケージをインストールする必要がある。X11環境で動作するDBusセッションバスを提供するためだったが、Waylandでは通常のdbusパッケージで良い。

#### Chromeの起動方法
Waylandでは`--ozone-platform-hint=wayland`オプションを使ってChromeにWaylandを使用するよう指示する。また`--enable-wayland-ime`を追加することでWayland上で日本語入力メソッド（IME）のサポートを有効にする。

### Dockerfile

```Dockerfile:Dockerfile
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

# 必要なパッケージをインストール
RUN apt-get update && \
    apt-get install -y wget pulseaudio socat alsa-base \
    libcanberra-gtk-module libcanberra-gtk3-module \
    fonts-ipafont-gothic fonts-ipafont-mincho fonts-noto-cjk \
    language-pack-ja dbus && \
    wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
    apt-get install -y ./google-chrome-stable_current_amd64.deb --no-install-recommends && \
    apt-get install -y weston mesa-utils sudo &&\
    apt-get clean && rm -rf /var/lib/apt/lists/*

RUN echo "#!/bin/bash\n\
set -e\n\
sudo /etc/init.d/dbus start || { echo 'Failed to start dbus'; exit 1; }\n\
export XDG_RUNTIME_DIR=/run/user/\$(id -u)\n\
sudo mkdir -p \$XDG_RUNTIME_DIR || { echo 'Failed to create XDG_RUNTIME_DIR'; exit 1; }\n\
sudo chmod 700 \$XDG_RUNTIME_DIR || { echo 'Failed to chmod XDG_RUNTIME_DIR'; exit 1; }\n\
sudo chown \$(id -un):\$(id -gn) \$XDG_RUNTIME_DIR || { echo 'Failed to chown XDG_RUNTIME_DIR'; exit 1; }\n\
export DBUS_SESSION_BUS_ADDRESS=unix:path=\$XDG_RUNTIME_DIR/bus\n\
dbus-daemon --session --address=\$DBUS_SESSION_BUS_ADDRESS --nofork --nopidfile --syslog-only &\n\
exec \"\$@\"" > /start.sh && chmod +x /start.sh

# ホストのUIDとGIDを利用してユーザーを作成する
ARG USER_ID
ARG GROUP_ID

# 既存のグループを確認して、なければ作成
RUN if ! getent group $GROUP_ID >/dev/null; then \
        groupadd -g $GROUP_ID hostgroup; \
    fi

# 既存のUIDに対応するユーザーがいない場合、新規ユーザーを作成して、PWなしでsudoを許可
RUN USER_NAME=$(getent passwd $USER_ID | cut -d: -f1) && \
    if [ -z "$USER_NAME" ]; then \
        USER_NAME="hostuser"; \
        useradd -u $USER_ID -g $GROUP_ID -m $USER_NAME; \
    fi && \
    echo "$USER_NAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# google-chrome を $USER_ID で実行
USER $USER_ID
CMD ["/start.sh", "google-chrome", "--disable-dev-shm-usage", " --ozone-platform-hint=wayland", "--enable-wayland-ime"]
```

#### Dockerイメージを作成

ホストPCで利用しているUIDとGIDを指定して`ubuntu24_waychrome`という名前をつけてDockerイメージを作成する。

```bash
docker build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) -t ubuntu24_waychrome .
```

#### Dockerコンテナを起動

```bash
docker run -it --rm \
  --name ubuntu24_waychrome \
  -v /run/user/$(id -u)/wayland-0:/run/user/$(id -u)/wayland-0:rw \
  -e WAYLAND_DISPLAY=wayland-0 \
  -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \
  --env="PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native" \
  --volume="${XDG_RUNTIME_DIR}/pulse/native:${XDG_RUNTIME_DIR}/pulse/native" \
  --volume="/etc/machine-id:/etc/machine-id:ro" \
  --volume="/run/user/$(id -u)/pulse:/run/user/$(id -u)/pulse" \
  --network=host \
  --device /dev/dri \
  --cap-add=SYS_ADMIN --security-opt seccomp=unconfined \
  ubuntu24_waychrome
  ```

#### Chromeの表示確認

[YoutubeでBig Buck Bunny](https://www.youtube.com/watch?v=aqz-KE-bpKQ)が音声つきで再生できた。

![Screenshot from 2024-08-18 15-31-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/b1507f0a-dda8-202f-0345-93ab4e3702df.png)

`chrome://version`でChromeのバージョンと起動オプションを確認する

![Screenshot from 2024-08-18 15-32-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/97e130b0-0fc5-d76f-a9b4-23255d7de3e4.png)

`chrome://gpu`でWaylandを利用が確認できる

![Screenshot from 2024-08-18 15-34-36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/3ee2e3d5-6979-ae52-907c-c258e85c18b2.png)

`chrome://gpu`でハードウェアアクセラレーションがEnable有効であることが(but at reduced performance:しかしパフォーマンスは落ちている)確認できる

![Screenshot from 2024-08-18 15-28-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/a9c543b8-cde6-34ea-a37b-ff8cb27e06da.png)

#### ハードウェアアクセラーションの問題

DockerコンテナからChromeブラウザを起動すると下記のように`libEGL`の`warning`が表示されている。`/dev/dri/renderD128`へのアクセス権限がない問題の解決が必要そうだ。

```bash
 * Starting system message bus dbus                                      [ OK ]
libEGL warning: failed to open /dev/dri/renderD128: Permission denied

libEGL warning: failed to open /dev/dri/renderD128: Permission denied
```

#### ハードウェアアクセラレーションの解決

Dockerイメージを`/bin/bash`付きで起動して`/dev/dri/renderD128`を確認する。

```bash:ホストPCから/bin/bash付きでコンテナを起動する
docker run -it --rm \
  --name ubuntu24_waychrome \
  -v /run/user/$(id -u)/wayland-0:/run/user/$(id -u)/wayland-0:rw \
  -e WAYLAND_DISPLAY=wayland-0 \
  -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \
  --env="PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native" \
  --volume="${XDG_RUNTIME_DIR}/pulse/native:${XDG_RUNTIME_DIR}/pulse/native" \
  --volume="/etc/machine-id:/etc/machine-id:ro" \
  --volume="/run/user/$(id -u)/pulse:/run/user/$(id -u)/pulse" \
  --network=host \
  --device /dev/dri \
  --cap-add=SYS_ADMIN --security-opt seccomp=unconfined \
  ubuntu24_waychrome /bin/bash
```
  
```bash:Dockerコンテナで確認
ls -la /dev/dri/renderD128
crw-rw---- 1 root systemd-resolve 226, 128 Aug 18 06:49 /dev/dri/renderD128
```

エラーはコンテナ内のChromeがWaylandディスプレイサーバーに接続しようとしたときに、`/dev/dri/renderD128`デバイスファイルにアクセスする権限が不足している。Chromeを起動するユーザーが`systemd-resolve`に所属している必要がある。

#### Dockerfileの修正

`usermod -aG systemd-resolve $USER_NAME`で`systemd-resolve`のグループにユーザを追加することで`/dev/dri/renderD128`へのアクセス権を追加する

```Dockerfile:修正したDockerfile
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

# 必要なパッケージをインストール
RUN apt-get update && \
    apt-get install -y wget pulseaudio socat alsa-base \
    libcanberra-gtk-module libcanberra-gtk3-module \
    fonts-ipafont-gothic fonts-ipafont-mincho fonts-noto-cjk \
    language-pack-ja dbus && \
    wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
    apt-get install -y ./google-chrome-stable_current_amd64.deb --no-install-recommends && \
    apt-get install -y weston mesa-utils sudo &&\
    apt-get clean && rm -rf /var/lib/apt/lists/*

RUN echo "#!/bin/bash\n\
set -e\n\
sudo /etc/init.d/dbus start || { echo 'Failed to start dbus'; exit 1; }\n\
export XDG_RUNTIME_DIR=/run/user/\$(id -u)\n\
sudo mkdir -p \$XDG_RUNTIME_DIR || { echo 'Failed to create XDG_RUNTIME_DIR'; exit 1; }\n\
sudo chmod 700 \$XDG_RUNTIME_DIR || { echo 'Failed to chmod XDG_RUNTIME_DIR'; exit 1; }\n\
sudo chown \$(id -un):\$(id -gn) \$XDG_RUNTIME_DIR || { echo 'Failed to chown XDG_RUNTIME_DIR'; exit 1; }\n\
export DBUS_SESSION_BUS_ADDRESS=unix:path=\$XDG_RUNTIME_DIR/bus\n\
dbus-daemon --session --address=\$DBUS_SESSION_BUS_ADDRESS --nofork --nopidfile --syslog-only &\n\
exec \"\$@\"" > /start.sh && chmod +x /start.sh

# ホストのUIDとGIDを利用してユーザーを作成する
ARG USER_ID
ARG GROUP_ID

# 既存のグループを確認して、なければ作成
RUN if ! getent group $GROUP_ID >/dev/null; then \
        groupadd -g $GROUP_ID hostgroup; \
    fi

# 既存のUIDに対応するユーザーがいない場合、新規ユーザーを作成して、PWなしでsudoを許可
RUN USER_NAME=$(getent passwd $USER_ID | cut -d: -f1) && \
    if [ -z "$USER_NAME" ]; then \
        USER_NAME="hostuser"; \
        useradd -u $USER_ID -g $GROUP_ID -m $USER_NAME; \
    fi && \
    echo "$USER_NAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
    # /dev/dri/renderD128にアクセス権を追加
    usermod -aG systemd-resolve $USER_NAME

# google-chrome を $USER_ID で実行
USER $USER_ID

CMD ["/start.sh", "google-chrome", "--disable-dev-shm-usage", " --ozone-platform-hint=wayland", "--enable-wayland-ime"]
```

#### Chromeの表示確認

修正したDockerファイルで作成したDockerイメージからコンテナを起動する。`chrome://gpu`の出力はGPUのハードウェアアクセラレーションに制限がないことを示している。

![Screenshot from 2024-08-18 16-31-38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/60a8f730-94f5-a8a0-af76-ecb9cdd7de10.png)

WebGLのベンチマークの[Space Rocks](https://webglsamples.org/spacerocks/spacerocks.html)でテストすると、たまに60fpsを切る場合もあるが、ほぼ60fpsをキープして表示される

![Screenshot from 2024-08-18 16-48-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/785cbc88-9cec-b336-700e-ba6408079665.png)


## 蛇足

### Wayland環境でスクリーンショットを撮りたい場合

```bash:gnome-screenshotをインストール
sudo apt-get install gnome-screenshot
```

メニューからGUIアプリを起動可能

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/d899fae7-dfb4-1913-5cfa-a454621a9e36.png)