Kubernetes Engine でGPUエンコーディング - NVENC

Google Kubernetes Engine のコンテナGPUで動画エンコード( NVENC )を行い、高速エンコードとスケーラビリティを両立できます。
Container Engine GPU の入門記事は機械学習向けのものが多くて、動画処理のものが見当たらなかったので記事を書きました。

Dockerビルドマシンの側では、GPUはもちろんNVIDIAドライバすら入れる必要がないので楽です。

<h1>準備</h1>
DockerイメージのビルドマシンはGoogle Compute EngineのVMを使うと手っ取り早く便利です。
今回はCentOS 7を使用しました。
なお、イメージをGoogleクラウドストレージ上にContainer Registryとして保存するので、VMにストレージへの読み取りと書き込み権限が必要です。

<h1>Dockerイメージをビルド</h1>
<h3>(1) Dockerをインストール</h3>
```
# yum install -y yum-utils
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# yum makecache fast
# yum install -y docker-ce
# systemctl start docker
```

<h3>(2) nvidia-docker2をインストール</h3>
```
# distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
# curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | tee /etc/yum.repos.d/nvidia-docker.repo
# yum -y install nvidia-docker2
# pkill -SIGHUP dockerd
```
<h3>(3) Dockerfileを作成</h3>
[こちら](https://hub.docker.com/r/nvidia/cuda/)に基づいてDockerfileを作成しています。
GKEではUbuntuベースが推奨されているようなので、Ubuntu16.04を選択しました。

```:Dockerfile
FROM ubuntu:16.04
LABEL maintainer "NVIDIA CORPORATION <cudatools@nvidia.com>"

RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates apt-transport-https gnupg-curl && \
    rm -rf /var/lib/apt/lists/* && \
    NVIDIA_GPGKEY_SUM=d1be581509378368edeec8c1eb2958702feedf3bc3d17011adbf24efacce4ab5 && \
    NVIDIA_GPGKEY_FPR=ae09fe4bbd223a84b2ccfce3f60f4b3d7fa2af80 && \
    apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub && \
    apt-key adv --export --no-emit-version -a $NVIDIA_GPGKEY_FPR | tail -n +5 > cudasign.pub && \
    echo "$NVIDIA_GPGKEY_SUM  cudasign.pub" | sha256sum -c --strict - && rm cudasign.pub && \
    echo "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64 /" > /etc/apt/sources.list.d/cuda.list && \
    echo "deb https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64 /" > /etc/apt/sources.list.d/nvidia-ml.list

ENV CUDA_VERSION 9.2.148

ENV CUDA_PKG_VERSION 9-2=$CUDA_VERSION-1
RUN apt-get update && apt-get install -y --no-install-recommends \
        cuda-cudart-$CUDA_PKG_VERSION && \
    ln -s cuda-9.2 /usr/local/cuda && \
    rm -rf /var/lib/apt/lists/*

# nvidia-docker 1.0
LABEL com.nvidia.volumes.needed="nvidia_driver"
LABEL com.nvidia.cuda.version="${CUDA_VERSION}"

RUN echo "/usr/local/nvidia/lib" >> /etc/ld.so.conf.d/nvidia.conf && \
    echo "/usr/local/nvidia/lib64" >> /etc/ld.so.conf.d/nvidia.conf

ENV PATH /usr/local/nvidia/bin:/usr/local/cuda/bin:${PATH}
ENV LD_LIBRARY_PATH /usr/local/nvidia/lib:/usr/local/nvidia/lib64

# nvidia-container-runtime
ENV NVIDIA_VISIBLE_DEVICES all
ENV NVIDIA_DRIVER_CAPABILITIES compute,utility
ENV NVIDIA_REQUIRE_CUDA "cuda>=9.2"

# FFMPEG
RUN set -x \
 && apt update \
 && apt install -y gcc git make pkg-config devscripts \
 && apt install -y yasm nasm zlib1g-dev \
 && cd /usr/local/src \
 && curl -L http://sourceforge.net/projects/lame/files/lame/3.99/lame-3.99.5.tar.gz -o lame-3.99.5.tar.gz \
 && tar xvzf lame-3.99.5.tar.gz \
 && cd lame-3.99.5 \
 && ./configure \
 && make \
 && make install \
 && cd /usr/local/src \
 && git clone http://git.videolan.org/git/x264.git \
 && cd x264 \
 && ./configure --enable-shared --disable-asm \
 && make \
 && make install \
 && export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig \
 && echo /usr/local/lib >> /etc/ld.so.conf \
 && ldconfig \
 && cd /usr/local/src \
 && curl -L https://github.com/FFmpeg/nv-codec-headers/releases/download/n8.0.14.6/nv-codec-headers-8.0.14.6.tar.gz -o nv-codec-headers-8.0.14.6.tar.gz \
 && tar xvzf nv-codec-headers-8.0.14.6.tar.gz \
 && cd nv-codec-headers-n8.0.14.6 \
 && make \
 && make install \
 && cd /usr/local/src \
 && git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg \
 && cd ffmpeg \
 && ./configure --enable-nvenc --enable-libx264 --enable-gpl --enable-libmp3lame \
 && make \
 && make install \
 && cd /usr/local/src \
 && rm -rf *

#この辺から下はご自身のプロジェクトに合わせてください。
# Node.js
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - \
 && apt-get install -y nodejs \
 && mkdir /var/video_encode
COPY ./video_encode /var/video_encode
CMD /usr/bin/node /var/video_encode/bin/www
EXPOSE 3000
```

<h3>(4) DockerビルドしてPUSH</h3>
下記の「asia.gcr.io」の部分は好きなリージョンに変えてもOK。
Dockerfileのあるディレクトリで、

```
# nvidia-docker build -t asia.gcr.io/自分のGCPプロジェクト名/test-encode .
# gcloud auth configure-docker
# nvidia-docker push asia.gcr.io/自分のGCPプロジェクト名/test-encode
```

<h1>Kubernetes Engine コンソールでの作業</h1>
<h3>(1) クラスタを作成</h3>
コンソールのクラスタメニュー > クラスタを作成
![cluster.png](https://qiita-image-store.s3.amazonaws.com/0/320823/9b16a918-4039-a146-3015-1d0bcef1b1d2.png)


* ゾーン/リージョンによって提供されるGPUが異なります。

* GPUを使うノードプールと使わないノードプールを分けて作成することが推奨です。
GPUを要求するPodは nvidia.com/gpu テイントによってGPUノードにスケジュールされる仕組みになっています。

<h3>(2) NVIDIAデバイスドライバの用意</h3>
クラスタの一覧で新しくクラスタが作成されたことを確認し、右にある「接続」メニューからCloud Shellを開きます。
NVIDIAのデバイスドライバをインストールするデーモンセットを設定します。

```
# kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/container-engine-accelerators/stable/nvidia-driver-installer/cos/daemonset-preloaded.yaml
結果:
daemonset.extensions "nvidia-driver-installer" created
```

<h3>(3) 先程のDockerイメージをデプロイ</h3>
コンソールのワークロードメニュー > デプロイ
![deploy.png](https://qiita-image-store.s3.amazonaws.com/0/320823/0d74dd17-4b06-c0cf-e7f8-68c05ff89173.png)



デプロイ後、ワークロードのメニューからYAMLを編集し、下記の resources の箇所を書き換えて nvidia.com/gpu でGPU数を指定して保存します。

```
apiVersion: extensions/v1beta1
kind: Deployment
... 略
    spec:
      containers:
      - name: 〇〇
        image: 〇〇
        resources:
          limits:
            nvidia.com/gpu: 1
```

※ 編集後、"Insufficient nvidia"などのエラーでPodがすぐに起動しない場合がありますが、しばらく待っていれば解消されます。

※ ワークロードのオートスケーラーの設定について
Pod毎にできるだけ同じGPUを使わないようにすることが望ましい場合、CPUターゲット値を低めにしたほうが良いです。CPUターゲット値が高いと、CPUをあまり消費しないアプリケーションの場合、同じノード内にPodが次々と作られていき、GPUを共有します。

<h3>(4) NVENCができるか確認</h3>
Podにexecして確認しましょう。
稼働中のPod名はワークロードのメニューで確認できます。

```:CloudShell
# kubectl exec -it Pod名 /bin/bash
```

Pod内に入ったら、h264_nvenc でエンコードしてみる。

```
# ffmpeg -i test.mov -vcodec h264_nvenc -b:v 2000k -acodec libmp3lame -b:a 128k output.mp4
```

<h3>(5) アプリケーションを公開（必要に応じて）</h3>
![lb.png](https://qiita-image-store.s3.amazonaws.com/0/320823/2f91179e-714e-7e21-21f0-958859740619.png)




以上