Запускаємо Jupyter на орбіту LXD

Чи доводилося вам експериментувати з кодом або системними утилітами в Linux так, щоб не трястися за базову систему і не знести все з тельбухом у разі помилки коду який повинен запуститися з root-привілеями?

А як щодо того, що припустимо, необхідно протестувати або запустити цілий кластер різноманітних мікросервісів на одній машині? Сотню чи навіть тисячу?

З віртуальними машинами керовані гіпервізором такі завдання вирішити може й вийде, але якою ціною? Наприклад, контейнер в LXD на базі дистрибутива Alpine Linux мінімально споживає всього 7.60MB ОЗУ, і де кореневий розділ після запуску займає 9.5MB! Як тобі таке, Ілон Маск? Рекомендую ознайомитись з базовими можливостями LXD - системи контейнерів у Linux

Після того, як загалом стало ясно, що таке контейнери LXD, підемо далі і подумаємо, а що, якби була така платформа-комбайн, де можна було б безпечно запускати код для хоста, генерувати графіки, динамічно (інтерактивно) зв'язувати UI- віджети з твоїм кодом, доповнювати код текстом із блэкджеком… форматуванням? Щось на кшталт інтерактивного блогу? Вауу… Хочу! Хочу! 🙂

Заглядай під кат де ми запустимо в контейнері лабораторія Юпітера — наступної генерації інтерфейсу користувача замість застарілого Jupyter Notebook, а також встановимо такі модулі Python як numpy, Панди, Матплотліб, IPyWidgets які дозволять витворяти все перераховане вище та зберігати це все у спеціальному файлі – IPython-ноутбуку.

Запускаємо Jupyter на орбіту LXD

План зльоту на орбіту ^

Запускаємо Jupyter на орбіту LXD

Накидаємо короткий план дій, щоб нам було простіше реалізувати схему вище:

  • Встановимо та запустимо контейнер на базі дистрибутива Alpine Linux. Ми будемо використовувати цей дистрибутив так як він спрямований на мінімалістичність і встановимо в нього тільки найнеобхідніший софт, нічого зайвого.
  • Додамо додатковий віртуальний диск у контейнері якому задамо ім'я hostfs та змонтуємо до кореневої ФС. Цей диск дозволить використовувати файли на хості із заданого каталогу всередині контейнера. Таким чином, дані будуть у нас незалежні від контейнера. У разі видалення контейнера дані залишаться на хості. Також, ця схема корисна для поділу одних даних між багатьма контейнерами, не використовуючи штатні мережеві механізми дистрибутива контейнера.
  • Встановимо Bash, sudo, необхідні бібліотеки, додамо та налаштуємо системного користувача
  • Встановимо Python, модулі та скомпілюємо для них бінарні залежності
  • Встановимо та запустимо лабораторія Юпітера, Налаштуємо зовнішній вигляд, встановимо розширення для нього.

У цій статті ми з вами почнемо з запуску контейнера, не розглядатимемо встановлення та налаштування LXD, все це ви можете знайти в іншій статті. Базові можливості LXD - системи контейнерів у Linux.

Встановлення та налаштування базової системи ^

Створюємо контейнер командою, в якій вказуємо образ. alpine3ідентифікатор для контейнера jupyterlab та за необхідності профілі конфігурації:

lxc init alpine3 jupyterlab --profile=default --profile=hddroot

Тут я використовую профіль конфігурації hddroot який вказує створити контейнер з root-розділом в Пул зберігання розташований на фізичному HDD диску:

lxc profile show hddroot

config: {}
description: ""
devices:
  root:
    path: /
    pool: hddpool
    type: disk
name: hddroot
used_by: []
lxc storage show hddpool

config:
  size: 10GB
  source: /dev/loop1
  volatile.initial_source: /dev/loop1
description: ""
name: hddpool
driver: btrfs
used_by:
- /1.0/images/ebd565585223487526ddb3607f5156e875c15a89e21b61ef004132196da6a0a3
- /1.0/profiles/hddroot
status: Created
locations:
- none

Це дає мені можливість експерементувати з контейнерами на диску HDD заощаджуючи ресурси SSD диска який також є в моїй системі 🙂 для якого у мене створений окремий профіль конфігурації ssdroot.

Після створення контейнера він перебуває в стані STOPPEDтому нам треба його стартанути запустивши в ньому init-систему:

lxc start jupyterlab

Виведемо список контейнерів у LXD використовуючи ключ -c який вказує які columns вивести на екран:

lxc list -c ns4b
+------------+---------+-------------------+--------------+
|    NAME    |  STATE  |       IPV4        | STORAGE POOL |
+------------+---------+-------------------+--------------+
| jupyterlab | RUNNING | 10.0.5.198 (eth0) | hddpool      |
+------------+---------+-------------------+--------------+

При створенні контейнера IP адреса вибралася випадковим чином, оскільки ми використовували профіль конфігурації default який був раніше налаштований у статті Базові можливості LXD - системи контейнерів у Linux.

Ми змінимо цю IP-адресу на більш запам'ятовується, створивши мережевий інтерфейс лише на рівні контейнера, а чи не лише на рівні профілю конфігурації як це нині у поточної конфігурації. Це не обов'язково робити, ви можете пропустити це.

Створюємо мережевий інтерфейс eth0 який лінкуємо з комутатором (мережевим мостом) lxdbr0 в якому ми включили NAT за минулою статтею та контейнером зараз буде доступ в Інтернет, а також інтерфейсу призначаємо статичну IP адресу 10.0.5.5:

lxc config device add jupyterlab eth0 nic name=eth0 nictype=bridged parent=lxdbr0 ipv4.address=10.0.5.5

Після додавання пристрою контейнер необхідно перезавантажити:

lxc restart jupyterlab

Перевіряємо статус контейнера:

lxc list -c ns4b
+------------+---------+------------------+--------------+
|    NAME    |  STATE  |       IPV4       | STORAGE POOL |
+------------+---------+------------------+--------------+
| jupyterlab | RUNNING | 10.0.5.5 (eth0)  | hddpool      |
+------------+---------+------------------+--------------+

Встановлення базового софту та налаштування системи ^

Для адміністрування нашого контейнера необхідно встановити наступний софт:

пакет
Опис

бити
The GNU Bourne Again shell

bash-завершення
Programmable completion for bash shell

Суду
Give certain users ability to run some commands as root

тінь
Password and account management tool suite with support for shadow files and PAM

тздата
Sources for time zone and daylight saving time data

нано
Pico editor clone with enhancements

Додатково, ви можете встановити підтримку в системі man-pages, встановивши наступні пакети. man man-pages mdocml-apropos less

lxc exec jupyterlab -- apk add bash bash-completion sudo shadow tzdata nano

Розберемо команди та ключі, які ми використовували:

  • lxc - Виклик клієнта LXD
  • exec — Метод клієнта LXD, який запускає команду у контейнері
  • jupyterlab - Ідентифікатор контейнера
  • -- — Спеціальний ключ, який вказує не інтерпретувати далі ключі як ключі lxc і передати весь рядок, що залишився, як є в контейнер
  • apk - Пакетний менеджер дистрибутива Alpine Linux
  • add — Метод пакетного менеджера, який встановлює вказані після команди пакети

Далі, встановимо в системі тайм-зону Europe/Moscow:

lxc exec jupyterlab -- cp /usr/share/zoneinfo/Europe/Moscow /etc/localtime

Після встановлення тайм-зони, пакет tzdata в системі більше не потрібен, він займатиме місце, тому видалимо його:

lxc exec jupyterlab -- apk del tzdata

Перевіряємо тайм-зону:

lxc exec jupyterlab -- date

Wed Apr 15 10:49:56 MSK 2020

Щоб не витрачати багато часу на налаштування Bash для нових користувачів у контейнері, наступними діями ми скопіюємо в нього з хостової системи вже готові skel-файли. Це дозволить прикрасити Bash у контейнері в інтрактивному режимі. У мене хостова система - Manjaro Linux і копіювані файли /etc/skel/.bash_profile, /etc/skel/.bashrc, /etc/skel/.dir_colors в принципі підходять до Alpine Linux і критичних проблем не викликають, але у вас це може бути інший дистрибутив і вам потрібно самостійно розібратися у разі помилки Bash, що запускається в контейнері.

Копіюємо skel-файли у контейнер. Ключ --create-dirs створить необхідні директорії, якщо вони не існують:

lxc file push /etc/skel/.bash_profile jupyterlab/etc/skel/.bash_profile --create-dirs
lxc file push /etc/skel/.bashrc jupyterlab/etc/skel/.bashrc
lxc file push /etc/skel/.dir_colors jupyterlab/etc/skel/.dir_colors

Для існуючого root-користувача скопіюємо в домашню директорію щойно скопійовані в контейнер skel-файли:

lxc exec jupyterlab -- cp /etc/skel/.bash_profile /root/.bash_profile
lxc exec jupyterlab -- cp /etc/skel/.bashrc /root/.bashrc
lxc exec jupyterlab -- cp /etc/skel/.dir_colors /root/.dir_colors

В Alpine Linux для користувачів встановлюється системна оболонка /bin/sh, ми замінимо її у root користувача на Bash:

lxc exec jupyterlab -- usermod --shell=/bin/bash root

Щоб root Користувач не був безпарольним, йому потрібно встановити пароль. Наступна команда згенерує та встановить йому новий випадковий пароль, який ви побачите на екрані консолі після її виконання:

lxc exec jupyterlab -- /bin/bash -c "PASSWD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 12); echo "root:$PASSWD" | chpasswd && echo "New Password: $PASSWD""

New Password: sFiXEvBswuWA

Також створимо нового системного користувача. jupyter для якого пізніше налаштуємо лабораторія Юпітера:

lxc exec jupyterlab -- useradd --create-home --shell=/bin/bash jupyter

Згенеруємо та встановимо йому пароль:

lxc exec jupyterlab -- /bin/bash -c "PASSWD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 12); echo "jupyter:$PASSWD" | chpasswd && echo "New Password: $PASSWD""

New Password: ZIcbzWrF8tki

Далі виконаємо дві команди, перша створить системну групу sudo, а друга додасть до неї користувача jupyter:

lxc exec jupyterlab -- groupadd --system sudo
lxc exec jupyterlab -- groupmems --group sudo --add jupyter

Переглянемо, до яких груп входить користувач jupyter:

lxc exec jupyterlab -- id -Gn jupyter

jupyter sudo

Все - ок, рухаємося далі.

Дозволимо всім користувачам, які входять до групи sudo використовувати команду sudo. Для цього виконайте наступний скрипт, де sed розкоментує рядок параметра у конфігураційному файлі /etc/sudoers:

lxc exec jupyterlab -- /bin/bash -c "sed --in-place -e '/^#[ t]*%sudo[ t]*ALL=(ALL)[ t]*ALL$/ s/^[# ]*//' /etc/sudoers"

Встановлення та налаштування JupyterLab ^

лабораторія Юпітера — це Python додаток, тому ми повинні спочатку встановити цей інтерпретатор. Також, лабораторія Юпітера ми будемо встановлювати за допомогою пітонівського пакетного менеджера pip, а не системного, тому що в системному репозиторії він може бути застарілим, і тому ми повинні вручну дозволити залежності для нього встановивши такі пакети. python3 python3-dev gcc libc-dev zeromq-dev:

lxc exec jupyterlab -- apk add python3 python3-dev gcc libc-dev zeromq-dev

Обновимо python-модулі та пакетний менеджер pip до актуальної версії:

lxc exec jupyterlab -- python3 -m pip install --upgrade pip setuptools wheel

Встановлюємо лабораторія Юпітера через пакетний менеджер pip:

lxc exec jupyterlab -- python3 -m pip install jupyterlab

Оскільки розширення в лабораторія Юпітера є експериментальними та офіційно вони не поставляються разом з пакетом jupyterlab, тому, ми повинні встановити та налаштувати це вручну.

Встановимо NodeJS і менеджер пакетів для нього - NPM, оскільки лабораторія Юпітера використовує їх для своїх розширень:

lxc exec jupyterlab -- apk add nodejs npm

Щоб розширення для лабораторія Юпітера які ми встановимо працювали, їх потрібно встановлювати в директорію користувача так як додаток буде запускатися від користувача jupyter. Проблема в тому, що немає параметра в команді запуску якої можна передати каталог, програма сприймає лише змінну оточення і тому ми її повинні визначити. Для цього ми пропишемо команду експорту змінної JUPYTERLAB_DIR в оточенні користувача jupyterу файл .bashrc, який виконується щоразу під час входу користувача до системи:

lxc exec jupyterlab -- su -l jupyter -c "echo -e "nexport JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab" >> .bashrc"

Наступною командою встановимо спеціальне розширення — менеджер розширень у лабораторія Юпітера:

lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build @jupyter-widgets/jupyterlab-manager"

Наразі вже все готове для першого запуску лабораторія Юпітера, але ми можемо встановити кілька корисних розширень:

  • toc — Table of Contents, генерує список заголовків у статті/ноутбуці
  • jupyterlab-horizon-theme - Тема оформлення UI
  • jupyterlab_neon_theme - Тема оформлення UI
  • jupyterlab-ubu-theme - Ще одна тема оформлення від автора цієї статті 🙂 Але в цьому випадку буде показана установка з репозиторію GitHub

Отже, виконайте наступні команди, щоб встановити ці розширення:

lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build @jupyterlab/toc @mohirio/jupyterlab-horizon-theme @yeebc/jupyterlab_neon_theme"
lxc exec jupyterlab -- su -l jupyter -c "wget -c https://github.com/microcoder/jupyterlab-ubu-theme/archive/master.zip"
lxc exec jupyterlab -- su -l jupyter -c "unzip -q master.zip && rm master.zip"
lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build jupyterlab-ubu-theme-master"
lxc exec jupyterlab -- su -l jupyter -c "rm -r jupyterlab-ubu-theme-master"

Після встановлення розширень ми повинні їх скомпілювати, тому що раніше при установці вказували ключ --no-build для економії часу. Зараз ми значно прискоримося скомпілювавши їх разом за один раз:

lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=$HOME/.local/share/jupyter/lab; jupyter lab build"

Наразі виконайте наступні дві команди для першого запуску лабораторія Юпітера. Можна було б його запустити однією командою, але в цьому випадку, команда запуску, яку в умі важко тримати, вона запам'ятовуватиметься bash'ем у контейнері, а не на хості, де і так команд вистачає для запису їх в історію 🙂

Логін у контейнері як користувач jupyter:

lxc exec jupyterlab -- su -l jupyter

Далі запустіть лабораторія Юпітера з ключами та параметрами як зазначено:

[jupyter@jupyterlab ~]$ jupyter lab --ip=0.0.0.0 --no-browser

Перейдіть на веб-браузер за адресою http://10.0.5.5:8888 і на сторінці введіть знак доступу який ви побачите у консолі. Скопіюйте його та вставте на сторінці, потім натисніть Увійти. Після входу, перейдіть зліва в меню розширень, як показано на малюнку нижче, де вам запропонують при активації менеджера розширень прийняти на себе ризики безпеки встановлюючи розширення від третіх осіб за які команда JupyterLab development відповідальності не несе:

Запускаємо Jupyter на орбіту LXD

Однак ми якраз для цього ізолюємо цілком лабораторія Юпітера і поміщаємо його в контейнер, щоб сторонні розширення, що вимагають і використовують NodeJS, не змогли як мінімум викрасти дані на диску крім тих, які ми відкриємо всередині контейнера. Дістатися до ваших приватних документів на хості /home процесам з контейнера навряд чи вийде, а якщо і вийде, то на це потрібно мати привілеї на файли в хостовій системі, тому що ми запускаємо контейнер в непривілейованому режимі. Виходячи з цієї інформації ви можете оцінити ризик включення розширень у лабораторія Юпітера.

Створені IPython-ноутбуки (сторінки в лабораторія Юпітера) зараз будуть створюватися в домашній директорії користувача /home/jupyter, але в наших планах розділити дані (розшарити) між хостом і контейнером, тому поверніться в консоль і зупиніть лабораторія Юпітера виконавши hotkey - CTRL+C і відповівши y на запит. Потім розірвіть інтерактивну сесію користувача jupyter виконавши хоткей CTRL+D.

Поділяємо дані з хостом ^

Щоб розділити дані з хостом, потрібно створити в контейнері такий пристрій, який це дозволяє робити і для цього виконайте наступну команду, де ми вказуємо наступні ключі:

  • lxc config device add — Команда додає конфігурацію пристрою
  • jupyter — Ідентифікатор контейнера, до якого додається конфігурація
  • hostfs - Ідентифікатор пристрою. Ви можете поставити будь-яке ім'я.
  • disk — Вказується тип пристрою
  • path — Вказується шлях у контейнері, до якого LXD змонтує цей пристрій.
  • source - Вказується джерело, шлях до каталогу на хості який ви хочете розділити з контейнером. Вкажіть шлях відповідно до ваших уподобань
lxc config device add jupyterlab hostfs disk path=/mnt/hostfs source=/home/dv/projects/ipython-notebooks

Для каталогу /home/dv/projects/ipython-notebooks має бути встановлена ​​роздільна здатність контейнерного користувача який зараз має UID рівний SubUID + UIDдивіться розділ Безпека. Привілеї контейнерів у статті Базові можливості LXD - системи контейнерів у Linux.

Встановлюємо дозвіл на хості, де власником буде контейнерний користувач jupyter, а змінна $USER вкаже вашого хостового користувача як групу:

sudo chown 1001000:$USER /home/dv/projects/ipython-notebooks

Привіт Світ! ^

Якщо у вас ще відкрита консольна сесія в контейнері з лабораторія Юпітера, то перезапустіть її з новим ключем --notebook-dir задавши значення /mnt/hostfs як шлях до кореня ноутбуків у контейнері для пристрою, який ми створили в попередньому кроці:

jupyter lab --ip=0.0.0.0 --no-browser --notebook-dir=/mnt/hostfs

Потім перейдіть на сторінку http://10.0.5.5:8888 і створіть перший ваш ноутбук натиснувши кнопку на сторінці як вказано на малюнку нижче:

Запускаємо Jupyter на орбіту LXD

Потім у полі на сторінці введіть код мовою Python, який виведе класичний Hello World!. Після введення натисніть CTRL+ENTER або кнопку «play» на панелі інструментів зверху, щоб JupyterLab виконав це:

Запускаємо Jupyter на орбіту LXD

На цьому майже все готове до використання, але буде нецікаво, якщо ми не встановимо додаткові Python-модулі (повноцінні додатки), які дозволяють значно розширити стандартні можливості Python у лабораторія Юпітератому рухаємося далі 🙂

PS Цікаво те, що стара реалізація Юпітер під кодовим ім'ям Jupyter Notebook нікуди не поділася і вона існує паралельно з лабораторія Юпітера. Для переходу до старої версії перейдіть за посиланням, додавши на адресу суфікс/tree, а перехід до нової версії здійснюється із суфіксом /lab, Але його не обов'язково вказувати:

Розширюємо можливості Python ^

У цьому розділі ми встановимо такі потужні модулі мови Python як numpy, Панди, Матплотліб, IPyWidgets результати роботи яких інтегруються в ноутбуки лабораторія Юпітера.

Перш ніж встановити перераховані модулі Python через пакетний менеджер pip ми повинні спочатку дозволити системні залежності в Alpine Linux:

  • g++ — Потрібен для компіляції модулів, оскільки деякі з них реалізовані мовою C + + і підключаються до Python у рантаймі як бінарні модулі
  • freetype-dev - Залежність для Python модуля Матплотліб

Встановлюємо залежності:

lxc exec jupyterlab -- apk add g++ freetype-dev

Є одна проблема, в поточному стані дистрибутива Alpine Linux скомпілювати нову версію NumPy не вийде, вилетить помилка компіляції, яку мені не вдалося вирішити:

ERROR: Could not build wheels for numpy which use PEP 517 and cannot be installed directly

Тому цей модуль ми встановимо як системний пакет, який поширює вже скомпільовану версію, але трохи старіший, ніж доступна зараз на сайті:

lxc exec jupyterlab -- apk add py3-numpy py3-numpy-dev

Далі встановлюємо Python-модулі через пакетний менеджер pip. Наберіться терпіння, оскільки деякі модулі компілюватимуться і це займе кілька хвилин. На моїй машині компіляція зайняла ~15 хвилин:

lxc exec jupyterlab -- python3 -m pip install pandas ipywidgets matplotlib

Чистимо кеші установок:

lxc exec jupyterlab -- rm -rf /home/*/.cache/pip/*
lxc exec jupyterlab -- rm -rf /root/.cache/pip/*

Тестуємо модулі в JupyterLab ^

Якщо у вас запущено лабораторія Юпітера, перезапустіть його, щоб активувати нові модулі. Для цього у консольній сесії натисніть CTRL+C там, де він у вас запущений і введіть y на зупинку, а потім запустіть заново лабораторія Юпітера натиснувши стрілку на клавіатурі «вгору», щоб не вводити команду заново і потім Enter щоб запустити:

jupyter lab --ip=0.0.0.0 --no-browser --notebook-dir=/mnt/hostfs

Перейдіть на сторінку http://10.0.5.5:8888/lab або оновіть сторінку в браузері, а потім введіть наступний код у новому комірці ноутбука:

%matplotlib inline

from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np

def f(m, b):
    plt.figure(2)
    x = np.linspace(-10, 10, num=1000)
    plt.plot(x, m * x + b)
    plt.ylim(-5, 5)
    plt.show()

interactive_plot = interactive(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))
output = interactive_plot.children[-1]
output.layout.height = '350px'
interactive_plot

У вас повинен вийти результат як на малюнку нижче, де IPyWidgets генерує UI-елемент на сторінці, який інтерактивно взаємодіє з вихідним кодом, а також Матплотліб виводить результат коду у вигляді картинки як графік функції:

Запускаємо Jupyter на орбіту LXD

Багато прикладів IPyWidgets ви можете знайти в туторіалах тут

Що ще? ^

Ви молодці, якщо залишилися і дійшли до кінця статті. Я спеціально не став викладати готовий скрипт наприкінці статті, який би встановив лабораторія Юпітера в «один клік», щоб заохотити трудівників 🙂 Але ви можете це зробити самостійно, тому що вже знаєте як зібравши команди в єдиний Bash скрипт 🙂

Також, ви можете:

  • Задати мережеве ім'я для контейнера замість IP адреси, прописавши його в простому /etc/hosts та у браузері набирати адресу http://jupyter.local:8888
  • Погратися з обмеженням ресурсів для контейнера, для цього прочитайте розділ у базових можливостях LXD або отримайте більше інформації на сайті розробника LXD.
  • Змінити тему оформлення:

Запускаємо Jupyter на орбіту LXD

І багато чого ще можете! На цьому все. Бажаю вам успіху!

UPDATE: 15.04.2020 18:30 — Виправив помилки у розділі «Hello, World!»
UPDATE: 16.04.2020 10:00 — Скоригував та додав текст в описі активації менеджера розширень лабораторія Юпітера
UPDATE: 16.04.2020 10:40 — Виправив знайдені помилки в тексті і трохи змінив на краще розділ «Встановлення базового софту та налаштування системи»

Джерело: habr.com

Додати коментар або відгук