Primary tabs

Поиск альтернатив Ansible в Python

Ссылка на оригинал - http://blog.rfox.eu/en/Explorations/Trying_Ansible_alternatives_in_python.html, автор публикации - Bystroushaak

Мой VPS (Virtual Private Server) стареет, и компания, которая его запускает, объявила, что в следующем месяце срок действия моей акции истечет, и теперь ее запуск обойдется более чем в три раза. Кроме того, я хотел бы обновить другие машины у меня дома, так как они используют старые Ubuntu.

Это заставило меня искать своего рода автоматизацию развертывания, так что я могу указать свою инфраструктуру в виде кода и в идеале никогда больше не проводить с ней много времени.

Почему не Ansible

Мне не нравятся языки конфигурации на основе YAML, которые перерастают в языки сценариев. Вот классический пример десятого правила Гринспуна:

Любая достаточно сложная программа на C или Fortran содержит специальную, неформально определенную, медленную реализацию половины Common Lisp.

Это кошмар для отладки, нет поддержки в IDE и так далее.

⚠️ Было указано, что VS Code имеет надстройку специально для Ansible

Чувство хаоса возникало каждый раз, когда я пытался использовать Ansible. Это побудило меня исследовать альтернативы, предпочтительно на python, которые определяли бы «рецепты» или что-то вроде простого кода на python.

Как оказалось, их не так много.

Fabric

https://github.com/fabric/fabric

Практически в каждой статье на тему «альтернатива Python ANSI» упоминается Fabric. Выглядит отлично, но как-то немного по-другому. Это система для запуска команд на удаленных хостах. Что, безусловно, является частью того, что делает Ansible, но мне также хотелось бы иметь некоторый уровень абстракции.

>>> def disk_free(c):
...     uname = c.run('uname -s', hide=True)
...     if 'Linux' in uname.stdout:
...         command = "df -h / | tail -n1 | awk '{print $5}'"
...         return c.run(command, hide=True).stdout.strip()
...     err = "No idea how to get disk space on {}!".format(uname)
...     raise Exit(err)

Я имею в виду, я не хочу запускать вручную, apt install nginx, а затем анализировать вывод и пытаться решить, была ли команда выполнена успешно (я делал это раньше с paramiko, и это не весело, поверьте мне).

Я хочу что-то немного более продвинутое, которое выполняет для меня разбор стандартных утилит, и в идеале с поддержкой нескольких ОС, поэтому, когда я решу использовать CentOS вместо сервера Ubuntu, который я сейчас использую, он может справиться с различными утилитами для меня.

Fabtools

Fabtools выглядит почти так же, как я хочу:

from fabric.api import *
from fabtools import require
import fabtools

@task
def setup():
    # Require some Debian/Ubuntu packages
    require.deb.packages([
        'imagemagick',
        'libxml2-dev',
    ])

    # Require a Python package
    with fabtools.python.virtualenv('/home/myuser/env'):
        require.python.package('pyramid')

    # Require an email server
    require.postfix.server('example.com')

    # Require a PostgreSQL server
    require.postgres.server()
    require.postgres.user('myuser', 's3cr3tp4ssw0rd')
    require.postgres.database('myappsdb', 'myuser')

    # Require a supervisor process for our app
    require.supervisor.process('myapp',
        command='/home/myuser/env/bin/gunicorn_paster /home/myuser/env/myapp/production.ini',
        directory='/home/myuser/env/myapp',
        user='myuser'
        )

    # Require an nginx server proxying to our app
    require.nginx.proxied_site('example.com',
        docroot='/home/myuser/env/myapp/myapp/public',
        proxy_url='http://127.0.0.1:8888'
        )

    # Setup a daily cron task
    fabtools.cron.add_daily('maintenance', 'myuser', 'my_script.py')

У этого есть только один недостаток; это также выглядит мертвым.

Последняя фиксация произошла 9 месяцев назад, существует 78 нерешенных проблем, 28 ожидающих запросов на извлечение и список поддерживаемых операционных систем уже давно:

  • Семья Debian:
    • Debian 6 ( сжатие ), 7 ( хрипеть ), 8 ( Джесси )
    • Ubuntu 10.04 ( ясный ), 12.04 ( точный ), 14.04 ( верный )

Это 14 в Ubuntu 14.04 (верный) это год выпуска: 2014.

Документация также отстой, и есть странная путаница с форками.

Fabrix

Затем есть Fabrix , который выглядит как нечто среднее между Fabric и Fabtools:

from fabrix.api import is_file_not_exists, yum_install
from fabrix.api import edit_file, edit_ini_section, replace_line

def install_php():

    if is_file_not_exists("/etc/yum.repos.d/epel.repo"):
        yum_install("https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm")

    if is_file_not_exists("/etc/yum.repos.d/remi-php70.repo"):
        yum_install("https://rpms.remirepo.net/enterprise/remi-release-7.rpm")

    edit_file("/etc/yum.repos.d/remi-php70.repo",
        edit_ini_section("[remi-php70]",
            replace_line("enabled=0", "enabled=1")
        )
    )

    yum_install("""
            php-cli
            php-common
            php-fpm
            php-gd
            php-mbstring
            php-mysql
            php-pdo
            php-pear
            php-pecl-imagick
            php-process
            php-xml
            php-opcache
            php-mcrypt
            php-soap
    """)

Это довольно забавно для меня, потому что я создал нечто очень похожее с paramiko некоторое время назад.

Он также выглядит мертвым, только 204 коммитов и один участник, последний коммит 15 месяцев назад. Я не хочу строить свою систему на чем-то уже мертвом.

pyinfra

pyinfra выглядит многообещающе и совсем не мертво: 2233 коммитов, 14 участников, последний коммит вчера . Об этом я и говорю!

from pyinfra.operations import apt

apt.packages(
    {'Install iftop'},
    'iftop',
    sudo=True,
    update=True,
)

Пример работает именно так, как я хотел; декларативный язык, важные параметры, как вы знаете, параметры, а не строки. Единственное, что странно - указывать описание как set, но как бы там ни было, я вижу здесь причину рассуждений.

Документация также перспективна:

Пробуем pyinfra

Поскольку pyinfra - единственная вещь, которая выглядит так, как будто она не мертва, и она может делать то, что я хочу, решение не так уж сложно. Итак, давайте попробуем это:

$ pip install --user pyinfra

Теперь давайте попробуем привет мир. Я борюсь с портом на мгновение, потому что я использую нестандартный порт для туннелирования через Wi-Fi отеля и аэропорта, быстрый взгляд, чтобы помочь показать, что я должен использовать --port параметр:

$ pyinfra kitakitsune.org --port 443 exec -- echo "hello world"
--> Loading config...
--> Loading inventory...

--> Connecting to hosts...
    [kitakitsune.org] Connected

--> Proposed changes:
    Ungrouped:
    [kitakitsune.org]   Operations: 1   Commands: 1   

--> Beginning operation run...
--> Starting operation: Server/Shell (u'echo hello world',)
[kitakitsune.org] hello world
    [kitakitsune.org] Success

--> Results:
    Ungrouped:
    [kitakitsune.org]   Successful: 1   Errors: 0   Commands: 1/1

Выглядит хорошо. Давайте попробуем создать более сложное развертывание для виртуального сервера Ubuntu, которое я создал некоторое время назад в VirtualBox. Я немного борюсь с inventory.pyфайлом, но потом нахожу в документации правильные параметры:

my_hosts = [
    ('192.168.0.106', {"ssh_port": "4433", "ssh_user": "b"}),
]

Я быстро проверяю, работает ли он со следующим deployment.pyфайлом:

from pyinfra.modules import server

server.shell('echo "hello world"')

Который я запускаю с помощью следующей команды:

pyinfra -v inventory.py deployment.py
--> Loading config...
--> Loading inventory...

--> Connecting to hosts...
    [192.168.0.106] Connected

--> Preparing operations...
    Loading: deployment.py
    [192.168.0.106] Ready: deployment.py

--> Proposed changes:
    Groups: my_hosts / inventory
    [192.168.0.106]   Operations: 1   Commands: 1   

--> Beginning operation run...
--> Starting operation: Server/Shell ('echo "hello world"',)
[192.168.0.106] >>> sh -c 'echo "hello world"'
[192.168.0.106] hello world
    [192.168.0.106] Success

--> Results:
    Groups: my_hosts / inventory
    [192.168.0.106]   Successful: 1   Errors: 0   Commands: 1/1

sudo

Поддержка sudo пароля была добавлена ​​после публикации этой статьи через # 305 в 0.15 выпущенной версии,

Чтобы использовать sudo, установите глобальную переменную USE_SUDO_PASSWORD на True, чтобы pyinfra запрашивала его в интерактивном режиме, или вы можете установить его в качестве пароля, и он будет использоваться автоматически. Вы также можете использовать --use_sudo_passwordпараметр в командной строке или в inventory.py.

from pyinfra.modules import server

USE_SUDO_PASSWORD=True

server.shell('echo "hello world"', sudo=True)

sudo=TrueПараметр говорит, что эта команда должна выполняться с помощью sudo . USE_SUDO_PASSWORD говорит, что sudo использует пароль, так как вы можете также использовать sudo без пароля, установив %sudo ALL=(ALL:ALL) NOPASSWD:ALLв /etc/sudoers.

pyinfra -v inventory.py deployment.py
--> Loading config...
--> Loading inventory...

--> Connecting to hosts...
    [192.168.0.106] Connected

--> Preparing operations...
    Loading: deployment.py
    Use of `pyinfra.modules` is deprecated, please use `pyinfra.operations`.
    [192.168.0.106] Ready: deployment.py

--> Proposed changes:
    Groups: my_hosts / inventory
    [192.168.0.106]   Operations: 1   Commands: 1   

--> Beginning operation run...
--> Starting operation: Server/Shell ('echo "hello world"',)
[192.168.0.106] sudo password: 
    [192.168.0.106] Success

--> Results:
    Groups: my_hosts / inventory
    [192.168.0.106]   Successful: 1   Errors: 0   Commands: 1/1

Настройка nginx

Давайте попробуем что-то более сложное - настроить сервер nginx и загрузить для него правильный файл конфигурации:

from pyinfra.modules import apt

SUDO=True

apt.packages('nginx', update=True,present=True)

И это сработало, за одним исключением, который является разбором выходных данных (да, именно поэтому анализ отстой):

Traceback (most recent call last):
  File "src/gevent/greenlet.py", line 854, in gevent._gevent_cgreenlet.Greenlet.run
  File "/home/bystrousak/.local/lib/python2.7/site-packages/pyinfra/api/util.py", line 471, in read_buffer
    _print(line)
  File "/home/bystrousak/.local/lib/python2.7/site-packages/pyinfra/api/util.py", line 455, in _print
    line = print_func(line)
  File "/home/bystrousak/.local/lib/python2.7/site-packages/pyinfra/api/connectors/util.py", line 61, in <lambda>
    print_func=lambda line: '{0}{1}'.format(print_prefix, line),
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2192' in position 74: ordinal not in range(128)
2020-06-11T23:55:28Z <Greenlet at 0x7fde3f4dd6b0: read_buffer('stdout', <paramiko.ChannelFile from <paramiko.Channel 2 (op, <Queue at 0x7fde39f60f30 queue=deque([('stdout', u, print_func=<function <lambda> at 0x7fde39f5e950>, print_output=True)> failed with UnicodeEncodeError

    [192.168.0.106] Success

--> Results:
    Groups: my_hosts / inventory
    [192.168.0.106]   Successful: 1   Errors: 0   Commands: 1/1

Что опять-таки странно, но я, вероятно, использую чешскую локализацию, поэтому я не удивлен.

Повторное выполнение показывает, что операция была выполнена успешно:

--> Loading config...
--> Loading inventory...

--> Connecting to hosts...
    [192.168.0.106] Connected

--> Preparing operations...
    Loading: deployment.py
    Loaded fact deb_packages
    [192.168.0.106] Ready: deployment.py

--> Proposed changes:
    Groups: my_hosts / inventory
    [192.168.0.106]   Operations: 1   Commands: 1   

--> Beginning operation run...
--> Starting operation: Apt/Packages ('nginx', u'update=True', u'present=True')
[192.168.0.106] >>> sudo -S -H -n sh -c 'apt-get update'
[192.168.0.106] Hit:1 http://archive.ubuntu.com/ubuntu bionic InRelease
[192.168.0.106] Hit:2 http://archive.ubuntu.com/ubuntu bionic-updates InRelease
[192.168.0.106] Hit:3 http://archive.ubuntu.com/ubuntu bionic-backports InRelease
[192.168.0.106] Hit:4 http://archive.ubuntu.com/ubuntu bionic-security InRelease
[192.168.0.106] Reading package lists...
    [192.168.0.106] Success

--> Results:
    Groups: my_hosts / inventory
    [192.168.0.106]   Successful: 1   Errors: 0   Commands: 1/1

Конфиг

Итак, как мне загрузить файл конфигурации для nginx?

from pyinfra.modules import files

files.put(
    'configs/nginx.conf',
    '/etc/nginx/nginx.conf',
    user='root',
    group='root',
    mode='644',
)

Вы также можете загружать файлы, синхронизировать целые каталоги и так далее. Довольно мило.

Запуск nginx

from pyinfra.modules import server

init.systemd('nginx', running=True, restarted=True, enabled=True)

Прекрасно.

Вывод

Мне очень нравится pyinfra. У этого есть свои причуды, но они - только маленькие раздражения, в отличие от больших раздражений, которые я нахожу в других продуктах, таких как Ansible. Но в отличие от Ansible, он понятен, прост в использовании и использует Python, который я знаю и люблю. Моя IDE может дать мне автозаполнение и отладчик, в отличие от других DSL на основе YAML.

Вот целый конфиг для развертывания nginx:

from pyinfra.modules import apt
from pyinfra.modules import init
from pyinfra.modules import files


SUDO=True
USE_SUDO_PASSWORD=True


apt.packages(
    'nginx',
    update=True,
    present=True,
)

files.put(
    'configs/nginx.conf',
    '/etc/nginx/nginx.conf',
    user='root',
    group='root',
    mode='644',
)

init.systemd(
    'nginx',
    running=True,
    restarted=True,
    enabled=True,
)

Добавить комментарий

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Не нашли ответ на свой вопрос? Возможно, вы найдете решение проблемы на нашем канале в Youtube! Здесь мы собрали небольшие, но эффективные инструкции. Смотрите и подписывайтесь на наш youtube-канал!

Смотреть на Youtube