8.2.4 Автоматизація розгортання сайту з Ansible на VPS

Автоматизація процесу розгортання дозволяє прискорити та спростити оновлення додатків, особливо коли йдеться про продакшн-сервер. У цьому посібнику ми детально розглянемо, як за допомогою Ansible розгорнути Next.js-додаток на Ubuntu-сервері, використовуючи ролі, шаблони та змінні.

Ansible — інструмент управління конфігурацією та автоматизації, який не потребує встановлення агентів на цільових серверах. Він ідеально підходить для розгортання Node.js-додатків, таких як Next.js, у Linux-середовищі.

Вимоги

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

Перед підключенням Ansible до сервера по SSH необхідно створити пару ключів, якщо вона ще не створена. Це забезпечить безпечне та автоматизоване підключення до віддаленого сервера.

Виконайте команду, замінивши your_email@example.com на Вашу електронну пошту:

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

Ця роль відповідає за базове налаштування сервера: оновлення пакетів, встановлення необхідних утиліт та налаштування мережевої безпеки. Вона створює надійну основу для наступних кроків автоматизації.

Оновлення системи, встановлення залежностей та налаштування фаєрвола:

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 перед запуском на робочих серверах, щоб уникнути непередбачених змін:

Для отримання додаткової інформації зверніться до офіційних джерел: