[BigData] PySpark on Jupyter Lab

Jupyter Notebook/Lab 是一個常用的互動式介面協助各種程式碼的開發我們在上一篇『建立自己的 Jupyter Notebook 伺服器』有稍微介紹過,一般常見的使用場景是在開發 python 的程式,但是 Jupyter Server 的 Kernel 功能可以擴充更多的互動式開發環境,例如 R, PySpark, SparkR, SparklyR 等等,檸檬爸最早接觸的是將 PySpark 註冊到 Jupyter Lab 裡面,實作的程式碼是透過 AZTK 的 Repository 學習到的,後來進一步將其擴充到 R 等等的使用場景,本篇將會呈現如何部署一個有 PySpark 核心的 Jupyter Lab。

AZTK 驅動包含有 PySpark 的 Jupyter Lab 服務範例

https://github.com/Azure/aztk/blob/master/aztk/spark/models/plugins/jupyter_lab/jupyter_lab.sh

下面的程式碼擷取重要的部分,其中最主要的地方在於:

  1. 定義 PYSPARK_DRIVER_PYTHON 使 pyspark 程式初始化時驅動 jupyter
  2. 定義 PYSPARK_DRIVER_PYTHON_OPTS  
  3. 創建一個 kernel.json 檔案
  4. 利用 pyspark 程式碼啟動
PYSPARK_DRIVER_PYTHON="/opt/conda/bin/jupyter"
JUPYTER_KERNELS="/opt/conda/share/jupyter/kernels"

mkdir $JUPYTER_KERNELS/pyspark
touch $JUPYTER_KERNELS/pyspark/kernel.json
cat << EOF > $JUPYTER_KERNELS/pyspark/kernel.json
{
    "display_name": "PySpark",
    "language": "python",
    "argv": [
        "python",
        "-m",
        "ipykernel",
        "-f",
        "{connection_file}"
    ],
    "env": {
        "SPARK_HOME": "$SPARK_HOME",
        "PYSPARK_PYTHON": "python",
        "PYSPARK_SUBMIT_ARGS": "--master spark://$MASTER_IP:7077 pyspark-shell"
    }
}
EOF
(PYSPARK_DRIVER_PYTHON=$PYSPARK_DRIVER_PYTHON PYSPARK_DRIVER_PYTHON_OPTS="lab --no-browser --port=8889 --allow-root" pyspark &)

缺點:使用以上的方法部署 jupyter notebook 的服務是可以成功的,但是缺點卻也隨之而來,包含 pyspark 綁定了 jupyter,無法在 terminal 的狀態再利用 pyspark 做偵錯,並且進一步限制了 jupyter lab 內部的其他 kernel 必須要同步使用 pyspark,在使用單純 python 的情況下造成資源的浪費。

改良驅動 Jupyter Lab 的方式

為了不要將驅動 pyspark 的方式綁定在 jupyter,我們將 PYSPARK_DRIVER_PYTHON 的定義移進 kernel.json 並且利用定義初始化腳本 PYTHONSTARTUP 的方式做到 SparkSession 的初始化,以下是參考的程式碼:

export JUPYTER_KERNELS=/usr/local/share/jupyter/kernels
mkdir $JUPYTER_KERNELS/pyspark
touch $JUPYTER_KERNELS/pyspark/kernel.json

cat << EOF > $JUPYTER_KERNELS/pyspark/kernel.json
{
    "display_name": "PySpark",
    "language": "python",
    "argv": [
        "python",
        "-m",
        "ipykernel",
        "-f",
        "{connection_file}"
    ],
    "env": {
        "SPARK_HOME": "$SPARK_HOME",
        "PYSPARK_PYTHON": "python",
        "PYSPARK_DRIVER_PYTHON": "jupyter",
        "PYSPARK_SUBMIT_ARGS": "--master spark://$MASTER_IP:7077 pyspark-shell",
        "PYTHONSTARTUP": "$JUPYTER_KERNELS/pyspark/init.py"
    }
}
EOF
cat << EOF > $JUPYTER_KERNELS/pyspark/init.py
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("PySpark Kernel").config("spark.master", "spark://$MASTER_IP:7077").getOrCreate()
EOF

jupyter lab --no-browser --port=8999 --allow-root --ip=0.0.0.0 &