Ubuntu22.04ベースのNVIDIA ドライバ付きコンテナを作成する


## 実現したいこと
Ubuntu22.04 のOSにNVIDIA ドライバをインストールし、
GPUコンテナの動作確認を行いたい。<br>
LLMの fine-tuning やら GPUを活用したコンテナを準備する機会が増えたので、ネット上になかなかまとまっていないGPUコンテナの作成手順を整理して高速で環境準備できるようにしたい。

前提として、AWS上でEC2 インスタンスを利用して動作検証を進める。


## 作業ステップ
- step1. EC2インスタンスの作成
- step2. Ubuntu22.04 の初期設定
- step3. Ubuntu22.04 でドライバの確認を行う
- step4. Ubuntu22.04 でドライバのインストールを行う
- step5. GPUの動作確認
- step6. GPUコンテナのサンプル

## EC2 インスタンスタイプの注意
https://aws.amazon.com/jp/ec2/instance-types/g4/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199265/0b122580-5dc0-3ecf-8232-5ca4e425ff95.png)

- g4ad.xlarge NG
ubuntu@ip-192-168-2-76:~$ sudo lspci | grep NVIDIA
NVIDIA driver が認識しない

- g4dn.xlarge OK
ubuntu@ip-192-168-2-76:~$ sudo lspci | grep NVIDIA
00:1e.0 3D controller: NVIDIA Corporation TU104GL [Tesla T4] (rev a1)

G4シリーズでも、g4adは NVIDIAドライバがなく、g4dnはNVIDIAドライバが認識する。間違いやすいので注意。

### 費用見積もり
g4dn.xlarge　インスタンスは 1h 0.626$
https://aws.amazon.com/jp/ec2/instance-types/g4/
1$=147円換算(2023/09/16現在)で
1h 0.626$ → 1h 92.022円 → 1day(24h) 2208円 → 1month(30days) 66,255円
もし一日 6時間、週5日使うとしたら
92.022 * 6h * 5 days * 4 weeks = 11,042.64円/月

## step1. EC2インスタンスの作成

必要なら vCPU 制限緩和申請を行う。
デフォルトでは利用可能な インスタンスタイプの利用可能な vCPUが0の場合もあるので。
https://repost.aws/ja/knowledge-center/ec2-on-demand-instance-vcpu-increase
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199265/11533ec7-d5e4-9ec3-8a68-afd434bfea4a.png)

EC2→制限→制限計算ツールのインスタンスタイプを入力。
現在の制限が4未満だった場合、g5/g4シリーズのインスタンスを立ち上げることができません。
画像右下の「オンデマンド制限の引き上げをリクエスト」からvCPU引き上げをリクエストしてください。

次に実際にインスタンスの作成をしていきます。

- OS選択：Ubuntu22.04
- インスタンスタイプの選択：g4dn.xlarge
- キーペア：必要なら選択、作成する
- セキュリティグループ：セキュリティグループの設定で作成したもの
- ボリューム：最低でも30GBはほしいので、ここでは 50GB指定
- インスタンスプロファイル：EC2用の IAMロールを作成して設定

基本的な動作確認をするのが目的なので、インスタンスタイプは最小(g4dn.xlarge)で確認してます。GPUコンテナの基本的な動作が確認できたら、後ほど必要なインスタンスタイプに切り替えます。

## step2. Ubuntu22.04 の初期設定

Ubuntu22.04 に対して、docker/docker compose を使える設定を行います。

https://github.com/docker/compose/releases/
で最新の docker compose のバージョンを確認する。
2023/09/14現在は 2.21.0

なので以下のスクリプトを作成して、
$ bash setup-ubuntu22.sh
を実行する。

setup-ubuntu22.sh

```bash
#!/bin/bash
sudo apt update
sudo apt-get install -y build-essential

# gccインストールの確認
gcc --version 

sudo apt-get install -y git
sudo apt-get install -y docker.io
## netstat コマンドを入れる。portチェック等で利用
sudo apt-get install -y net-tools
## ubuntu-drivers コマンドを入れる。ドライバ確認で利用
sudo apt-get install -y ubuntu-drivers-common alsa-base

sudo service docker start
# docker バージョンの確認
docker --version

sudo mkdir -p /usr/local/lib/docker/cli-plugins 

# 環境変数 VERにDocker Composeのバージョンを入れる
VER=2.21.0

sudo curl \
  -L https://github.com/docker/compose/releases/download/v${VER}/docker-compose-$(uname -s)-$(uname -m) \
  -o /usr/local/lib/docker/cli-plugins/docker-compose

sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose 
sudo ln -s /usr/local/lib/docker/cli-plugins/docker-compose /usr/bin/docker-compose 

# docker compose バージョンの確認
docker-compose --version 
```

## step3. Ubuntu22.04 でドライバの確認を行う

VGA が NVIDIA のものであるか確認します。

```bash
$ lspci | grep NVIDIA
```

### その他のマシンの状況確認

```bash
CPUを確認する
$ cat /proc/cpuinfo

CPU のコア数をチェックする
$ cat /proc/cpuinfo | grep processor | wc -l

MEMORYを確認する
$ cat /proc/meminfo
$ free -h でも OK
```

推奨のドライバを確認します。

```bash
$ ubuntu-drivers devices
== /sys/devices/pci0000:00/0000:00:1e.0 ==
modalias : pci:v000010DEd00001EB8sv000010DEsd000012A2bc03sc02i00
vendor   : NVIDIA Corporation
model    : TU104GL [Tesla T4]
manual_install: True
driver   : nvidia-driver-525-server - distro non-free
driver   : nvidia-driver-535 - distro non-free recommended
driver   : nvidia-driver-535-server - distro non-free
driver   : nvidia-driver-525 - distro non-free
driver   : nvidia-driver-418-server - distro non-free
driver   : nvidia-driver-450-server - distro non-free
driver   : nvidia-driver-470 - distro non-free
driver   : nvidia-driver-470-server - distro non-free
driver   : xserver-xorg-video-nouveau - distro free builtin
```

### ドライバ確認時にエラーが表示される場合
ubuntu-drivers コマンドは ubuntu-drivers-common を入れると認識します。<br>
alsa-base を追加すると、ubuntu-drivers実行に
「aplay command not found」のエラーは消えます。

したがって、以下のコマンドでライブラリをインストールします。
setup-ubuntu22.sh に記載しています。

```bash
sudo apt-get install -y ubuntu-drivers-common alsa-base
```

ライブラリ alsa-base をインストールしていない場合のアラート表示

```bash
$ ubuntu-drivers devices
ERROR:root:aplay command not found
== /sys/devices/pci0000:00/0000:00:1e.0 ==
modalias : pci:v000010DEd00001EB8sv000010DEsd000012A2bc03sc02i00
vendor   : NVIDIA Corporation
model    : TU104GL [Tesla T4]
manual_install: True
driver   : nvidia-driver-525-server - distro non-free
…
```

「ubuntu-drivers devices」実行結果の model 行を確認すると
TU104GL [Tesla T4]のように NVIDIA デバイスのプロダクトタイプ、シリーズが分かります。

https://www.nvidia.com/Download/index.aspx?lang=en-us
にアクセスして、上記で調べた内容を選択すると、Nvidia Driver の正確な
バージョンを把握できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199265/615a99fc-8e0e-9bea-9c05-e0d53212251c.png)

Product Type/Product Series/Product/OS/CUDA Toolkitを選択して「Search」ボタンを押下すると、以下のような表示になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199265/1a13b693-a074-40b5-6fb2-25507b128826.png)

ここでは、バージョンは 535.104.05 であると確認できます。


## step4. Ubuntu22.04 でドライバのインストールを行う

### 4-1. NVIDIA ドライバのインストール

https://www.nvidia.com/Download/index.aspx?lang=en-us
で確認したドライババージョンを DRIVER_VERSION 変数に設定し、以下の
スクリプトを準備、NVIDIA ドライバのインストールを行います。

```bash
$ bash nvidia-driver-install.sh
```

nvidia-driver-install.sh

```bash
#!/bin/bash
BASE_URL=https://us.download.nvidia.com/tesla
# 確認したバージョンを指定
DRIVER_VERSION=535.104.05
curl -fSsl -O ${BASE_URL}/${DRIVER_VERSION}/NVIDIA-Linux-x86_64-${DRIVER_VERSION}.run
sudo bash NVIDIA-Linux-x86_64-${DRIVER_VERSION}.run
```

インストール開始で以下の表示になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199265/40ae262f-e892-e050-d17f-ccbe723c5778.png)

警告が表示されますが、OK押下して続行。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199265/a021ffa0-cdf5-755c-b69e-79a048949610.png)

32bit互換のライブラリをインストールするかを問われますが、Noを選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199265/57c20a8b-9672-273d-dc4f-c49a69a3d9cb.png)

警告が表示されますが、OK押下して続行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199265/86403838-03ed-c609-6d1e-ac915f6dc733.png)

OKを押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199265/0467a918-0137-3cfe-55f8-57ce5a13bcb5.png)

これでインストールは完了。


### 4-2. nvidia-container-toolkit のインストール
Docker上でもGPUを認識させるためにnvidia-container-toolkitも入れます。
https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html

```bash
bash nvidia-controller-toolkit-install.sh
```

nvidia-controller-toolkit-install.sh

```bash
#!/bin/bash
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
      && curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
      && curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
            sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
            sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt update
sudo apt-get install -y nvidia-docker2
sudo nvidia-ctk runtime configure --runtime=docker

sudo systemctl restart docker

sudo apt-get install -y nvidia-cuda-toolkit

# GPU認識確認
nvidia-smi
```

インストールが終わると、OSの再起動を実施します。ついでにライブラリの更新も実行。

```bash
$ sudo reboot
$ sudo apt-get dist-upgrade -y
```

## step5. GPUの動作確認

### 5.1 ドライバとCUDAの確認

コンテナ上でドライバが認識されているか確認します。

```bash
$ nvidia-smi
Wed Sep 13 09:33:11 2023       
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.104.05             Driver Version: 535.104.05   CUDA Version: 12.2     |
|-----------------------------------------+----------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |
|                                         |                      |               MIG M. |
|=========================================+======================+======================|
|   0  Tesla T4                       Off | 00000000:00:1E.0 Off |                    0 |
| N/A   42C    P0              26W /  70W |      2MiB / 15360MiB |      5%      Default |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+
                                                                                         
+---------------------------------------------------------------------------------------+
| Processes:                                                                            |
|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |
|        ID   ID                                                             Usage      |
|=======================================================================================|
|  No running processes found                                                           |
+---------------------------------------------------------------------------------------+

```

CUDAの確認を行います。

```bash
$ nvcc -V
```

### 5.2 コンテナの動作確認

Ubuntu22.04 でドライバの認識、CUDAの動作確認が終わったら、
以下のコマンドを実行して、Docker内でGPUを動かせるかどうかを確認します。
<b>「--gpus all」を付与し忘れないように注意。</b>

```bash
$ bash gpu-run-test.sh
```

gpu-run-test.sh

```bash
docker run --rm --gpus all \
    nvcr.io/nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04 \
    bash -c "nvidia-smi; nvcc -V"
```

ここで、ホストOS同様にコンテナの中でも NVIDIAドライバが認識され、CUDAも認識できて入ればOKです。

ここではベースのコンテナとして以下を利用しています。

nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04
https://hub.docker.com/layers/nvidia/cuda/11.8.0-cudnn8-devel-ubuntu22.04/images/sha256-5079d0d0f36cc63050a0f5c010d769c68b8e959c2d2a45f8ec44dd7e5c1bf7f9?context=explore

## step6. GPUコンテナのサンプル

Docker内でGPUを動かせるようになったら、次はGPUコンテナをプロジェクトで
利用するための Dockerfile/docker-compose の設定を作成してみます。
ここでは、Pythonでいくつかライブラリを入れたGPUコンテナを準備してみます。

### 6.1 Dockerfile の準備

ファイル構成
```
＋ Dockerfile
＋ requirements.txt
```

requirements.txt

```
pandas==2.1.0
numpy
matplotlib
scikit-learn
```

Dockerfile

```
# Ubuntu22.04 + CUDA11.8 のGPUコンテナ
FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04
LABEL maintainer="mshinoda"

RUN apt-get update && apt-get dist-upgrade -y
RUN apt-get -y install apt-utils
RUN apt-get -y install locales && \
    localedef -f UTF-8 -i ja_JP ja_JP.UTF-8

ENV TZ=Asia/Tokyo
ENV DEBIAN_FRONTEND=noninteractive
ENV PROJECT_ROOTDIR /var/batch/
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get install -y apt-file software-properties-common

# coreutils for cat /meld for diff
RUN apt-get install -y coreutils vim less nkf jq zip unzip wget meld sudo git curl telnet locate
RUN apt-get install -y python3-ldb-dev gcc libffi-dev libcurl4-openssl-dev
RUN apt-get install -y tmux
# for pip install
RUN apt-get install -y python3-distutils python3-testresources
RUN apt-get install -y python3-pip

# add sudo user
RUN groupadd -g 1100 developer && \
    useradd  -g      developer -G sudo -m -s /bin/bash operator && \
    echo 'operator:operatorpass' | chpasswd

RUN echo 'Defaults visiblepw'              >> /etc/sudoers
RUN echo 'operator ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

USER operator
RUN sudo mkdir -p ${PROJECT_ROOTDIR} && sudo chmod 777 -R ${PROJECT_ROOTDIR}
WORKDIR /var/batch
RUN echo 'alias ll="ls -al"' >> /home/operator/.bashrc
RUN echo 'export PATH=${PATH}:/home/operator/.local/bin' >> /home/operator/.bashrc
RUN export PATH=${PATH}:/home/operator/.local/bin
ENV DIR_PIP /home/operator/.local/bin

# install application materials
USER root
COPY requirements.txt ${PROJECT_ROOTDIR}/requirements.txt
RUN pip3 install --upgrade pip
RUN pip3 install --trusted-host pypi.python.org --no-cache-dir -r ${PROJECT_ROOTDIR}/requirements.txt
```

ビルド実行
```
$ docker build -t container01 .
```

起動確認
```
$ export image_id=`docker images|grep ^container01| sed -e 's/  */ /g'|cut -d" " -f3`
$ docker run --gpus all --name container01 -itd ${image_id}
```

コンテナの中に入る
```
$ export container_id=`docker ps -a |grep -v CONTAINER |grep "container01"|cut -d" " -f1`
$ docker exec -it ${container_id} bash
```

コンテナの中で改めてドライバが認識されているか確認します。
```
# ドライバ確認
(container01)$ nvidia-smi

# CUDAの確認
(container01)$ nvcc -V
```


### 6.2 docker compose でGPUアクセス

GPUコンテナを実現するDockerfileが準備できたら、docker composeでその制御についても整理しておきます。

赤字の部分の設定を追加して、GPUアクセスの設定を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199265/79c41e34-f338-4397-5eee-f74eee81ea26.png)

```
services:
  test:
    image: tensorflow/tensorflow:latest-gpu
    command: python -c "import tensorflow as tf;tf.test.gpu_device_name()"
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]
```

詳細はこちら。<br>
docker compose でGPUアクセス
https://docs.docker.jp/compose/gpu-support.html

以下は steamlit を使った GPUコンテナと mysql コンテナを内部ネットワークで接続させる場合の docker-compose.yml ファイル例

```
version: "3.8"
services:
  # Web Application(streamlit)
  web:
    restart: always
    build:
      context: web
      dockerfile: Dockerfile
    container_name: streamlit
    env_file:
      - ./env-file/stg.env
    working_dir: /usr/src/app
    tty: true
    volumes:
      - ./web/app:/usr/src/app
    ports:
      - 80:8501
    networks:
      shared-nw:
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]
    command: streamlit run main.py --server.port 8501

  # mysql
  mysqldb:
    image: mysql:8.0
    container_name: mysql_container
    env_file:
      - ./env-file/stg.env
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./mysql/sql:/docker-entrypoint-initdb.d
    ports:
      - 3306:3306
    networks:
      shared-nw:

networks:
  shared-nw:
    driver: bridge
```


env-file/stg.env

```
# for mysql
MYSQL_DATABASE=db01
MYSQL_ROOT_PASSWORD=passw0rd
MYSQL_USER=db_user
MYSQL_PASSWORD=passw0rd
TZ=Asia/Tokyo
```

これでGPUコンテナを高速で準備できるようになります。

## 参考文献

AWS EC2 G4インスタンス
https://aws.amazon.com/jp/ec2/instance-types/g4/

vCPU 制限緩和申請
https://repost.aws/ja/knowledge-center/ec2-on-demand-instance-vcpu-increase

docker compose
https://github.com/docker/compose/releases/

Nvidia Driver の正確なバージョンを確認
https://www.nvidia.com/Download/index.aspx?lang=en-us

nvidia-container-toolkit
https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html

AWS EC2 GPUインスタンスでStable Diffusionを使う
https://qiita.com/nijigen_plot/items/27f8e987dda6aea5836d

nvidia/cuda:11.8.0-devel-ubuntu22.04
https://hub.docker.com/layers/nvidia/cuda/11.8.0-devel-ubuntu22.04/images/sha256-2ce1115b4dcc0c7ca6454695fbb959b08d3ac5be4dcc2b8c99df1e225223d326?context=explore

nvidia/cuda:12.2.0-devel-ubuntu22.04
https://hub.docker.com/layers/nvidia/cuda/12.2.0-devel-ubuntu22.04/images/sha256-6950bd04caba9bf0fa75f8242ece9d34550685bfaa4b42d9ac341acbac7d6608?context=explore

nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04
https://hub.docker.com/layers/nvidia/cuda/11.8.0-cudnn8-devel-ubuntu22.04/images/sha256-5079d0d0f36cc63050a0f5c010d769c68b8e959c2d2a45f8ec44dd7e5c1bf7f9?context=explore