Docker初心者でも、GPUを使うプログラム(アプリ)を高火力DOKで動かしたい

## はじめに

株式会社晴工雨読の片山と申します。晴工雨読の概要はこちらからご覧ください。

https://seikoudoku.co.jp/

Tech ブログ第 5 弾は、「GPU を使うプログラムは持っているけど、Docker を使ってコンテナ実行サービスで実行したことがない」という方に向けて、自分の手元にある GPU を使うプログラムをコンテナ実行サービスで動かす方法について丁寧に解説します。

## 想定読者

- 大学生レベルのエンジニアリング力がある方
- GPU を使って動かしたい具体的なアプリケーション（学習・推論 AI など）がある方
- 手元にある家庭用の GPU では力不足で、高性能な GPU を安価に試したい方
- Docker を使ったことがない、あるいは Docker で GPU を使ったことがない方

今回利用するのは、さくらインターネットが提供するコンテナ実行サービスの、高火力 DOK です。高火力 DOK は、高性能 GPU を従量課金制かつ比較的低価格で利用できる点が特徴です。一方で、比較的新しいサービスのためにドキュメント以外の事例が少ない状況なので、ここで利用方法の Tips を残しておきたいと思います。

https://www.sakura.ad.jp/koukaryoku-dok/

## Docker とは？

Docker とは、コンテナと呼ばれる仮想化された環境でアプリケーションを実行するための技術です。コンテナは、アプリケーションの実行に必要なすべての部品（コード、ライブラリ、設定ファイルなど）をまとめてパッケージ化します。これにより、異なる環境（開発環境、テスト環境、本番環境など）でも、常に同じようにアプリケーションを実行できるようになります。

Docker を使うメリットは多々ありますが、例えば以下のものが挙げられます。

- 環境構築を容易にできる！
  アプリケーションの実行に必要な環境を Dockerfile と呼ばれるファイルに記述することで、誰でも同じ環境を簡単に構築できます。これにより、開発環境と本番環境の違いによる問題や、チームメンバー間で環境構築の手間を減らすことができます。特に GPU を使う場合でも、GPU を使うアプリケーションの実行に必要な環境（CUDA ドライバなど）を Dockerfile に記述することで、誰でも同じ GPU 環境を簡単に構築できます。
- 移植性が向上する！
  コンテナは、ホスト OS に依存しないため、様々な環境で実行できます。これにより、クラウド環境への移行や、異なるサーバー間でのアプリケーションの移動が容易になります。
- 開発効率が向上する！
  コンテナを使うことで、アプリケーションの開発、テスト、デプロイのプロセスを効率化できます。例えば、開発環境をコンテナで構築し、テスト環境もコンテナで構築することで、開発からテストまでの流れをスムーズにすることができます。

いかがですか？ここでは簡単に解説しましたが、Docker は、アプリケーションの開発・実行が効率化される強力なツールであることは間違いありません。この機会に、Docker の門を叩いてみましょう！

https://knowledge.sakura.ad.jp/13265/

## 実際のプログラムをコンテナ実行サービスで動かすまでを解説

今回は、簡単な画像分類タスクを行うプログラム(tutorial.py)を例にとって、実際のプログラムをコンテナ実行サービスで動かす方法について、6 ステップで解説していきます。プログラムそのものの解説というよりは、そのプログラムを「どのように高火力 DOK に対応させるか」「Docker イメージにするか」を解説したいので、GPU を利用する画像分類タスクでありつつも、短めで簡単な以下のプログラム(`tutorial.py`)が手元にあると想定して進めていきます。

```python:tutorial.py
import tensorflow as tf
from tensorflow.python.client import device_lib
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Input
from tensorflow.keras.preprocessing import image
import numpy as np
from PIL import Image
from logging import getLogger, Formatter, INFO, FileHandler
logger = getLogger(__name__)
logger.setLevel(INFO)

# FileHandlerの設定
if save_dir == None:
   fh = FileHandler('your_log.log')
else:
   fh = FileHandler(f'{save_dir}/your_log.log')
fh.setLevel(INFO)
format = Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(name)s - %(funcName)s - %(message)s')
fh.setFormatter(format)
logger.addHandler(fh)

# データセットの読み込み
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

# データの正規化
x_train = x_train / 255.0
x_test = x_test / 255.0

# モデルの構築
model = Sequential([Input(shape=(32, 32, 3)), Conv2D(32, (3, 3), activation='relu'), MaxPooling2D((2, 2)), Flatten(), Dense(10, activation='softmax')])

# モデルのコンパイル
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# モデルの学習
logger.info(f'{device_lib.list_local_devices()}')
if tf.config.list_physical_devices('GPU'):
   logger.info('GPU is available')
else:
   logger.info('GPU is not available')

model.fit(x_train, y_train, epochs=10, batch_size=32, validation_data=(x_test, y_test))

# モデルの保存
model.save('my_model.keras')
logger.info('Model is saved to my_model.keras')

# 新しい画像の分類
img_path = 'your_image.jpg'  # ここに分類したい画像のパスを指定
img = image.load_img(img_path, target_size=(32, 32))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = x / 255.0

predictions = model.predict(x)
predicted_class = np.argmax(predictions)
logger.info(f'Predicted class: {predicted_class}')
```

### 1. Dockerfile を作成

まず、Docker コンテナでプログラムを実行するにあたって、そのプログラムがどのような環境で実行されているのか、実行ファイルの起動コマンドは何か、等を記述したファイル『Dockerfile』というものを作成していきます。Dockerfile のファイル名は必ず`Dockerfile`としてください。
同じ階層に`requirements.txt`と`tutorial.py`をおいてください。

```Dockerfile:Dockerfile
# Python言語の実行環境を指定
FROM python:3.9-slim

# 環境の中にワーキングディレクトリを作成
WORKDIR /app

# 利用するファイルをコピー
COPY ./requirements.txt /app/
COPY ./tutorial.py /app/

# 環境設定ファイルを元に環境構築
RUN pip install --no-cache-dir -r requirements.txt

# 実行ファイルを起動コマンドで実行
CMD ["python", "tutorial.py"]
```

Dockerfile については簡単に掲載しておきます。なんとなくの理解で一通り実行したい方は、ここだけ読んでいただければひとまず十分かと思います。詳しくは下の記事をじっくり読んでいただければ勉強になると思います。

簡単にいうと、Dockerfile は、新しいパソコンを作るための説明書のようなものです。新しいパソコンを作るには、まず、どんなパソコンにしたいのかを決めなければなりません。例えば、「ゲームをするための高性能なパソコン」や「プログラミングをするためのシンプルなパソコン」など、目的によって必要なものが変わってきます。Dockerfile は、この「どんなパソコンにしたいのか」という部分を、コンピュータが理解できる言葉で書いたものです。例えば、「Ubuntu という OS をベースに、Python と Django をインストールして、Web アプリを実行できる環境を作りたい」といった具合です。この Dockerfile の指示に従って、Docker というソフトウェアが自動的にパソコン（この場合は、Docker コンテナと呼ばれる仮想的なパソコン）を作ってくれます。

より正確にいうと、Dockerfile は、Docker イメージを作成するためのテキストファイルです。このファイルに記述された手順に従って、Docker エンジンがベースとなるイメージから新しいイメージをビルドします。ビルドされたイメージから、コンテナを起動することができます。Dockerfile は、コンテナの再現性のある構築を可能にし、開発環境や本番環境の管理を効率化します。

- [【Docker 初心者】Dockerfile の書き方について](https://qiita.com/gon0821/items/f9e3bcbb6cb01d4ef7fa "【Docker初心者】Dockerfileの書き方について")
- [Dockerfile を書くベストプラクティス](https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html "Dockerfileを書くベストプラクティス")

また、今回の環境設定ファイルは、以下のように指定します。

```text:requirements.txt
tensorflow
numpy
Pillow
```

### 2. 出力ファイルの保存先を変更

あくまで仮想環境でプログラムが実行されるので、出力されたファイルを見る(ダウンロードする)方法がローカルとは異なります。高火力 DOK では、あらかじめ設定されている環境変数(SAKURA_ARTIFACT_DIR)を用いて、以下のように変更を加えて、後ほど説明する管理画面から出力結果やログをダウンロードすることができます。ローカルでも実行確認するために、ここでは条件分岐を活用して、環境変数が取得できた場合(高火力 DOK 上で実行した場合)にうまくパスが設定されるように記述しています。

```Python:tutorial.py(抜粋)
# 環境変数を変数に格納
import os
save_dir = os.environ.get('SAKURA_ARTIFACT_DIR')

'''
(中略)
'''

# 環境変数が取得できていればその変数でパスを作成し、保存先として設定する
if save_dir == None:
   model.save('my_model.keras')
   logger.info('Model is saved to my_model.keras')
else:
   model.save(f'{save_dir}/my_model.keras')
   logger.info(f'Model is saved to {save_dir}/my_model.keras')

```

https://manual.sakura.ad.jp/cloud/koukaryoku-container/running-tasks.html#koukaryoku-container-environment-variables

### 3. Docker イメージを作成してローカルで実行確認(任意)

ローカルで実行確認したい場合には、Dockerfile のある階層に移動して、以下のように Docker イメージを作成し、実行確認できます。

```bash:bash
$ docker build -t myapp:latest .
$ docker run myapp
```

ここで、`myapp`には任意のアプリ名、`latest`は最新版として保存するためのコマンド、`-t`はそれらのタグづけというものを行うためのコマンドを意味しています。また、2 行目で`docker run --env-file .env myapp`とすると、同階層に環境変数ファイルがあれば読み込むことができます。

### 4. Docker にログイン

準備が完了したら、Docker Hub へイメージをアップロードして高火力 DOK で実行する準備をしていきます。以下のように Docker にログインします。既にアカウントを作成して一度コマンドからログインしたことがある場合には、設定ファイルが作成されているためこのまま先に進むことができます。

```bash:bash
$ docker login
Authenticating with existing credentials...
Login Succeeded
```

ここで、以下の出力が出た場合は、`Enter`を押してブラウザが立ち上がるのを待ちましょう。

```bash:bash
$ docker login
USING WEB-BASED LOGIN
To sign in with credentials on the command line, use 'docker login -u <username>'

Your one-time device confirmation code is: XXXX-XXXX
Press ENTER to open your browser or submit your device code here: https://login.docker.com/activate

Waiting for authentication in the browser…
```

すると、Docker のサインインページに遷移するので、ログインまたはサインアップしてください。サインアップ完了後、改めて`docker login`コマンドを実行するか、https://login.docker.com/activate に接続して確認コード(`XXXX-XXXX`の部分)を入力しましょう。

### 5. Docker Hub に Docker イメージをプッシュ

ログインができたら、イメージをビルドし、タグづけ、そしてプッシュを行います。ただし、`yourusername`にはユーザ名を、`yourimagename`には任意のイメージ名を入れてください。

```bash:bash
$ docker buildx create --use
$ docker buildx build --platform linux/amd64 -t yourusername/yourimagename:latest --push .
```

ここで、`–platform linux/amd64`は buildx を利用してアーキテクチャの違いに対応したイメージの作成を行うコマンドです。Apple シリコンは arm アーキテクチャなので、特に注意が必要です。というのも、高火力 DOK は amd アーキテクチャでコンテナを実行するため、対応したイメージにしておかないと実行エラーとなってしまいます。また、`–push`はビルドしたものをそのまま Docker Hub にアップロードする引数です。

:::note warn
この方法では、Docker Hub での公開設定は public になっているので、private にしたい場合は公開設定を変更してください。また、private なリポジトリへのアクセス方法は public のものと異なるので、ドキュメント等で確認して進めてください。
[プライベート・リポジトリ - 自分用 Docker Hub レジストリ | Docker-docs-ja](https://docs.docker.jp/v1.11/docker-hub/repos.html#id8 "プライベート・リポジトリ")
:::

さらに詳しくは、マルチプラットフォームイメージを作成する方法について確認してみてください。

https://zenn.dev/bells17/articles/docker-buildx

### 6. コンテナ実行サービスにて実行確認

以下の記事を参考に、高火力 DOK で Docker イメージを実行しましょう。

https://qiita.com/hinatakatayama/items/f60656de7ef3a4eea998#4-%E9%AB%98%E7%81%AB%E5%8A%9Bdok%E3%81%A7docker%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92%E5%AE%9F%E8%A1%8C

実行が完了すると、以下のようにログを直接確認したり（プレビューボタン）、アーティファクト（出力ファイル一式）をダウンロードする（ダウンロードボタン）ことができます。

![スクリーンショット 2025-01-28 9.38.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900397/960f8451-6d67-41c2-f7d8-d8b865da10f8.png)

## おわりに

自分の手元にあるアプリケーションをコンテナ実行サービスで動かす方法について丁寧に解説してみました。

公式ドキュメントでは、「事前準備」とされている部分をバプリックに共有することができ、嬉しく思います。ぜひ実行したプログラムを記事として公開していただいたり、コメント等でフィードバックいただけるとさらに嬉しいです。

https://manual.sakura.ad.jp/cloud/koukaryoku-container/running-tasks.html#

最終的なプログラムの全体は、GitHub にて公開しているので、確認してみてください。

https://github.com/HinataKatayama00/dok-docker-tutorial

### 参考文献

- [Docker って何？簡単な言葉で解説する Docker の基本](https://qiita.com/riku_ogawa/items/4ffc6aaf7a3df053a4ad "Dockerって何？簡単な言葉で解説するDockerの基本")
- [Python における logging 徹底攻略](https://qiita.com/Broccolingual/items/9838443aa6838a867041 "Pythonにおけるlogging徹底攻略")
- [docker login | Docker-docs-ja](https://docs.docker.jp/engine/reference/commandline/login.html "docker login")
- [Docker Buildx | docker docs](https://matsuand.github.io/docs.docker.jp.onthefly/buildx/working-with-buildx/ "Docker Buildx")