【画像で説明】DockerでAnaconda環境をつくり、コンテナの中でVSCodeを使う

# やること
1. 公式のAnacondaイメージをベースに自前のイメージをつくります。
2. コンテナを立ち上げ、Jupyter Labを起動します。
3. ブラウザから、コンテナ内で立ち上げたJupyter Labを操作します。
4. VSCodeでコンテナに接続します。
5. VSCodeからもコンテナ内部を編集できることを確認します。


# なにがうれしいの？？

- Anacondaをホストマシンにインストールすることのリスク回避（Homebrewやpipとの衝突）
- 複数人で開発環境を共有できる



# 環境
- MacOS X 10.14.5 (Mojave)
- VSCode 1.35.1
- Docker 18.09.2

# Dockerイメージをつくる

### Dockerfile

Anaconda公式イメージをベースにします。
https://hub.docker.com/r/continuumio/anaconda3

```Dockerfile

# ベースイメージ名:タグ名
FROM continuumio/anaconda3:2019.03

# pipをアップグレードし必要なパッケージをインストール
RUN pip install --upgrade pip && \
    pip install autopep8 && \
    pip install Keras && \
    pip install tensorflow 

# コンテナ側のルート直下にworkdir/（任意）という名前の作業ディレクトリを作り移動する
WORKDIR /workdir

# コンテナ側のリッスンポート番号
# 明示しているだけで、なくても動く
EXPOSE 8888

# ENTRYPOINT命令はコンテナ起動時に実行するコマンドを指定（基本docker runの時に上書きしないもの）
# "jupyter-lab" => jupyter-lab立ち上げコマンド
# "--ip=0.0.0.0" => ip制限なし
# "--port=8888" => EXPOSE命令で書いたポート番号と合わせる
# ”--no-browser” => ブラウザを立ち上げない。コンテナ側にはブラウザがないので 。
# "--allow-root" => rootユーザーの許可。セキュリティ的には良くないので、自分で使うときだけ。
# "--NotebookApp.token=''" => トークンなしで起動許可。これもセキュリティ的には良くない。
ENTRYPOINT ["jupyter-lab", "--ip=0.0.0.0", "--port=8888", "--no-browser", "--allow-root", "--NotebookApp.token=''"]

# CMD命令はコンテナ起動時に実行するコマンドを指定（docker runの時に上書きする可能性のあるもの）
# "--notebook-dir=/workdir" => Jupyter Labのルートとなるディレクトリを指定
CMD ["--notebook-dir=/workdir"]
```

### イメージビルド
Dockerfileが置いてあるディレクトリで以下を実行

```bash
docker build -t ANATA_NO_IMAGE_NAME .
```
### -t
ビルドしたイメージに名前(:タグ名)をつけるオプションです。

### . (最後のドット)
ビルドコンテキストの指定。
Dockerfileのあるディレクトリを指定します。
ここでは`.`なのでカレントディレクトリを指定しています。
docker buildコマンドではビルドコンテキスト内にあるDockerfileを自動で読みにいきます。


# コンテナ起動

## ① docker runコマンドで起動する場合

```bash
docker run -it \
-p 8080:8888 \
--rm \
--name ANATA_NO_CONTAINER_NAME(任意) \ 
--mount type=bind,src=`pwd`,dst=/workdir  \
ANATA_NO_IMAGE_NAME（上でビルドしたもの）
```

## このコマンド何してるの？？

### docker run
``` docker pull ``` と ``` docker create ``` と ``` docker start ``` を1発で行うやつ。
イメージがホストマシン上になければDocker Hubからpullしてきて、コンテナつくって、コンテナを起動します。


### -it
```-i``` と ```-t```をまとめて書いてるものです。

- ```-i``` or ```--interactive```
=> ホスト側のシェルからコンテナ内を操作できるようにする。

- ```-t``` or ```--tty```
=> 仮想ttyの割当て。キーバインドを使用したり、色を出力したりシェル操作を便利に行うために指定。

こちらが非常にわかりやすいです。
https://teratail.com/questions/121780
https://teratail.com/questions/19477
https://teratail.com/questions/100044


### -p 8080:8888
```ホスト側のポート番号```:```コンテナ側のポート番号``` をひも付けます。
ローカルのブラウザから```localhost:8080```でコンテナ内で立ち上げたjupyterをひらけるように。

### --rm 
コンテナを停止したときに自動で削除(docker rm)までしてくれます。

### --name ANATA_NO_CONTAINER_NAME
任意のコンテナ名をつける。
なくても動きます。
これを指定しない場合、自動でコンテナ名が付与されます。


### --mount type=bind,src=\`pwd\`,dst=/workdir
ホスト側のディレクトリとコンテナ側のディレクトリを同期させる。

- type=bind => マウントタイプにbindを指定(他にvolume,tmpfsの指定が可能)
- src=\`pwd\` => ホスト側のディレクトリにカレントディレクトリを指定(任意のディレクトリでOK)
- dst=/workdir => コンテナ側のディレクトリにルート直下のworkdir/を指定(任意のディレクトリでOK)

**※``` -v ``` でも ``` --mount ```と同じことができます。**


- ``` -v ``` => ホスト側に存在しないディレクトリをマウントしようとすると新規作成
- ``` --mount ``` => ホスト側に存在しないディレクトリをマウントしようとするとエラー


`--mount` を使った方が誤って空のディレクトリをマウントしてしまう事故などを防げます。

例えば空のディレクトリをマウントしてしまうと、必要なファイルが覆い隠されてしまうためコンテナが正常に動作しなくなる場合がある。
(見た目上は上書きされたように見えるが、マウント元のディレクトリの中身でマウント先のディレクトリの中身が隠れているだけで削除されるわけではない）

公式でも以下のように ``` --mount ``` オプションでの書き方が推奨されているようです。

> ヒント: はじめて利用する方は --mount を利用してください。 上級ユーザーは -v や --volume を用いることに慣れているかもしれませんが、--mount を利用するように心がけてください。 --mount の方が簡単に利用することができるとの調査もあります。

https://matsuand.github.io/docs.docker.jp.onthefly/storage/bind-mounts/

### ANATA_NO_IMAGE_NAME
先程docker buildで作ったイメージ名を指定します。



## ② docker-composeで起動する場合

上記``` docker run 〜 ```コマンドをコード化したようなものです。


### docker-compose.yml

```docker-compose.yml
version: '3'　# docker-composeファイルの書式バージョン。最新の’３’を指定（2019/6/27現在）
services:
  dev: # 任意の名前（ディレクトリ名 + dev がコンテナ名となります）
    build:
      context: .
      dockerfile: Dockerfile
    image: ANATA_NO_IMAGE_NAME
    ports:
    - "8080:8888"
    volumes:
    - .:/workdir
```

### コンテナ起動

docker-compose.ymlが置いてあるディレクトリで以下を実行

```bash
docker-compose up
```

# Jupyter Labを開く
ブラウザのアドレスバーに　```localhost:8080```　を入力しEnter。
=> Jupyter Labが開きます。マウントさせたディレクトリと同じものが入っていればOK。

### 同期されるか確認
ローカルのディレクトリで新しくファイルを作ってみます。

```bash
touch test.ipynb
```
再読込ボタンでJupyter Labのエクスプローラを更新して、``` test.ipynb ``` が追加されていれば成功です。

# コンテナ停止

### docker runで起動した場合

```bash
docker stop ANATA_NO_CONTAINER_NAME
```

### docker-compose up　で起動した場合

```bash
docker-compose stop
```

# コンテナ削除

### docker runで起動した場合

```bash
docker rm ANATA_NO_CONTAINER_NAME
```


### docker-compose up　で起動した場合

```bash
docker-compose down
```

# コンテナの中でもVSCodeつかいたい人
:raised_hand:


# 拡張機能のインストール
表示 > コマンドパレット > ``` 拡張 ```と打つ > ビュー：拡張機能を表示する
![スクリーンショット 2019-06-24 10.34.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/09742a06-bfb4-de19-7b9c-8d605692f729.png)

サイドバーの入力欄に``` remote ``` と打つ > ```Remote - Containers```を選択 > インストール

![スクリーンショット 2019-06-28 11.26.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/c979e3bf-d930-8b15-8601-96dc927e8b74.png)

これが出てくればOK
![スクリーンショット 2019-06-28 11.32.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/056d0be9-3b7e-d796-3ed4-e70f3b1f4ec7.png)


# もう一回コンテナ起動

- コンテナ名 => ``` test_container ```
- 使用イメージ => ``` anaconda_test_image ```

として起動してみます。

```bash
docker run -it -p 8080:8888 --name test_container \ 
--mount type=bind,src=`pwd`,dst=/workdir anaconda_test_image
```

# コンテナに接続

左下のボタンを押して、
``` Remote - Containers: Attach to Running Container… ```を選択

![スクリーンショット 2019-06-28 12.26.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/5cd9e8e9-f1bd-60b6-3ad0-cba1a5206e71.png)


起動中のコンテナ一覧が出てくるので、接続したいコンテナを選択します。

![スクリーンショット 2019-06-28 13.00.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/2921b29a-2b96-127d-46cc-4efd5511e0f4.png)


新しいウィンドウが開きます。

左下のボタンが``` Attached Container ~ ```となります。
![スクリーンショット 2019-06-28 13.02.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/884b9379-b74f-adee-3eed-c177e3b74093.png)


以下の手順で、マウント先（コンテナ側）のディレクトリを選択します。

![スクリーンショット 2019-06-28 13.03.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/d1e1a3f0-dbad-7009-4394-fc234636f415.png)

マウント元（ホスト側）のディレクトリと同じ内容が表示されればOK。

![スクリーンショット 2019-06-28 13.55.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/2ce58107-df47-5bd2-34d8-73ea660dafe0.png)

Jupyterで新規ファイル作成してみます。
```Untitled.ipynb```がVSCode側にも即反映されるのが確認できるかと思います。

![スクリーンショット 2019-06-28 13.51.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/8306940a-61ee-12e3-d131-84de6518704e.png)

# コンテナ側に拡張機能インストール
ローカルでVSCodeにインストールされている一部の拡張機能が、コンテナ側には入っていません。
必要に応じでインストールします。

![スクリーンショット 2019-06-28 14.13.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/68a163b1-6e8d-76c7-fb30-1c29a7e5bae5.png)


# 最後に
ここまで読んで頂きありがとうございました。
VSCodeでJupyterのように実行結果を表示させる方法も書いたので、もしよろしければ。
=> [【画像で説明】VSCodeをJupyter化する](https://qiita.com/komiya_____/items/547ae8b5a9b031f18b59)

# 参照
ありがとうございました！

- https://www.udemy.com/docker-k/
- https://www.qoosky.io/techs/0a0bd52cd3
- http://onoz000.hatenablog.com/entry/2018/02/11/142347
- https://qiita.com/shibukawa/items/0daab479a2fd2cb8a0e7
- https://qiita.com/hgsgtk/items/0fc14802c3234e95427f
- https://teratail.com/questions/121780
- https://teratail.com/questions/19477
- https://teratail.com/questions/100044
- https://qiita.com/yoskeoka/items/01c52c069123e0298660
- https://code.visualstudio.com/docs/remote/containers