В этой статье мы с вами познакомимся на базовом уровне с 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 - формируется самостоятельно из domain

  • os_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 и развернули проект на проде

Благодарю вас за то, что прочитали данную статью

Комментарии (4)