Dockerでuid/gid指定可能かつsudo使用可能なデスクトップ環境を構築する(XRDP編)


2019/9/23 追記

この記事の内容を基にしたメージを、docker hubに登録しました。

https://hub.docker.com/r/yama07/docker-ubuntu-lxde

以下のタグが付いているので、pullして使ってみてください。

- `ubuntu16.04_ja`: Ubuntu16.04ベース [(xrdp/Dockerfile.ubuntu16.04)](https://github.com/yama07/docker-ubuntu-lxde/blob/master/xrdp/Dockerfile.ubuntu16.04)
- `ubuntu18.04_ja`: Ubuntu18.04ベース [(xrdp/Dockerfile.ubuntu18.04)](https://github.com/yama07/docker-ubuntu-lxde/blob/master/xrdp/Dockerfile.ubuntu18.04)
- `ubuntu18.04-pulseaudio_ja`: 音声転送可能なUbuntu18.04ベース [(xrdp/Dockerfile.ubuntu18.04_pulseaudio)](https://github.com/yama07/docker-ubuntu-lxde/blob/master/xrdp/Dockerfile.ubuntu18.04_pulseaudio)

Dockerfile等はGitHubの方を参照してください。
https://github.com/yama07/docker-ubuntu-lxde/tree/master/xrdp

なお、この記事自体の内容は多少古いですが、このまま残します。

---

以下の記事の続編です。
[Dockerでuid/gid指定可能かつsudo実行可能なユーザとしてコンテナを起動する](http://qiita.com/yama07/items/a521234dc91f923ba655)

# はじめに

[以前の記事](http://qiita.com/yama07/items/a521234dc91f923ba655)では、Dockerコンテナ上のCUIの快適な環境を構築しました。
今回は、GUIの快適な環境を構築します。

ここでいう快適とは、以下の条件を満たす環境のことを指しています。（あくまで私にとっての快適です。）

- uid/gidを指定して起動できる。（コンテナ<->ホスト間でファイル共有しやすい）
- sudoが使える。（パッケージを自由に追加できる）

要するに、ちょっとパッケージを試しに入れて、データを加工して取り出して、いらなくなったら環境を捨てるということを、デスクトップ環境においてもお手軽にできるようにします。

![スクリーンショット 2017-09-02 23.16.43.png](https://qiita-image-store.s3.amazonaws.com/0/125684/4822c33d-0967-8d34-b687-328991bec669.png)

デスクトップ環境は、Docker + XRDP + XLDEで構成しています。

リモートデスクトップとしてXRDPを選択した理由は以下２点です。

- クライアントソフトの追加インストールが不要（主にWindowsを使うので）
- VNCよりもサクサク動く

なお、気が向いたら「VNC編」も書く予定です。

# 背景

最近、Androidアプリの開発をしているのですが、会社から支給されたノートパソコンでAndroidStudioを動かすのがキツくなってきました:disappointed_relieved:

色々と負荷を下げる設定を試みましたが、メールアプリケーションとAndroidStudioを同時に動かしている状態でメモリ使用率90%を超え、全体的に動作がモッサリしています。
さらに最近では、サブ画面にAndroidStudioを表示させると画面が表示されなくなるという現象が発生し始めました。

幸いなことに、個人のノートパソコンとは別に、それなりのスペックの共用サーバが提供されており、そこでは勝手にパッケージを追加したりはできませんが、dockerが使用可能です。

こういった事情から、コンテナでデスクトップ環境を立てて、手元のノートパソコンからアクセスするというところに行き着きました。

なお、この記事では汎用的なデスクトップ環境の構築について書いているので、AndroidStudioのインストールについては書いていません。
（気が向けば、あるいは需要があれば記事を書くかもしれません。）

# Dockerホストの環境

```sh
$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"

$ docker --version
Docker version 1.13.1, build 092cba3
```

# Dockerイメージの作り方

## Dockerfileとか

Dockerfileと、endpointとなるシェルスクリプトの２つから成ります。

```sh:Dockerfile
FROM ubuntu:16.10

RUN apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y \
      lxde \
      xrdp \
      ibus \
      ibus-mozc \
      language-pack-ja-base \
      language-pack-ja \
      fonts-ipafont-gothic \
      fonts-ipafont-mincho \
    && apt-get clean \
    && rm -rf /var/cache/apt/archives/* \
    && rm -rf /var/lib/apt/lists/*

# RDPのポートを公開する
EXPOSE 3389

RUN echo "startlxde" > /etc/skel/.xsession

# 日本語ロケールを設定する
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
    && echo 'Asia/Tokyo' > /etc/timezone
RUN locale-gen ja_JP.UTF-8 \
    && echo 'LC_ALL=ja_JP.UTF-8' > /etc/default/locale \
    && echo 'LANG=ja_JP.UTF-8' >> /etc/default/locale
ENV LANG=ja_JP.UTF-8 \
    LANGUAGE=ja_JP:ja \
    LC_ALL=ja_JP.UTF-8

# デフォルトのユーザ名とパスワード
ENV DEFAULT_USER=developer \
    DEFAULT_PASSWD=xrdppasswd

# sudoを使用できるようにする
RUN echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/ALL

# 一般ユーザがユーザ/グループを追加できるようにする
RUN chmod u+s /usr/sbin/useradd \
    && chmod u+s /usr/sbin/groupadd

# XRDPのendpointスクリプトを配置する
COPY endpoint.sh /opt/
RUN chmod +x /opt/endpoint.sh
CMD ["/opt/endpoint.sh"]
```

```bash:endpoint.sh
#!/bin/bash -e

USER_ID=$(id -u)
GROUP_ID=$(id -g)
PASSWD=${PASSWD:-${DEFAULT_PASSWD}}
USER=${USER:-${DEFAULT_USER}}

# グループを作成する
echo "GROUP_ID: $GROUP_ID"
if [ x"$GROUP_ID" != x"0" ]; then
    groupadd -g $GROUP_ID $USER
fi

# ユーザを作成する
echo "USER_ID: $USER_ID"
if [ x"$USER_ID" != x"0" ]; then
    export HOME=/home/$USER
    useradd -d ${HOME} -m -s /bin/bash -u $USER_ID -g $GROUP_ID $USER
fi

# パーミッションを元に戻す
sudo chmod u-s /usr/sbin/useradd
sudo chmod u-s /usr/sbin/groupadd

# ログインユーザ名を設定する
USER=$(whoami)
echo "USER: $USER"

# ログインパスワードを設定する
echo "PASSWD: $PASSWD"
echo ${USER}:${PASSWD} | sudo chpasswd

[ ! -e ${HOME}/.xsession ] && cp /etc/skel/.xsession ${HOME}/.xsession

echo "#############################"

# XRDPサーバを起動する
sudo bash -c "/etc/init.d/xrdp start && tail -F /var/log/xrdp-sesman.log"
```

やっていることは、[以前の記事](http://qiita.com/yama07/items/a521234dc91f923ba655)で書いていることと基本同じです。
xrdpやlxdeのインストールや、日本語環境設定が追加されています。

ubuntu:16.10をベースイメージとしていますが、ubuntu:14.04や16.04も同様に使用することができます。しかし、ubuntu:16.04以前はxrdpがv0.6のため日本語キーボード配列に対応しておりませんので、[ここ](https://xrdp.vmeta.jp/%e3%82%ad%e3%83%bc%e9%85%8d%e5%88%97%e3%81%ab%e3%81%a4%e3%81%84%e3%81%a6)を参考にキーマップを変更してください。なお、ubuntu:16.10のxrdpはv0.9なので、追加手順なしで日本語キーボードを使用できます。

xrdpの設定は見ての通り、デフォルトのままです。
好みに合わせてxrdpサーバの設定(xrdp.iniやsesman.iniに記述)をおこなってください。

## docker build

普通に`docker build`をおこないます。

```sh
## Dockerfileを作成
$ vi Dockerfile

## endpointスクリプトを作成
$ vi endpoint.sh


$ docker build -t lxde_xrdp:ubuntu16.10_ja .
```

# Dockerコンテナの使い方

## docker run

XRDPサーバのコンテナを立ち上げます。

```sh
$ docker run --rm -it \
    --privileged \
    -p 3389:3389 \
    -u $(id -u):$(id -g) \
    -e USER=yama \
    -e PASSWD=yamapasswd \
    lxde_xrdp:ubuntu16.10_ja
```

オプションは以下の通りです。

- `-p port:3389`
クライアントから接続されるポートを`port`に設定してください。
- `-u user:group`
コンテナを起動するUIDを`user`に、GIDを`group`に設定してください。
指定しない場合は、rootユーザ(UID=0,GID=0)として起動します。
なお、rootユーザとして起動した場合は、日本語入力(mozc)が利用できません。
- `-e USER=loginUser`
RDPによるログインユーザを`loginUser`に設定してください。
指定しない場合は、“developer”となります。ただし、rootユーザとしてコンテナを起動した際は“root”となります。
- `-e PASSWD=loginPasswd`
RDPによるログインパスワードを`loginPasswd`に設定してください。
指定しない場合は、“xrdppasswd”となります。

`--privileged`オプションは動作環境によっては不要です。
mozcで「変換エンジンプログラムの起動に失敗しました。」とエラーが発生した場合は、このオプションをつけてください。

## クライアントから接続

Windowsを利用する場合は、付属の「リモートデスクトップ」があれば追加でクライアントソフトをインストールする必要はありません。
Macの場合は「Microsoft Remote Desktop」、Linuxの場合は「FreeRDP」や「rdesktop」、「Remmina」などで接続してください。

接続方法は、リモートデスクトップクライアントを起動し、DockerホストのIP、および`docker run`で設定した接続ポート、ログインユーザ／パスワードでログインします。
ユーザ／パスワードの情報は、コンテナを起動した際に以下のように出力されますので、そこでも確認できます。
（`-d`オプションでバックグラウンド実行した場合は、`docker logs`で確認できます。）

```sh
$ docker run ...オプション省略...

GROUP_ID: 1000
USER_ID: 1000
USER: yama
PASSWD: yamapasswd
#############################
 * Starting Remote Desktop Protocol server 
...
```

ログインに成功すると、「No session for pid (プロセスID)」とエラーダイアログが出てくるかもしれませんが、特に問題ない（と思う）ので、「OK」をクリックしてください。（今のところ動作に問題ないので、深く原因調査をしていません。すみません:sweat:）
![スクリーンショット 2017-09-02 23.58.43.png](https://qiita-image-store.s3.amazonaws.com/0/125684/a634922b-8714-374b-8316-497d6dc9b5dc.png)

また、クリップボード管理ソフト「Clipit」が履歴保存を有効化するのか訊いてきます。
「履歴データは、パスワードのようなセンシティブなデータであっても平文で保存するけどいい？」みたいなことを言っています。構わなければ「Yes」をクリックしてください。
![スクリーンショット 2017-09-02 23.59.02.png](https://qiita-image-store.s3.amazonaws.com/0/125684/4d6a47b5-5d10-538e-02a1-3f8c5616d758.png)

# 使い道

背景のところでも述べていますが、個人的な開発環境として使えます。
また、突然の開発メンバ増員の際も、Dockerイメージを使いまわして、即座に開発環境を提供することもできます。

今回のDockerイメージは汎用的なので、そのままだとできることが限られています。なので、別途必要なパケージをインストールしたイメージを作成する必要があると思います。私の場合は、今回のイメージをベースイメージ(Dockerfileの`FROM`)として、JDKやAndroidStuioなど開発に必要なソフトをインストールしたイメージを作成し、使用しています。

# その他

## Dockerホストのバージョンについて

`Docker version 17.06.0-ce`とubuntu:16.10(ホストOSではなくベースイメージ)の組み合わせは、私が用意した検証環境において、**動作しなかった**です。XRDPサーバの起動でエラーが発生します。
どうやら、IPv6周りの設定の相性が悪いようで、Dockerホストの設定次第でうまく動くのかもしれません。
私の普段使用しているサーバが`Docker version 1.13.1`だったこともあり、~~めんどくさくて~~あまり深く調査していません。

なお、xrdpのバージョンがv0.6であるubuntu:14.04やubuntu:16.04であれば、`Docker version 17.06.0-ce`であっても動作します。

## 私の普段の使い方

以下のようにコンテナ起動の際に`-v`オプションを付与し、ホームディレクトリをマウントして使用しています。

```sh
$ docker run --rm -it \
    --privileged \
    -p 3389:3389 \
    -u $(id -u):$(id -g) \
    -e USER=yama \
    -e PASSWD=yamapasswd \
    -v ${HOME}/container_home:/home/yama \
    lxde_xrdp:ubuntu16.10
```

コンテナ内のデスクトップ環境において、ほとんどの個人設定はホームディレクトリに保存されるので、ホームディレクトリをマウントしておけば、コンテナを停止＆起動しても個人の設定が維持されます。
また、コンテナ内で永続化したいデータもホームディレクトリ配下に置いておけば、コンテナを停止しても問題ありませんし、ホストとのデータのやり取りにも便利です。
もちろん、ホストのuid/gidを引き継いでいるので、ファイルパーミッションのせいでファイル／ディレクトリが削除できないといったような問題もありません。