Ansible AWX и Semaphore: веб-интерфейс для автоматизации инфраструктуры на 500 серверах

Исходная ситуация

MSP-компания «ИТСервис» обслуживает 10 клиентов с суммарным парком в 500 серверов — от bare-metal в дата-центрах до облачных инстансов в Yandex Cloud и Selectel. Ansible-плейбуки писались и запускались локально с ноутбуков инженеров. У команды из 8 человек не было единого источника правды: каждый хранил свои inventory-файлы, версии плейбуков различались, а результаты выполнения нигде не логировались.

Основные проблемы:

  • Нет аудита — невозможно понять, кто, когда и что запускал. После инцидента с удалением production-базы у одного клиента руководство потребовало полный audit trail.
  • Нет RBAC — младшие инженеры имели доступ ко всем серверам всех клиентов. Утечка одного SSH-ключа компрометировала всю инфраструктуру.
  • Ручное обновление inventory — при добавлении нового сервера приходилось обновлять файлы у всех инженеров. Забытые серверы не получали патчей безопасности.
  • Отсутствие scheduling — регулярные задачи (патчинг, проверки compliance) запускались вручную и часто забывались.

AWX vs Semaphore: почему мы выбрали AWX

Перед внедрением мы протестировали три решения: AWX (open-source версия Ansible Tower), Semaphore UI и Rundeck.

КритерийAWXSemaphoreRundeck
RBACПолноценный (Organization → Team → User)Базовый (Admin/User)ACL-политики
Dynamic InventoryВстроенная поддержка 30+ источниковНет (только static)Через плагины
Workflow TemplatesВизуальный конструкторНетWorkflow Steps
Credentials ManagementVault, HashiCorp, CyberArkБазовый (SSH/password)Key Storage
APIПолный REST APIREST APIREST API
Ресурсы (RAM)6-8 GB512 MB2-4 GB
Сложность установкиВысокая (K8s/Docker)Низкая (один бинарник)Средняя (Java)

Semaphore подкупает простотой — один бинарник, 512 MB RAM, установка за 5 минут. Для маленькой команды с 20-50 серверами это идеальный выбор. Но для MSP с 10 клиентами критичен RBAC на уровне организаций: инженер клиента А не должен видеть серверы клиента Б. AWX это умеет из коробки, Semaphore — нет.

Rundeck мы отвергли из-за того, что он не «нативен» для Ansible — плейбуки выполняются как один из типов задач, а не как основной механизм.

Установка AWX через K8s Operator

AWX с версии 18+ перешёл на Kubernetes-native деплой через оператор. Мы развернули его на кластере из трёх нод (4 vCPU, 16 GB RAM каждая) на базе K3s — легковесного дистрибутива Kubernetes.

Установка K3s на первой ноде:

# Первая нода (master)
curl -sfL https://get.k3s.io | sh -s - --disable traefik \
  --write-kubeconfig-mode 644

# Получаем токен для worker-нод
cat /var/lib/rancher/k3s/server/node-token

# Worker-ноды
curl -sfL https://get.k3s.io | K3S_URL=https://master:6443 \
  K3S_TOKEN="токен" sh -

Устанавливаем AWX Operator:

# Устанавливаем kustomize
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/

# Создаём манифест для оператора
mkdir -p awx-deploy && cd awx-deploy

cat > kustomization.yaml << 'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - github.com/ansible/awx-operator/config/default?ref=2.12.0
images:
  - name: quay.io/ansible/awx-operator
    newTag: 2.12.0
namespace: awx
EOF

kubectl create namespace awx
kustomize build . | kubectl apply -f -

# Ждём готовности оператора
kubectl -n awx wait --for=condition=Available \
  deployment/awx-operator-controller-manager --timeout=300s

Создаём инстанс AWX:

cat > awx-instance.yaml << 'EOF'
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx-production
  namespace: awx
spec:
  service_type: ClusterIP
  ingress_type: ingress
  ingress_hosts:
    - hostname: awx.itservice.local
  postgres_storage_class: local-path
  postgres_storage_requirements:
    requests:
      storage: 50Gi
  projects_persistence: true
  projects_storage_class: local-path
  projects_storage_size: 20Gi
  extra_settings:
    - setting: REMOTE_HOST_HEADERS
      value: "['HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR']"
    - setting: SESSIONS_PER_USER
      value: "3"
EOF

kubectl apply -f awx-instance.yaml

# Получаем пароль admin
kubectl -n awx get secret awx-production-admin-password \
  -o jsonpath='{.data.password}' | base64 -d

Через 5-7 минут AWX доступен по адресу awx.itservice.local. Потребление ресурсов в нашей конфигурации: PostgreSQL — 1.2 GB RAM, AWX Web — 1.8 GB, AWX Task — 2.5 GB, Redis — 256 MB.

Проекты, Inventory и динамическая интеграция с NetBox

В AWX «проект» — это Git-репозиторий с плейбуками. Мы создали структуру из трёх репозиториев в GitLab:

  • ansible-common — общие роли (hardening, monitoring agent, backup agent)
  • ansible-client-roles — специфичные роли для разных клиентов
  • ansible-maintenance — плейбуки для регулярных задач (патчинг, отчёты)

Настройка проекта через API (используем для автоматизации через CI/CD):

curl -s -k -H "Authorization: Bearer $AWX_TOKEN" \
  -H "Content-Type: application/json" \
  -X POST https://awx.itservice.local/api/v2/projects/ \
  -d '{
    "name": "Common Roles",
    "organization": 1,
    "scm_type": "git",
    "scm_url": "git@gitlab.itservice.local:ansible/ansible-common.git",
    "scm_branch": "main",
    "scm_update_on_launch": true,
    "scm_update_cache_timeout": 300,
    "credential": 3
  }'

Для inventory мы интегрировали AWX с NetBox — единым источником правды об инфраструктуре. Каждый сервер заносится в NetBox при вводе в эксплуатацию, а AWX автоматически синхронизирует inventory:

# netbox_inventory.yml — конфигурация динамического inventory
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.itservice.local
token: "{{ lookup('env', 'NETBOX_TOKEN') }}"
validate_certs: true

config_context: true
group_by:
  - site
  - tenant
  - device_role
  - platform

query_filters:
  - status: active
  - has_primary_ip: true

compose:
  ansible_host: primary_ip4.address | ipaddr('address')
  ansible_user: custom_fields.ansible_user | default('ansible')
  ansible_port: custom_fields.ssh_port | default(22)

В AWX создаём Inventory Source с типом «Sourced from a Project» и указываем файл netbox_inventory.yml. Синхронизация выполняется каждые 5 минут. Новый сервер, добавленный в NetBox, автоматически появляется в AWX через 5 минут.

Job Templates, Workflow и Scheduling

Job Template — основная единица работы в AWX. Для каждой регулярной задачи мы создали отдельный шаблон. Пример шаблона для патчинга серверов:

# patch_servers.yml
---
- name: Patch Linux servers
  hosts: all
  become: true
  serial: "20%"
  max_fail_percentage: 10

  pre_tasks:
    - name: Create snapshot before patching
      uri:
        url: "https://api.selectel.ru/vpc/v1/servers/{{ server_uuid }}/snapshots"
        method: POST
        headers:
          X-Token: "{{ selectel_token }}"
        body_format: json
        body:
          name: "pre-patch-{{ ansible_date_time.date }}"
      when: cloud_provider == 'selectel'
      delegate_to: localhost

  tasks:
    - name: Update package cache
      apt:
        update_cache: true
        cache_valid_time: 3600
      when: ansible_os_family == 'Debian'

    - name: Upgrade all packages
      apt:
        upgrade: safe
        autoremove: true
      register: apt_result
      when: ansible_os_family == 'Debian'

    - name: Update packages (RHEL)
      dnf:
        name: '*'
        state: latest
        security: true
      register: dnf_result
      when: ansible_os_family == 'RedHat'

    - name: Check if reboot is required
      stat:
        path: /var/run/reboot-required
      register: reboot_required

    - name: Reboot if needed
      reboot:
        reboot_timeout: 300
        post_reboot_delay: 30
      when: reboot_required.stat.exists | default(false)

  post_tasks:
    - name: Verify services are running
      service_facts:

    - name: Assert critical services
      assert:
        that:
          - ansible_facts.services[item].state == 'running'
        fail_msg: "Service {{ item }} is not running after patch!"
      loop: "{{ critical_services | default(['sshd', 'zabbix-agent2']) }}"

Workflow Template объединяет несколько Job Template в цепочку с условными переходами. Наш workflow для патчинга:

  1. Pre-check — проверяет доступность серверов и состояние мониторинга
  2. Snapshot → при успехе: Patch → при успехе: VerifyReport
  3. При сбое на любом этапе: RollbackAlert

Scheduling настроен через AWX для регулярных задач:

  • Патчинг — каждую субботу в 03:00 (окно обслуживания клиента)
  • Compliance check — ежедневно в 06:00
  • Inventory sync из NetBox — каждые 5 минут
  • Backup verification — каждое воскресенье в 05:00

RBAC: изоляция клиентов в MSP-модели

Ключевое требование для MSP — ни один инженер не должен иметь доступ ко всем клиентам одновременно. AWX реализует это через иерархию Organization → Team → User.

Мы создали структуру:

# Скрипт создания RBAC-структуры через AWX API
import requests

AWX_URL = "https://awx.itservice.local/api/v2"
HEADERS = {
    "Authorization": "Bearer ${AWX_TOKEN}",
    "Content-Type": "application/json"
}

clients = [
    {"name": "АльфаБанк-Тех", "engineers": ["ivanov", "petrov"]},
    {"name": "РосНефть-ИТ", "engineers": ["sidorov", "kozlov"]},
    {"name": "СберТехнологии", "engineers": ["smirnov", "novikov", "morozov"]},
]

for client in clients:
    # Создаём организацию для каждого клиента
    org = requests.post(f"{AWX_URL}/organizations/", headers=HEADERS,
        json={"name": client["name"], "description": f"Клиент {client['name']}"}
    ).json()

    # Создаём команды внутри организации
    for role_name in ["admins", "operators", "viewers"]:
        team = requests.post(f"{AWX_URL}/teams/", headers=HEADERS,
            json={"name": f"{client['name']}-{role_name}", "organization": org["id"]}
        ).json()

    # Inventory привязываем к организации
    inv = requests.post(f"{AWX_URL}/inventories/", headers=HEADERS,
        json={
            "name": f"{client['name']}-servers",
            "organization": org["id"],
            "description": f"Серверы клиента {client['name']}"
        }
    ).json()

Роли в AWX:

  • Admin — полный доступ к организации: создание Job Templates, управление credentials
  • Execute — запуск существующих Job Templates без доступа к credentials
  • Read — просмотр результатов выполнения, логов, inventory (для аудиторов клиента)

Credentials хранятся в AWX в зашифрованном виде (AES-256). Инженер с ролью Execute может запустить плейбук, который использует SSH-ключ клиента, но не может увидеть или скачать сам ключ. Это критично для MSP: даже при увольнении инженера мы просто деактивируем его учётную запись без необходимости ротации ключей.

Notification Templates и интеграция с Telegram

Каждый Job Template в AWX может отправлять уведомления при старте, успехе, сбое. Мы настроили интеграцию с Telegram-ботом для оперативного оповещения команды.

Создание Notification Template через AWX UI или API:

# Notification Template для Telegram
curl -s -k -H "Authorization: Bearer $AWX_TOKEN" \
  -H "Content-Type: application/json" \
  -X POST https://awx.itservice.local/api/v2/notification_templates/ \
  -d '{
    "name": "Telegram Ops Channel",
    "organization": 1,
    "notification_type": "webhook",
    "notification_configuration": {
      "url": "https://api.telegram.org/bot/sendMessage",
      "http_method": "POST",
      "headers": {
        "Content-Type": "application/json"
      }
    },
    "messages": {
      "started": {
        "body": "{\"chat_id\": \"-100xxxx\", \"text\": \"🔄 AWX Job Started\\n{{ job.name }}\\nInventory: {{ job.inventory }}\\nUser: {{ job.started_by }}\", \"parse_mode\": \"HTML\"}"
      },
      "success": {
        "body": "{\"chat_id\": \"-100xxxx\", \"text\": \"✅ AWX Job Success\\n{{ job.name }}\\nHosts: {{ job.hosts.ok }}/{{ job.hosts.total }}\\nDuration: {{ job.elapsed }}\", \"parse_mode\": \"HTML\"}"
      },
      "error": {
        "body": "{\"chat_id\": \"-100xxxx\", \"text\": \"❌ AWX Job FAILED\\n{{ job.name }}\\nFailed hosts: {{ job.hosts.failures }}\\nURL: {{ job.url }}\", \"parse_mode\": \"HTML\"}"
      }
    }
  }'

Помимо Telegram, мы настроили интеграцию с внешними системами через AWX REST API. Пример: GitLab CI pipeline при мерже в main автоматически запускает деплой через AWX:

# .gitlab-ci.yml
deploy_production:
  stage: deploy
  script:
    - |
      JOB_ID=$(curl -s -k -H "Authorization: Bearer $AWX_TOKEN" \
        -H "Content-Type: application/json" \
        -X POST "${AWX_URL}/api/v2/job_templates/15/launch/" \
        -d '{"extra_vars": {"app_version": "'$CI_COMMIT_TAG'", "environment": "production"}}' \
        | jq -r '.id')
      
      echo "AWX Job ID: $JOB_ID"
      
      # Ожидаем завершения
      while true; do
        STATUS=$(curl -s -k -H "Authorization: Bearer $AWX_TOKEN" \
          "${AWX_URL}/api/v2/jobs/${JOB_ID}/" | jq -r '.status')
        echo "Status: $STATUS"
        case $STATUS in
          successful) echo "Deploy completed"; exit 0 ;;
          failed|error|canceled) echo "Deploy failed"; exit 1 ;;
          *) sleep 10 ;;
        esac
      done
  only:
    - tags

Результаты и рекомендации

После 3 месяцев эксплуатации AWX в продакшене:

МетрикаДо AWXПосле AWX
Время на патчинг 500 серверов8 часов (ручной запуск)45 минут (автоматический)
Инциденты из-за несогласованных изменений3-4 в месяц0
Время onboarding нового инженера2 недели2 дня
Compliance checks выполнено~60% серверов100% ежедневно
Среднее время реакции на алерт25 минут3 минуты (авторемедиация)

Рекомендации по выбору инструмента:

  • Semaphore — для команды из 1-3 человек с 20-50 серверами. Минимальные ресурсы, простая установка, хватает базового RBAC.
  • AWX — для MSP, enterprise, команд с требованиями к RBAC, audit trail, workflow. Нужен Kubernetes или мощный Docker-хост.
  • Rundeck — если нужна автоматизация не только Ansible, но и скриптов на Bash/Python/PowerShell в единой системе.

Важный совет: начинайте с малого. Мы сначала перенесли в AWX только задачу патчинга, убедились в стабильности, и только потом мигрировали остальные плейбуки. Полная миграция заняла 6 недель, из них 2 недели — обучение команды.

Часто задаваемые вопросы

Да, существует docker-compose вариант установки, но он не поддерживается официально с версии 18+. Для production рекомендуется K3s — легковесный Kubernetes, который устанавливается за 30 секунд одной командой и потребляет всего 512 MB RAM сверх AWX.
AWX — upstream open-source проект, из которого Red Hat собирает коммерческий продукт Ansible Automation Platform. Разница: AAP имеет LTS-поддержку, Automation Hub для сертифицированных коллекций, Automation Mesh для распределённого выполнения и Execution Environments. AWX обновляется часто и не имеет гарантий обратной совместимости.
AWX масштабируется горизонтально: можно увеличивать количество pod-ов awx-task (воркеров). На 500 серверах достаточно 2 воркера, на 2000+ серверах — 4-6 воркеров. Узкое место обычно PostgreSQL: при 10000+ джобов в день рекомендуем отдельный кластер PostgreSQL с репликацией.
Да, для команды из 1-5 человек с 50-100 серверами Semaphore — отличный выбор. Он потребляет 512 MB RAM против 6 GB у AWX, устанавливается за 5 минут, имеет простой и интуитивный интерфейс. Переход на AWX имеет смысл, когда появляются требования к RBAC на уровне организаций или workflow templates.
Параллельная эксплуатация: установите AWX, перенесите плейбуки в Git, создайте inventory из текущих hosts-файлов. Начните с некритичных задач (отчёты, мониторинг), убедитесь что результаты идентичны, затем переводите production-задачи. Процесс занимает 2-4 недели.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#ansible#awx#semaphore#автоматизация#rbac#netbox#inventory#workflow
Комментарии 0

Оставить комментарий

загрузка...