Запускаем Jupyter на орбиту LXD
- четверг, 16 апреля 2020 г. в 00:27:38
Приходилось ли вам эксперементировать с кодом или системными утилитами в Linux так, чтобы не трястись за базовую систему и не снести всё с потрохами в случае ошибки кода который должен запустится с root-привилегиями?
А как на счет того, что допустим, необходимо протестировать или запустить в проде целый кластер разнообразных микросервисов на одной машине? Сотню или даже тысячу?
С виртуальными машинами управляемые гипервизором такие задачи решить может и получится, но какой ценой? Например, контейнер в LXD на базе дистрибутива Alpine Linux минимально потребляет всего 7.60MB
ОЗУ, и где корневой раздел после запуска занимает 9.5MB
! Как тебе такое, Илон Маск? Рекомендую ознакомиться с базовыми возможностями LXD — системы контейнеров в Linux
После того, как в целом стало ясно, что такое контейнеры LXD, пойдем дальше и подумаем, а что, если бы была такая платформа-комбайн, где можно было бы безопасно запускать код для хоста, генерировать графики, динамически (интерактивно) связывать UI-виджеты с твоим кодом, дополнять код текстом с блекджеком... форматированием? Что-то типа интерактивного блога? Вауу… Хочу! Хочу! :)
Заглядывай под кат где мы запустим в контейнере JupyterLab — следующей генерации пользовательского интерфейса вместо устаревшего Jupyter Notebook, а также установим такие модули Python как NumPy, Pandas, Matplotlib, IPyWidgets которые позволят вытворять всё перечисленное выше и сохранять это всё в специальном файле — IPython-ноутбуке.
Накидаем краткий план действий, чтобы нам было проще реализовать схему выше:
hostfs
и смонтируем к корневой ФС. Этот диск даст возможность использовать файлы на хосте из заданного каталога внутри контейнера. Тем самым данные будут у нас независимы от контейнера. В случае удаления контейнера, данные остануться на хосте. Также, эта схема полезна для разделения одних данных между многими контейнерами не используя штатные сетевые механизмы дистрибутива контейнера.В этой статье мы с вами начнём с запуска контейнера, не будем здесь рассматаривать установку и настройку LXD, всё это вы можете найти в другой статье — Базовые возможности LXD — системы контейнеров в Linux.
Создаём контейнер командой в которой указываем образ — alpine3
, идентификатор для контейнера — jupyterlab
и при необходимости профили конфигурации:
lxc init alpine3 jupyterlab --profile=default --profile=hddroot
Здесь я использую профиль конфигурации hddroot
который указывает создать контейнер с root-разделом в Storage Pool расположенным на физическом 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 |
+------------+---------+------------------+--------------+
Для администрирования нашего контейнера необходимо установить следующий софт:
Package | Description |
---|---|
bash | The GNU Bourne Again shell |
bash-completion | Programmable completion for the bash shell |
sudo | Give certain users the ability to run some commands as root |
shadow | Password and account management tool suite with support for shadow files and PAM |
tzdata | Sources for time zone and daylight saving time data |
nano | 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
— Вызов клиента LXDexec
— Метод клиента LXD, который запускает команду в контейнереjupyterlab
— Идентификатор контейнера--
— Специальный ключ, который указывает не интерпретировать дальше ключи как ключи для lxc
и передать всю оставшуюся строку как есть в контейнерapk
— Пакетный менеджер дистрибутива Alpine Linuxadd
— Метод пакетного менеджера который инсталлирует указанные после команды пакетыДалее, установим в системе тайм-зону 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-пользователь не был беспарольным, ему нужно установить пароль. Следующая команда установит пароль jupyter
для root
:
lxc exec jupyterlab -- /bin/bash -c "echo \"root:jupyter\" | chpasswd"
Вы можете использовать свой пароль или сгенерировать и установить пароль следующим Bash скриптом:
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
под которого настроим jupyterlab
:
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 приложение, поэтому мы должны прежде установить этот интерпретатор. Также, JupyterLab мы будем устанавливать с помощью питоновского пакетного менеджера 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
Устанавливаем JupyterLab через пакетный менеджер pip
:
lxc exec jupyterlab -- python3 -m pip install jupyterlab
Так как расширения в JupyterLab являются экспериментальными и официально они не поставляются вместе с пакетом jupyterlab, поэтому, мы должны установить и настроить это вручную.
Установим NodeJS и менеджер пакетов для него — NPM, так как JupyterLab использует их для своих расширений:
lxc exec jupyterlab -- apk add nodejs npm
Чтобы расширения для JupyterLab которые мы установим работали, их нужно устанавливать в пользовательскую директорию так как приложение будет запускаться от пользователя jupyter
. Проблема в том, что нет параметра в команде запуска которой можно передать каталог, приложение воспринимает только переменную окружения и поэтому мы её должны определить. Для этого, мы пропишем команду экспорта переменной JUPYTERLAB_DIR
в окружении пользователя jupyter
, в файл .bashrc
, который выполняется каждый раз при входе пользователя в систему:
lxc exec jupyterlab -- su -l jupyter -c "echo -e \"\nexport JUPYTERLAB_DIR=\$HOME/.local/share/jupyter/lab\" >> .bashrc"
Следующей командой установим специальное расширение — менеджер расширений в JupyterLab:
lxc exec jupyterlab -- su -l jupyter -c "export JUPYTERLAB_DIR=\$HOME/.local/share/jupyter/lab; jupyter labextension install --no-build @jupyter-widgets/jupyterlab-manager"
Сейчас уже всё готово для первого запуска JupyterLab, но мы можем еще установить несколько полезных расширений:
toc
— Table of Contents, генерирует список заголовков в статье/ноутбукеjupyterlab-horizon-theme
— Тема оформления UIjupyterlab_neon_theme
— Тема оформления UIjupyterlab-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"
Сейчас выполните следующие две команды для первого запуска JupyterLab. Можно было бы его запустить одной командой, но в этом случае, команда запуска, которую в уме трудо держать, она будет запоминаться bash'ем в контейнере, а не на хосте, где и так команд хватает для записи их в историю :)
Логинимся в контейнере как пользователь jupyter
:
lxc exec jupyterlab -- su -l jupyter
Далее запустите JupyterLab с ключами и параметрами как указано:
[jupyter@jupyterlab ~]$ jupyter lab --ip=0.0.0.0 --no-browser
Перейдите в web-браузере по адресу http://10.0.5.5:8888 и на открывшейся странице введите token доступа который вы увидите в консоли. Скопируйте его и вставьте на странице, затем нажмите Login. После входа, перейдите слева в меню расширений, как показано на рисунке ниже, где вы должны активировать менеджер расширений и принять на себя риски по безопасности устанавливая расширения от третих лиц за которые команда JupyterLab development ответственности не несёт :)
Созданные IPython-ноутбуки (страницы в JupyterLab) будут создаваться в домашней директории пользователя jupyter
, но в наших планах разделить данные между хостом и контейнером, поэтому, вернитесь в консоль и остановите JupyterLab выполнив hotkey — CTRL+C
и ответив y
на запрос. Затем разорвите интерактивную ссесию пользователя jupyter
выполнив хоткей CTRL+D
.
P.S. Интересно то, что старая реализация Jupyter под кодовым именем Jupyter Notebook никуда не делась и она существует параллельно с JupyterLab. Для перехода к старой версии перейдите по ссылке добавив в адресе суффикс/tree
, а переход к новой версии осуществляется с суффиксом /lab
, но его не обязательно указывать:
Чтобы разделить данные с хостом, нужно создать в контейнере такое устройство, которое это позволяет делать и для этого выполните следующую команду где мы указываем следующие ключи:
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
Если у вас еще открыта консольная сессия в контейнере с JupyterLab, то перезапустите её с новым ключом --notebook-dir
задав значение /mnt/hostfs
в качестве пути до корня ноутбуков в контейнере для устройства которое мы создали в предыдущем шаге.
Для этого нажмите в консоли CTRL+C
в текущей сессии и ответьте на запрос y
, чтобы завершить сессию JupyterLab, а затем запустите команду:
jupyter lab --ip=0.0.0.0 --no-browser --notebook-dir=/mnt/hostfs
Перейдите на страницу http://10.0.5.5:8888 и создайте первый ваш ноутбук нажав кнопку на странице как указано на рисунке:
Затем в поле на странице введите код на языке Python который выведет классический Hello World!
. По окончании ввода нажмите CTRL+ENTER
или кнопку "play" на панели инструментов сверху чтобы JupyterLab выполнил это:
На этом почти всё готово к использованию, но будет неинтересно, если мы не установим дполнительные Python-модули (полноценные приложения) которые позволяют значительно расширить стандартные возможности Python в JupyterLab, поэтому, двигаемся дальше :)
В этом разделе мы установим такие мощные модули языка Python как NumPy, Pandas, Matplotlib, IPyWidgets результаты работы которых интегрируются в ноутбуки JupyterLab.
Прежде чем установить перечисленные модули Python через пакетный менеджер pip
мы должны вначале разрешить системные зависимости в Alpine Linux:
g++
— Нужен для компиляции модулей, так как некоторые из них реализованы на этом языке и подключаются к Python в рантайме как бинарные модулиfreetype-dev
— зависимость для Python модуля MatplotlibУстанавливаем зависимости:
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
на запрос остановки, а затем запустите заново JupyterLab нажав стрелочку на клавиатуре "вверх", чтобы не вводить команду заново и потом 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-элемент на странице который интерактивно взаимодействует с исходным кодом, а также Matplotlib выводит результат кода в виде картинки как график функции:
Многие примеры IPyWidgets вы можете найти в туториалах здесь
Вы молодцы, если остались и дошли до самого конца статьи. Я специально не стал выкладывать готовый скрипт который бы установил JupyterLab в "один клик" в конце статьи, чтобы поощрить труженников :) Но вы можете это сделать самостоятельно, так как уже знаете как, собрав команды в единый Bash скрипт :)
Также, вы можете:
/etc/hosts
И много чего ещё вы можете! На этом я с вами прощаюсь, пока!
UPDATE: 15:04:2020 18:30 — Исправил ошибки в главе "Hello, World!"