gym.render() でDISPLAYのエラーが出たときの対処法

# 困ったこと
dockerコンテナでOpen AI Gymのサンプルコードを実行しようとしたら、次のようなエラーメッセージが出てしまいました。


```bash:ターミナル
Traceback (most recent call last):
  File "hello_gym.py", line 8, in <module>
    env.render()
  File "/usr/local/lib/python3.6/dist-packages/gym/core.py", line 240, in render
    return self.env.render(mode, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/gym/envs/classic_control/cartpole.py", line 174, in render
    from gym.envs.classic_control import rendering
  File "/usr/local/lib/python3.6/dist-packages/gym/envs/classic_control/rendering.py", line 25, in <module>
    from pyglet.gl import *
  File "/usr/local/lib/python3.6/dist-packages/pyglet/gl/__init__.py", line 244, in <module>
    import pyglet.window
  File "/usr/local/lib/python3.6/dist-packages/pyglet/window/__init__.py", line 1880, in <module>
    gl._create_shadow_window()
  File "/usr/local/lib/python3.6/dist-packages/pyglet/gl/__init__.py", line 220, in _create_shadow_window
    _shadow_window = Window(width=1, height=1, visible=False)
  File "/usr/local/lib/python3.6/dist-packages/pyglet/window/xlib/__init__.py", line 165, in __init__
    super(XlibWindow, self).__init__(*args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/pyglet/window/__init__.py", line 570, in __init__
    display = pyglet.canvas.get_display()
  File "/usr/local/lib/python3.6/dist-packages/pyglet/canvas/__init__.py", line 94, in get_display
    return Display()
  File "/usr/local/lib/python3.6/dist-packages/pyglet/canvas/xlib.py", line 123, in __init__
    raise NoSuchDisplayException('Cannot connect to "%s"' % name)
pyglet.canvas.xlib.NoSuchDisplayException: Cannot connect to "None"
```

# 実行環境
実行環境は次のとおりです。

|  | バージョン |
|:--:|:-----------:|
| OS | Ubuntu18.04 |
| python | 3.6 |
| gym | 0.18.0 |
| pyglet | 1.5.0 |
| xvfb | 2:1.19.6 |
| x11vnc | 0.9.13 |

Ubintu18.04ベースのdockerコンテナにvncを設定して、render()の出力結果をブラウザで確認しようとしています。

# 考えたこと
- VNCの設定ができていない
- X displayの設定ができていない
- gymサンプルコードのエラー（render周り）
- pygletのバージョンが合っていない（pygletは最新を入れていました）

gymのサンプルコードは次のとおりです。

```python:sample.py
import gym
env = gym.make('CartPole-v0')
env.reset()
for _ in range(1000):
    env.render()
    env.step(env.action_space.sample()) # take a random action
env.close()
```
(実はこのコードは、別の理由でエラーが起こるのですが、それはまた別の記事で…)

# 原因
DISPLAYの設定ができていませんでした。

ターミナルで環境変数を確認します。

```bash:ターミナル
$ printenv
```

一覧がずらずらっと表示されて

```bash:ターミナル
略
LANG=C.UTF-8
OLDPWD=/home/user
VISIBLE=now
USER=user
PWD=/home/user/workspace
HOME=/home/user
略
```
`DISPLAY=`の表示がありません。設定したはずなのに…。

# DISPLAY 環境変数の設定 (うまくいかなかった例)
次の３つの方法は試しましたが、うまくいきませんでした。
解決策を早く知りたい方は[ジャンプ](#解決策)

### 設定法１
もともとはDockerfileの中で設定していました（できているつもりでした）

```Dockerfile:Dockerfile
ENV DISPLAY=:0
```
でも、コンテナ内の環境変数は設定できていませんでした。

### 設定法２ `docker run`のオプションで設定する
次に試したのがこの方法。

```bash:ターミナル
docker run -e DISPLAY=$DISPLAY 以下略
```
ちなみに、`-e`を`--env`に変えても、だめでした笑

### 設定法３　`startup.sh`で設定する
`startup.sh`は、ざっくり言うとコンテナを起動する際に、実行させることのできるシェルスクリプトです。
（よくわかっていないだけw）

Dockerfileの最後に`CMD`命令を記述できますが、複数のコマンドを実行させたい場合に、
それらを`startup.sh`の中に記述することで実行させます。

```Dockerfile:Dockerfile
CMD ["/startup.sh"]
```

なぜ`startup.sh`を使ったかというと、`ssh`と`vnc`を`supervisord`によって起動させたかったので、`/usr/bin/supervisord -c /etc/supervisor/supervisord.conf`は少なくともbashで実行する必要があったからです。
そのため、コマンドを2行記述しました。

```bash:startup.sh
#!/bin/bash
/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
export DISPLAY=:0
```

結局この方法でも、エラーは解決しませんでした。

# 解決策
Dockerfileの中で次のように記述します。

```Dockerfile
RUN echo "export DISPLAY=:0"  >> /etc/profile
```

これで、コンテナ内の環境変数にDISPLAYを設定することができました。

# まとめ
dockerコンテナ内で`gym`を実行した際に`pyglet.canvas.xlib.NoSuchDisplayException: Cannot connect to "None"`というエラーが表示されたら、
DISPLAYの環境変数が設定できていないかもしれません。

Dockerfile内で`RUN echo "export DISPLAY=:0"  >> /etc/profile`と実行すると設定できました。