GitLabを使ってNVIDIA Dockerに自動デプロイ

機械学習とかGPUを使うプログラムを書いているとき，リモートのGPU搭載マシンで実行させたいときってないでしょうか．．．
ローカルPCにGPU積んでないとか，積んでるけどリモートのGPUが強力だからそっちで学習がっつり回したいとか，ファンうるさいからあっちでやってくれとか．

私は，GitレポジトリにあげてリモートでPullして実行をよくやります．が，CIツールを使って，もうちょっといい感じにできないかなと試してみましたので，そのときのメモ・手順をここに置きたいと思います．

#### 構築イメージ

閉じた環境でやりたかったので，GitLabを利用しローカルにGitレポジトリマネージャー，CIサーバ，ランナーを構築します．そしてgit pushからのGPUマシンにデプロイをしていきます．

また，他の環境にも展開したいときサクッと行きたいので，Docker Composeを利用して構築します．
デプロイ先では，そこの環境を汚したくないので，ここもDockerを使います．Dockerコンテナ上でGPUを使うことになるので，NVIDIA Dockerで構築します．

こんなかんじ．
<img width="621" alt="EnvImage.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450728/b596d25f-f4b0-ec27-3595-94634893bf5a.png">


## 事前準備

1. GitLab構築マシン（CIサーバー側）
2. GPU搭載Linuxマシン（デプロイ先）

※ 両方にDocker Compose，2にはNVIDIA Dockerもインストールしてください．

1, 2は同一マシンでも問題ありません．
ただし，NVIDIA Dockerのサポート対象の関係上[[参考](https://github.com/NVIDIA/nvidia-docker/wiki/Frequently-Asked-Questions#platform-support)]，2がLinuxであることは必須です．

本稿ではDocker Compose，NVIDIA Dockerのインストール方法については割愛させていただきます．インストール方法については，公式ドキュメントなどをご参考ください．

- Docker Compose: http://docs.docker.jp/compose/install.html
- NVIDIA Docker: https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(version-2.0)


## GitLabを構築

まずGitLabをDocker Composeで構築します．
以下はYAMLの例です．`initial_shared_runners_registration_token`でランナー登録時に使用するトークンを与えています．（今回はテストのため非常に安直）

実行コマンド例）`docker-compose -f gitlab.yml up -d`

```yml:gitlab.yml

version: '2'

services:
  gitlab:
    image: 'gitlab/gitlab-ce:latest'
    container_name : gitlab
    restart: always
    hostname: 'mac-book.local'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://mac-book.local'
        gitlab_rails['time_zone'] = 'Asia/Tokyo'
        gitlab_rails['initial_shared_runners_registration_token'] = 'token-share'
    ports:
      - '80:80'
      # - '443:443'
      # - '22:22'
    volumes:
      - './etc/gitlab:/etc/gitlab'
      - './log:/var/log/gitlab'
      - './data:/var/opt/gitlab'
```

## GitLab RunnerをNVIDIA Dockerに構築＆ワーカー登録 

GPU搭載マシン側にGitLab Runnerコンテナを構成します．YAMLの例は下記に示します．

ポイントは，`/var/run/docker.sock:/var/run/docker.sock`です．
ランナーをコンテナで動作させている場合，Docker in Docker (dind)を使うかと思います．
NVIDIA Dockerもdindいけるかもですが，NVIDIA Driverのマウントとか，私の実力では修羅の道になりそうなので，[ここ](https://github.com/NVIDIA/nvidia-docker/wiki/Frequently-Asked-Questions#can-i-use-it-with-docker-in-docker-aka-dind)にあるようにDockerソケットのマウントでシンプルに行きます．
**※ Dockerソケットのマウントはセキュリティ上好ましくない行為ですので，ご注意を．**

```yml:gitlab-runner.yml
version: '2'

services:
  gitlab-runner:
    image: 'gitlab/gitlab-runner:latest'
    container_name : gitlab-runner
    volumes:
      # - './etc/gitlab-runner:/etc/gitlab-runner'
      - '/var/run/docker.sock:/var/run/docker.sock'
    dns: '192.168.X.X'
```

参考元：https://docs.gitlab.com/runner/install/docker.html

例ではURLにドメインを利用しているので，DNSを設定しています．
コメントアウト部分は設定ファイルのディレクトリです．有効にする場合，下記のような設定ファイルを事前に配置しておく必要があります．（無いとエラーで怒られます）

```config.toml
concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800
```

ランナーのコンテナを立てたら，ワーカーの登録を行います．登録のコマンド例は以下の通りです（例は全レポジトリ共有のワーカーの登録になります）．`runtime`オプションは必ず`NVIDIA`にしてください．
また，ワーカーを登録するごとに上記`config.toml`に設定状況が記録されるので，永続化しておくのが良いと思います．

各オプションの意味は下記URLや，`gitlab-runner register`のヘルプで確認できます．
参考：https://docs.gitlab.com/runner/configuration/advanced-configuration.html

```sh:登録コマンド
docker-compose -f gitlab-runner.yml exec gitlab-runner gitlab-runner register \
--non-interactive \
--request-concurrency 2 \
--url http://mac-book.local \
--registration-token "token-share" \
--executor docker \
--docker-image ubuntu:latest \
--docker-runtime nvidia \
--docker-dns "192.168.X.X" \
--docker-privileged=true \
--docker-pull-policy "never" \
--description "docker-runner" \
--tag-list "docker,oneshot" \
--run-untagged \
--locked="false"
```

登録が成功すると，以下のようにGitLabの管理者ページの登録ランナー一覧に表示されます．
<img width="1235" alt="RegRunner.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450728/f0d5ddb9-f7db-3e68-5a00-11648c4a9be0.png">

今回ドメインを使ってやっているので，ランナーとワーカー登録時にDNSを教えています．
ちなみに，ランナーはジョブ取得時といったGitLabとコネクションするとき，ワーカーはGitレポジトリ引っ張ってくるときにDNS使ってます．


## Let's Try!!

実際にデプロイがうまくいくか，レポジトリを適当に作ってテストしてみます．

CI用YAMLは以下の感じです．PyTorchでGPU調べてみるだけのシンプルなものです．`nvidia-smi`するだけでもよかったかも．

```yml:gitlab-ci.yml
image: 'pytorch/pytorch:latest'

deploy:
  stage: deploy
  script:
    - python3 test-gpu.py
```

```python:test-gpu.py
import torch 
print('torch.cuda.is_available() ...', torch.cuda.is_available())
print('torch.cuda.get_device_name(0) ...', torch.cuda.get_device_name(0))
```

ジョブの実行結果はこちら（GitLabのレポジトリ→CI/CDから確認できます）．どうやらうまくいったみたいです．
<img width="591" alt="pytorch-do.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450728/60e5ec0e-380a-47e0-c334-5597ac7a266c.png">


## さいごに
CIツール使ってGPUコンテナに自動デプロイ的なことをやりました．

ただ，デプロイ先でのデータ永続化について扱っていないため，せっかく学習させたモデルが消えてしまったりします:scream:
このままでは正直使えないので，そこらへん別投稿とかで書けたらなぁと思ったり．
あと，最近購入したJetsonをデプロイ先にして遊びたいなぁ．．

ありがとうございました．