Привет, друзья!
В этой серии статей я делюсь с вами своим опытом решения различных задач из области веб-разработки и не только.
В этой статье мы научимся разворачивать Angular+Java веб-приложение на виртуальном сервере Ubuntu Linux с помощью Ansible.
Интересно? Тогда прошу под кат.
Предположим, что у нас есть веб-приложение интернет-магазина, состоящее из 2 частей:
- фронтенд на JavaScript (Angular, но это неважно), статика которого раздается с помощью Node.js (http-server) (не совсем стандартный подход, обычно это делается с помощью Nginx)
- бэкенд на Java (Spring), взаимодействующий с облачной базой данных PostgreSQL
Репозиторий проекта хранится в GitLab. В проекте настроен конвейер (GitLab CI/CD), который после сборки отправляет артефакты фронта (архив .tar.gz) и бэка (файл .jar) в соответствующие репозитории Nexus.
Наша задача — развернуть это приложение на виртуальном сервере Ubuntu Linux. Разумеется, это можно сделать вручную, но давайте немного автоматизируем данный процесс с помощью Ansible.
Предварительные условия:
- на вашей машине должен быть установлен
ansible(инструкция для Ubuntu) - на виртуальном сервере должен быть создан пользователь
ansibleс необходимыми правами доступа
❯ Пара слов об Ansible
Ansible — это инструмент с открытым исходным кодом для автоматизации ИТ-процессов, таких как:
- настройка серверов
- развертывание приложений
- управление конфигурациями
- оркестрация (координация работы нескольких машин)
На самом высоком уровне Ansible работает следующим образом:
- использует SSH для подключения к серверам (агенты не требуются — это безагентская система)
- настройки описываются в YAML-файлах (называются playbook’и).
- позволяет описывать желаемое состояние системы, а не пошаговые команды (декларативный подход)
Основные компоненты Ansible:
| Компонент | Назначение |
|---|---|
| Inventory | Список управляемых хостов |
| Playbook | YAML-файл с задачами |
| Task | Отдельное действие (например, установить пакет) |
| Role | Структурированная группа задач и файлов |
| Module | Встроенные функции (например, apt, yum, copy, service и др.) |
Пример простого playbook’а:
# хосты - серверы - hosts: webservers # суперпользователь - sudo become: yes # задачи tasks: - name: Установить nginx # модуль apt: name: nginx state: present
Преимущества Ansible:
- простота — не требует установки агентов
- использует простой YAML-синтаксис
- хорошо масштабируется
- подходит для небольших и средних задач
Дополнительные материалы:
- Ansible для начинающих
- Ansible для новичков: что это, как устроен и зачем нужен
- Ansible для начинающих: инструкции и команды
Хороший бесплатный курс на Stepik:
❯ Конфигурация Ansible
Создаем директорию devops-ansible со следующей структурой:
. ├── ansible.cfg ├── inventory.yaml ├── playbook.yml ├── README.md └── roles ├── backend │ ├── defaults │ │ └── main.yml │ ├── tasks │ │ ├── download.yml │ │ ├── install.yml │ │ ├── main.yml │ │ ├── service.yml │ │ └── setup.yml │ └── templates │ └── backend.service.j2 └── frontend ├── defaults │ └── main.yml ├── tasks │ ├── download.yml │ ├── install.yml │ ├── main.yml │ ├── nodesource.yml │ ├── service.yml │ └── setup.yml └── templates └── frontend.service.j2
Определяем основные настройки Ansible в файле ansible.cfg:
[defaults] roles_path = ./roles [ssh_connection] timeout = 30
Определяем список управляемых хостов в файле inventory.yaml:
all: hosts: vm1: ansible_host: <ip вашего виртуального сервера> ansible_user: ansible
vm1 — это имя хоста (синоним/алиас), который мы задаем для удобства. Другими словами, vm1 — это логическое имя, под которым Ansible будет знать этот сервер. Оно не обязано совпадать с реальным именем машины или DNS-именем.
Через ansible_host мы указываем реальный IP-адрес (или доменное имя), куда Ansible должен подключаться.
Через ansible_user мы указываем, под каким пользователем подключаться по SSH.
Аналогия с телефонным справочником:
vm1— имя контакта (чтобы было удобно обращаться)ansible_host— номер телефона (реальный IP)ansible_user— кто звонит (под каким пользователем логиниться)
Определяем группы задач в файле playbook.yaml:
--- - name: Деплой backend и frontend hosts: all remote_user: ansible roles: - backend - frontend
В данном случае remote_user можно опустить, поскольку мы указали ansible_user в inventory.yaml. remote_user требуется в следующих случаях:
- если мы хотим переопределить пользователя, указанного в inventory
- если в inventory нет
ansible_user, и мы хотим задать пользователя на уровне playbook’а
❯ Конфигурация роли/группы задач frontend
Работаем с директорией ansible/roles/frontend.
Определяем переменные в файле defaults/main.yml:
# Данные для доступа к репозиторию Nexus, в котором хранится архив фронта `.tar.gz`. # В корне проекта необходимо создать файл `.env` с этими данными nexus_service_url: "{{ lookup('env', 'NEXUS_SERVICE_URL') }}" nexus_repo_frontend_name: "{{ lookup('env', 'NEXUS_REPO_FRONTEND_NAME') }}" nexus_repo_user: "{{ lookup('env', 'NEXUS_REPO_USER') }}" nexus_repo_pass: "{{ lookup('env', 'NEXUS_REPO_PASS') }}" # Пользователь для развертывания фронта frontend_user: "www-data" # Директория для распаковки архива frontend_dest: "/var/www-data" # Версия Node.js (20+) node_version: "20.x" # Порт сервера для раздачи статики frontend_port: 80 # Адрес бэка backend_url: "http://localhost:8080"
Определяем группы задач в файле tasks/main.yml:
--- # Задачи выполняются в порядке определения - include_tasks: nodesource.yml - include_tasks: install.yml - include_tasks: setup.yml - include_tasks: download.yml - include_tasks: service.yml
Задача добавления NodeSource (nodesource.yml):
--- - name: Добавить GPG-ключ NodeSource become: true # Чтобы apt доверял этому репозиторию apt_key: url: 'https://deb.nodesource.com/gpgkey/nodesource.gpg.key' state: present - name: Добавить NodeSource репозиторий become: true # Это позволяет установить Node.js через apt как обычный пакет, # но из NodeSource, а не из стандартного репозитория apt_repository: repo: 'deb https://deb.nodesource.com/node_{{ node_version }} {{ ansible_distribution_release }} main' state: present filename: 'nodesource'
Зачем нужен NodeSource перед установкой Node.js? NodeSource — это сторонний репозиторий, который предоставляет актуальные версии Node.js, которых нет в стандартных репозиториях большинства дистрибутивов Linux (особенно Debian/Ubuntu).
Задачи установки Node.js (в комплекте с npm) и http-server (install.yml):
--- - name: Установить Node.js и npm become: true apt: name: - nodejs state: present update_cache: yes - name: Установить http-server become: true community.general.npm: name: http-server # Глобальная установка global: yes
Что делает update_cache: yes? Это параметр apt, который обновляет локальный кэш списка пакетов (apt update) перед установкой. Это важно, поскольку:
- если мы только что добавили новый репозиторий (например, NodeSource), apt еще не знает о доступных там пакетах, пока не обновит кэш
- без
update_cache: yesкоманда может не найти нужный пакет, даже если он уже есть в источнике
Задачи создания сервисного пользователя и директории для распаковки архива фронта (setup.yml):
--- - name: Создать сервисного пользователя www-data become: true user: name: '{{ frontend_user }}' # Не создавать домашнюю/пользовательскую директорию create_home: no # Системный пользователь system: yes shell: /usr/sbin/nologin - name: Создать директорию {{ frontend_dest }} become: true file: path: '{{ frontend_dest }}' state: directory owner: '{{ frontend_user }}' group: '{{ frontend_user }}' mode: '0755'
shell: /usr/sbin/nologin — это способ запретить пользователю вход в систему через терминал (SSH, консоль и т.д.). Это важно, поскольку:
- пользователь
www-dataсоздается только для запуска процессов или владения файлами, а не для работы от его имени - это повышает безопасность, потому что никто не сможет использовать этого пользователя для интерактивной сессии
Задачи скачивания и распаковки архива фронта (download.yml):
--- - name: Получить последнюю версию фронтенда из Nexus uri: # Адрес Nexus-сервера # `sort=version` — сортировка по версии, чтобы последняя версия была первой в списке url: '{{ nexus_service_url }}/rest/v1/search/assets?repository={{ nexus_repo_frontend_name }}&sort=version' method: GET url_username: '{{ nexus_repo_user }}' url_password: '{{ nexus_repo_pass }}' # Принудительно использовать basic auth force_basic_auth: yes # Вернуть содержимое ответа (`.json`), чтобы мы могли с ним работать return_content: yes # Сохраняем результат запроса в переменную `nexus_response` register: nexus_response - name: Извлечь последнюю версию фронтенда из Nexus set_fact: download_url: "{{ nexus_response.json['items'][0].downloadUrl }}" - name: Скачать последнюю версию фронтенда из Nexus get_url: url: '{{ download_url }}' # Куда скачать? dest: '/tmp/frontend.tar.gz' url_username: '{{ nexus_repo_user }}' url_password: '{{ nexus_repo_pass }}' force_basic_auth: yes - name: Распаковать фронтенд become: true unarchive: src: '/tmp/frontend.tar.gz' # Куда распаковать? dest: '{{ frontend_dest }}' # Файл уже на удаленном сервере, не нужно его копировать с локальной машины remote_src: yes
Мы используем set_fact, чтобы создать переменную download_url, которая содержит прямую ссылку на скачивание самого свежего артефакта:
items[0]— первый (а значит, самый новый) элемент в массиве артефактов (мы выполнили сортировку по версии в первой задаче)downloadUrl— ключ в JSON, содержащий ссылку на файл
Задачи создания и запуска сервиса фронта (service.yml):
--- - name: Скопировать systemd unit-файл become: true template: src: frontend.service.j2 dest: /etc/systemd/system/frontend.service mode: '0644' - name: Перезапустить systemd become: true systemd: daemon_reload: yes - name: Включить и запустить сервис фронтенда become: true systemd: name: frontend enabled: yes state: started
Шаблон сервиса фронта выглядит так (templates/frontend.service.j2):
[Unit] Description=Frontend Service After=network.target [Service] User={{ frontend_user }} Group={{ frontend_user }} WorkingDirectory={{ frontend_dest }}/dist/frontend ExecStart=/usr/bin/http-server -p {{ frontend_port }} --proxy {{ backend_url }} Restart=always AmbientCapabilities=CAP_NET_BIND_SERVICE [Install] WantedBy=multiuser.target
❯ Конфигурация роли/группы задач backend
Работаем с директорией ansible/roles/backend.
Определяем переменные в файле defaults/main.yml:
# Данные для доступа к репозиторию Nexus, в котором хранится файл бэка `.jar`. # В корне проекта необходимо создать файл `.env` с этими данными nexus_service_url: "{{ lookup('env', 'NEXUS_SERVICE_URL') }}" nexus_repo_backend_name: "{{ lookup('env', 'NEXUS_REPO_BACKEND_NAME') }}" nexus_repo_user: "{{ lookup('env', 'NEXUS_REPO_USER') }}" nexus_repo_pass: "{{ lookup('env', 'NEXUS_REPO_PASS') }}" # Пользователь для развертывания бэка backend_user: 'backend' # Путь к исполняемому файлу. # app-name - название нашего приложения jar_path: '/opt/app-name/bin/backend.jar'
Определяем группы задач в файле tasks/main.yml:
--- # Задачи выполняются в порядке определения - import_tasks: install.yml - import_tasks: setup.yml - import_tasks: download.yml - import_tasks: service.yml
Задача установки Java нужной версии (install.yml)
--- - name: Установить OpenJDK 16 become: true apt: # В принципе, версию Java тоже можно вынести в переменную name: openjdk-16-jdk state: present update_cache: yes
Задачи создания сервисного пользователя и директории для исполняемого файла бэка (setup.yml):
--- - name: Создать сервисного пользователя backend become: true user: name: '{{ backend_user }}' create_home: no system: yes shell: /usr/sbin/nologin - name: Убедиться, что директория /opt/app-name/bin существует become: true file: path: /opt/app-name/bin state: directory owner: '{{ backend_user }}' group: '{{ backend_user }}' mode: '0755' - name: Убедиться, что директория /var/app-name существует become: true file: path: /var/app-name state: directory owner: '{{ backend_user }}' group: '{{ backend_user }}' mode: '0755'
/opt/app-name/bin — это директория для исполняемого файла бэка, а зачем нам директория /var/app-name? /var/ — это стандартная системная директория для:
- данных, которые меняются во время работы приложения: временные файлы, логи, кэш, БД (например,
sqlite) и т.д. - данных, которые нельзя хранить в
/opt, потому что они могут изменяться и должны быть доступны определенным сервисам, бэкапам, ротации логов и т.п.
Мы будем хранить в этой директории логи бэка.
Задача скачивания исполняемого файла бэка (download.yml):
--- - name: Получить список артефактов бэкенда из Nexus uri: url: '{{ nexus_service_url }}/rest/v1/search/assets?repository={{ nexus_repo_backend_name }}&maven.extension=jar&sort=version' method: GET url_username: '{{ nexus_repo_user }}' url_password: '{{ nexus_repo_pass }}' force_basic_auth: yes return_content: yes register: nexus_response - name: Извлечь последнюю версию бэкенда из Nexus set_fact: download_url: "{{ nexus_response.json['items'][0].downloadUrl }}" - name: Скачать последнюю версию бэкенда из Nexus become: true get_url: url: '{{ download_url }}' dest: '{{ jar_path }}' url_username: '{{ nexus_repo_user }}' url_password: '{{ nexus_repo_pass }}' force_basic_auth: yes
Задачи создания и запуска сервиса бэка (service.yml):
--- - name: Скопировать systemd unit-файл become: true template: src: backend.service.j2 dest: /etc/systemd/system/backend.service mode: '0644' - name: Перезагрузить systemd become: true systemd: daemon_reload: yes - name: Включить и запустить сервис бэкенда become: true systemd: name: backend state: started enabled: yes
Шаблон сервиса бэка выглядит так (templates/backend.service.j2):
[Unit] Description=Backend Service After=network.target [Service] User={{ backend_user }} Group={{ backend_user }} StandardOutput=append:/var/app-name/backend.log WorkingDirectory=/opt/app-name/bin ExecStart=/usr/bin/java -jar backend.jar Restart=always [Install] WantedBy=multi-user.target
❯ Итого
Команда для запуска Ansible:
# Выполняется в корневой директории (`ansible`). # Не забудьте создать файл `.env` с данными для доступа к репозиториям Nexus source .env && ansible-playbook playbook.yml -i inventory.yaml
Мы рассмотрели далеко не все возможности, предоставляемые Ansible, но думаю вы получили неплохое представление о том, что и как позволяет делать этот замечательный инструмент. Наряду с другими популярными решениями для автоматизации ИТ-процессов (Terraform, Docker, Kubernetes и т.д.), Ansible на сегодняшний день является важной частью арсенала DevOps-инженера.
Happy devopsing!
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
ссылка на оригинал статьи https://habr.com/ru/articles/913426/

Добавить комментарий