Приходилось ли вам экспериментировать с кодом или системными утилитами в Linux так, чтобы не трястись за базовую систему и не снести всё с потрохами в случае ошибки кода который должен запустится с root-привилегиями?
А как на счет того, что допустим, необходимо протестировать или запустить целый кластер разнообразных микросервисов на одной машине? Сотню или даже тысячу?
С виртуальными машинами управляемые гипервизором такие задачи решить может и получится, но какой ценой? Например, контейнер в LXD на базе дистрибутива Alpine Linux минимально потребляет всего 7.60MB
ОЗУ, и где корневой раздел после запуска занимает 9.5MB
! Как тебе такое, Илон Маск? Рекомендую ознакомиться с базовыми возможностями LXD — системы контейнеров в Linux
После того, как в целом стало ясно, что такое контейнеры LXD, пойдем дальше и подумаем, а что, если бы была такая платформа-комбайн, где можно было бы безопасно запускать код для хоста, генерировать графики, динамически (интерактивно) связывать UI-виджеты с твоим кодом, дополнять код текстом с блекджеком... форматированием? Что-то типа интерактивного блога? Вауу… Хочу! Хочу! :)
Заглядывай под кат где мы запустим в контейнере JupyterLab — следующей генерации пользовательского интерфейса вместо устаревшего Jupyter Notebook, а также установим такие модули Python как NumPy, Pandas, Matplotlib, IPyWidgets которые позволят вытворять всё перечисленное выше и сохранять это всё в специальном файле — IPython-ноутбуке.
Навигация
- План взлёта на орбиту
- Установка и настройка базовой системы
- Установка базового софта и настройка системы
- Установка и настройка JupyterLab
- Разделяем данные с хостом
- Hello, World!
- Расширяем возможности Python
- Тестируем модули в JupyterLab
- Что ещё?
План взлёта на орбиту ^
Накидаем краткий план действий, чтобы нам было проще реализовать схему выше:
- Установим и запустим контейнер на базе дистрибутива Alpine Linux. Мы будем использовать этот дистрибутив так как он направлен на минималистичность и установим в него только самый необходимый софт, ничего лишнего.
- Добавим дополнительный виртуальный диск в контейнере которому зададим имя —
hostfs
и смонтируем к корневой ФС. Этот диск даст возможность использовать файлы на хосте из заданного каталога внутри контейнера. Тем самым данные будут у нас независимы от контейнера. В случае удаления контейнера, данные останутся на хосте. Также, эта схема полезна для разделения одних данных между многими контейнерами не используя штатные сетевые механизмы дистрибутива контейнера. - Установим Bash, sudo, необходимые библиотеки, добавим и настроим системного пользователя
- Установим Python, модули и скомпилируем для них бинарные зависимости
- Установим и запустим JupyterLab, настроим внешний вид, установим расширения для него.
В этой статье мы с вами начнём с запуска контейнера, не будем рассматривать установку и настройку 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 для новых пользователей в контейнере, следующими действиями мы скопируем в него такие файлы как .bash_profile
, .bashrc
, .dir_colors
из директории /etc/skel
вашей системы, это приукрасит Bash в контейнере, в интерактивном режиме. У меня, установка этих файлов из дистрибутива Manjaro Linux не вызывает критических проблем в контейнере, но у вас может быть иначе, и вам нужно самостоятельно разобраться с этим в случае ошибки в контейнере.
Копируем 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
для которого позже настроим 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 ^
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"
Почистим кеши установок:
lxc exec jupyterlab -- su -l jupyter -c "jupyter lab clean && jlpm cache clean && npm cache clean --force"
Далее выполните следующие две команды для первого запуска JupyterLab. Можно было бы его запустить одной командой, но в этом случае, команда запуска 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 ответственности не несёт:
Однако, мы как раз для этого изолируем целиком JupyterLab и помещаем его в контейнер, чтобы сторонние расширения требующие и использующие NodeJS не смогли как минимум похитить данные на диске кроме тех, которые мы откроем внутри контейнера. Добраться к вашим приватным документам на хосте в /home
процессам из контейнера вряд ли получится, а если и получится, то на это нужно иметь привилегии на файлы в хостовой системе, так как мы запускаем контейнер в непривилегированном режиме. Исходя из этой информации вы можете оценить риск включения расширений в JupyterLab.
Созданные IPython-ноутбуки (страницы в JupyterLab) сейчас будут создаваться в домашней директории пользователя — /home/jupyter
, но в наших планах разделить данные (расшарить) между хостом и контейнером, поэтому, вернитесь в консоль и остановите JupyterLab выполнив 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
Hello, World! ^
Если у вас еще открыта консольная сессия в контейнере с JupyterLab, то перезапустите её с новым ключом --notebook-dir
задав значение /mnt/hostfs
в качестве пути до корня ноутбуков в контейнере для устройства которое мы создали в предыдущем шаге:
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, поэтому, двигаемся дальше :)
P.S. Интересно то, что старая реализация Jupyter под кодовым именем Jupyter Notebook никуда не делась и она существует параллельно с JupyterLab. Для перехода к старой версии перейдите по ссылке добавив в адресе суффикс/tree
, а переход к новой версии осуществляется с суффиксом /lab
, но его не обязательно указывать:
- Jupyter Notebook — http://10.0.5.5:8888/tree
- Jupyter Lab — http://10.0.5.5:8888/lab
Расширяем возможности Python ^
В этом разделе мы установим такие мощные модули языка Python как NumPy, Pandas, Matplotlib, IPyWidgets результаты работы которых интегрируются в ноутбуки JupyterLab.
Прежде чем установить перечисленные модули Python через пакетный менеджер pip
мы должны вначале разрешить системные зависимости в Alpine Linux:
g++
— Нужен для компиляции модулей, так как некоторые из них реализованы на языке C++ и подключаются к 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 matplotlib ipywidgets
Чистим кеши установок:
lxc exec jupyterlab -- rm -rf /home/*/.cache/pip/*
lxc exec jupyterlab -- rm -rf /root/.cache/pip/*
Тестируем модули в JupyterLab ^
Если у вас запущен 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 скрипт :)
Также, вы можете:
- Задать сетевое имя для контейнера вместо IP адреса прописав его в простом
/etc/hosts
и в браузере набирать адрес http://jupyter.local:8888 - Поиграться с ограничением ресурсов для контейнера, для этого прочтите главу в базовых возможностях LXD или получите больше информации на сайте разработчика LXD.
- Поменять тему оформления:
И много чего ещё вы можете! На этом всё. Желаю вам успехов!
UPDATE: 15.04.2020 18:30 — Исправил ошибки в главе "Hello, World!"
UPDATE: 16.04.2020 10:00 — Скорректировал и добавил текст в описании активации менеджера расширений JupyterLab
UPDATE: 16.04.2020 10:40 — Исправил найденные ошибки в тексте и немного изменил к лучшему главу "Установка базового софта и настройка системы"