На хабре уже обсуждалась тема создания виртуальных машин vagrant c комментариями "зачем мне это нужно?". В своей статье я хочу ответить на этот вопрос. Когда у вас есть проект, и в процессе его реализации к вашей команде присоединяются новые сотрудники, возникает проблема отсутствия у них идентичных условий для запуска и тестирования проекта на своих машинах. И тут вам в помощь приходит виртуальная машина Vagrant. На примере django-проекта, созданного в моей статье по etl, я хочу показать, как каждый новый участник вашего проекта за четыре консольные команды развернёт у себя среду разработки со всеми зависимостями. Это позволит в кратчайшие сроки приступить к процессу работы.
В качестве альтернативы виртуальной машине можно использовать docker-контейнеры. Но есть нюанс, что для работы с контейнерами требуется соответствующая квалификация.
Итак, представим, что у нас есть джанго-проект, который мы хотим развивать в команде.
Мы создадим виртуальную машину для дальнейшей разработки с предустановленным pyenv для управления версиями питона и poetry для управления зависимостями.Также настроим интерпретатор в IDE PyCharm Pro и VS Code.
Все исходники из данной статьи выложены в репозиторий.
Устанавливаем ПО
На хостовой (основной) операционной системе каждого участника проекта должны быть:
ssh-клиент
Oracle VM virtual box
Vagrant
Make (инструкция для тех, у кого Windows)
IDE PyCharm Pro или VS Code (этот пункт опционален)
Обозначим структуру проекта
Маленький django-проект находится в папке etl. Конфигурационная папка проекта config, папка приложения etl_app. Про остальные файлы и папки читайте далее.
.
├── Makefile
├── Vagrantfile
├── etl
│ ├── README.md
│ ├── __init__.py
│ ├── config
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── celery.py
│ │ ├── settings.py
│ │ ├── settings_local.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── etl_app
│ │ ├── __init__.py
│ │ ├── apps.py
│ │ ├── etl.py
│ │ └── tasks.py
│ └── manage.py
├── poetry.lock
├── pyproject.toml
└── vagrant
├── playbook.yml
├── postgres.yml
├── requirements.yml
└── templates
└── settings_local.j2
Начинаем с Ansible
Ansible — система управления конфигурациями для Linux. Конфигурировать мы будем создаваемую виртуальную машину через сценарий (далее плейбуки) в формате YAML с описанием требуемых состояний управляемой системы. Сценарий — это описание состояния ресурсов системы, в котором она должна находиться в конкретный момент времени, включая установленные пакеты, запущенные службы, созданные файлы и многое другое. Ansible проверяет, что каждый из ресурсов системы находится в ожидаемом состоянии и пытается исправить состояние ресурса, если оно не соответствует ожидаемому.
Для выполнения задач используется система модулей. Каждая задача представляет собой её имя, используемый модуль и список параметров, характеризующих задачу. Система поддерживает переменные, фильтры обработки переменных (поддержка осуществляется библиотекой Jinja2), условное выполнение задач, параллелизацию, шаблоны файлов. Адреса и настройки целевых систем содержатся в файлах «инвентаря» (inventory). Поддерживается группирование. Для реализации набора однотипных задач существует система ролей.
Из всего вышеперечисленного мы воспользуемся ролями, чтобы писать меньше кода, и шаблонизатором Jinja2.
Все плейбуки пишем в папке vagrant.
requirements.txt
В нашем проекте будет использована роль python_developer из репозитория ролей ansible-galaxy. Она (роль) представляет из себя готовый набор задач, которые установят для нас pyenv и poetry. Прописываем роль в файле зависимостей requirements.txt:
- name: staticdev.python-developer
src: https://github.com/staticdev/ansible-role-python-developer.git
postgres.yml
В этом плейбуке мы пропишем набор задачи по созданию базы данных и схем в ней:
---
- name: Add postgreuser permission for createbd
become: true
become_user: postgres
shell: psql -U postgres -c 'ALTER USER {{ postgres_user }} CREATEDB'
- name: Create database
become: true
become_user: postgres
postgresql_db:
name: "{{ postgres_db }}"
encoding: UTF-8
owner: "{{ postgres_user }}"
- name: Create schemas
become: true
become_user: postgres
community.postgresql.postgresql_schema:
db: "{{ postgres_db }}"
name:
- main
- my_schema
owner: "{{ postgres_user }}"
Обратите внимание на строки вида {{ some_var }} - это переменные. Их мы зададим в следующем плейбуке
playbook.yml
Здесь мы пишем наш основной сценарий. Он определяет - что надо установить на нашу виртуальную машину, а также использует описанные ранее плейбуки.
---
- hosts: all
vars:
app_settings_dir: config
postgres_user: sergei
postgres_password: sergei
postgres_db: demo
python_version: 3.10.6
roles:
- role: staticdev.python-developer
vars:
pyenv_global:
- "{{ python_version }}"
pyenv_python_versions:
- "{{ python_version }}"
tasks:
- name: add GPG key
ansible.builtin.apt_key:
url: https://keyserver.ubuntu.com/pks/lookup?op=get&fingerprint=on&search=0x1655A0AB68576280
id: 68576280
state: present
become: yes
- name: Install base needed apt-packages
ansible.builtin.apt:
pkg:
- wget
- unzip
- redis-server
- wget
- python3-psycopg2
- acl
state: latest
become: yes
become_method: sudo
- name: Set up Postgres repo
shell: |
echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
args:
warn: no
become: yes
- name: Install postgresql
apt:
name: postgresql-15
update_cache: yes
become: yes
- name: Start and enable postgres
ansible.builtin.service:
name: postgresql
enabled: yes
state: started
become: yes
- name: Create user
community.postgresql.postgresql_user:
name: "{{ postgres_user }}"
password: "{{ postgres_password }}"
become: yes
become_user: postgres
- name: Create db
include_tasks: postgres.yml
- name: Ensure Redis is started
service: name=redis-server state=started enabled=yes
become: yes
become_method: sudo
- name: Exporting broker url to env
lineinfile:
dest: "/etc/environment"
state: present
regexp: "^REDIS_URL="
line: "REDIS_URL=redis://127.0.0.1:6379/0"
become: yes
become_method: sudo
- name: Exporting db url to env
lineinfile:
dest: "/etc/environment"
state: present
regexp: "^DATABASE_URL="
line: "DATABASE_URL=postgres://{{ postgres_user }}:{{ postgres_password }}@127.0.0.1:5432/{{ postgres_db }}"
become: yes
become_method: sudo
- name: Make settings file from template
template:
src: /vagrant/vagrant/templates/settings_local.j2
dest: "/vagrant/etl/{{ app_settings_dir }}/settings_local.py"
- name: set python version for project
ansible.builtin.command: /home/vagrant/.local/bin/poetry env use /home/vagrant/pyenv/shims/python
args:
chdir: /vagrant/
Разделы плейбука:
vars - задаем переменные
roles - запускаем роли (предопределенный набор задач).
pyenv_python_versions
- список версий питона, которые вы хотите установить на ВМ,pyenv_global
- какую из этих версий сделать глобальной.tasks - перечень задач, которые образуют набор консольных команд. Только таски ещё отслеживают ответы выполняемых команд. Если какая-либа из команд вернут ошибку, то выполнение плейбука прекратится. В примере выше я устанавливаю Redis (строки 29 и 73). Вы вольны устанавливать любой набор пакетов.
Чуть подробнее остановимся на задаче Make settings file from template. Этой команда берет шаблон файла settings_local.j2 по пути src, прогоняет его через шаблонизатор Jinja2 - см. DEBUG в фрагменте кода ниже. Затем копирует готовый модуль в расположение dest. Самая очевидная польза, это возможность исключить файл settings_local.py конфигурационной папки проекта из индекса гита. Как следствие - settings_local.py это действительно локальный (простите за тавтологию) модуль. Такая методика позволяет для прода задействовать другие значения переменных.
Был settings_local.j2:
import os
import dj_database_url
DEBUG = {{ debug|lower|capitalize }}
...
Стал settings_local.py:
"""Локальные настройки проекта"""
import os
import dj_database_url
DEBUG = True
...
Готовим к запуску виртуальную машину Vagrant
Для запуска виртуальной машины нам потребуется Vagrantfile:
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/focal64"
config.vm.network "forwarded_port", guest: 8000, host: 8001
config.vm.network "forwarded_port", guest: 5432, host: 5433
config.vm.provider "virtualbox" do |v|
v.customize ["modifyvm", :id, "--uart1", "0x3F8", "4"]
v.customize ["modifyvm", :id, "--uartmode1", "file", File::NULL]
v.memory = 2048
end
# ansible_local
config.vm.provision "ansible_local" do |ansible|
ansible.verbose = "vv"
ansible.galaxy_role_file = 'vagrant/requirements.yml'
ansible.galaxy_roles_path = '/home/vagrant/.ansible/roles'
ansible.galaxy_command = 'ansible-galaxy install --ignore-errors --force --role-file=%{role_file}'
ansible.playbook = "vagrant/playbook.yml"
end
end
Пояснения по номерам строк:
5 - образ операционной системы
7- проброс 8000-ый порт ВМ, который как правило используется для запуска сервера разработки Django, на 8001-ый порт хостовой машины. Оба порта могут быть изменены по вашему усмотрению.
8 - порт 5432 СУБД Postgres пробрасываем на порт 5433 хостовой машины. Порт 5433 может быть изменен по вашему усмотрению.
12 - объем оперативной памяти ВМ в Мбайтах.
16 - объявляем блок команд для запуска ansible.
17 - уровень детализации в консоли, регулируется количеством символов v.
18 - путь файлам зависимостей.
19 - путь для хранения файлов ролей asnible. Подробнее об этом в документации. В данному случае они будут хранится в папке пользователя и не фигурировать в проекте.
20 - консольная команда установки зависимостей ansible. Я её "кастомизировал" через
--ignore_errors
, чтобы не было ошибки при повторной установке зависимостей.21 - путь до основного плейбука, который и запускает всю "магию".
Запускаем виртуальную машину
Создание и каждый последующий запуск производится с помощью консольной команды vagrant up
. Полный алгоритм выглядит так:
В директории проекта запустить консольную команду
vagrant up
.Сразу (не дожидаясь выполнения п.1) запустить окно Oracle VM virtual box. Сделать активной создаваемую виртуальную машину (дополнительных окон открывать не надо). До полного запуска ВМ это окно держать активным.
Первый запуск может длиться 15-20 минут, потому что идет создание ВМ и установка пакетов. В это время следите, чтобы компьютер не переходил в "сон". Возможно для первого запуска понадобится VPN.
Другие команды для управления ВМ:
vagrant halt
- остановить ВМvagrant ssh
- зайти на ВМvagrant destroy
- "разобрать" ВМ
Используем make
Для работы с виртуальной машиной нам достаточно команд из предыдущего раздела. Но дополнительно мы можем наиболее часто употребимые вынести в make. Процесс установки на Ubuntu описан здесь, на Windows здесь.
Создадим Makefile:
# инициализировать виртуальную машину
up:
vagrant up
# установить зависимости проекта
install:
vagrant ssh -c "cd /vagrant/ && poetry install"
# сделать дата-миграции бд
migrate:
vagrant ssh -c "cd /vagrant/etl && poetry run python manage.py makemigrations && poetry run python manage.py migrate"
# запустить сервер разработки
runserver:
vagrant ssh -c "cd /vagrant/etl && poetry run python manage.py runserver 0.0.0.0:8000"
Обратите внимание на последнюю команду. Благодаря пробросу портов в Vagrantfile сервер разработки будет доступен на хостовой машине по адресу http://localhost:8001.
Настраиваем IDE
Для полноценной интеграции интерпретатора в оболочку IDE, последние должны уметь работать через ssh-подключение. Параметры для подключения:
host 127.0.0.1
port 2222. Если вы одновременно запускаете не одну ВМ, то у каждой последующей будет другой порт. Будьте внимательны. Порт вы увидите в отчете команды по инициализации ВМ.
username vagrant
key pair (подключение идет по ключу, путь относительно директории, где находится Vagrantfile):
.vagrant/machines/default/virtualbox/private_key
PyCharm Pro
К сожалению, интеграция с интерпретатором в версии Community исключена, т.к. отсутствует функционал ssh-подключения. В этом случае можно в терминале зайти на виртуальную машину и все запуски кода производить из консоли виртуальной машины.
Подключение к удаленной БД в Community также невозможно. Но pgAdmin (более чем) полноценно заменит функционал PyCharm Pro в этой части.
Настройка интерпретатора
Инструкция доступна по ссылке на сайте разработчика IDE. Дополнительное к параметрам подключения по ssh, нам понадобится путь до интерпретатора внутри ВМ. Его можно получить составной командой в консоли виртуальной машиныcd /vagrant/ && poetry run which python
Подключение к БД
К базе данных подключаемся также по ssh. Инструкция доступна по ссылке на сайте разработчика IDE. Данные для подключения возьмите из начала текущего раздела. Логин/пароль/имя БД у нас указаны в playbook.yml в разделе vars.
VS Code
Интеграция с интерпретатором производится в два шага:
1) Зафиксируйте настройку подключения к в ВМ в конфигурации ssh-клиента вашей хостовой машины. Для этот выполните команду vagrant ssh-config
на хостовой машине в папке проекта. Вывод будет примерно такой:
Host default
HostName 127.0.0.1
User vagrant
Port 2222
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile <some path>/.vagrant/machines/default/virtualbox/private_key
IdentitiesOnly yes
LogLevel FATAL
Добавьте эти строки в конфигурационный файл вашего ssh-клиента. Как правило это ~/.ssh/config
. default замените на что-либо вам понятное и относящееся к проекту.
2) Настройте в VS Code интерпретатор по ssh-подключению. Предварительно убедитесь, что у вас установлены плагины Remote SSH и Python от Microsoft.
Подключаемся к ВМ:
В консоли виртуальной машины составной командойcd /vagrant/ && poetry run which python
получаем путь до интерпретатора. Например /home/vagrant/.cache/pypoetry/virtualenvs/etl-base-vs8V2ZPt-py3.10/bin/python
Указываем этот путь в настройках плагина Python:
Подробная инструкция есть по ссылке на сайте разработчика IDE.
Подводим итоги
Мы создали виртуальную машину без нагрузки в виде GUI. Показали как можно работать с ней с помощью программ, установленных на хостовой машине. Для "полного счастья" можно установить ssh-клиент с графическим интерфейсом, чтобы управлять файлами в привычной среде, как вариант - MobaXterm.
В начале статьи я обещал, что каждый участник сможет развернуть ваш проект у себя на машине за четыре консольные команды. Вот они:
git clone <repo> # клонируем репозиторий
make up # поднимаем ВМ
make install # устанавливаем зависимости проекта
make migrate # создаем требуемые таблицы базы данных
Благодарю за внимание, жду ваших комментариев и с удовольствием отвечу на все вопросы.
Комментарии (7)
amkartashov
00.00.0000 00:00Я рекомендую связку tilt + docker for desktop + vscode для локального окружения.
* врубаем kubernetes engine в докере
* используем helm_resource для деплоя зависимостей (куча готовых чартов для postgres/redis/ и тд)
* docker_build для образа своего софта
* свой софт деплоится через тот же helm_resource или через k8s_yaml
* дополнительно можно ingress_nginx задеплоить и выставлять наружу (на локалхост) свои сервисы через .7f000001.nip.io. Если хочется потестить с честным SSL - get.localhost.localdirect
Сложная часть обычно c удалённым дебагом. Проще всего с go, с python придётся повозиться, для poetry проектов надо debugpy в зависимости подключать и запускать через poetry run python -m debugpy --listen 0.0.0.0:5679 src/main.py arg1 arg2 arg3
Плюсы по сравнению с вагрантом:
* можно в куб и докер
* можно организовать hot reload с локального кода, а не разрабатывать внутри вм
* можно локально сервисы заавтоматизировать таким же образом
* меньше оверхед иногда. Иногда наоборот :)
* не надо ансибл
molybdenum
00.00.0000 00:00+2Виртуальная машина Vagrant
<занудство-on>
Vagrant не является вирутальной машиной. Врутальные машины создает Oracle VritualBox, а Vagrant это просто обёртка-менеджер, которая позволяет легко их создавать, конфигурировать и уничтожать.
<занудство-off>
Urvin
00.00.0000 00:00+1<душнила-mode>
Вообще-то в в Vagrant можно подключать различные бекенды виртуальных машин, не обязательно VritualBox
</душнила-mode>
AYamangulov
00.00.0000 00:00В качестве альтернативы виртуальной машине можно использовать docker-контейнеры. Но есть нюанс, что для работы с контейнерами требуется соответствующая квалификация.
Прошу обратить внимание, что для выполнения всех действий в статье также требуется альтернативная квалификация (только в Vagrant + Ansible, как минимум). А особенно - что перечень описанных действий, на мой взгляд, существенно длиннее, чем если поднять локальный стенд на docker + что-нибудь вспомогательное, например, docker-compose, плюс расшарить общий проект с ветками где-нибудь в github/gilab/bitbucket. И это совсем не сложно, стек всем известен и хорошо отлажен. А вот о vagrant не могу сказать, чтобы он был столь же распространенным и активно использовался бы. ИМХО - как раз для него небольшое самообучение потребуется.
Безусловно, автор проделал хорошую работу и подробно описал интересный кейс. Возможно, также, что в каких-то случаях, действительно, возможны объективные причины, которые КАТЕГОРИЧЕСКИ требуют использования vagrant vs docker, где он имеет какие-то явные и реальные преимущества. Только я, например, таких преимуществ особо не нашел, может быть, кто-то их действительно знает? - Тогда прошу их описать, хотя бы в комментариях.
SergeyKlimov_Creator Автор
00.00.0000 00:00Здесь важен контекст. Речь идёт о группах, в которых были выпускники курсов. Я использовал два варианта - на докере и на вагранте. Вагрант требовал от меня больше знаний и объяснений на входе в проект. Потом вопросов "как это мне запустить?" было минимум, потому что участники работали с привычным интерфейсом (по другому я это объяснить не могу). С докером было наоборот. Сначала приходилось объяснять что-такое докер, а затем попутно решать вопросы "а у меня слетело, а я перезапустил" и т.д.
Hrodvitnir
Мне кажется, что лечить отсутствие квалификации Vagrant'om это некоторый оверхед. Тем более, что контейнер тоже настроил, а потом просто подвязываешь к нему IDE.
Но с Vagrant мы так работали тоже, но тогда не было Docker:) Ну или, если точнее, он не был так популярен