С нуля до Junior DevOps в 2026 году. Часть 7.2. Ansible в DevOps: Docker, Kubernetes, CI/CD и Terraform

В предыдущих статьях мы познакомились с основами 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
└── ApplicationTerraform отвечает преимущественно за 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 → ConfigurationTerraform → Ansible
На практике между двумя инструментами необходимо передать информацию о созданных серверах. Например, Terraform создал:
web-01 → 10.10.1.10
web-02 → 10.10.1.11
web-03 → 10.10.1.12Ansible должен получить эти адреса и понять, к каким серверам подключаться. Для небольших проектов можно сформировать обычный 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.101IP-адреса можно заменить на свои.
Создаём проект
Создадим каталог:
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.j2services:
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: restartedHandler будет вызван только после изменения 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 testMolecule создаёт тестовое окружение, применяет 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.ymlname: 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 \
--diffLint
ansible-lint .Syntax Check
ansible-playbook \
playbooks/site.yml \
--syntax-checkMolecule
molecule testПосле этого уже можно отправлять проект в GitHub:
git add .
git commit -m "Add Docker deployment and CI"
git pushGitHub 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, сбор метрик и логов, и т.д., а также построение полноценной системы мониторинга инфраструктуры.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.