シングルノードのKubernetesクラスタを構築し、Sparkジョブを実行する（Ubuntu 18.04 LTS、minikube利用、hypervisorなし）

 
今回の記事では、VirtualBoxなどhypervisorを使わずに、minikubeを使って単一ノードのUbuntu 18.04 LTS上にKubernetesクラスタを構築し、さらにKubernetesクラスタ上でサンプルのSparkジョブを実行する手順を紹介します。

# minikubeとは

minikubeは、ローカルでKubenetesを動かすためのツールです。

https://kubernetes.io/docs/setup/learning-environment/minikube/

> Minikube is a tool that makes it easy to run Kubernetes locally. Minikube runs a single-node Kubernetes cluster inside a Virtual Machine (VM) on your laptop for users looking to try out Kubernetes or develop with it day-to-day.

# 注意事項
* 通常、minikubeを使う場合は、VirtualBoxなどhypervisorを使ってKubernetesクラスタを立ち上げます。
* ただし、ドキュメントによるとhypervisorを使わずにローカルのDocker上に構築する方法も提供されているようです。
* この方法は安定性などに問題があるようですが、学習用途で使う分には問題ないと思います。

https://kubernetes.io/docs/tasks/tools/install-minikube/#install-a-hypervisor

> Minikube also supports a --vm-driver=none option that runs the Kubernetes components on the host and not in a VM. Using this driver requires Docker and a Linux environment but not a hypervisor.

# 動作環境

今回はGCP上にGCEインスタンスをたてて、その上に構築しました。

* マシンタイプ：n1-standard-4（4 vCPU, 15GB memory）
* ディスクサイズ：30GB
* CPU platform: Intel Haswell
* Ubuntu 18.04 LTS
* Docker v18.09.7
* minikube v.1.2.0
* kubectl v1.15.0


# GCE インスタンスを作成する

```
$ gcloud compute instances create test-k8s \
  --boot-disk-size 30G \
  --machine-type n1-standard-4 \
  --image-project ubuntu-os-cloud \
  --image-family ubuntu-1804-lts \
  --zone us-central1-a
```

#  Docker をインストールする

* Hypervisorなしでminikubeを使う場合は、Dockerをインストールする必要があります。
* インストール手順は以下を参考にしました。https://docs.docker.com/install/linux/docker-ce/ubuntu/

```
$ sudo apt-get update
$ sudo apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-get install -y docker-ce docker-ce-cli containerd.io
```

# kubectlをインストールする

* kubectlはKubenetesを操作するためのCLIツールです。

> The Kubernetes command-line tool, kubectl, allows you to run commands against Kubernetes clusters. You can use kubectl to deploy applications, inspect and manage cluster resources, and view logs

* インストール手順は以下を参考にしました。Ubuntuの場合、パッケージマネジャ snap でインストール可能です。
https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-linux

```
$ sudo snap install kubectl --classic
```

# minikubeをインストールする

* パッケージマネジャーを使ったインストール方法が見つからなかったため、以下の手順で手動インストールします。
https://kubernetes.io/docs/tasks/tools/install-minikube/#install-minikube-using-a-package

```
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 \
  && chmod +x minikube
$ sudo install minikube /usr/local/bin
```

# Kubernetesクラスタの起動

* 今回はHypervisorを使わないため、 `--vm-driver=none` を指定します。
* 動作確認用のサンプルSparkジョブの動作にメモリ8GBが必要なため、オプションとして `--memory 8192` をつけています。
* マシンタイプとして `n1-standard-4（4 vCPU, 15GB memory）` を使っているため、 CPUコア数も `4` を指定しています。

```
$ sudo minikube start --vm-driver=none --cpu 4 --memory 8192
$ sudo mv /home/yohei/.kube /home/yohei/.minikube $HOME
$ sudo chown -R $USER $HOME/.kube $HOME/.minikube
```

# サンプルSparkジョブの実行

* 今回は、次の記事を参考にπを計算するSparkジョブを実行します。 https://qiita.com/ysakashita/items/1f87646ae804ba509d07
* 引用した記事の通り、ジョブの認証・認可はKubernetesのサービスアカウントで行うため、ジョブで使うサービスアカウントを作成しておきます。

```
$ kubectl create serviceaccount spark
$ kubectl create clusterrolebinding spark-role --clusterrole=edit --serviceaccount=default:spark --namespace=default
```

* クラスタのIPアドレスを確認します。

```
$ kubectl cluster-info
Kubernetes master is running at https://10.128.0.11:6443
KubeDNS is running at https://10.128.0.11:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
```

* 以下でSparkジョブを実行します。
* 今回は、Kubenetesのサポートが始まった2.3系の最新のバイナリパッケージをダウンロードし、spark-submitを実行しました。
* なお、指定しているイメージ `yohei1126/spark:v2.3.3` は、ビルド済みのSparkをDockerイメージにしたものです。イメージの作成方法は、次のドキュメントを参照。https://spark.apache.org/docs/2.3.0/running-on-kubernetes.html#docker-images

```
$ sudo apt-get install openjdk-8-jdk -y
$ wget https://www-us.apache.org/dist/spark/spark-2.3.3/spark-2.3.3-bin-hadoop2.7.tgz
$ tar zxvf spark-2.3.3-bin-hadoop2.7.tgz
$ cd spark-2.3.3-bin-hadoop2.7
$ bin/spark-submit \
  --master k8s://https://10.128.0.11:6443 \
  --deploy-mode cluster \
  --conf spark.executor.instances=3 \
  --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark \
  --conf spark.kubernetes.container.image=yohei1126/spark:v2.3.3 \
  --class org.apache.spark.examples.SparkPi \
  --name spark-pi \
  local:///opt/spark/examples/jars/spark-examples_2.11-2.3.3.jar
```

* `Exit code: 0` で終了すればジョブは成功しています。

```
2019-07-02 08:57:56 INFO  LoggingPodStatusWatcherImpl:54 - State changed, new state: 
         pod name: spark-pi-e39a8e8f7faf3c9fa861ae024e93b742-driver
         namespace: default
         labels: spark-app-selector -> spark-d0860239ee0f4118aeb8fee83bd00fa2, spark-role -> driver
         pod uid: 01e8f4c0-ae85-4252-92f7-11dbdd2e2b0d
         creation time: 2019-07-02T08:57:10Z
         service account name: spark
         volumes: spark-token-bnm7w
         node name: minikube
         start time: 2019-07-02T08:57:10Z
         container images: yohei1126/spark:v2.3.3
         phase: Succeeded
         status: [ContainerStatus(containerID=docker://c8c7584c7b704b8f2321943967f84d58267a9ca9d1e1852c2ac9eafb76816dc1, image=yohei1126/spark:v2.3.3, imageID=docker-pullable://yohei11
26/spark@sha256:d3524f24fe199dcb78fd3e1d640261e5337544aefa4aa302ac72523656fe2af1, lastState=ContainerState(running=null, terminated=null, waiting=null, additionalProperties={}), name=s
park-kubernetes-driver, ready=false, restartCount=0, state=ContainerState(running=null, terminated=ContainerStateTerminated(containerID=docker://c8c7584c7b704b8f2321943967f84d58267a9ca
9d1e1852c2ac9eafb76816dc1, exitCode=0, finishedAt=Time(time=2019-07-02T08:57:56Z, additionalProperties={}), message=null, reason=Completed, signal=null, startedAt=Time(time=2019-07-02T
08:57:21Z, additionalProperties={}), additionalProperties={}), waiting=null, additionalProperties={}), additionalProperties={})]
2019-07-02 08:57:56 INFO  LoggingPodStatusWatcherImpl:54 - Container final statuses:
         Container name: spark-kubernetes-driver
         Container image: yohei1126/spark:v2.3.3
         Container state: Terminated
         Exit code: 0
2019-07-02 08:57:56 INFO  Client:54 - Application spark-pi finished.
2019-07-02 08:57:56 INFO  ShutdownHookManager:54 - Shutdown hook called
2019-07-02 08:57:56 INFO  ShutdownHookManager:54 - Deleting directory /tmp/spark-9cbaba30-9277-4bc9-9c55-8acf78711e1d
```

* ログを確認すると確かにπの計算結果が出力されています。

```
$ kubectl get pods
NAME                                               READY   STATUS      RESTARTS   AGE
spark-pi-e39a8e8f7faf3c9fa861ae024e93b742-driver   0/1     Completed   0          43m
yohei_onishi@test-k8s:~$ kubectl logs spark-pi-e39a8e8f7faf3c9fa861ae024e93b742-driver
...
Pi is roughly 3.141395706978535
...
```