Pull to refresh

Виртуальная машина Vagrant для разработки в команде

Level of difficultyMedium
Reading time9 min
Views12K

На хабре уже обсуждалась тема создания виртуальных машин vagrant c комментариями "зачем мне это нужно?". В своей статье я хочу ответить на этот вопрос. Когда у вас есть проект, и в процессе его реализации к вашей команде присоединяются новые сотрудники, возникает проблема отсутствия у них идентичных условий для запуска и тестирования проекта на своих машинах. И тут вам в помощь приходит виртуальная машина Vagrant. На примере django-проекта, созданного в моей статье по etl, я хочу показать, как каждый новый участник вашего проекта за четыре консольные команды развернёт у себя среду разработки со всеми зависимостями. Это позволит в кратчайшие сроки приступить к процессу работы.

В качестве альтернативы виртуальной машине можно использовать docker-контейнеры. Но есть нюанс, что для работы с контейнерами требуется соответствующая квалификация.

Итак, представим, что у нас есть джанго-проект, который мы хотим развивать в команде.

Мы создадим виртуальную машину для дальнейшей разработки с предустановленным pyenv для управления версиями питона и poetry для управления зависимостями.Также настроим интерпретатор в 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. Полный алгоритм выглядит так:

  1. В директории проекта запустить консольную команду vagrant up.

  2. Сразу (не дожидаясь выполнения п.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:

в окне поиска набираем > и выбираем Python: Select Interpreter
в окне поиска набираем > и выбираем Python: Select Interpreter
Вставляем полученный ранее путь до интерпретатора
Вставляем полученный ранее путь до интерпретатора

Подробная инструкция есть по ссылке на сайте разработчика IDE.

Подводим итоги

Мы создали виртуальную машину без нагрузки в виде GUI. Показали как можно работать с ней с помощью программ, установленных на хостовой машине. Для "полного счастья" можно установить ssh-клиент с графическим интерфейсом, чтобы управлять файлами в привычной среде, как вариант - MobaXterm.

В начале статьи я обещал, что каждый участник сможет развернуть ваш проект у себя на машине за четыре консольные команды. Вот они:

git clone <repo>  # клонируем репозиторий
make up  # поднимаем ВМ
make install  # устанавливаем зависимости проекта
make migrate  # создаем требуемые таблицы базы данных

Благодарю за внимание, жду ваших комментариев и с удовольствием отвечу на все вопросы.

Tags:
Hubs:
Rating0
Comments7

Articles