Pycharm × リモート実行 × Docker

# はじめに
　この記事は，DeepLearningを行うために、リモートにある開発用マシンで動くコンテナ上でコードを実行しつつ、pycharmで開発・編集を行えるようにするしたときの備忘録です。例えば，研究室で開発マシンを共有しているときは，環境が競合しないように個々人でコンテナを立てて開発を行うことになると思います。同じようなことをしたい人がいれば参考にしてください。

今回の環境構築により以下の利点が得られます。

- pycharmのような優れた統合開発環境でコーディングができる
- GPUなどが積んである高スペックなマシン上で実行できる
- GUI周りで必要だった設定も要らない(PycharmでGUI部分が処理される)
- 深層学習用に整備されたコンテナを使用できる


# 事前準備
pycharm，Docker，nvidia-docker，CUDA，sshなどのインストールに関してはすでに済ませてあることを前提とします。ただし，PycharmはProfessionalエディションでなければいけません。学生は無料でインストールできます。

# Dockerイメージの作成
Dockerfileを使って構築していきます。
**※完成形のDockerfileはこのページの一番下にあります。**

元となるイメージは，今回はGPUを使うのでnvidia/cudaのものを使います。
まず，イメージをpullで取得します。

```sh
$ docker pull nvidia/cuda:[タグ：自分の環境にあったもの]
```
タグは以下のサイトから開発マシンの環境にあったものを選択して下さい
<https://hub.docker.com/r/nvidia/cuda/tags/>

今回自分の環境ではcudaのバージョンは8.0だったため，Dockerfileの元イメージは以下のものになります。

```dockerfile:Dockerfile
FROM nvidia/cuda:8.0-cudnn6-runtime-ubuntu16.04
```


## DL環境（anaconda3，tensorflow，keras）
ここから必要なライブラリを揃えていきます。
まずはディープラーニングが行える環境を整えます。今回はAnaconda3を使います。
いろいろ試しましたが，本家のcontinuumio/anaconda3のDockerfileを参考にするのがやはり一番いいと思います。
参考：<https://hub.docker.com/r/continuumio/anaconda3/~/dockerfile/>

上記のものにコメントを付与しておきます。

```dockerfile:Dockerfile

# この後インストールするanacondaにPATHを通しておく
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
ENV PATH /opt/conda/bin:$PATH

# 必要なパッケージ等のインストール
# 2行目のlibgllib2.0-0等は，opencvを入れたときに必要となるパッケージです（無いとインポートエラーになります）。
RUN apt-get update --fix-missing && apt-get install -y wget bzip2 ca-certificates \
    libglib2.0-0 libxext6 libsm6 libxrender1 \
    git mercurial subversion

# anacondaのインストールです。4行目以降，何をやっているかはよくわかってませんが，
# やっとくことで後でopencvを入れる時にmp4対応のものがインストールされます。
RUN wget --quiet https://repo.continuum.io/archive/Anaconda3-5.1.0-Linux-x86_64.sh -O ~/anaconda.sh && \
    /bin/bash ~/anaconda.sh -b -p /opt/conda && \
    rm ~/anaconda.sh && \
    ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \
    echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc && \
    echo "conda activate base" >> ~/.bashrc

# tensorflowとkerasのインストール(cudaのバージョンに合わせるために1.4.1に指定)
RUN pip install tensorflow-gpu==1.4.1 keras
```

## opencvのインストール
ここは人によっては飛ばしてくれてもかまいません。
opencvは，今回mp4ファイルも扱えるようにしたかったため，ffmpegもインストールできるようにする必要があります。この部分の環境構築は手間取る可能性もありますが，前節のやり方をすると特に何もやらず以下のコマンドだけで良くなることが分かりました。

```dockerfile:Dockerfile
RUN conda install -y opencv
```

ここまででpython関係のインストールは終了
※リモートでやる必要のない方はこれ以降の作業は必要ありません。


## sshで外部からコンテナにアクセスできるようにする
外部から実行環境にアクセスできるようにします。Pycharmと連携させれば，実行だけコンテナー内で行うということもできるようになります。
※コンテナーに直接sshで繋ぐのはセキュリティ的に問題があるかもしれませんが，今回は開発マシンはインターネットには繋いでおらず，ローカルネットワーク内のGPUマシンに接続するという前提なので無視します。

以下のサイトを参考にしました。
参考：<https://qiita.com/FaithnhMaster/items/a7e9aea842b9eb7392e2#%E5%AE%9F%E7%8F%BE%E3%81%99%E3%82%8B%E3%81%AE%E3%81%AB%E4%BD%BF%E3%81%A3%E3%81%9F%E7%92%B0%E5%A2%83>
※ただし，今回はdocker-composeは使っていません。

ここで，bashrc等のPATH部分にanacondaのパスも含めることを忘れずに行います。
本当は最初のENVでここで設定しているもの全部を含めてしまった方がいいかもしれません。

それでは完成形のDockerfileを以下に示します。


```dockerfile:Dockerfile
FROM nvidia/cuda:8.0-cudnn6-runtime-ubuntu16.04

ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
ENV PATH /opt/conda/bin:$PATH

# アップデートと足りないライブラリの取得及びAnaconda3のインストール
RUN apt-get update --fix-missing && apt-get install -y wget bzip2 ca-certificates \
    libglib2.0-0 libxext6 libsm6 libxrender1 \
    git mercurial subversion

RUN wget --quiet https://repo.continuum.io/archive/Anaconda3-5.1.0-Linux-x86_64.sh -O ~/anaconda.sh && \
    /bin/bash ~/anaconda.sh -b -p /opt/conda && \
    rm ~/anaconda.sh && \
    ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \
    echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc && \
    echo "conda activate base" >> ~/.bashrc
    
# tensorflowとkerasのインストール(cudaのバージョンに合わせるために1.4.1に指定)
RUN pip install tensorflow-gpu==1.4.1 keras
# opencvのインストール
RUN conda install -y opencv

# ssh
RUN apt-get install -y git openssh-server
# sshサービスの起動とリモートでGPUを使うための設定
RUN ( echo "#!/bin/bash"; \
      echo ""; \
      echo "service ssh start"; \
      echo "tail -f /dev/null"; ) > /root/entrypoint.sh && \
    chmod +x /root/entrypoint.sh && \
    sed -i.bak 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
    echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config && \
    ( echo ""; \
      echo "export PATH=/usr/local/nvidia/bin:/usr/local/cuda/bin:/opt/conda/bin:$PATH"; \
      echo "export LIBRARY_PATH=/usr/local/cuda/lib64/stubs:"; \
      echo "export LD_LIBRARY_PATH=/usr/local/nvidia/lib:/usr/local/nvidia/lib64"; \
    ) >> /root/.bashrc && \
    mkdir /root/.ssh && chmod 700 /root/.ssh && \
    ( echo "PATH=/usr/local/nvidia/bin:/usr/local/cuda/bin:/opt/conda/bin:$PATH"; \
      echo "LIBRARY_PATH=/usr/local/cuda/lib64/stubs:"; \
      echo "LD_LIBRARY_PATH=/usr/local/nvidia/lib:/usr/local/nvidia/lib64"; \
    ) >> /root/.ssh/environment

# ssh用パスワードの設定
RUN echo 'root:root' | chpasswd

EXPOSE 22
CMD ["/root/entrypoint.sh"]

```

※もう一度言いますが，最初の元イメージとtensorflowは自分の環境にあったバージョンを選択してください。

また，今回はssh接続のためのパスワード設定もDockerfileの中で行ってしまっていますが，Dockerfileを共有，公開したりする場合はセキュリティ的に非常にまずいので，その場合は面倒くさいですが，パスワード設定部分はコメントアウトした上でイメージをビルドし，コンテナーを立ち上げてその中で設定してから，イメージ化してください。
より安全性を求めるならパスワードではなく鍵を使った方がいいと思います。

## ビルドとコンテナーの立ち上げ
上記のDockerfileからイメージを作成します。

```sh
$ cd Dockerfileのあるディレクトリ
$ docker build -t イメージ名 .
```
イメージからコンテナーを起動します

```sh
$ nvidia-docker run --rm --name コンテナ名 -v $PWD:/home/ -p 8022:22 -itd イメージ名
```

- --rm：コンテナストップ時にコンテナ削除
- -v：マウントしたいローカルフォルダを指定
    - コード実行に必要なファイルがある場所を指定する
- -p：ポートフォワーディング（Dockerホストマシンの8022ポートへの通信をこのコンテナの22ポートに）
- -d：バックグラウンド実行

# Pycharmとの連携
前節で立ち上げたコンテナに，Pycharmからリモートアクセスする手順を示します。
まず，目的のprojectを立ち上げます。[File]→[Settings]→[Project Interpriter]を開き，Project Interpriterの右の設定マークをクリック，[Add...]を選択します。
![無題.png](https://qiita-image-store.s3.amazonaws.com/0/239881/3331297f-1aff-e4e2-02b1-0f1d9b181303.png)
そして，左のタブからSSH Interpreterを選択し(リモートのコンテナに繋ぐので)，リモートマシンのアドレスをHostに，コンテナのユーザ名をUsernameに入力します(ユーザ名はそのままだとrootのみ)。そして，赤丸で囲ったPortは，今回コンテナのssh(22番ポート)にはリモートマシンの8022ポートをフォワードするようにして外部に公開しているので(-p 8022:22) ，この部分は8022にしておきます。
![無題2.png](https://qiita-image-store.s3.amazonaws.com/0/239881/d895800f-1688-f2e5-e24d-81ff3eb99a85.png)
これで[Next]を押し，コンテナに設定したパスワード(Dockerfile内)を入力します。次に，接続先のインタプリタと同期するフォルダを設定します。インタプリタは今回使うDockerfileだと，「/opt/conda/bin/python」を設定すればよいですが，コンテナ内で以下のコマンドで確認しておくとよいと思われます。

```sh
$ which python
/opt/conda/bin/python
```

フォルダの同期先はデフォルトだとtmpフォルダになっているので，好きなフォルダに設定します。[Automatically upload to server]はチェックを外しておいた方がいいです。以上で設定は完了です。
![無題.png](https://qiita-image-store.s3.amazonaws.com/0/239881/894c27f1-d1dd-bf0a-ce56-cfd116e649ef.png)
ここで設定したリモートインタプリタを使うことで，Pycharmでコーディングしたプログラムをリモートマシンのコンテナ上で実行することが出来ます。

なお，編集したファイルは[Tools]→[Development]→[Sync with Deployed to...]からリモートマシンと同期が行えます。編集を反映させるには同期を行ってから実行をしなければいけません。
※この部分は少し面倒くさくなってしまいます。

また，たまに同期先の設定が消えてしまうことがあるため，その場合は，[Settings]→[Deployment]から，[Mappings]タブを選び，Deployment path on server 'xxxx'のところに同期するフォルダを書きます(例えば，「/home/work/」など)。

以上で設定は終わりです。