8.2.4 Автоматизация развертывания сайта с помощью Ansible на VPS

Автоматизация процесса деплоя позволяет ускорить и упростить обновление приложений, особенно когда речь идёт о продакшн-сервере. В этом руководстве мы подробно рассмотрим, как с помощью Ansible развернуть Next.js-приложение на Ubuntu-сервере, используя роли, шаблоны, переменные.

Ansible — инструмент управления конфигурацией и автоматизации, который не требует установки агентов на целевых серверах. Он идеально подходит для развёртывания Node.js-приложений, таких как Next.js, в Linux-окружении.

Требования

Генерация SSH-ключа

Перед подключением Ansible к серверу по SSH необходимо создать пару ключей, если она ещё не создана. Это обеспечит безопасное и автоматизированное подключение к удалённому серверу.

Выполните команду заменив your_email@example.com на Ваш email:

ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

Затем добавьте публичный ключ в файл ~/.ssh/authorized_keys на вашем сервере :

# замените user на имя Вашего пользователя на сервере и IP на ip Вашего сервера
ssh-copy-id -i ~/.ssh/id_rsa.pub user@IP

После этого Ansible сможет подключаться к серверу без ввода пароля.

Локальная установка Ansible

Ansible устанавливается на локальной машине разработчика или администратора, а не на целевой сервер. Он управляет удалёнными серверами через SSH, поэтому нет необходимости устанавливать Ansible на каждую машину, на которую вы хотите развернуть приложение.

sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible

Эти команды нужно выполнить на вашей локальной машине, откуда будет запускаться Ansible.

Структура проекта Ansible

nextjs-deploy/
├── ansible.cfg           # Основной конфигурационный файл Ansible
├── inventory.ini         # Инвентарь с IP-адресами и данными для подключения к серверам
├── playbook.yml          # Главный сценарий, описывающий порядок выполнения ролей
└── roles/                # Каталог с ролями, каждая из которых выполняет отдельную часть автоматизации
    ├── common/           # Роль, выполняющая базовую подготовку сервера
    │   ├── handlers/     # Обработчики событий (например, рестарт служб)
    │   └── tasks/        # Основные задачи: установка пакетов, настройка фаервола и т.д.
    ├── nodejs/           # Роль для установки Node.js и PM2
    │   └── tasks/        # Задачи по установке Node.js и вспомогательных инструментов
    └── nextjs/           # Роль, управляющая деплоем самого Next.js-приложения
        ├── handlers/     # Обработчики (например, перезапуск PM2)
        └── tasks/        # Задачи: клонирование репозитория, установка зависимостей, сборка и конфигурация

Настройка инвентаря

Инвентарь в Ansible — это файл, в котором описаны адреса серверов и параметры подключения к ним. Он необходим для указания целевых машин, на которые будут выполняться автоматизированные действия. Здесь мы опишем IP-адрес сервера, пользователя и путь к приватному SSH-ключу.

# Необходимо заменить IP, user, id_rsa_path на ip адрес Вашего сервера, имя пользователя и путь до файла с ssh-ключем. 
[web_servers]
web1 ansible_host=IP_СЕРВЕРА ansible_user=user ansible_ssh_private_key_file=id_rsa_path

[all:vars]
ansible_python_interpreter=/usr/bin/python3

Основной playbook

Playbook — это основной сценарий, в котором Ansible описывает, какие действия нужно выполнить на целевых серверах. В данном случае он управляет последовательностью ролей, необходимых для полного развертывания Next.js-приложения.

- name: Deploy NextJS application to Ubuntu server
  hosts: web_servers
  become: yes
  roles:
    - common
    - nodejs
    - nextjs

Роли Ansible

В Ansible роли используются для структурирования кода автоматизации. Каждая роль содержит в себе конкретную задачу, такую как установка Node.js, настройка nginx или клонирование приложения. Это помогает сделать playbook модульным, легко читаемым и повторно используемым.

Роль common

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

Обновление системы, установка зависимостей и настройка firewall:

nextjs-deploy/roles/common/tasks/main.yml
# - обновляем кэш пакетов apt

- name: Update apt cache
  apt: update_cache: yes

# - устанавливаем полезные утилиты, такие как git, curl, nginx и другие системные зависимости

- name: Install packages
  apt:
    name:
      - git
      - curl
      - build-essential
      - nginx
      - ufw
    state: present
		
# - настраиваем фаервол UFW, открывая доступ только для SSH и HTTP/HTTPS

- name: Enable UFW and allow Nginx/SSH
  ufw:
    rule: allow
    name: "{{ item }}"
  loop:
    - OpenSSH
    - "Nginx Full"

# - включаем фаервол с политикой запрета всех неразрешённых соединений по умолчанию

- name: Enable firewall
  ufw:
    state: enabled
    policy: deny

Роль nodejs

Установка Node.js и PM2:

nextjs-deploy/roles/nodejs/tasks/main.yml

# - подключаем внешний репозиторий NodeSource для установки актуальной версии Node.js;

- name: Add NodeSource repository
  shell: curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -

# - устанавливаем сам Node.js из репозитория;

- name: Install Node.js
  apt:
    name: nodejs
    state: present
		
# - устанавливаем PM2 — процесс-менеджер, необходимый для управления и перезапуска Next.js-приложения в продакшене.

- name: Install PM2
  npm:
    name: pm2
    global: yes

Роль nextjs

Скачивание, установка, сборка, запуск:

nextjs-deploy/roles/nextjs/tasks/main.yml

# - клонируем репозиторий Next.js-приложения из Git;

- name: Clone repo
  git:
    repo: "{{ nextjs_repo_url }}"
    dest: "{{ nextjs_app_path }}"
    version: "{{ nextjs_repo_branch }}"
  notify: restart nextjs application

# - устанавливаем все зависимости с помощью npm;

- name: Install dependencies
  npm:
    path: "{{ nextjs_app_path }}"
    state: present
		
# - выполняем сборку production-версии приложения;


- name: Build NextJS
  shell: cd {{ nextjs_app_path }} && npm run build

# - создаём конфигурационный файл PM2, описывающий, как запускать приложение;

- name: Create PM2 config
  template:
    src: pm2-config.json.j2
    dest: "{{ nextjs_app_path }}/pm2-config.json"

# - настраиваем nginx как обратный прокси для доступа к приложению по домену;


- name: Configure Nginx
  template:
    src: nginx-site.conf.j2
    dest: /etc/nginx/sites-available/{{ nextjs_app_name }}

# - активируем конфигурацию nginx, добавив символическую ссылку в каталог `sites-enabled`.

- name: Enable Nginx site
  file:
    src: /etc/nginx/sites-available/{{ nextjs_app_name }}
    dest: /etc/nginx/sites-enabled/{{ nextjs_app_name }}
    state: link

Переменные приложения

roles/nextjs/defaults/main.yml
nextjs_app_name: my-nextjs-app        # Название Next.js-приложения, которое будет использоваться в PM2 и nginx
nextjs_app_path: ~/www/my-nextjs-app   # Путь на сервере, где будет размещено приложение
nextjs_app_port: 3000                     # Порт, на котором будет работать приложение внутри контейнера/на сервере
nextjs_repo_url: https://github.com/youruser/repo.git   # Ссылка на репозиторий с кодом приложения
nextjs_repo_branch: main                 # Ветка Git, из которой будет клонироваться приложение
nextjs_domain_name: example.com          # Домен, по которому будет доступно приложение через Nginx

Шаблоны (templates)

Шаблоны в Ansible используются для генерации конфигурационных файлов с переменными, подставляемыми во время выполнения. Это позволяет гибко адаптировать конфигурации под разные окружения и упрощает развёртывание. В данном проекте мы используем шаблоны для настройки PM2 и Nginx.

Этот шаблон создаёт конфигурационный файл для PM2 — процесс-менеджера, который будет управлять запуском Next.js-приложения в продакшене.

pm2-config.json.j2
{
  "apps": [{
    "name": "{{ nextjs_app_name }}",
    "script": "npm",
    "args": "start",
    "cwd": "{{ nextjs_app_path }}",
    "instances": "max",
    "exec_mode": "cluster",
    "env": {
      "NODE_ENV": "production",
      "PORT": "{{ nextjs_app_port }}"
    }
  }]
}

Шаблон описывает конфигурацию виртуального хоста Nginx, который будет выполнять роль обратного прокси для Next.js-приложения.

nginx-site.conf.j2
server {
    listen 80;
    server_name {{ nextjs_domain_name }};
    location / {
        proxy_pass http://localhost:{{ nextjs_app_port }};
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Выполните следующую команду для проверки playbook и развёртывания приложения.

ansible-playbook -i inventory.ini playbook.yml --check

Совет: Всегда проверяйте свои плейбуки с флагом –check перед запуском на рабочих серверах, чтобы избежать непредвиденных изменений:

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