В предыдущих статьях мы познакомились с основами Ansible, создали Inventory, написали первые Playbook и научились использовать Roles, Templates, Variables и Ansible Vault. Теперь пришло время посмотреть на Ansible с другой стороны.В реальных проектах Ansible редко существует изолированно. Он может работать вместе с Terraform, Docker, Kubernetes, GitHub Actions, GitLab CI или Jenkins.
Например, Terraform создаёт виртуальные машины и сети, Ansible устанавливает на них необходимое программное обеспечение, Docker запускает контейнеры, а CI/CD автоматически выполняет весь процесс после изменения кода. В этой статье объединим изученные инструменты в единую систему и разберём, где именно находится Ansible в современной DevOps-инфраструктуре.
Terraform + Ansible: два инструмента — две задачи
Terraform ▼ Создание инфраструктуры ├── Network ├── Virtual Machine ├── Disk └── Load Balancer │ ▼ Ansible ├── Users ├── Packages ├── Docker ├── Nginx ├── Configuration └── Application
Terraform отвечает преимущественно за Infrastructure as Code, а Ansible — за Configuration Management. Например, Terraform может создать виртуальную машину:
resource "..." "web" { # параметры виртуальной машины }
После её создания Ansible может подключиться к серверу и установить Docker:
- name: Install Docker hosts: web become: true tasks: - name: Install Docker ansible.builtin.apt: name: docker.io state: present
Получается последовательность:
Terraform → Infrastructure → Ansible → Configuration
Terraform → Ansible
На практике между двумя инструментами необходимо передать информацию о созданных серверах. Например, Terraform создал:
web-01 → 10.10.1.10 web-02 → 10.10.1.11 web-03 → 10.10.1.12
Ansible должен получить эти адреса и понять, к каким серверам подключаться. Для небольших проектов можно сформировать обычный Inventory вручную. Однако в динамической инфраструктуре такой подход быстро становится неудобным. Если Terraform каждый день создаёт и удаляет серверы, поддерживать список IP-адресов вручную практически невозможно. Здесь появляется Dynamic Inventory.
Dynamic Inventory
Обычный Inventory выглядит примерно так:
[web] 10.10.1.10 10.10.1.11 [database] 10.10.2.10
Но инфраструктура может измениться:
10.10.1.10 → удалён 10.10.1.11 → удалён 10.10.1.20 → создан 10.10.1.21 → создан
Если Inventory не обновить, Ansible будет использовать устаревшую информацию. Dynamic Inventory позволяет получать список серверов непосредственно из внешнего источника. Например:
Cloud Provider ▼ Dynamic Inventory ▼ Ansible
В зависимости от инфраструктуры источником могут быть:
AWS;
Azure;
Google Cloud;
Kubernetes;
VMware;
другие системы.
Это особенно полезно в больших и постоянно изменяющихся инфраструктурах.
Provisioners: почему их используют осторожно
Terraform поддерживает механизм Provisioners, который позволяет выполнять команды после создания ресурса. Например:
provisioner "remote-exec" { inline = [ "sudo apt update", "sudo apt install nginx -y" ] }
На первый взгляд это удобно: Terraform создаёт сервер и сразу выполняет команды. Но для полноценной настройки серверов такой подход обычно не является лучшим решением. Provisioners:
сложнее контролировать;
хуже интегрируются с повторным применением конфигурации;
могут сделать процесс развёртывания менее предсказуемым;
смешивают создание инфраструктуры и её настройку.
Provisioners при этом могут использоваться для отдельных сценариев, но не должны автоматически становиться основным способом конфигурации серверов.
Terraform → Ansible
На практике существует несколько способов передать информацию от Terraform к Ansible. Самый простой вариант — Terraform создаёт сервер, а его IP-адрес используется в Inventory. Например:
Terraform ▼ 192.168.56.101 ▼ Ansible Inventory ▼ Ansible Playbook
Для небольшого проекта можно использовать статический Inventory:
all: children: web: hosts: web-01: ansible_host: 192.168.56.101
В больших инфраструктурах удобнее использовать Dynamic Inventory.
Практический проект: Ansible + Docker Compose + Molecule + CI/CD
Теперь соберём всё изученное в один небольшой, но уже приближенный к реальному проект. Наша задача — не просто выполнить несколько команд на сервере, а построить воспроизводимый процесс:
Developer ▼ Git ▼ GitHub Actions ├── ansible-lint ├── syntax check └── Molecule ▼ Ansible Role ▼ Ubuntu Server │ ├── Docker Engine ├── Docker Compose v2 ├── deploy │ └── /opt/myapp │ └── Docker Compose ├── Nginx ├── Redis └── PostgreSQL
В результате мы получим проект, который можно:
применить к серверу через Ansible;
повторно запустить без лишних изменений;
проверить
--check;проверить через
ansible-lint;протестировать роль через Molecule;
запускать автоматические проверки через GitHub Actions.
При этом мы разделим тестирование роли и реальный деплой. Molecule будет проверять логику роли в изолированном Docker-окружении, а полноценный Docker Compose deployment — на отдельной Ubuntu-машине. Такой подход позволяет не пытаться устанавливать Docker daemon внутри тестового контейнера.
Что понадобится
Для практики понадобятся:
Control Node с Linux;
Ansible;
Git;
Docker на Control Node — он потребуется Molecule;
Ubuntu Managed Node;
SSH-доступ к Managed Node;
GitHub-репозиторий.
В качестве Managed Node можно использовать физический сервер, облачную VM или локальную виртуальную машину VirtualBox.
В примере предположим:
Control Node 192.168.56.1 Managed Node 192.168.56.101
IP-адреса можно заменить на свои.
Создаём проект
Создадим каталог:
mkdir ansible-docker-demo cd ansible-docker-demo
Создадим базовую структуру:
ansible-docker-demo/ ├── ansible.cfg ├── requirements.yml ├── inventory/ │ └── hosts.yml ├── playbooks/ │ └── site.yml ├── roles/ │ └── docker_app/ │ ├── defaults/ │ │ └── main.yml │ ├── handlers/ │ │ └── main.yml │ ├── tasks/ │ │ ├── main.yml │ │ └── deploy.yml │ ├── templates/ │ │ └── compose.yml.j2 │ └── meta/ │ └── main.yml ├── molecule/ │ └── docker_app/ │ ├── converge.yml │ ├── molecule.yml │ └── verify.yml └── .github/ └── workflows/ └── ansible.yml
Здесь впервые используем полноценную Role.
Ansible Role позволяет вынести повторяемую конфигурацию в стандартную структуру каталогов и затем подключать её из разных Playbook. Ansible рекомендует использовать Roles для организации переиспользуемой автоматизации.
Устанавливаем зависимости
На Control Node создадим виртуальное окружение Python:
python3 -m venv .venv source .venv/bin/activate
Устанавливаем инструменты:
pip install \ ansible-core \ ansible-lint \ molecule \ molecule-plugins[docker]
ansible-lint используется для статического анализа Ansible-кода, а Molecule — для тестирования Roles и Playbook. Ansible относит оба инструмента к основным средствам проверки Ansible-кода. Теперь создадим requirements.yml:
--- collections: - name: community.docker
Устанавливаем коллекцию:
ansible-galaxy collection install -r requirements.yml
Коллекция community.docker не входит в ansible-core. Она устанавливается отдельно и содержит, в частности, community.docker.docker_compose_v2.
Настраиваем Ansible
Создадим ansible.cfg:
[defaults] inventory = inventory/hosts.yml roles_path = roles host_key_checking = False interpreter_python = auto_silent
Параметр roles_path говорит Ansible, где искать локальные Roles. Теперь Inventory.
inventory/hosts.yml
--- all: children: web: hosts: web-01: ansible_host: 192.168.56.101 ansible_user: ubuntu
Проверим структуру:
ansible-inventory --graph
Получим примерно:
@all: |--@ungrouped: |--@web: | |--web-01
Проверяем SSH
До запуска Playbook сначала проверим обычное SSH-подключение:
ssh ubuntu@192.168.56.101
Если оно работает, проверяем Ansible:
ansible web -m ansible.builtin.ping
Результат:
web-01 | SUCCESS => { "changed": false, "ping": "pong" }
Здесь ping — не ICMP ping. Модуль проверяет, способен ли Ansible подключиться к серверу и выполнить действие.
Устанавливаем Docker Engine и Compose v2
Теперь подготовим Managed Node. Docker рекомендует устанавливать Docker Engine на Ubuntu через официальный APT-репозиторий. В состав установки входят Docker Engine, Docker CLI, containerd.io, Buildx и Docker Compose plugin. Важный момент: не будем использовать Ubuntu-пакет docker.io и отдельно надеяться на наличие Compose.
Нам нужны:
docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Создадим playbooks/site.yml:
--- - name: Prepare Docker host hosts: web become: true tasks: - name: Install prerequisites ansible.builtin.apt: name: - ca-certificates - curl state: present update_cache: true - name: Create Docker keyrings directory ansible.builtin.file: path: /etc/apt/keyrings state: directory mode: "0755" - name: Download Docker GPG key ansible.builtin.get_url: url: https://download.docker.com/linux/ubuntu/gpg dest: /etc/apt/keyrings/docker.asc mode: "0644" - name: Configure Docker repository ansible.builtin.copy: dest: /etc/apt/sources.list.d/docker.sources mode: "0644" content: | Types: deb URIs: https://download.docker.com/linux/ubuntu Suites: {{ ansible_facts.distribution_release }} Components: stable Architectures: {{ ansible_facts.architecture }} Signed-By: /etc/apt/keyrings/docker.asc - name: Install Docker ansible.builtin.apt: name: - docker-ce - docker-ce-cli - containerd.io - docker-buildx-plugin - docker-compose-plugin state: present update_cache: true - name: Start Docker ansible.builtin.systemd: name: docker state: started enabled: true
Запускаем:
ansible-playbook playbooks/site.yml
Проверяем Docker:
ansible web \ -m ansible.builtin.command \ -a "docker --version"
И Compose:
ansible web \ -m ansible.builtin.command \ -a "docker compose version"
Docker Compose v2 работает через CLI plugin docker compose. Именно этот plugin требуется модулю community.docker.docker_compose_v2. В актуальной документации минимальное требование для модуля — Docker Compose plugin версии 2.18.0 или новее. Проверяем непосредственно Docker:
ansible web \ -m ansible.builtin.command \ -a "docker run --rm hello-world"
Официальная документация Docker также использует hello-world как проверку успешной установки.
Создаём пользователя deploy
Теперь перенесём настройку приложения в Role. Создадим:
roles/docker_app/defaults/main.yml
--- app_user: deploy app_group: deploy app_dir: /opt/myapp
Теперь roles/docker_app/tasks/main.yml:
--- - name: Create application group ansible.builtin.group: name: "{{ app_group }}" state: present - name: Create application user ansible.builtin.user: name: "{{ app_user }}" group: "{{ app_group }}" groups: docker append: true shell: /bin/bash create_home: true - name: Create application directory ansible.builtin.file: path: "{{ app_dir }}" state: directory owner: "{{ app_user }}" group: "{{ app_group }}" mode: "0755" - name: Deploy Compose file ansible.builtin.template: src: compose.yml.j2 dest: "{{ app_dir }}/compose.yml" owner: "{{ app_user }}" group: "{{ app_group }}" mode: "0644" notify: Restart application - name: Deploy application ansible.builtin.include_tasks: file: deploy.yml
Здесь Ansible:
создаёт
deploy;создаёт каталог;
передаёт Compose;
запускает отдельный набор задач для deployment.
Членство в группе docker удобно для практики, но важно понимать его последствия: доступ к Docker daemon предоставляет пользователю практически уровень привилегий, сопоставимый с root. Поэтому в production модель доступа к Docker необходимо проектировать отдельно.
Создаём Docker Compose
Создадим:
roles/docker_app/templates/compose.yml.j2
services: web: image: nginx:alpine ports: - "80:80" restart: unless-stopped redis: image: redis:alpine restart: unless-stopped postgres: image: postgres:16 environment: POSTGRES_DB: example POSTGRES_USER: app POSTGRES_PASSWORD: example restart: unless-stopped
Для практики этого достаточно. В production пароль PostgreSQL нельзя хранить непосредственно в Git-репозитории. Здесь можно использовать Ansible Vault, внешний Secret Manager или механизм секретов конкретной платформы.
Запускаем Compose через Ansible
Создадим:
roles/docker_app/tasks/deploy.yml
--- - name: Start Docker Compose application community.docker.docker_compose_v2: project_src: "{{ app_dir }}" state: present
Это принципиальный момент. Не используем:
ansible.builtin.command: cmd: docker compose up -d
Вместо этого описываем желаемое состояние:
community.docker.docker_compose_v2: project_src: /opt/myapp state: present
Модуль community.docker.docker_compose_v2 предназначен именно для управления multi-container Docker applications через Docker Compose CLI plugin. В документации также указано, что при повторном запуске с теми же параметрами он может работать идемпотентно.
Старый community.docker.docker_compose предназначался для Compose v1 и удалён из community.docker начиная с версии 4.0.0. Для современных проектов необходимо использовать docker_compose_v2.
Добавляем Handler
Создадим:
roles/docker_app/handlers/main.yml
--- - name: Restart application community.docker.docker_compose_v2: project_src: "{{ app_dir }}" state: restarted
Handler будет вызван только после изменения Compose-файла. Например:
compose.yml │ ▼ Изменился? ┌──┴──┐ │ │ Да Нет │ │ ▼ ▼ Restart Ничего
Это намного лучше, чем выполнять перезапуск при каждом запуске Playbook.
Подключаем Role
Теперь playbooks/site.yml дополняем:
--- - name: Prepare Docker host hosts: web become: true tasks: - name: Install prerequisites ansible.builtin.apt: name: - ca-certificates - curl state: present update_cache: true - name: Create Docker keyrings directory ansible.builtin.file: path: /etc/apt/keyrings state: directory mode: "0755" - name: Download Docker GPG key ansible.builtin.get_url: url: https://download.docker.com/linux/ubuntu/gpg dest: /etc/apt/keyrings/docker.asc mode: "0644" - name: Configure Docker repository ansible.builtin.copy: dest: /etc/apt/sources.list.d/docker.sources mode: "0644" content: | Types: deb URIs: https://download.docker.com/linux/ubuntu Suites: {{ ansible_facts.distribution_release }} Components: stable Architectures: {{ ansible_facts.architecture }} Signed-By: /etc/apt/keyrings/docker.asc - name: Install Docker ansible.builtin.apt: name: - docker-ce - docker-ce-cli - containerd.io - docker-buildx-plugin - docker-compose-plugin state: present update_cache: true - name: Start Docker ansible.builtin.systemd: name: docker state: started enabled: true - name: Deploy application ansible.builtin.include_role: name: docker_app
Теперь весь deployment выполняется одной командой:
ansible-playbook playbooks/site.yml
Проверяем результат
Проверяем контейнеры:
ansible web \ -m ansible.builtin.command \ -a "docker compose -f /opt/myapp/compose.yml ps"
Должны появиться:
web redis postgres
Можно подключиться к серверу:
ssh ubuntu@192.168.56.101
И проверить:
cd /opt/myapp docker compose ps
Проверяем Nginx:
curl http://192.168.56.101
Если всё настроено правильно, сервер вернёт HTML стандартной страницы Nginx. Теперь наш deployment уже можно представить так:
Terraform ▼ Ubuntu VM ▼ Ansible ├── Docker ├── deploy └── /opt/myapp │ ▼ Docker Compose ┌────┼────┐ ▼ ▼ ▼ Nginx Redis PostgreSQL
Проверяем идемпотентность
Теперь одна из самых важных проверок. Запускаем Playbook ещё раз:
ansible-playbook playbooks/site.yml
Первый запуск должен содержать изменённые задачи:
changed > 0
После второго запуска количество изменений должно значительно уменьшиться, а при отсутствии изменений основной deployment должен завершиться без повторного создания ресурсов. Почему это важно?
Потому что Ansible описывает желаемое состояние:
«пользователь deploy должен существовать»
а не последовательность действий:
«создай пользователя deploy»
То же самое относится к Compose:
state: present
означает, что проект должен находиться в соответствующем состоянии. Документация community.docker.docker_compose_v2 прямо указывает поддержку идемпотентного поведения при повторном запуске с теми же параметрами.
Используем Check Mode
Перед применением изменений можно выполнить:
ansible-playbook \ playbooks/site.yml \ --check \ --diff
Получается workflow:
Изменение ▼ --check --diff ▼ Проверка ▼ Review ▼ Применение
--check особенно полезен при работе с production-инфраструктурой.
Однако не стоит воспринимать Check Mode как абсолютную гарантию того, что реальное выполнение будет полностью идентично: поддержка check mode зависит от конкретного модуля и его взаимодействия с внешними системами.
Добавляем ansible-lint
Теперь проверим качество самого Ansible-кода. Запускаем:
ansible-lint .
ansible-lint анализирует Playbook и Roles и выявляет распространённые ошибки, устаревшие конструкции и нарушения рекомендуемых практик. Например, отдельные правила проверяют неоправданное использование shell и ошибки в условиях. Если lint сообщает об ошибках, лучше исправить сам код, а не сразу отключать правила.
Например, вместо:
- name: Restart service ansible.builtin.shell: systemctl restart nginx
предпочтительнее:
- name: Restart service ansible.builtin.service: name: nginx state: restarted
Именно поэтому в нашем проекте запуск Compose выполняется специализированным модулем community.docker.docker_compose_v2, а не через shell.
Добавляем Molecule
Теперь самое интересное — тестирование Role. Molecule нужен не для проверки самого production-сервера, а для автоматической проверки того, что Role выполняет ожидаемые действия. В нашем случае Role отвечает за:
deploy user application directory compose.yml
Установка Docker Engine остаётся ответственностью отдельного Playbook. Такое разделение полезно и архитектурно:
Playbook ├── Docker host preparation │ └── docker_app Role ├── user ├── directory └── Compose configuration
Конфигурация Molecule
Создадим:
molecule/docker_app/molecule.yml
--- driver: name: docker platforms: - name: instance image: ubuntu:24.04 pre_build_image: true provisioner: name: ansible verifier: name: ansible
Здесь Molecule использует Docker как driver. Сам Docker должен быть доступен на Control Node, поскольку тестовая инфраструктура будет запускаться в контейнерах.
Converge
Создадим:
molecule/docker_app/converge.yml
--- - name: Converge hosts: all become: true vars: molecule_test: true roles: - role: docker_app
Но здесь возникает важный вопрос. Role содержит задачу:
community.docker.docker_compose_v2
Для неё на тестовом контейнере потребовались бы Docker CLI и Compose plugin. Поэтому не будем пытаться запускать production deployment внутри обычного Molecule-контейнера. Внесём небольшое изменение в roles/docker_app/tasks/main.yml.
--- - name: Create application user ansible.builtin.user: name: "{{ app_user }}" group: "{{ app_group }}" groups: docker append: true shell: /bin/bash create_home: true - name: Create application directory ansible.builtin.file: path: "{{ app_dir }}" state: directory owner: "{{ app_user }}" group: "{{ app_group }}" mode: "0755" - name: Deploy Compose file ansible.builtin.template: src: compose.yml.j2 dest: "{{ app_dir }}/compose.yml" owner: "{{ app_user }}" group: "{{ app_group }}" mode: "0644" - name: Deploy application ansible.builtin.include_tasks: file: deploy.yml when: not molecule_test | default(false)
Теперь:
Production ▼ user directory compose deployment
а Molecule:
Molecule ▼ user directory compose
То есть Molecule тестирует саму Role, не подменяя production-окружение.
Создаём Verify
Создадим:
molecule/docker_app/verify.yml
--- - name: Verify hosts: all become: true tasks: - name: Check deploy user ansible.builtin.getent: database: passwd key: deploy - name: Check application directory ansible.builtin.stat: path: /opt/myapp register: app_dir - name: Assert application directory exists ansible.builtin.assert: that: - app_dir.stat.exists - app_dir.stat.isdir - name: Check Compose file ansible.builtin.stat: path: /opt/myapp/compose.yml register: compose_file - name: Assert Compose file exists ansible.builtin.assert: that: - compose_file.stat.exists - compose_file.stat.isreg
Теперь тест проверяет реальное состояние контейнера:
deploy существует + /opt/myapp существует + compose.yml существует
Запускаем Molecule
Выполняем:
molecule test
Molecule создаёт тестовое окружение, применяет Role, выполняет проверки и затем уничтожает тестовую инфраструктуру. Упрощённо процесс выглядит так:
Create ▼ Prepare ▼ Converge ▼ Verify ▼ Destroy
Если одна из проверок не проходит, molecule test завершается с ошибкой. Таким образом, у нас появляется автоматическая проверка Role без использования production-сервера.
Что именно тестирует Molecule
Важно понимать границы теста. Molecule здесь проверяет:
Role ├── создаёт пользователя ├── создаёт каталог └── создаёт Compose-файл
А отдельный integration test на Ubuntu проверяет:
Docker Engine + Docker Compose + Real Compose deployment + HTTP
Это не недостаток, а разделение уровней тестирования. Получается:
Unit/Role-level ▼ Molecule Integration ▼ Ubuntu VM Production ▼ Real servers
Добавляем Git
Теперь сохраним проект в Git.
git init git add . git commit -m "Initial Ansible Docker deployment"
Создаём .gitignore:
.venv/ __pycache__/ *.retry .molecule/
Если в будущем в проекте появятся Vault-файлы, SSH-ключи или локальные секреты, их также нельзя отправлять в публичный репозиторий.
GitHub Actions
Теперь автоматизируем проверки. Создадим:
.github/workflows/ansible.yml
name: Ansible CI on: push: pull_request: jobs: lint: name: Ansible Lint runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install tools run: | python -m pip install --upgrade pip pip install ansible-core ansible-lint molecule "molecule-plugins[docker]" - name: Install Ansible collections run: | ansible-galaxy collection install -r requirements.yml - name: Run ansible-lint run: ansible-lint . syntax: name: Ansible Syntax Check runs-on: ubuntu-latest needs: lint steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install Ansible run: | python -m pip install --upgrade pip pip install ansible-core - name: Install collections run: | ansible-galaxy collection install -r requirements.yml - name: Syntax check run: | ansible-playbook playbooks/site.yml --syntax-check molecule: name: Molecule runs-on: ubuntu-latest needs: syntax steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install tools run: | python -m pip install --upgrade pip pip install ansible-core ansible-lint molecule "molecule-plugins[docker]" - name: Install collections run: | ansible-galaxy collection install -r requirements.yml - name: Run Molecule run: molecule test
Теперь после каждого push или pull request GitHub Actions выполняет:
GitHub ▼ Checkout ▼ ansible-lint ▼ syntax-check ▼ Molecule
Если хотя бы один этап завершится с ошибкой, workflow будет считаться неуспешным.
Добавляем CD
Пока GitHub Actions выполняет только проверки — это CI (Continuous Integration). Для полноценного CD (Continuous Delivery/Deployment) runner должен иметь сетевой доступ к Managed Node. В нашем учебном примере Managed Node находится в приватной сети. 192.168.56.101 .Поэтому GitHub-hosted runner не сможет автоматически подключиться к нему без дополнительной сетевой инфраструктуры. В production для этого можно использовать, например:
self-hosted GitHub Actions runner внутри приватной сети;
VPN или другой защищённый сетевой канал;
публично доступный сервер с корректно настроенным firewall;
отдельную deployment-инфраструктуру.
Поэтому следующий workflow рассматриваем как архитектурный пример CD:
Lint ▼ Syntax ▼ Molecule ▼ Deploy ▼ Production
Например, deployment можно запускать только после успешного прохождения тестов и только для main. Упрощённый job:
deploy: name: Deploy runs-on: ubuntu-latest needs: - lint - syntax - molecule\ if: github.ref == 'refs/heads/main' && github.event_name == 'push' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install Ansible run: | python -m pip install --upgrade pip pip install ansible-core - name: Install collections run: | ansible-galaxy collection install -r requirements.yml - name: Configure SSH run: | mkdir -p ~/.ssh echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 - name: Deploy run: | ansible-playbook \ -i inventory/hosts.yml \ playbooks/site.yml
Но перед использованием такого job в production нужно изменить Inventory и добавить секреты. SSH-ключ, пароль, токены и другие секреты нельзя хранить непосредственно в YAML workflow или Git-репозитории.
Полный pipeline
Теперь наша архитектура выглядит так:
GitHub ▼ Pull Request ▼ Lint ▼ Syntax ▼ Molecule ▼ Code Review ▼ Merge ▼ Deploy ▼ Ansible ▼ Ubuntu Server ┌──────────┼──────────┐ ▼ ▼ ▼ Docker deploy Config ▼ Compose v2 ┌──────┼──────┐ ▼ ▼ ▼ Nginx Redis PostgreSQL
Теперь изменение инфраструктурного кода проходит тот же принцип, что и обычный application code:
Изменение ▼ Pull Request ▼ Lint ▼ Tests ▼ Review ▼ Merge ▼ Deploy
Проверяем весь сценарий вручную
Перед тем как доверять deployment CI/CD, полезно пройти полный цикл на тестовой VM.
Первый запуск
ansible-playbook playbooks/site.yml
Проверяем:
curl http://192.168.56.101
Второй запуск
ansible-playbook playbooks/site.yml
Проверяем отсутствие лишних изменений.
Check Mode
ansible-playbook \ playbooks/site.yml \ --check \ --diff
Lint
ansible-lint .
Syntax Check
ansible-playbook \ playbooks/site.yml \ --syntax-check
Molecule
molecule test
После этого уже можно отправлять проект в GitHub:
git add . git commit -m "Add Docker deployment and CI" git push
GitHub Actions автоматически выполнит проверки.
Что произошло с точки зрения DevOps
В начале у нас был только сервер. Вручную пришлось бы:
SSH ▼ apt update ▼ Docker ▼ Compose ▼ user ▼ directory ▼ compose.yml ▼ docker compose up
Теперь всё это описано кодом. Ansible приводит инфраструктуру к заданному состоянию:
Ubuntu ├── Docker installed ├── Docker enabled ├── deploy exists ├── /opt/myapp exists ├── compose.yml exists └── application running
А CI добавляет ещё один уровень:
Infrastructure as Code ▼ Static Analysis ▼ Automated Tests ▼ Code Review ▼ Deployment
Именно здесь становится заметна разница между простым использованием Ansible и инфраструктурой как кодом.
Почему мы не устанавливаем Docker через Role приложения
Можно было поместить абсолютно всё в одну Role:
docker_app ├── Docker ├── user ├── directories ├── Compose └── deployment
Для учебного проекта это сработало бы. Но такое разделение лучше масштабируется:
Playbook │ ├── Docker host preparation │ └── docker_app ├── user ├── directories ├── configuration └── deployment
Позже можно выделить отдельные Roles:
roles/ ├── docker/ ├── users/ ├── nginx/ └── application/
Тогда одна Role отвечает за Docker, другая — за пользователей, третья — за приложение.
Что можно улучшить в production
Наш проект специально остаётся небольшим. Для production стоило бы:
фиксировать версии коллекций;
использовать отдельные inventories для окружений;
ограничивать права CI/CD;
не давать pipeline необязательный доступ к production;
использовать секрет-хранилище;
проводить deployment через review;
разделять инфраструктуру и конфигурацию.
Ресурсы для обучения
1. Официальная документация Ansible
Главный источник для изучения Ansible. Здесь есть Getting Started, документация по Playbook, Inventory, Modules, Roles, Variables и другим компонентам. Документация поддерживает актуальные версии Ansible и отмечает, в какой версии появились новые возможности.
2. Ansible Getting Started
Если официальная документация кажется слишком большой, начать лучше с отдельного Quick Start: установка Ansible → создание проекта → Inventory → первый Playbook.
3. LabEx
LabEx предлагает интерактивные лабораторные задания по Ansible, где можно изучать инструмент непосредственно через терминал браузера. Это удобно для тех, кто хочет не просто читать про Inventory, Playbook, Modules и Roles, а сразу выполнять команды и проверять результат.
4. Ansible for DevOps — Jeff Geerling
Один из наиболее полезных ресурсов именно для DevOps-практики. Книга начинается с базовых понятий, а затем переходит к Playbook, развёртыванию приложений, Docker и работе с инфраструктурой. Автор регулярно обновляет электронную версию; исходный текст книги также доступен открыто.
5. Ansible Galaxy
Ansible Galaxy — User Guide — официальный каталог, где можно находить, устанавливать и публиковать Ansible Roles и Collections. В документации есть поиск ролей и коллекций, установка конкретных версий, requirements.yml и управление зависимостями.
Отдельно стоит посмотреть Ansible Galaxy Developer Guide, если читатель захочет создавать и публиковать собственные Roles или Collections.
6. ansible-lint
Ansible Lint — статический анализатор для Ansible Playbooks, Roles и Collections. Он помогает находить распространённые ошибки, соблюдать рекомендуемые практики и поддерживать единый стиль кода.
7. Molecule
Molecule — фреймворк для тестирования Ansible Roles, Playbooks и Collections. Он позволяет создавать тестовые сценарии и проверять автоматизацию на контейнерах, виртуальных машинах и других системах, доступных Ansible.
Заключение
Ansible занимает важное место между инфраструктурой и приложением. Главная идея, которую стоит вынести из этого блока: DevOps — это не набор отдельных инструментов, а способ автоматизировать весь жизненный цикл инфраструктуры и приложения. Terraform создаёт инфраструктуру, Ansible приводит серверы к нужному состоянию, Docker и Kubernetes запускают приложения, а CI/CD связывает эти процессы и позволяет выполнять их автоматически.
На этом базовый блок Ansible заканчивается. Следующий шаг — перейти от отдельных инструментов к мониторингу и наблюдаемости (Observability): Prometheus, Grafana, Loki, сбор метрик и логов, и т.д., а также построение полноценной системы мониторинга инфраструктуры.
sergey2212
Спасибо за ваш труд. Спасибо что показали сам концепт и вариации применения ansible.
Классная штука по себе скажу. Чаще использую как слепок инфры что бы потом не мучаться а развернуть все с 1 го клика)