В этой статье мы с вами познакомимся на базовом уровне с Ansible, и развернем с помощью него на сервере PHP проект
Знакомство с Ansible
Что это за чудо-инструмент такой?
Ansible - это инструмент каждого YAML-чемпиона, с помощью него можно развертывать приложения, настраивать конфиги и автоматизации задач через ssh
Возможно, вы слышали про него вместе с фразой - Инфраструктура как код (IaC), потому что с помощью него в большинстве своем настраивают инфраструктуру
Основные концепции:
Playbook - это yaml файл который содержит набор задач для последовательного их выполнения
---
- name: Update web servers
hosts: webservers
tasks: ...
- name: Update db servers
hosts: databases
tasks: ...
В примере указан сценарий обновление веб сервера и базы данных
В каждом сценарии мы описываем:
name- имя сценарияhosts- название хоста/хостов из inventory файлаtasks- набор задач, можно указывать напрямую инструкции или подключать отдельные файлы с самой задачей
Для детального ознакомление, перейдите по ссылке
Task - это yaml файл с неким набором команд, который отвечает за свою область (установка пакетов, настройка веб-сервера и т.д)
---
- name: Update web servers
hosts: webservers
tasks:
- name: Ensure apache is at the latest version
ansible.builtin.yum:
name: httpd
state: latest
- name: Write the apache config file
ansible.builtin.template:
src: /srv/httpd.j2
dest: /etc/httpd.conf
- name: Update db servers
hosts: databases
tasks:
- name: Ensure postgresql is at the latest version
ansible.builtin.yum:
name: postgresql
state: latest
- name: Ensure that postgresql is started
ansible.builtin.service:
name: postgresql
state: started
Здесь уже явно смотрим выполнения задач в playbook
В задаче мы можем описать:
name- название задачиansible.builtin.- модуль и его параметры
Модуль в задаче облегчает выполнения операций путем заготовленного сценария внутри модуля, а взаимодействие происходит с помощью передачи аргументов модулю
Для детального ознакомление, перейдите по ссылке
Vars - это yaml файл в котором содержится набор переменных, которые мы можем использовать в task и template файлах
---
repo_url: "https://github.com/deniskorbakov/laravel-12-frankenphp-docker.git"
path_to_remote_directory: "/var/www/laravel"
В файле мы описывает переменные, которые мы хотим использовать в наших task и template файлах
- hosts: app_servers
vars:
app_path: "{{ path_to_remote_directory }}/22"
Чтобы использовать переменные, мы открываем и закрываем птички, а в них указываем имя нашей переменой
Для детального ознакомление, перейдите по ссылке
Template - это j2 файл, который мы можем переиспользовать в task файлах, например для копирования конфигурационных файлов с заготовленными переменными
server {
listen 80;
listen [::]:80;
server_name {{ domain }};
server_tokens off;
root {{ path_to_remote_directory }}/public;
...
}
Данный файл содержит переменные, которые будут определены в ходе выполнения playbook, тем самым позволяют гибко настраивать различные файлы
Для детального ознакомление перейдите по ссылке
Inventrory - это ini файл, который содержит список хостов которыми мы можем управлять через Ansible
[web]
host1
host2 ansible_port=222 # defined inline, interpreted as an integer
[web:vars]
http_port=8080 # all members of 'web' will inherit these
myvar=23 # defined in a :vars section, interpreted as a string
С помощью этого файлы мы описываем наши хосты, которые в дальнейшем мы сможем указывать в playbook
Для детального ознакомление перейдите по ссылке
Заключение по Ansible:
С помощью данных концептов мы можем описывать сценарии задач в плейбуках, добавлять переиспользуемые переменные и шаблонные файлы, а так же иметь возможность выполнять задачи на нескольких хостах
Про проект на PHP
Данный шаблон содержит frankenphp, docker-compose окружение, веб сокеты через centrifugo, Open Api Doc и готовую авторизацию
В нем уже заранее описан playbook для развертывания на проде
Пишем Playbook
Описание того, что предстоит сделать:
На проде нам надо будет установить нужные пакеты, настроить nginx для проксирования нашего проекта, выпустить сертификаты для домена, развернуть и настроить сам проект
Настраиваем inventory:
[webservers]
144.124.249.213
[all:vars]
ansible_connection=ssh
ansible_user=champion
Заполняем доступы ssh для сервера, на котором будем разворачивать проект
Указываем переменные:
---
repo_url: "https://github.com/deniskorbakov/laravel-12-frankenphp-docker.git"
path_to_remote_directory: "/var/www/laravel"
domain: "v543323.hosted-by-vdsina.com"
url: "https://{{ domain }}"
os_environment:
- key: APP_URL
value: "{{ url }}"
- key: APP_ENV
value: "production"
- key: APP_DEBUG
value: "false"
- key: OCTANE_HTTPS
value: "true"
Заполняем следующие переменные:
repo_url- url нашего проекта в гитхабеpath_to_remote_directory- путь где будет лежать наш проект на сервереdomain- указываем который привязан к нашему серверуurl- формируется самостоятельно из domainos_environment- заполняем переменные для env которые заменим на проде
Создаем Playbook:
- name: Expand the environment
hosts: webservers
vars_files:
- ../vars/default.yml
tasks:
- name: Init Packages
ansible.builtin.include_tasks: ../tasks/packages/init.yml
- name: Setup Docker
ansible.builtin.include_tasks: ../tasks/docker/setup.yml
- name: Clone Project
ansible.builtin.include_tasks: ../tasks/sync/copy.yml
- name: Init App
ansible.builtin.include_tasks: ../tasks/app/init.yml
- name: Configure Nginx
ansible.builtin.include_tasks: ../tasks/system/nginx.yml
- name: Produce Certificates
ansible.builtin.include_tasks: ../tasks/system/cert.yml
- name: Rebuild App
ansible.builtin.include_tasks: ../tasks/app/rebuild.yml
Здесь мы указывает алиас наших хостов из inventory.ini в hosts, добавляем файл с переменными и указываем задачи, которые выполняться по очереди при запуске
Описываем Tasks:
Дальше по порядку рассмотрим каждую задачу
Init Packages
---
- name: Install required packages
ansible.builtin.apt:
name:
- apt-transport-https
- ca-certificates
- curl
- software-properties-common
- gnupg
- make
- git
- nginx
- socat
- certbot
- python3-certbot-nginx
state: present
update_cache: yes
Здесь мы устанавливаем все необходимые нам пакеты для работы
Setup Docker
---
- name: Add Docker GPG key
ansible.builtin.apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Add Docker repository
ansible.builtin.apt_repository:
repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable
state: present
filename: docker
- name: Install Docker
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
state: present
update_cache: yes
- name: Start and enable Docker service
ansible.builtin.service:
name: docker
state: started
enabled: yes
- name: Add user to docker group
ansible.builtin.user:
name: "{{ ansible_user | default('ansible') }}"
groups: docker
append: yes
- name: Install Docker Compose
ansible.builtin.get_url:
url: https://github.com/docker/compose/releases/download/v2.29.2/docker-compose-linux-x86_64
dest: /usr/local/bin/docker-compose
mode: '0755'
- name: Create symbolic link for Docker Compose
ansible.builtin.file:
src: /usr/local/bin/docker-compose
dest: /usr/bin/docker-compose
state: link
- name: Verify Docker Compose installation
ansible.builtin.command: docker-compose --version
register: docker_compose_version
changed_when: false
В данной задаче мы устанавливаем docker и docker-compose для дальнейшей работы
Clone Project
---
- name: Check if directory exists
ansible.builtin.stat:
path: "{{ path_to_remote_directory }}"
register: project_dir_stat
- name: Create project dir
ansible.builtin.file:
path: "{{ path_to_remote_directory }}"
state: directory
mode: 0755
when: not project_dir_stat.stat.exists
- name: Git clone
block:
- name: Clone repository
ansible.builtin.git:
repo: "{{ repo_url }}"
dest: "{{ path_to_remote_directory }}"
version: "{{ branch | default('main') }}"
register: clone_result
retries: 3
delay: 5
until: clone_result is succeeded
when: not project_dir_stat.stat.exists
Здесь мы создаем директорию для проекта, если она еще не создана, и клонируем наш проект, который мы указывали в файле переменных
Init App
---
- name: Create Storage Public Dir
ansible.builtin.file:
path: "{{ path_to_remote_directory }}/storage/app/public"
state: directory
mode: 0755
- name: Copy env.example
ansible.builtin.copy:
src: "{{ path_to_remote_directory }}/.env.example"
dest: "{{ path_to_remote_directory }}/.env"
remote_src: yes
- name: Set vars in ENV
lineinfile:
path: "{{ path_to_remote_directory }}/.env"
state: present
regexp: "^{{ item.key }}="
line: "{{ item.key }}={{ item.value}}"
with_items: "{{ os_environment }}"
become: yes
- name: Init Project
ansible.builtin.command:
cmd: make init-prod
chdir: "{{ path_to_remote_directory }}"
register: command_result
failed_when: "'FAILED' in command_result.stderr"
Здесь мы создаем public директорию, копируем env.example в env и заменяем определенные переменные, которые у нас явно указаны в файле с переменными для ansible и запускаем make init-prod для инициализации проекта на проде
Configure Nginx
---
- name: Delete default dir
ansible.builtin.file:
state: absent
path: /var/www/html
- name: Copy config
ansible.builtin.template:
src: ../templates/nginx_conf.j2
dest: "/etc/nginx/sites-enabled/{{ domain }}"
- name: Reload Nginx
ansible.builtin.systemd:
state: reloaded
name: nginx
В данной задаче удаляем дефолтную директорию, копируем config для nginx и перезапускаем процесс nginx
Produce Certificates
---
- name: Obtain SSL certificate with certbot
ansible.builtin.command: |
certbot \
--force-renewal \
--nginx \
--noninteractive \
--agree-tos \
--cert-name {{ domain }} \
-d {{ domain }} \
-m test@gmail.com \
--verbose
args:
creates: "/etc/letsencrypt/live/{{ domain }}/cert.pem"
become: yes
register: certbot_result
Здесь уже мы выпускаем сертификаты для нашего домена через certbot
Rebuild App
---
- name: Pause for 2 min
ansible.builtin.pause:
minutes: 2
- name: Restart app
ansible.builtin.command:
cmd: make restart
chdir: "{{ path_to_remote_directory }}"
become: yes
register: restart_result
- name: Pause for 2 min
ansible.builtin.pause:
minutes: 2
- name: Update project
ansible.builtin.command:
cmd: make update-project
chdir: "{{ path_to_remote_directory }}"
become: yes
register: update_result
В данной задаче мы перезапускаем наши контейнеры и обновляем данные проекты, чтобы все заработало наверняка !
Результат проделанной работы:
Теперь мы с вами написали ваш первый playbook и познакомились с чудо-инструментом ansible для YAML чемпионов
Жду комментарии под постом о том, что можно улучшить или как бы вы писали данный playbook ;)
Итог
Сегодня с вами узнали немного об Ansible, Изучили его базовые концепции, написали с вами Playbook и развернули проект на проде
Благодарю вас за то, что прочитали данную статью