← Назад к вопросам
Ansible playbook для настройки Nginx и firewall
2.3 Middle🔥 191 комментариев
#Ansible и управление конфигурацией
Условие
Напишите Ansible playbook, который:
- Устанавливает Nginx на группу серверов
- Копирует конфигурационный файл из шаблона (Jinja2)
- Настраивает firewall (ufw или firewalld), открывая порты 22, 80, 443
- Перезапускает Nginx при изменении конфигурации
- Включает Nginx в автозапуск
Требования
- Используйте handlers для перезапуска сервисов
- Применяйте tags для выборочного запуска задач
- Добавьте проверку синтаксиса конфигурации перед перезапуском
Вопросы
- Чем отличается Ansible от Terraform?
- Как хранить секреты в Ansible?
- Что такое идемпотентность и почему она важна?
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
1. Структура Ansible проекта
ansible-project/
├── inventory.ini # Инвентарь хостов
├── ansible.cfg # Конфигурация Ansible
├── playbooks/
│ ├── site.yml # Главный playbook
│ └── nginx.yml # Playbook для Nginx
├── roles/
│ ├── nginx/
│ │ ├── tasks/
│ │ │ └── main.yml
│ │ ├── handlers/
│ │ │ └── main.yml
│ │ ├── templates/
│ │ │ └── nginx.conf.j2
│ │ ├── defaults/
│ │ │ └── main.yml
│ │ └── vars/
│ │ └── main.yml
│ └── firewall/
│ └── tasks/
│ └── main.yml
├── group_vars/
│ └── all.yml
└── host_vars/
└── example.com.yml
2. inventory.ini - Инвентарь хостов
[webservers]
web1.example.com ansible_user=ubuntu ansible_password=secret
web2.example.com ansible_user=ubuntu ansible_password=secret
web3.example.com ansible_user=ubuntu ansible_password=secret
[webservers:vars]
ansible_become=yes
ansible_become_method=sudo
http_port=80
https_port=443
[all:vars]
ansible_python_interpreter=/usr/bin/python3
3. ansible.cfg - Конфигурация
[defaults]
host_key_checking = False
inventory = ./inventory.ini
roles_path = ./roles
host_key_checking = False
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
4. playbooks/nginx.yml - Main Playbook
---
- name: Configure web servers with Nginx and firewall
hosts: webservers
become: yes
gather_facts: yes
pre_tasks:
- name: Update package cache
apt:
update_cache: yes
cache_valid_time: 3600
tags:
- packages
- always
roles:
- role: firewall
tags:
- firewall
- security
- role: nginx
tags:
- webserver
- nginx
post_tasks:
- name: Verify Nginx is running
systemd:
name: nginx
state: started
enabled: yes
tags:
- verify
- nginx
- name: Test Nginx connectivity
uri:
url: "http://localhost/"
status_code: 200
retries: 3
delay: 5
tags:
- verify
- health-check
5. roles/nginx/tasks/main.yml
---
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
tags:
- nginx
- packages
# После установки, если пакет был установлен, будет запущен handler
notify: restart nginx
- name: Create Nginx config directory
file:
path: /etc/nginx/conf.d
state: directory
owner: root
group: root
mode: '0755'
tags:
- nginx
- configuration
- name: Deploy Nginx configuration from template
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes
notify: restart nginx
tags:
- nginx
- configuration
- name: Deploy site configuration
template:
src: site.conf.j2
dest: /etc/nginx/sites-available/default
owner: root
group: root
mode: '0644'
notify: reload nginx
tags:
- nginx
- configuration
- name: Enable site
file:
src: /etc/nginx/sites-available/default
dest: /etc/nginx/sites-enabled/default
state: link
notify: reload nginx
tags:
- nginx
- name: Verify Nginx configuration syntax
command: nginx -t
register: nginx_test
changed_when: false
tags:
- nginx
- verify
- name: Display Nginx syntax check result
debug:
msg: "{{ nginx_test.stdout }}"
tags:
- nginx
- verify
- name: Start and enable Nginx service
systemd:
name: nginx
state: started
enabled: yes
daemon_reload: yes
tags:
- nginx
- service
- name: Create index.html
copy:
content: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx</title>
</head>
<body>
<h1>Server: {{ ansible_hostname }}</h1>
<p>IP Address: {{ ansible_default_ipv4.address }}</p>
</body>
</html>
dest: /var/www/html/index.html
owner: www-data
group: www-data
mode: '0644'
tags:
- nginx
- content
6. roles/nginx/handlers/main.yml
---
# Handler для перезапуска Nginx после изменения конфигурации
- name: restart nginx
systemd:
name: nginx
state: restarted
daemon_reload: yes
listen: restart nginx
# Handler для reload без прерывания соединений
- name: reload nginx
systemd:
name: nginx
state: reloaded
listen: reload nginx
# Handler для проверки конфигурации
- name: verify nginx config
command: nginx -t
register: nginx_test_result
failed_when: nginx_test_result.rc != 0
changed_when: false
listen: verify nginx
7. roles/nginx/templates/nginx.conf.j2
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 768;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# Performance optimizations
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 20M;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml application/atom+xml image/svg+xml
text/x-component text/x-cross-domain-policy;
# Include virtual hosts
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
8. roles/nginx/templates/site.conf.j2
server {
listen {{ http_port }} default_server;
listen [::]:{{ http_port }} default_server;
server_name {{ server_name | default('_') }};
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen {{ https_port }} ssl http2 default_server;
listen [::]:{{ https_port }} ssl http2 default_server;
server_name {{ server_name | default('_') }};
# SSL configuration
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Root directory
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
location / {
try_files $uri $uri/ =404;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
9. roles/nginx/defaults/main.yml
---
http_port: 80
https_port: 443
server_name: "{{ ansible_fqdn }}"
worker_processes: auto
worker_connections: 768
keepalive_timeout: 65
10. roles/firewall/tasks/main.yml
---
# Определяем используемый firewall
- name: Check if firewalld is installed
command: which firewall-cmd
register: firewalld_check
failed_when: false
changed_when: false
tags:
- firewall
- always
- name: Check if ufw is installed
command: which ufw
register: ufw_check
failed_when: false
changed_when: false
tags:
- firewall
- always
# Конфигурация firewalld
- block:
- name: Enable firewalld
systemd:
name: firewalld
state: started
enabled: yes
tags:
- firewall
- name: Configure firewalld for SSH
firewalld:
service: ssh
permanent: yes
state: enabled
immediate: yes
tags:
- firewall
- ssh
- name: Configure firewalld for HTTP
firewalld:
service: http
permanent: yes
state: enabled
immediate: yes
tags:
- firewall
- http
- name: Configure firewalld for HTTPS
firewalld:
service: https
permanent: yes
state: enabled
immediate: yes
tags:
- firewall
- https
when: firewalld_check.rc == 0
tags:
- firewall
# Конфигурация UFW (для Debian/Ubuntu)
- block:
- name: Enable UFW
ufw:
state: enabled
policy: deny
direction: incoming
tags:
- firewall
- name: Configure UFW for SSH
ufw:
rule: allow
port: '22'
proto: tcp
comment: 'SSH access'
tags:
- firewall
- ssh
- name: Configure UFW for HTTP
ufw:
rule: allow
port: '80'
proto: tcp
comment: 'HTTP access'
tags:
- firewall
- http
- name: Configure UFW for HTTPS
ufw:
rule: allow
port: '443'
proto: tcp
comment: 'HTTPS access'
tags:
- firewall
- https
- name: Enable UFW
ufw:
state: enabled
tags:
- firewall
when: ufw_check.rc == 0
tags:
- firewall
11. Команды для запуска playbook
# Синтаксис playbook
ansible-playbook playbooks/nginx.yml --syntax-check
# Dry-run (показать что будет изменено без реальных изменений)
ansible-playbook playbooks/nginx.yml --check
# Запуск всех задач
ansible-playbook playbooks/nginx.yml
# Запуск только задач с определенным тегом
ansible-playbook playbooks/nginx.yml --tags "firewall"
ansible-playbook playbooks/nginx.yml --tags "nginx,verify"
# Пропуск задач с определенным тегом
ansible-playbook playbooks/nginx.yml --skip-tags "health-check"
# Запуск для конкретного хоста
ansible-playbook playbooks/nginx.yml -l web1.example.com
# Вывод более подробных логов
ansible-playbook playbooks/nginx.yml -vv
# Запуск с определенным пользователем
ansible-playbook playbooks/nginx.yml --user=ansible --ask-pass
# Запуск с ask-become-pass (спросить пароль для sudo)
ansible-playbook playbooks/nginx.yml --ask-become-pass
12. group_vars/all.yml - Глобальные переменные
---
ansible_user: ubuntu
ansible_become: yes
ansible_become_method: sudo
http_port: 80
https_port: 443
server_name: "{{ ansible_fqdn }}"
nginx_worker_processes: auto
nginx_worker_connections: 768
13. host_vars/web1.example.com.yml - Переменные для конкретного хоста
---
server_name: web1.example.com
http_port: 8080 # Отличается от дефолтного
14. Чем отличается Ansible от Terraform?
Terraform
- Цель: Provisioning инфраструктуры (IaC)
- Что делает: Создает облачные ресурсы (EC2, VPC, RDS, и т.д.)
- Подход: Декларативный ("ВОТ какая должна быть инфраструктура")
- State: Ведет state файл с текущим состоянием
- Идемпотентность: Встроена - применение плана несколько раз = одинаковый результат
- Скорость: Быстро для provisioning, может быть медленным для конфигурации
# Terraform - создание инфраструктуры
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Ansible
- Цель: Configuration Management и Orchestration
- Что делает: Настраивает ОС и приложения на существующих серверах
- Подход: Procedural ("ДЕЛАЙ это, потом это, потом это")
- State: Не ведет state файл, полагается на idempotent модули
- Идемпотентность: Зависит от модулей (большинство идемпотентны)
- Скорость: Медленнее при первом запуске, быстрее при перезапусках
# Ansible - конфигурация серверов
- name: Install and configure Nginx
hosts: webservers
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
Сравнительная таблица
| Аспект | Terraform | Ansible |
|---|---|---|
| Тип | IaC (Infrastructure) | CM (Configuration) |
| Подход | Декларативный | Процедурный |
| State | Ведет state файл | Нет state файла |
| Агент | Без агента | Без агента (SSH) |
| Обучение кривая | Средняя | Низкая |
| Скорость provisioning | Быстро | Медленно |
| Скорость переконфигурации | Медленно | Быстро при повторе |
| Лучше всего для | Облако инфра | Конфигурация серверов |
Когда использовать?
# Terraform -> Ansible
Terraform (создает EC2) -> Ansible (устанавливает Nginx на EC2)
# Практический пример
terraform apply # Создает инстансы
ansible-playbook playbooks/nginx.yml # Конфигурирует их
15. Хранение секретов в Ansible
Вариант 1: ansible-vault (встроено)
# Шифрование файла с пароль
ansible-vault create secrets/passwords.yml
# Введите пароль при запросе
# Содержимое файла
database_password: "super_secret_password"
api_key: "sk_live_xxxxx"
# Использование в playbook
- name: Configure database
mysql_user:
name: app_user
password: "{{ database_password }}" # Из зашифрованного файла
# Запуск playbook с vault
ansible-playbook playbooks/nginx.yml --ask-vault-pass
ansible-playbook playbooks/nginx.yml --vault-password-file=/path/to/vault/pass
# Редактирование зашифрованного файла
ansible-vault edit secrets/passwords.yml
# Просмотр зашифрованного файла
ansible-vault view secrets/passwords.yml
# Переключение пароля vault
ansible-vault rekey secrets/passwords.yml
Вариант 2: Environment переменные
# В shell
export ADMIN_PASSWORD="secret123"
# В playbook
- name: Configure app
environment:
API_KEY: "{{ lookup('env', 'API_KEY') }}"
Вариант 3: HashiCorp Vault
- name: Get secret from HashiCorp Vault
set_fact:
db_password: "{{ lookup('hashi_vault', 'secret=secret/data/database:password') }}"
- name: Configure database
mysql_user:
password: "{{ db_password }}"
Вариант 4: AWS Secrets Manager
- name: Get AWS secret
set_fact:
db_password: "{{ lookup('amazon.aws.aws_secret', 'my-secret') }}"
Best practices
# .gitignore
secrets/
*.vault
.env
.env.local
# Не коммитьте незашифрованные секреты!
# ВСЕГДА используйте ansible-vault
16. Идемпотентность и почему она важна
Определение
Идемпотентность — свойство задачи, при котором многократное применение дает тот же результат, что и однократное применение.
# ✅ ИДЕМПОТЕНТНАЯ задача
- name: Install Nginx
apt:
name: nginx
state: present
# Результаты:
# 1-й запуск: Установит Nginx (changed: true)
# 2-й запуск: Nginx уже установлен, не будет изменений (changed: false)
# 3-й запуск: Nginx уже установлен, не будет изменений (changed: false)
# Результат всегда одинаков!
# ❌ НЕ идемпотентная задача
- name: Add line to file (using shell)
shell: echo "new_line" >> /etc/config.txt
# Результаты:
# 1-й запуск: Добавит строку (changed: true)
# 2-й запуск: Добавит еще одну строку (changed: true)
# 3-й запуск: Добавит ЕЩЕ одну строку (changed: true)
# Результат различается! Это НЕ идемпотентно
Почему это важно?
- Безопасность: Можно запустить playbook несколько раз без побочных эффектов
- Восстановление: При сбое можно пересчитать нужные части
- Масштабируемость: Можно применять один playbook к новым серверам
- CI/CD: Можно запускать playbook при каждом коммите
Примеры идемпотентных модулей
# ✅ apt - идемпотентен
- apt:
name: nginx
state: present
# ✅ file - идемпотентен
- file:
path: /var/myapp
state: directory
mode: '0755'
# ✅ systemd - идемпотентен (если использовать правильно)
- systemd:
name: nginx
state: started
enabled: yes
# ✅ lineinfile - идемпотентен
- lineinfile:
path: /etc/config.txt
line: 'param=value'
state: present
# ❌ shell/command - НЕ идемпотентны (обычно)
- shell: |
echo "data" > /tmp/file.txt
# Каждый запуск перезапишет файл - это ОК в данном случае
# Но если это какая-то команда с побочными эффектами - проблема
Как писать идемпотентные playbook
# ❌ ПЛОХО
- name: Restart Nginx
shell: systemctl restart nginx
# При каждом запуске perезапускает Nginx
# ✅ ХОРОШО
- name: Restart Nginx
systemd:
name: nginx
state: restarted
when: nginx_config_changed
# Перезапускает только если конфигурация изменилась
# ✅ ЛУЧШЕ - используйте handlers
- name: Update Nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx # Handler запустится только если файл изменился
- name: restart nginx
systemd:
name: nginx
state: restarted
listen: restart nginx # Это handler