KubernetesでTensorFlowOnSparkを試してみた③ Standaloneモード vs KubernetesのClusterモード

February 16, 2020

今回はStandaloneモードとkubernetesのクラスタモードとを比較してみました。Standaloneモードはkubernetesのようなクラスタを管理するシステムを使わず、sparkに内包されているクラスタマネージャを使用してクラスタを構築します。今回は、dockerを使って1台のMACに1つのマスターとワーカー を立ててみました。マスター、ワーカー を実行するためのイメージを構築するDockerfileはこちらを参考に以下のように作成しました。TensorFlowOnSparkもインストールしています。今回は特にminioやHDFSを使用しませんので、hadoopやaws関連のインストールは行っていません。SPARK_NO_DAEMONIZEをyesに設定しているのは、マスター、ワーカー のプロセスをフォアグラウンドで実行し、dockerコンテナがすぐに終了しないようするためです。

FROM amazoncorretto:8u212

#ENV SPARK_VERSION=3.0.0-preview2
ENV SPARK_VERSION=2.4.5
ENV HADOOP_VERSION=2.7

RUN yum install -y tar gzip \
    && curl -O http://apache.mirror.iphh.net/spark/spark-${SPARK_VERSION}/spark-${SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}.tgz \
    && tar xzf spark-${SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}.tgz \
    && mv spark-${SPARK_VERSION}-bin-hadoop${HADOOP_VERSION} /spark \
    && rm spark-${SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}.tgz

RUN yum -y update && \
    yum install -y python3 python3-pip && \
    pip3 install --upgrade pip setuptools 
RUN pip3 install numpy 
RUN pip3 install tensorflow
RUN pip3 install tensorflowonspark
RUN pip3 install tensorflow_datasets 

ENV PYSPARK_PYTHON=/usr/bin/python3
ENV SPARK_NO_DAEMONIZE=yes

ADD ./TensorFlowOnSpark/examples /examples

EXPOSE 8080

マスター、ワーカー は下記のようなdocker-compose.ymlを作成してビルド、起動しました。先ほどのページを参考にさせていただいています。学習データやモデルファイルをおく場所として、sharedと言うボリュームを作成し、/mydataにマウントしています。

version: '2'

services:
  master:
    build: ./ 
    networks:
      - back
    ports:
      - 4040:4040
      - 8080:8080
    command: /spark/sbin/start-master.sh 
    volumes:
      - shared:/mydata
    environment:
      - SPARK_MASTER_HOST=0.0.0.0
  worker:
    build: ./
    networks:
      - back
    depends_on:
      - master
    ports:
      - 8081-8089:8081
    command: /spark/sbin/start-slave.sh --host 0.0.0.0 spark://master:7077 
    volumes:
      - shared:/mydata

networks:
  back:

volumes:
  shared:

そして、mnistのサンプルソースを使って下記のようにデータを生成、訓練を実行しました。epochsは学習するときのエポック数です。指定しないと3になります。

# mnistのデータを準備
docker-compose exec master \
/spark/bin/spark-submit \
    --master spark://localhost:7077 \
    --conf spark.executor.instances=1 \
    local:///examples/mnist/mnist_data_setup.py \
    --num_partitions=1 \
    --output /mydata/mnist/data

# 学習を実行
docker-compose exec master \
/spark/bin/spark-submit \
    --master spark://localhost:7077 \
    --conf spark.executor.instances=1 \
    local:///examples/mnist/keras/mnist_spark.py \
    --images_labels /mydata/mnist/data/csv/train \
    --epochs 10 \
    --export_dir /mydata/mnist/export

所要時間を比較すると、以下のようになりました。

cluster executor数 epochs=3 epochs=10
Standalone 1 6分15秒 19分21秒
Kubernetes 1 7分55秒 24分11秒
Kubernetes 5 5分53秒 16分13秒
Kubernetes 7 7分31秒 21分39秒

あまり細かい分析はしていませんが、単純にexecutorの数を増やしてもそれほど効果はなさそうです。また、増やしすぎると逆効果になりますね。Kubernetesのクラスタを構成しているノードは同じPCに複数存在しているVM(KVM、VirtualBoxを使用)なので、CPU数が足りなくなっている可能性もあります。また、Kubernetesのexecutor=1の場合、Standaloneよりもかなり遅いですが、これはKubernetesの方は適当に作ったプライベートなクラスタで、ノードのスペックもバラバラのため、たまたまスペックの低いノードに当たってしまった可能性もあります。また、Kubernetesのクラスタの方は、HDFSを使用していたり、executor、driverのインスタンス間のネットワークが物理的に離れている分、遅くなっているのかもしれません。

補足ですが、Kubernetesでexecutor数を3にして実行時間を測定したかったのですが、何回やっても以下のようなtensorflowのエラーが出てしまうので、測定できませんでした。executor数を7に変更すると全くエラーが出なくなりましたので、executorが1つのタスクの実行に時間がかかると発生するバグではないか、と思ってます。

UnavailableError: Socket closed Additional GRPC error information: {"created":"@1579711911.023658059","description":"Error received from peer","file":"external/grpc/src/core/lib/surface/call.cc","file_line":1039,"grpc_message":"Socket closed","grpc_status":14}