Привет, Хабр!

В этой статье хочется поделится с вами настройкой девелоперского окружения под разработку скриптов на Python и запуск их в изолированном окружении, практически за пару кликов в редакторе VS Code.

В качестве изолированного окружения будет рассмотрена первичная настройка и создание контейнера на базе LXC под управлением ОС Ubuntu 17.10. Почему не Docker? Сложно ответить, выдать какие-то аргументы. От части концепция виртуализации ОС субъективно ближе по духу, чем виртуализация процессов, но принцип который здесь будет изложен, с некоторыми поправками, подойдет к запуску кода в Docker-контейнерах.



Что вы получите?


  • Научитесь запускать в один клик python-скрипты в контейнере, а также удаленный debug из VS Code
  • Узнаете как установить LXD, настроить сеть и запустить первый контейнер
  • Ознакомитесь с установкой и настройкой редактора исходного кода VS Code, набором расширений которые позволят комфортно писать и отлаживать код.

Чего здесь не будет?


  • Не будет полного обзора LXC/LXD, вам придется самостоятельно найти дополнительную информацию о Linux-контейнерах.
  • Не будет рассмотрен python модуль virtualenv для создания виртуального окружения на хосте разработчика как альтернатива контейнерам.
  • Не будет сравнения в духе Docker vs LXD, выводы вы сделаете самостоятельно на основании вашего опыта.

Содержание:



Выбор backend хранилища


Перед установкой LXD необходимо определиться, какой тип backend хранилища для контейнеров будет использоваться. Позже изменить будет нельзя. Доступно несколько вариантов: ZFS, btrfs, LVM или файловая система хоста. Для сравнения возможностей доступна таблица сравнения в официальной документации. Предлагаю выбрать ZFS который позволит в будущем легко создавать COW (copy-on-write) снимки (snapshots) контейнеров. Установим в системе пакет утилит работы с ZFS:

$ sudo apt install zfsutils-linux

Установка пакета LXD


Здесь нет сложностей, пакет входит в стандартную поставку системы, поэтому командуем в консоли:


$ sudo apt install lxd

После установки LXD, текущий пользователь будет добавлен в системную группу lxd, но чтобы продолжить настройку с необходимыми привелегиями в системе, необходимо сейчас выполнить переактивацию сессии пользователя. Просто выйдите и заново войдите в сессию вашего Desktop Meneger’а или перезагрузитесь полностью. Проверить, состоит ли текущая сессия пользователя в группе lxd можно через команду id:


$ id 
uid=1000(user1) gid=1000(user1) groups=1000(user1),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare),129(lxd)

Если вы видите группу lxd, то всё впорядке, можно продолжать дальше.

Инициализация LXD


Инициализация LXD проходит в интерактивном режиме от имени текущего пользователя который состоит в группе lxd. Выполните команду и следуйте инструкциям и комментариям представленным ниже:


$ lxd init
 
# Здесь нас спрашивают, хотим ли мы сейчас создать пул хранилища? Не путать с пулом ZFS!
# По умолчанию ответ <yes>, нажимаем <ENTER>
Do you want to configure a new storage pool (yes/no) [default=yes]?
 
# Задаем желаемое имя для пула хранилища и нажимаем <ENTER>
Name of the new storage pool [default=default]: lxdpool
 
# Здесь нам нужно определить тип пула хранилища, выбираем ZFS
Name of the storage backend to use (dir, lvm, zfs) [default=zfs]: zfs
 
# Создать сейчас ZFS пул? Т.е. в пуле хранилища lxdpool создастся ZFS пул.
# Соглашаемся и нажимаем <ENTER>
Create a new ZFS pool (yes/no) [default=yes]?
 
# Здесь спрашивают, хотим ли мы использовать существующее блочное устройство?
# Например, это может быть ранее нами подготовленный файл созданный через утилиту dd
# или это может быть отдельный жеский диск или партиция.
# По умолчанию ответ равен <no>, жмем <ENTER>
Would you like to use an existing block device (yes/no) [default=no]?
 
# Каким размером создать loop-устройство (zfs pool)?
# Указываем число в GB, минимально 1GB
Size in GB of the new loop device (1GB minimum) [default=15GB]: 5
 
# Желаете сделать LXD доступным по сети?
# По умолчанию LXD не слушает сеть, единственный способ говорить с ним - через локальный unix сокет /var/lib/lxd/unix.socket
# Отвечаем <no>, жмем <ENTER>:
Would you like LXD to be available over the network (yes/no) [default=no]?
 
# Желаете чтобы образы в локальном репозитории автоматически обновлялись?
Would you like stale cached images to be updated automatically (yes/no) [default=yes]? no
 
# Желаете сейчас создать сетевой мост? Отвечаем <no>, мы его позже создадим и настроим
Would you like to create a new network bridge (yes/no) [default=yes]? no
 
# LXD has been successfully configured.

Конфигурация сети LXС


Если сеть не настроить сейчас, а приступить к созданию контейнеров, то при их создании мы получим предупреждение, что контейнер не присоединен ни к одной из сетей. Это не будет являться ошибкой, просто к ним не будет сетевого доступа через интерфейсы. В начале можно создать необходимое количество контейнеров, а затем сконфигурировать сеть и присоединить к ней контейнеры с помощью команды lxc network attach. Сейчас нам необходимо определится, какой тип сети строить. Доступно несколько вариантов построения LXC-сетей, мы же выберем вариант bridge (простой программный коммутатор между хостом и контейнерами) и построим сеть на его основе.

При создании сетевого моста мы можем сразу задать свойства после указания его имени или положиться на автоконфигурацию которая задаст подсеть IPv4 и IPv6 случайным выбором. По умолчанию, параметр равен ipv4.nat=false и по каким-то причинам пакеты транзитного трафика с контейнеров не будут маршрутизироваться дальше хоста на другие локальные сегменты сети даже тогда, когда в ядре хоста явно включить IP forwarding. Видимо NAT в LXD больше чем транслятор адресов с локальных сегментов сети в глобальные, так как при активации этого параметра также активируется IP forwarding на хосте, поэтому мы явно его включим:


$ lxc network create lxdbr0 ipv6.address=none ipv4.address=10.0.3.1/24 ipv4.nat=true
Network lxdbr0 created
$ lxc network list
+--------+----------+---------+-------------+---------+
|  NAME  |   TYPE   | MANAGED | DESCRIPTION | USED BY |
+--------+----------+---------+-------------+---------+
| ens33  | physical | NO      |             | 0       |
+--------+----------+---------+-------------+---------+
| lxdbr0 | bridge   | YES     |             | 0       |
+--------+----------+---------+-------------+---------+

Создание профиля конфигурации контейнера


Конфигурация контейнера условно делится на два уровня, глобальная и локальная. Глобальная конфигурация задается в профиле конфигурации который присоединяется к конкретному контейнеру или группе контейнеров, а локальная конфигурация применяется только для конкретного контейнера. Т.е., все установки в профиле наследуются контейнером, а локальные установки имеют приоритет над профилем. К одному контейнеру можно присоединить несколько профилей.

Создадим профиль LXD для того, чтобы во вновь создаваемых контейнерах по умолчанию был интерфейс типа nic который будет слинкован с коммутатором (сетевым мостом) lxdbr0 на хосте:


$ lxc profile copy default network0
$ lxc profile device add network0 eth0 nic parent=lxdbr0 nictype=bridged

Создание контейнера


Для примера создадим контейнер и назовем его pytest, который будет доступен из хоста по этому имени. Выполним команду launch которая загрузит образ из репозитория ubuntu: версию 17.10 и архитектуры amd64 и из этого образа создаст контейнер. Дополнительно, опцией укажем, что этому контейнеру необходимо задать конфигурацию из профиля network0:


$ lxc launch ubuntu:17.10/amd64 --profile network0 pytest

Далее пойдет загрузка из сети образа. Немного подождем, а чуть позже можем увидеть доступные контейнеры в системе и их состояние:


$ lxc list
+--------+---------+------------------+------+------------+-----------+
|  NAME  |  STATE  |       IPV4       | IPV6 |    TYPE    | SNAPSHOTS |
+--------+---------+------------------+------+------------+-----------+
| pytest | RUNNING | 10.0.3.13 (eth0) |      | PERSISTENT | 0         |
+--------+---------+------------------+------+------------+-----------+

Установка ПО в контейнере


Здесь мы с вами установим необходимое ПО в контейнере. Запустим внутри контейнера пакетный менеджер который актуализирует систему:


$ lxc exec pytest -- /bin/bash -c 'apt update && apt upgrade -y'

Следом, установим менеджер пакетов pip (Python package installer), а также python-модуль ptvsd (Visual Studio remote debugging server for Python). Модуль ptvsd необходимо установить обязательно версии 3.0.0, на более свежих версиях (актуальная 3.2.1.0) я получил ошибку, которая обозначена в issue на github.com. Автор расширения Python к VS Code которое взаимодействует с ptvsd как временное решение рекомендует использовать версию 3.0.0.


$ lxc exec pytest -- /bin/bash -c 'apt install python3-pip -y'
$ lxc exec pytest -- /bin/bash -c 'pip3 install ptvsd==3.0.0'

По части LXD мы с вами завершили настройку.

Установка VS Code и расширений


Варианты установки приложения описаны на официальной страничке по адресу code.visualstudio.com/docs/setup/linux, мы же немного модифицируем команды:


$ cd ~
$ wget -c https://packages.microsoft.com/keys/microsoft.asc
$ cat microsoft.asc | gpg --dearmor > microsoft.gpg
$ sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
$ sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list'
$ sudo apt update
$ sudo apt install code

После выполнения у нас в системе появится VS Code последней версии из репозитория Microsoft.

P.S. На момент написания статьи вышла новая версия 1.18.0 в которой, в стабильную сборку вошли на мой взгляд одни из наиболее ожидаемых (2 года) сообществом возможности «Multi-root workspaces» и «Git indicators in the Explorer» Обо всех изменениях можно почитать на этой страничке.

Приступим к установке расширений. Если вы впервые устанавливаете VS Code, например, после использования Atom (как автор), то рекомендую обратить внимание на расширения в списке ниже, а для достижения цели этой статьи будет достаточно установить только Python и Code Runner. Расширения устанавливаются с marketplace'а в левой части редактора.


Настройка конфигурации VS Code


Следующие настройки необязательны, они прямо не относятся к запуску Python скриптов в контейнерах, но это приблизит вас к тому, если хотите чтобы было визуально как в Atom'е.

В открытом редакторе откройте настроки редактора: file->Preferences->Settings. В открывшемся окне, справа, в USER_SETTINGS внесите следующие изменения и сохраните (Ctrl + S):

{
    // Общие настройки
    "telemetry.enableTelemetry": false,
    "editor.fontFamily": "Menlo, Consolas, 'DejaVu Sans Mono', monospace",
    "editor.lineHeight": 21,
    "editor.minimap.enabled": true,
    "editor.minimap.showSlider": "always",
    "editor.minimap.renderCharacters": false,
    "files.insertFinalNewline": true,
    "files.trimFinalNewlines": true,
    "explorer.openEditors.visible": 0,

    // Настройка интегрированного терминала:
    "terminal.integrated.lineHeight": 1.25,
    "terminal.integrated.scrollback": 2000,

    // Настройка темы:
    "workbench.iconTheme": "file-icons",
    "workbench.colorTheme": "One Dark Pro",
    "workbench.colorCustomizations": {
        // https://code.visualstudio.com/docs/getstarted/theme-color-reference
        "editor.lineHighlightBackground": "#2C323C"
    },

    // Настройка подсветки скобок
    "editor.matchBrackets": false,
    "subtleBrackets.styles": {
        "global": {
            "color": "#61afef",
            "borderColor": "#61afef",
            "borderWidth": "1px",
            "borderStyle": "none none solid none"
        }
    },

    // Python
    "python.pythonPath": "python3",
    
    // Настройка кодо-запускателя:
    "code-runner.executorMap": {
        "python": "python3 $fileName"
    }
}

Линковка каталогов Хост <-> Контейнер


Мы с вами уже проделали большую работу и настало время заключительной части. Сейчас у нас почти всё готово для запуска скриптов в контейнере.

Создадим каталог projects, например, по адресу /home/user1/projects. В нём будут располагаться наши будущие python-проекты. Этот каталог мы слинкуем с контейнером, т.е., в контейнере создадим устройство типа disk и свяжим этот каталог с внутренним /mnt/hostfs. Выполняем следующие команды:


$ mkdir ~/projects
$ lxc config device add pytest hostfs disk path=/mnt/hostfs source=/home/user1/projects/

Если мы локально создадим каталог или файл в каталоге /home/user1/projects, то он немедленно отразится в контейнере по адресу /mnt/hostfs. Убедиться в этом можно запустив интерактивно оболочку BASH в контейнере и выполнив команду ls:


$ lxc exec pytest -- /bin/bash
$ ls -l /mnt/hostfs/

Настройка VS Code на запуск скриптов в контейнере


И так, сейчас мы можем вернуться в наш редактор, если вы его закрыли, то вновь откройте. Нажмите в меню File -> New File. Создастся новая вкладка в которую нужно внести следующий python-код, который мы будем запускать в контейнере. Сохраните его в каталоге /home/user1/projects/pytest/test.py, а затем файл (вкладку) можно закрыть.

import os

#### BEGIN. Visual Studio remote debugging server for Python
# import ptvsd

# print('Waiting connection on port 3000')
# ptvsd.enable_attach('my_secret', address=('0.0.0.0', 3000))
# ptvsd.wait_for_attach()
# ptvsd.break_into_debugger()
#### END. Visual Studio remote debugging server for Python


with open('/etc/lsb-release') as f:
     lsb_release = f.readlines()

info = {
    'HOSTNAME': os.uname().nodename,
    'DISTRIB': lsb_release[3][21:-2],
    'USER': os.environ['USER'],
    'LOGNAME': os.environ['LOGNAME'],
    'HOME': os.environ['HOME'],
    'PWD': os.environ['PWD']
}

print(info)


VS Code «скажет», что у вас не установлен модуль pylint, но он нам сейчас не нужен, вы его можете установить позже, самостоятельно, через менеджер модулей pip. Сейчас нужно открыть workspace в VS Code, для этого слева в File Explorer нажмите кнопку Open Folder и выберите каталог /home/user1/projects/pytest/:



VS Code переинициализируется, полностью закроются все окна и откроется workspace в котором будет виден файл test.py. Откройте его, дважды кликнув на имя слева в File Explorer VS Code.
Вызовите настройки workspace'а, нажав в меню File -> Preferences -> Setting, далее в окне справа вверху вместо USER_SETTINGS выберите из выпадающего списка WORKSPACE_SETTINGS:



Внесите и сохраните следующие настройки так, чтобы у вас получилось вот так:


{
    "code-runner.executorMap": {
        // Запуск от имени пользователя root:
        // "python": "lxc exec pytest -- /bin/bash -c 'python3 /mnt/hostfs/pytest/$fileName'"
        // Запуск от имени встроенного в контейнер пользователя ubuntu:
        "python": "lxc exec pytest -- /bin/bash -c 'su -l ubuntu -c \"python3 /mnt/hostfs/pytest/$fileName\"'"
    },
    "code-runner.runInTerminal": true
}

В файловом эксплорере вы можете обнаружить файл settings.json в каталоге .vscode, там сохраняются настройки воркспейса (текущего проекта).
Вернитесь к открытому файлу test.py и запустите его на исполнение через команду из контекстного меню Run code:



В выводе интегрированного терминала вы увидите информацию в которой можно убедится, что код исполнялся на удаленной машине (контейнере). Теперь, всякий раз, когда нужно запустить проект в контейнере, достаточно внести настройки в WORKSPACE_SETTINGS или создать файл .vscode/settings.json относительно каждого проекта и затем немного подправить пути в настройках.

Удаленный Debug в VS Code


Раскомментируйте строчки в файле test.py и сохраните этот файл:

#### BEGIN. Visual Studio remote debugging server for Python
import ptvsd

print('Waiting connection on port 3000')
ptvsd.enable_attach('my_secret', address=('0.0.0.0', 3000))
ptvsd.wait_for_attach()
ptvsd.break_into_debugger()
#### END. Visual Studio remote debugging server for Python

Всякий раз, когда есть необходимость удаленного дебага, эти строчки кода необходимо вставить в тот файл, который запускается на исполнение. Модуль ptvsd на стороне контейнера открывает порт 3000 и ожидает подключение внешнего дебаггера. При желании, вы можете сконфигурировать ptvsd на свой лад.

Запустите код, вызвав контекстное меню Run Code. В интегрированном терминале вы увидите сообщение, что скрипт ожидает подключения на порту 3000. Перейдите в дебаггер, и создайте новую конфигурацию для этого проекта:



У вас откроется файл launch.json на редактирование в котором уже будут предустановки. Внесите в него следующий блок кода:

        {
            "name": "Python LXC",
            "type": "python",
            "request": "attach",
            "localRoot": "${workspaceRoot}",
            "remoteRoot": "/mnt/hostfs/pytest",
            "host": "pytest",
            "port": 3000,
            "secret": "my_secret"
        }

Сохраните его, он появится в каталоге .vscode/launch.json рядом с файлом setting.json. Затем, выберите слева в низподающем списке Python LXC, это та конфигурация дебаггера, которую мы только что внесли:



Нажмите Start Debugging. В окне с файлом test.py вы увидите работающий дебагер и подтверждение, что код запущен удаленно:



На этом всё. Настройка завершена. Всё что нужно в дальнейшем для нового проекта python, это сконфигурировать два конфиг-файла .vscode/settings.json и .vscode/launche.json. Если у вас есть вопросы по теме, прошу задавать в комментариях. Спасибо за внимание, успешных вам проектов!

UPD от 10.11.2017 21:35 MSK.
В комментариях, пользователь SirEdvin задал вопрос о возможности автодополнения (или IntelliSense в терминах Microsoft) в редакторе тех python-модулей, которые установлены только в контейнере, т.е., этих модулей нет на локальной машине на которой запускается VS Code.
В статье этот момент был упущен, поэтому с радостью дополняю небольшой главою:

Настройка автодополнения (IntelliSense) для внешних python-библиотек


Например, в вашем проекте используется модуль, который не поставляется в коробке по умолчанию с python, и вы этот модуль установили только в контейнере. Как же сделать так, чтобы VS Code «понимал», автодополнял, выводил подсказки в редакторе при этом не устанавливая модуль на хосте, а получал из модуля в контейнере?
Для решения этого вопроса, откройте настройки WORKSPACE_SETTINGS по адресу: ВАШ_ПУТЬ_К_ПРОЕКТУ/.vscode/settings.json и добавьте параметр python.autoComplete.extraPaths, а в качестве ключей укажите список каталогов контейнера в которых находятся python модули.

Например, наш контейнер доступен в файловой системе хоста по адресу: /var/lib/lxd/containers/pytest/rootfs/. Это корневая файловая система контейнера pytest. Зная, где Python устанавливает внешние библиотеки, мы с легкостью можем добавить этот путь, чтобы получить полный путь к внешним библиотекам. Для таких модулей как numpy полный путь будет: /var/lib/lxd/containers/pytest/rootfs/usr/local/lib/python3.6/dist-packages. Этот путь мы добавим в настройку python.autoComplete.extraPaths, вот так:



Сохраните настройки и вернитесь к вашему проекту. Попробуйте импортировать модуль numpy и вызвать автодополнение клавишей CTRL + SPACE. У вас должно получиться так как на экране:



Если сразу не получается, не расстраивайтесь, VS Code кеширует результаты и это сразу может не заработать, попробуйте закрыть-открыть редактор, поиграться с клавишами CTRL+SPACE после ключевого слова from… import.

На этом всё :)

Комментарии (11)


  1. SirEdvin
    10.11.2017 13:55

    А можно ли как-то так сделать подсказку по удаленному интерпретатору?


    1. microcoder Автор
      10.11.2017 17:42

      Не совсем понял вопрос. Какую подсказку? Наведением курсора мыши на переменную/объект, чтобы смотреть ее содержание?


      1. SirEdvin
        10.11.2017 17:43

        Что-то такое. Что бы еще оно использовало удаленный интерпретатор для того, что бы импорты анализировать и прочее.


        1. microcoder Автор
          10.11.2017 18:05

          Просмотр переменных/объектов наведением указателя мыши в коде в режиме дебага работает. Или чтобы по нажатию исходник модуля открывался в редакторе? Модули в проекте и локально установленные откроются через CTRL + Клик мыши на названии модуля, а вот удаленные, установленные только в контейнере, посмотреть не удается.


          1. SirEdvin
            10.11.2017 18:06

            Не обязательно исходники модуля, а подсказки по импорту, например.
            Что бы from wiskas import автоматически предлагал варианты. Такое можно сделать, например, через python-language-server.


            1. microcoder Автор
              10.11.2017 19:03

              Сейчас стало понятно. Это важный вопрос, спасибо. Ответов у меня пока нет :(


            1. microcoder Автор
              10.11.2017 21:00

              Нашлось простое решение :) Как оказалось, в расширении Python к VS Code есть опция, которой можно указать список внешних модулей/библиотек Python которые импортируются в auto complete engine. Достаточно указать путь в WORKSPACE SETTING (файл .vscode/settings.json) к библиотекам в контейнере, например так (это один из из путей который ищет python, вы можете расширить этот список):

              // List of paths to libraries and the like that need to be imported by auto complete engine. E.g. when using Google App SDK, the paths are not in system path, hence need to be added into this list.
                  "python.autoComplete.extraPaths": ["/var/lib/lxd/storage-pools/lxdpool/containers/pytest/rootfs/usr/local/lib/python3.6/dist-packages/"],
              

              По адресу /var/lib/lxd/storage-pools/lxdpool/containers/pytest/rootfs/ находится корневая система контейнера, относительно ее вы можете найти установленные библиотеки и указать полный путь в параметре «python.autoComplete.extraPaths».
              Вот пример, автодополнение работает для numpy, который установлен только в контейнере:

              Если сразу не заработало, попробуйте перезагрузить VS Code, а также удалить любые символы после from… import и вызвать сразу CTRL + SPACE. Иногда не сразу срабатывает, где-то кешируется.


              1. microcoder Автор
                10.11.2017 22:19

                P.S. Обновил статью, добавил информацию по автодополнению.


              1. SirEdvin
                11.11.2017 12:34

                Жаль у меня docker, там такой велосипед надо будет каждый раз делать при пересоздании контейнера :)


  1. Sovetnikov
    10.11.2017 17:43

    Всё замечательно, но с осторожностью отнеситесь к продуктам Microsoft.
    Много лет работал на стеке .NET и знаю что они работают в стиле Паниковского: «я вас всех продам, куплю, и снова продам, но уже по более дорогой цене».

    За последние годы они конечно одумались и сильно ломанулись в сторону Linux и Open Source, но не очень верится что это они делают для людей.
    Есть другие проверенные решения, например pydevd и его поддерживает не одно IDE.


    1. SirEdvin
      10.11.2017 20:09

      Вот когда допилят https://atom.io/packages/ide-python, тогда может и получится свалить :(