Kubeflow – Jupyter Notebooks 커스텀 이미지

주피터 노트북 커스텀 이미지

주피터 노트북 커스텀 이미지 만드는 방법

주피터 노트북에서 사용할 사용자 커스텀 이미지를 만드는 방법에 대해서 알아보겠습니다.

Kubeflow에서 사용자가 만든 커스텀 이미지를 사용하려면 몇 가지 요구 사항을 충족해야합니다. Kubeflow는 컨테이너 이미지가 실행되면, 주피터가 자동적으로 시작되는 것으로 알고 있습니다. 그래서 컨테이너 이미지에 주피터를 시작하는 기본 명령을 설정해야합니다.

먼저 주피터를 시작하는 명령어가 필요합니다. 다음은 주피터를 실행하는 명령어 입니다.

jupyter notebook

그리고 주피터에게 설정 정보를 넘겨줘야합니다. 다음은 주피터 실행에 필요한 설정 정보들입니다.

  • 작업 디렉토리 : /home/jovyan 디렉토리는 쿠버네티스 PV와 마운트 됩니다. –notebook-dir=/home/jovyan
  • 접근 허용 IP : 주피터 노트북 서버에 모든 IP 에서 접근가능하도록 합니다. –ip=0.0.0.0
  • 노트북 루트 권한 : 사용자가 노트북을 루트로 실행하는것을 허용합니다. –allow-root
  • 포트 설정 : 주피터 포드의 포트를 설정합니다. –port=8888
  • 인증 비활성화 : 주피터의 인증 기능을 비활성화 합니다. Kubeflow에서 사용하는 istio가 인증을 담당하기 때문에, 주피터에서 제공하는 기능을 비활성화 시키는것입니다. –NotebookApp.token=” –NotebookApp.password=”
  • 모든 오리진(origin) 허용 : 주피터 노트북 서버에 모든 오리진이 접근할 수 있도록 허용합니다. –NotebookApp.allow_origin=’*’
  • 기본 URL 설정 : Kubeflow에서 노트북 서버를 관리하는 노트북 컨트롤러는 NB_PREFIX 라는 환경 변수를 사용하여 노트북 서버의 기본 URL을 넘겨줍니다. –NotebookApp.base_url=${NB_PREFIX}

다음은 Dockerfile에 포함해야할 CMD 예제입니다.

ENV NB_PREFIX /

CMD ["sh","-c", "jupyter notebook --notebook-dir=/home/jovyan --ip=0.0.0.0 --no-browser --allow-root --port=8888 --NotebookApp.token='' --NotebookApp.password='' --NotebookApp.allow_origin='*' --NotebookApp.base_url=${NB_PREFIX}"]

주의 하실 점은 ${NB_PREFIX} 라는 환경 변수를 사용하기 때문에 sh 이나 bash 등을 이용해서 노트북을 실행해야합니다.

주피터 노트북 커스텀 이미지 만들기

기존 주피터 노트북 이미지로 만들기

Kubeflow에서 기본으로 제공하는 주피터 노트북 이미지를 가지고 커스텀 이미지를 만들어 보겠습니다.

다음은 https://github.com/kubeflow/kubeflow/blob/master/components/tensorflow-notebook-image/Dockerfile 을 약간 수정한 Dockerfile 입니다.

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

ARG BASE_IMAGE=tensorflow/tensorflow:2.1.0-py3-jupyter

FROM $BASE_IMAGE

ARG TF_SERVING_VERSION=0.0.0
ARG NB_USER=jovyan

# TODO: User should be refactored instead of hard coded jovyan

USER root

ENV DEBIAN_FRONTEND noninteractive

ENV NB_USER $NB_USER

ENV NB_UID 1000
ENV HOME /home/$NB_USER
ENV NB_PREFIX /


# Use bash instead of sh
SHELL ["/bin/bash", "-c"]

RUN apt-get update && apt-get install -yq --no-install-recommends \\
  apt-transport-https \\
  build-essential \\
  bzip2 \\
  ca-certificates \\
  curl \\
  g++ \\
  git \\
  gnupg \\
  graphviz \\
  locales \\
  lsb-release \\
  openssh-client \\
  sudo \\
  unzip \\
  vim \\
  wget \\
  zip \\
  emacs \\
  python3-pip \\
  python3-dev \\
  python3-setuptools \\
  && apt-get clean && \\
  rm -rf /var/lib/apt/lists/*

# Install Nodejs for jupyterlab-manager
RUN curl -sL <https://deb.nodesource.com/setup_12.x> | sudo -E bash -
RUN apt-get update && apt-get install -yq --no-install-recommends \\
  nodejs \\
  && apt-get clean && \\
  rm -rf /var/lib/apt/lists/*

ENV DOCKER_CREDENTIAL_GCR_VERSION=1.4.3
RUN curl -LO <https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases/download/v${DOCKER_CREDENTIAL_GCR_VERSION}/docker-credential-gcr_linux_amd64-${DOCKER_CREDENTIAL_GCR_VERSION}.tar.gz> && \\
    tar -zxvf docker-credential-gcr_linux_amd64-${DOCKER_CREDENTIAL_GCR_VERSION}.tar.gz && \\
    mv docker-credential-gcr /usr/local/bin/docker-credential-gcr && \\
    rm docker-credential-gcr_linux_amd64-${DOCKER_CREDENTIAL_GCR_VERSION}.tar.gz && \\
    chmod +x /usr/local/bin/docker-credential-gcr

# Install AWS CLI
RUN curl "<https://s3.amazonaws.com/aws-cli/awscli-bundle.zip>" -o "/tmp/awscli-bundle.zip" && \\
    unzip /tmp/awscli-bundle.zip && ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws && \\
    rm -rf ./awscli-bundle


RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \\
    locale-gen

ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US.UTF-8

# Create NB_USER user with UID=1000 and in the 'users' group
# but allow for non-initial launches of the notebook to have
# $HOME provided by the contents of a PV
RUN useradd -M -s /bin/bash -N -u $NB_UID $NB_USER && \\
    chown -R ${NB_USER}:users /usr/local/bin && \\
    mkdir -p $HOME

RUN export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \\
    echo "deb <https://packages.cloud.google.com/apt> $CLOUD_SDK_REPO main" > /etc/apt/sources.list.d/google-cloud-sdk.list && \\
    curl <https://packages.cloud.google.com/apt/doc/apt-key.gpg> | apt-key add - && \\
    apt-get update && \\
    apt-get install -y google-cloud-sdk kubectl

# Install Tini - used as entrypoint for container
RUN cd /tmp && \\
    wget --quiet <https://github.com/krallin/tini/releases/download/v0.18.0/tini> && \\
    echo "12d20136605531b09a2c2dac02ccee85e1b874eb322ef6baf7561cd93f93c855 *tini" | sha256sum -c - && \\
    mv tini /usr/local/bin/tini && \\
    chmod +x /usr/local/bin/tini

# NOTE: Beyond this point be careful of breaking out
# or otherwise adding new layers with RUN, chown, etc.
# The image size can grow significantly.

# Install base python3 packages
RUN pip3 --no-cache-dir install \\
    jupyter-console==6.0.0 \\
    jupyterlab \\
    xgboost \\
    kubeflow-fairing==0.7.1.1


RUN docker-credential-gcr configure-docker && chown ${NB_USER}:users $HOME/.docker/config.json

# Configure container startup
EXPOSE 8888
USER jovyan
ENTRYPOINT ["tini", "--"]
CMD ["sh","-c", "jupyter lab --notebook-dir=/home/${NB_USER} --ip=0.0.0.0 --no-browser --allow-root --port=8888 --NotebookApp.token='' --NotebookApp.password='' --NotebookApp.allow_origin='*' --NotebookApp.base_url=${NB_PREFIX}"]

베이스 이미지를 tensorflow/tensorflow:2.1.0-py3-jupyter 로 사용하였고, 파이썬 패키지인 kubeflow-fairing을 0.7.1 버전으로 설치하였습니다. 그리고 CMD를 수정해서 주피터 노트북이 아니라, 주피터 랩이 실행되게 하였습니다.

docker build -t kangwoo/tensorflow-2.1.0-notebook-cpu:1.0.0 .

생성한 컨테이너 이미지를 컨테이너 이미지 레지스트리에 푸시 하려면 접근 권한이 필요합니다. “컨테이너 이미지 레지스트리에 접근할 수 있도록 도커 설정하기”를 참고하기 바랍니다.

docker push kangwoo/tensorflow-2.1.0-notebook-cpu:1.0.0

노트북 목록 화면에서, “NEW SERVER” 버튼을 클릭하여, 새로운 노트북 서버를 생성하는 페이지로 이동합니다.

“Custom Image”를 체크하고, 새로 만든 커스텀 이미지 주소를 입력합니다.

나머지 필드에는 적당한 값을 입력하고, “LAUNCH” 버튼을 클릭하여, 새로운 노트북 서버를 생성합니다.

노트북 서버 목록에서 CONNECT 버튼을 누르고, 노트북 랩에 접속할 수 있습니다.

Kubeflow – Jupyter Notebooks 살펴보기

Kubeflow 주피터 노트북 살펴보기

주피터 노트툭은 데이터 과학자 뿐만 아니라, 데이터 엔지니어에게도 중요한 도구입니다. Kubeflow의 주피터 노트북은 컨테이너 기반이라서 격리된 환경을 제공합니다. 그래서 텐서플로우(TensorFlow), 파이토치(PyTorch), MXNet 같은 머신러닝 프레임워크를 간섭없이 사용할 수 있습니다. 그리고 쿠버네티스 기반에서 작동하기 때문에 CPU와 GPU 같은 리소스를 보다 효율적으로 사용할 수 있습니다.

Kubeflow는 데이터 과학자들이나 데이터 팀 같은 사용자가 작업을 실행할 수 있는 고유한 네임스페이스를 부여 할 수 있습니다. 이 네임스페이스는 보안과 리소스를 격리하는데 사용할 수 있습니다. 쿠버네티스 리소스 할당량을 사용하여, 플랫폼 관리자는 개인이나 팀에게 사용할 수 있는 리소스 양을 제한 할 수 있습니다.

Kubeflow에서 제공하는 주피터 노트북은 클러스터에서 직접 주피터 인스턴스를 생성할 수 있습니다. 그리고 생성된 주피터 인스턴스는 인증 및 접근 제어가 잘 통합되어 있기 때문에, 허락된 사용자가 아니면 접근할 수 없습니다.

주피터 노트북 생성하기

Kubeflow가 설치되었다면, 사용자는 Kubeflow의 중앙 대시보드를 활용하여 노트북을 실행할 수 있습니다.

왼쪽 메뉴에서 “Notebook Servers”를 클릭하여, 노트북 서비스 화면으로 이동할 수 있습니다.

노트북 서비스 화면으로 이동하면, 현재 선택된 네임스페이스 안에 생성된 노트북 서버 목록을 볼 수 있습니다.

“NEW SERVER” 버튼을 클릭하면, 새로운 노트북 서버의 생성 정보를 입력할 수 있는 페이지가 나타납니다.

“Name” 필드에 원하는 노트북 서버의 이름을 입력할 수 있습니다. 이름은 문자와 숫자를 사용 할 수 있고, 공백은 사용할 수 없습니다.

“Namespace” 필드에는 현재 선택되어 있는 네임스페이스 이름이 기본적으로 입력되어 있습니다.

“Image” 필드는 노트북 서버에서 사용할 주피터 컨테이너 이미지를 선택할 수 있습니다. 미리 제공되는 기본 이미지를 사용할 수도 있고, 사용자가 만든 커스텀 이미지도 사용할 수 있습니다.

다음은 미리 제공되는 기본 이미지 화면입니다.

기본 이미지 목록에는 텐스플로우 1.15.2 버전과 2.1.0이 포함된 노트북을 제공하고 있으며, CPU 버전과 GPU 버전을 나누어서 제공하고 있습니다.

GPU 이미지 사용하려면, 두 가지 조건이 만족되어야합니다.

첫번째는 Kubeflow가 설치된 쿠버네티스 클러스터에서 GPU를 사용할 수 있어야 합니다.

다음 명령어를 실행하면 쿠버네티스 클러스터에서 사용 가능한 nvidia GPU 갯수를 조회할 수 있습니다.

kubectl get nodes "-o=custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\\.com/gpu"

사용 가능한 GPU 리소스가 있다면, 다음과 같은 응답 결과를 얻을 수 있습니다.

NAME     GPU
mortar   1

두번째는 입력 양식의 맨 아래에 있는 “GPUs” 부분에서 GPU를 할당해 주어야합니다. 당연히 사용 가능한 GPU가 있어야만 합니다.

커스텀 이미지 옵션을 선택하면, 사용할 이미지 주소를 직접 입력할 수 있습니다. 이미지 주소 형식은 “registry/image:tag” 입니다. 주피터 노트북의 커스텀 이미지를 생성하는 방법은 뒤에 나오는 “주피터 노트북 커스텀 이미지 생성하기“를 참고하시기 바랍니다.

참고로 Kubeflow에서 기본으로 제공하는 주피터 노트북 이미지는 https://console.cloud.google.com/gcr/images/kubeflow-images-public/GLOBAL 에서 조회해 볼 수 있습니다.

“CPU / RAM” 부분에서는 노트북 서버가 사용할 CPU와 메모리를 지정할 수 있습니다.

“Workspace Volume” 부분에서는 노트북 서버에서 사용할 개인 작업 공간 볼륨을 지정할 수 있습니다. Kubeflow는 쿠버네티스의 PV(영구 볼륨 : Persistent Volume) 사용하여 작업 공간 볼륨을 할당합니다. PV는 노트북 서버가 삭제되더라도 남아있기 때문에, 데이터를 유지할 수 있습니다.

“Type” 필드는 새로운 PV를 만들지, 기존에 존재하는 PV를 사용할지를 선택할 수 있게 해줍니다. “New”는 새로운 PV 생성을 의미하며, “Exsiting”은 기존 PV를 사용하다는 것을 의미합니다.

“Name” 필드는 PVC(PersistentVolumeClaim)의 이름입니다. 노트북 서버가 생성될 때, 해당 이름으로 PVC가 생성되고, 쿠버네티스의 동적 프로비저너(Dynamic Provisioner)에 의해서 PV가 생성되게 됩니다.

“Size” 필드는 볼륨의 크기입니다.

“Mode” 필드는 PV의 접근 모드(Access Mode) 입니다.

  • ReadWriteOnce : 단일 노드에서 볼륨을 읽기/쓰기로 마운트 할 수 있습니다
  • ReadOnlyMany : 복수개의 노드에서 볼륨을 읽기 전용으로 마운트 할 수 있습니다
  • ReadWriteMany : 복수개의 노드에서 볼륨을 읽기 / 쓰기로 마운트 할 수 있습니다

“Mount Point” 필드는 는 볼륨을 마운트할 경로입니다.

“Data Volumes” 부분에서는 필요에 따라, 데이터 볼륨을 추가 할 수 있습니다.

“Confiurations” 부분에서는 필요에 따라, PodDefault 라는 CR을 사용해서 추가 구성을 설정할 수 있습니다. 이 옵션을 사용하려면 PodDefault 리소스를 만들어야 합니다.

PodDefault는 환경 변수나 볼륨 등 공통 데이터를 포드(pod)에 주입하기 위해서 만들어진 Kubeflow CR 입니다.

다음은 team-secret 라는 볼륨을 마운트하는 PodeDefault 매니페스트 입니다.

apiVersion: "kubeflow.org/v1alpha1"
kind: PodDefault
metadata:
  name: add-team-secret
  namespace: admin
spec:
 selector:
  matchLabels:
    add-user-secret: "true"
 desc: "Add team credential"
 volumeMounts:
 - name: secret-volume
   mountPath: /secret/team
 volumes:
 - name: secret-volume
   secret:
    secretName: team-secret

PodDefault를 생성한 후, 노트북 서버 생성 화면을 새로 고치면 “Confiurations” 부분에서 나타는 것을 알 수 있습니다.

만약 이 “Add team credentail” 옵션을 선택해서 노트북 서버를 생성하게 되면, 노트북 서버의 포드에 아래 PodDefault에 정의한 부분이 반영됩니다.

다음은 “Add team credentail” 노트북 서버 포드의 일부분 입니다.

apiVersion: v1
kind: Pod
metadata:
  labels:
    add-user-secret: "true"
...
    volumeMounts:
    - mountPath: /secret/team
      name: secret-volume
...
  volumes:
  - name: secret-volume
    secret:
      defaultMode: 420
      secretName: team-secret
...

“GPUs” 부분에서는 노트북 서버에서 사용할 GPU 갯수를 설정할 수 있습니다.

“Miscellaneous Settings” 부분에서는 공유 메모리 활성화에 대한 설정을 변경할 수 있습니다. 기본값은 공유 메모리가 활성화 된 것입니다. PyTorch와 같은 일부 라이브러리는 멀티 프로세싱에 공유 메모리를 사용합니다. 현재 쿠버네티스에는 공유 메모리를 활성화시키는 방법이 없기 때문에, Kubeflow는 /dev/shm 라는 빈 디렉토리를 만듭니다.

맨 아래이 있는 “LAUNCH” 버튼을 클릭하면, 노트북 서버를 생성하기 시작하고, 노트북 서버 목록 페이지로 이동합니다. 목록 페이지의 “Status” 컬럼에 있는 상태 아이콘에 마우스 커서를 가져가면, 상태를 알 수 있니다.

노트북 서버를 생성하는데 몇 분이 걸릴 수 있습니다

좀 더 자세한 상태를 보고 싶으면, 포드를 이벤트를 조회해 보면 됩니다.

다음은 admin 이라는 네임스페이스의 rain 이라는 노트북 서버의 포드를 조회해 본 명령어입니다.

kubectl -n admin describe pod -l notebook-name=rain

다음과 같은 응답 결과를 얻을 수 있습니다.

...
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  6m23s  default-scheduler  Successfully assigned admin/rain-0 to mortar
  Normal  Pulled     6m22s  kubelet, mortar    Container image "gcr.io/istio-release/proxy_init:release-1.3-latest-daily" already present on machine
  Normal  Created    6m22s  kubelet, mortar    Created container istio-init
  Normal  Started    6m22s  kubelet, mortar    Started container istio-init
  Normal  Pulling    6m21s  kubelet, mortar    Pulling image "gcr.io/kubeflow-images-public/tensorflow-1.14.0-notebook-cpu:v-base-ef41372-1177829795472347138"
  Normal  Pulled     5m44s  kubelet, mortar    Successfully pulled image "gcr.io/kubeflow-images-public/tensorflow-1.14.0-notebook-cpu:v-base-ef41372-1177829795472347138"
  Normal  Created    5m43s  kubelet, mortar    Created container rain
  Normal  Started    5m43s  kubelet, mortar    Started container rain
  Normal  Pulled     5m43s  kubelet, mortar    Container image "gcr.io/istio-release/proxyv2:release-1.3-latest-daily" already present on machine
  Normal  Created    5m43s  kubelet, mortar    Created container istio-proxy
  Normal  Started    5m43s  kubelet, mortar    Started container istio-proxy

노트북 서버 생성이 완료되면, 노트북 서버 목록 페이지에서 다음과 같은 화면을 볼 수 있습니다.

생성한 노트북 서버의 상태가 녹색 체크 표시 아이콘이면 정상적으로 만들어진것 입니다. 우측에 있는 “CONNECT” 버튼을 클릭하면, 노트북 서버에 접속할 수 있습니다.

다음은 노트북 서버에 접속한 화면입니다.

주피터 노트북 삭제하기

노트북 서버를 삭제하려면 노트북 서머 목록 페이지에서, 오른쪽 끝에 있는 휴지통 모양을 아이콘을 클릭하면 됩니다.

휴지통 아이콘을 클릭하면, 정말로 노트북 서버를 삭제할 것인지 물어봅니다. “DELETE” 버튼을 클릭하면, 노트북 서버는 삭제됩니다.

쿠버네티스에서 직접 삭제하고 싶으면, kubectl 사용해서 삭제하면 됩니다.

다음은 admin 이라는 네임스페이스의 rain 이라는 노트북 서버를 삭제하는 명령어입니다.

kubectl -n admin delete notebook rain

노트북 서버를 삭제해도, 생성한 PV는 삭제되지 않습니다. 더 이상 필요없는 PV는 kubectl을 사용해서 삭제하면됩니다. 엄밀히 말하면, PVC를 삭제하면 PV가 자동으로 삭제되기 때문에 PVC를 삭제하면 됩니다. 노트북 서버를 생성할때 입력한 볼륨 이름이 PVC 이름이기 때문에, 볼륨 이름을 기억하고 있어야합니다.

볼륨 이름이 기억나지 않는다면, 노트북 서버 목록 페이지의 볼륨 컬럼에서 확인할 수 있습니다. 볼륨 컬럼을 클릭하면, 볼륨 목록이 화면에 나타납니다.

다음은 기본값으로 생성한 rain 이라는 노트북 서버의 볼륨 목록입니다.

“workspace-rain”이라는 볼륨과 “dshm” 이라는 볼륨이 보입니다. “dshm”는 공유 메모리 때문에 사용하는 볼륨이기 때문에 따로 삭제하지 않아도 됩니다.

다음은 admin 이라는 네임스페이스의 workspace-rain 이라는 PVC를 삭제하는 명령어입니다.

kubectl -n admin delete pvc workspace-rain

PVC는 노트북 서버를 먼저 삭제한 후 삭제하는 것이 좋습니다. PVC를 사용하고 있는 노트북 서버가 있을 경우 삭제가 안되기 때문입니다. 정확히 말하면 “Terminating”에서 더이상 진행되지 않습니다. 만약 이런 경우가 발생하면, 해당 PVC를 사용하는 노트북 서버를 삭제하면 됩니다.

주피터 노트북에서 쿠버네티스 사용하기

Kubeflow의 Profile 을 이용해서 네임스페이스를 생성한 경우, 네임스페이스에는 default-editordefault-viewer 라는 두 개의 서비스 계정(ServiceAccount)이 만들어집니다. 이중에서 default-editor 라는 서비스 계정은 주피터 노트북 포드를 실행할 때 서비스 계정으로 사용됩니다. 이 서비스 계정은 kubeflow-edit 라는 클러스터롤(ClusterRole)이 바인딩되어 있으며, 여기에는 Pods, Deployments, Services, Jobs, TfJobs, PyTorchJobs 등의 많은 쿠버네티스 권한이 존재하고 있습니다.

다음은 kubeflow-edit 라는 클라서트롤이 가지고 있는 권한을 보는 명령어입니다.

kubectl describe clusterrole kubeflow-edit

그리고 Kubeflow에서 제공하는 기본 주피터 이미지에는 kubectl 이 포함되어 있습니다.

그래서 주피터 노트북에서 쿠버네티스 리소스를 사용할 수 있는 것입니다.

주피터에서 노트북을 하나 생성한 후, 노트북 셀에 다음 명령어를 실행해서 쿠버네티스 포드 목록을 조회할 수 있습니다.

!kubectl get pod

명령어를 입력한 후 shift + enter 를 누르면 셀을 실행 할 수 있습니다.