Больше работайте с Molecule, чтобы убедиться, что ваша инфраструктура работает. Используйте компоновку, идемпотентность, несколько контейнеров и внутренние зависимости, чтобы при развертывании веб-сайта ваши роли Ansible вели себя должным образом.

В первой части статьи мы рассмотрели основы настройки Molecule и провели нескольких простых тестов. В этой статье мы углубимся в конфигурацию Molecule и различные проверки, которые она может выполнять.

Мы рассмотрим запуск двух разных контейнеров, проверку идемпотентности и проверку синтаксиса с помощью linting. Вы можете найти код Ansible, с которого мы начинаем, на Github. Не стесняйтесь смотреть дальше на завершенный код здесь — https://github.com/PCritchfield/ansible/tree/master/ansible_molecule_pt2/laravel_website_corrected/laravel_role.

Роль

Роль, которую мы будем использовать, является практическим примером, который может отражать реальный сценарий. Однако он не должен рассматриваться как готовый к применению. С учетом сказанного, давайте подробнее рассмотрим, что он делает.

Функция этой роли заключается в развертывании веб-сайта Laravel. Он участвует в установке Nginx, PHP 8.1 (и пакетов), а также Composer; на основе роли будет создан сайт Laravel. Наконец, мы будем использовать Molecule для развертывания контейнеров, проверки успешного выполнения задач миграции базы данных, подтверждения идемпотентности и гарантии соответствия нашего кода стандартам компоновки.

.
├── README.md
├── defaults
│   └── main.yml
├── files
│   ├── database.php
│   └── laravel.conf
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── molecule
│   ├── collections.yml
│   ├── default
│   │   ├── converge.yml
│   │   ├── molecule.yml
│   │   └── tests
│   │       ├── conftest.py
│   │       └── test_default.py
│   └── requirements.yml
├── tasks
│   ├── deploy_site.yml
│   ├── main.yml
│   ├── nginx_install.yml
│   └── php_install.yml
├── templates
│   └── env.j2
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

Задача

Можно начать с просмотра разнообразных файлов с задачами:

---
- name: Include Nginx install
  include_tasks: nginx_install.yml

- name: Install php
  include_tasks: php_install.yml

- name: Deploy website
  include_tasks: deploy_site.yml

Отсюда мы будем использовать определенные файлы для установки ключевых веб-компонентов (Nginx и PHP) и развертывания веб-сайта Laravel.

Задачи по установке Nginx просты: обновите apt cache, установите пакеты, запустите службу и установите конфигурацию. Здесь важно отметить строку 5 cache_valid_time: 3600. Этот параметр говорит Ansible не запускать apt update, если кэш обновлен менее 3600 секунд назад, что необходимо для тестирования идемпотентности. Этот файл задачи также удаляет файл конфигурации Nginx для веб-сайта.

---
- name: Only run "update_cache=yes" if the last one is more than 3600 seconds ago
  ansible.builtin.apt:
    update_cache: yes
    cache_valid_time: 3600

- name: install nginx
  apt:
    name: "{{ item }}"
    state: present
  with_items:
    - nginx=1.18.*
    - git

- name: Make sure a service unit is running
  sysvinit:
    state: started
    name: nginx
    enabled: true

- name: copy over config
  copy:
    src: laravel.conf
    dest: /etc/nginx/sites-available/default
    mode: u+rw,g-r,o-r

tasks/nginx_install.yml

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        index index.php;
        error_log  /var/log/nginx/error.log;
        access_log /var/log/nginx/access.log;
        root /var/www/laravel/public;
        location ~ \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/run/php/php8.1-fpm.sock;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
        }
        location / {
           try_files $uri $uri/ /index.php?$query_string;
        }

}

file/laravel.conf

Задачи в php_install.yml установят PHP 8.1 и различные пакеты, необходимые для веб-сайта. После установки PHP мы также настроим Composer. Список пакетов и composer_path находятся в списке в defaults/main.yml.

- name: Only run "update_cache=yes" if the last one is more than 3600 seconds ago
  ansible.builtin.apt:
    update_cache: yes
    cache_valid_time: 3600

- name: install php packages
  apt:
    name: "{{ php_pkgs }}"
    state: present

- name: Checks if Composer is already installed
  command: "{{ composer_path }}"
  ignore_errors: true
  register: composer_installed

- name: Install Composer if its not already installed
  block:

    - name: Download Composer Installation Script (if not already installed)
      get_url:
        url: https://getcomposer.org/installer
        dest: /tmp/composer-setup.php

    - name: Run Composer Installer (if not already installed)
      command:
        cmd: /usr/bin/php /tmp/composer-setup.php
        creates: composer.phar

    - name: Copy Composer Executable to /usr/local/bin
      become: true
      copy:
        src: composer.phar
        dest: /usr/local/bin/composer
        remote_src: yes
        mode: '0755'

    - name: Remove phar
      file:
        path: composer.phar
        state: absent

    - name: Remove Install Script
      file:
        path: /tmp/composer-setup.php
        state: absent

  when: composer_installed is failed

tasks/php_install.yml

---
# defaults file for laravel_role
php_pkgs:
    - php8.1
    - php8.1-mbstring
    - php8.1-gettext
    - php8.1-zip
    - php8.1-fpm
    - php8.1-curl
    - php8.1-mysql
    - php8.1-gd
    - php8.1-cgi
    - php8.1-soap
    - php8.1-sqlite3
    - php8.1-xml
    - php8.1-redis
    - php8.1-bcmath
    - php8.1-imagick
    - php8.1-intl

composer_path: /usr/local/bin/composer

defaults/main.yml

Последний набор задач развертывает код для сайта Laravel. Я использую проект, созданный Джеффри Уэй, Laravel From Scratch Blog Project, который создаст локальный сайт блога при развертывании с использованием Molecule.

Вышеуказанные задачи позволяют клонировать проект на наш хост, который создается как .env, так и databse.php файлы. Мы установим переменные окружения, необходимые для файла .env, в molecule.yml. Далее мы обновляем зависимости Composer и запускаем установку. Наконец, мы используем Artisan для запуска миграций, заполнения базы данных и генерации APP_KEY для защиты нашего сайта.

---
- name: create /var/www/ directory
  file: 
    dest: /var/www/
    state: directory
    owner: www-data
    group: www-data
    mode: 0700

- block:
  - name: Clone git repository
    git:
      dest: /var/www/laravel
      repo: https://github.com/JeffreyWay/Laravel-From-Scratch-Blog-Project.git
      update: no
    register: repo 

  - name: set .env file
    template:
      src: env.j2
      dest: /var/www/laravel/.env

  - name: set database.php conf file
    copy:
      src: database.php
      dest: /var/www/laravel/config/database.php
      mode: u+rw,g-rw,o-r

  - name: Composer update
    command:
      cmd: composer update
      chdir: /var/www/laravel

  - name: composer install
    command:
      cmd: composer install
      chdir: /var/www/laravel

  - name: php artisan steps
    command:
      cmd: php artisan {{ item }}
      chdir: /var/www/laravel
    with_items:
      - migrate --seed --force
      - storage:link
      - config:clear
      - key:generate --force
  become: true
  become_user: www-data
  notify:
      - restart php8.1-fpm
      - restart nginx

tasks/deploy_site.php

DB_CONNECTION={{ lookup('env','DB_CONNECTION') }}
DB_HOST={{ lookup('env','DB_HOST') }}
DB_PORT={{ lookup('env','DB_PORT') }}
DB_DATABASE={{ lookup('env','DB_DATABASE') }}
DB_USERNAME={{ lookup('env','DB_USERNAME') }}
DB_PASSWORD={{ lookup('env','DB_PASSWORD') }}
APP_ENV={{ lookup('env','APP_ENV') }}
APP_DEBUG={{ lookup('env','APP_DEBUG') }}
APP_KEY=

templates/.env.j2

<?php

use Illuminate\Support\Str;

return [

    'default' => env('DB_CONNECTION', 'mysql'),

    'connections' => [

        'mysql' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => 'InnoDB ROW_FORMAT=DYNAMIC',
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

    ],

    'migrations' => 'migrations',

];

files/database.php

Вот с чего мы начнем. Имейте в виду, что мы не тестируем сам веб-сайт с помощью Molecule, мы просто проверяем возможность развертывания сайта. И по мере того, как мы исследуем идемпотентность и linting, мы будем соответствующим образом обновлять эти файлы. Но, прежде чем мы перейдем к компоновке, давайте посмотрим, как мы запускаем миграции или начальные компоненты без установки базы данных. Чтобы сделать это, нам нужно посмотреть на конфигурацию нашей Molecule и на то, как использовать несколько контейнеров.

Molecule

Платформы: Несколько контейнеров

Одним из преимуществ Molecule и Docker – они предоставляют средства для развертывания нескольких контейнеров. а это значит, что если мы хотим протестировать базу данных или роль обмена сообщениями, мы можем это сделать. В нашем случае это позволяет нам развернуть контейнер MySQL, выполнить миграции и ввести исходные данные для нашей роли Laravel.

Теперь давайте посмотрим на файл molecule.yml. Подобно тому, что мы делали в части 1, в блоке platforms: - это то место, где мы хотим настроить наши контейнеры. Этот раздел позволяет нам настраивать контейнеры аналогично docker-compose. Здесь мы определяем сети, тома, изображения и открытые порты. Для этого набора тестов мы используем контейнеры, созданные Джеффом Герлингом. Эти контейнеры специально разработаны для обеспечения тестирования сервисов, развернутых Ansible в контейнерах.

platforms:
  - name: site
    image: "geerlingguy/docker-ubuntu2204-ansible:latest"
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    privileged: true
    pre_build_image: true
    published_ports:
      - 0.0.0.0:8080:80/tcp
    networks:
      - name: "laravel"
  - name: mysql
    image: "geerlingguy/docker-ubuntu2204-ansible:latest"
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    privileged: true
    pre_build_image: true
    published_ports:
      - 0.0.0.0:3306:3306/tcp
    networks:
      - name: "laravel"

Краткое объяснение некоторых опций, которые я использую здесь:

name: значение используется в качестве имени хоста контейнера как для контейнерной сети, так и для инвентаризации Ansible

Volumes: подключение тома требуется для правильного запуска службы systemd

privlaged: опция сообщает контейнеру, должен ли он запускаться от имени root

pre_build_image: опция уведомляет Molecule о необходимости извлечения контейнера из реестра вместо его сборки

published_ports: как и -p для Docker, указывает Molecule на необходимость сопоставить список портов между хостом и контейнером. Это означает, что как только у нас будет успешный converge, мы сможем перейти на localhost: 8080 и увидеть, что наш сайт работает.

networks: использует команду docker network для создания отдельной сети для запуска контейнеров. В этом случае нужная нам сеть называется “laravel”.

Важно отметить, что можно использовать любое расположение изображений в этом блоке. Например, мы могли бы использовать несколько операционных систем для тестирования роли параллельно или идентичные контейнеры для тестирования кластера. Единственное, что следует иметь в виду – это название, указанное в platforms: как именно Molecule использует их в блоке для создания вашего инвентаря.

Provisioner: Переменные среды

Существует множество методов Ansible и Molecule для обработки переменных среды. Я решил предоставить их в блоке Provisioner по двум причинам. Во-первых, это обеспечивает четкость чтения molecule.yml, так как все находится в одном месте. Во-вторых, поскольку мы развертываем больше, чем просто нашу роль Laravel с Molecule, мне нужно было одно место для значений, доступных как сайту, так и базе данных.

provisioner:
  name: ansible
  env:
    DB_CONNECTION: mysql
    DB_HOST: mysql
    DB_PORT: 3306
    DB_DATABASE: blog
    DB_USERNAME: molecule
    DB_PASSWORD: moleculepass
    APP_ENV: local
    APP_DEBUG: true

Converge:деплой двух ролей

На этом этапе мы можем развернуть два разных контейнера в общей сети и предоставить Ansible некоторые переменные среды. Итак, как же нам подготовить эти отдельные контейнеры? Для этого необходимо несколько ключевых элементов. Мы можем начать с добавления зависимостей с помощью Ansible Galaxy и файла requirements.yml. Затем мы можем добавить блок dependency: в наш файл Molecule.

dependency:
  name: galaxy
  options:
    ignore-certs: True
    ignore-errors: True
    role-file: molecule/requirements.yml

dependency block for molecule.yml

---
roles:
  - name: geerlingguy.mysql-fork
    src: https://github.com/PCritchfield/ansible-role-mysql.git

molecule/requirements.yml

Эти фрагменты сообщают Molecule, что нам нужно установить мою развилку роли geerlingguy.mysql с GitHub, прежде чем мы обработаем наш converge. Теперь, когда у нас есть роль, которая установит MySQL в наш контейнер, нам нужно запустить ее. Мы должны взглянуть на наш файл converge.yml. В предыдущей статье говорилось, что converge.yml – это учебное пособие, которое Molecule будет использовать для создания наших контейнеров. С этой целью нашему converge необходимо будет настроить таргетинг на несколько хостов. Ранее я упоминал, что название, которое мы даем каждому контейнеру в блоке platforms:  – это то, что Molecule использует для инвентаризации, позволяя нам сделать что-то похожее на следующее:

---
- name: Converge - DB
  hosts: mysql
  vars:
    mysql_databases:
      - name: "{{ lookup('env','DB_DATABASE') }}"
    mysql_users:
      - name: "{{ lookup('env','DB_USERNAME') }}"
        password: "{{ lookup('env','DB_PASSWORD') }}"
        host: site.laravel
        priv: "*.*:ALL"
  tasks:
    - name: "Setup MySQL DB"
      include_role:
        name: "geerlingguy.mysql-fork"

- name: Converge - Site
  hosts: site
  tasks:
    - name: "Include laravel_role"
      include_role:
        name: "laravel_role"

В этот момент molecule.yml должен выглядеть примерно так:

---
dependency:
  name: galaxy
  options:
    ignore-certs: True
    ignore-errors: True
    role-file: molecule/requirements.yml
driver:
  name: docker
platforms:
  - name: site
    image: "geerlingguy/docker-ubuntu2204-ansible:latest"
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    privileged: true
    pre_build_image: true
    published_ports:
      - 0.0.0.0:8080:80/tcp
    networks:
      - name: "laravel"
  - name: mysql
    image: "geerlingguy/docker-centos7-ansible:latest"
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    privileged: true
    pre_build_image: true
    published_ports:
      - 0.0.0.0:3306:3306/tcp
    networks:
      - name: "laravel"
provisioner:
  name: ansible
  env:
    DB_CONNECTION: mysql
    DB_HOST: mysql
    DB_PORT: 3306
    DB_DATABASE: blog
    DB_USERNAME: molecule
    DB_PASSWORD: moleculepass
    APP_ENV: local
    APP_DEBUG: true
verifier:
  name: testinfra

Наконец, вся подготовительная работа завершена, и мы должны выполнить команду: molecule converge . Если все работает правильно, Molecule должен извлечь роль MySQL из Galaxy, запустить два контейнера и запустить плейбуки на их целевых хостах. Мы публикуем сообщения в контейнерах, поэтому мы можем перейти к http://localhost:8080 и взаимодействовать с функциональным блогом.

Идемпотентность?

И да, и нет.

Да, так как у нас есть уверенность в том, что задачи, которые зависят от разных хостов, могут делать то, что нам нужно.

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

Давайте начнем с определения идемпотентности. Согласно Википедии, “Идемпотентность – это свойство определенных операций в математике и информатике, посредством которого они могут быть применены несколько раз без изменения результата за пределами первоначального применения”. Это означает, что независимо от того, сколько раз мы запускаем плейбуки Ansible, изменения должны применяться только при первом запуске задачи.

Вот почему идемпотентность жизненно важна для ваших ролей Ansible. Зачем устанавливать что-то дважды? Не рискуйте обновлять текущую версию Java или Nginx, когда вы можете использовать Molecule для определения возможных изменений, если вы дважды запустите свою роль.

Когда мы запустим команду idempotence, Molecule повторно запустит converge.yml и убедится, что ни одна из задач не создает изменений. Давайте посмотрим, как будет выглядеть этот результат. Запустите команду molecule idempotence, и вы должны получить следующее:

PLAY RECAP *********************************************************************
mysql                      : ok=32   changed=1    unreachable=0    failed=0    skipped=23   rescued=0    ignored=0
site                       : ok=20   changed=7    unreachable=0    failed=0    skipped=5    rescued=0    ignored=0
CRITICAL Idempotence test failed because of the following tasks:
*  => geerlingguy.mysql : Ensure MySQL users are present.
*  => geerlingguy.mysql : Ensure MySQL users are present.
*  => laravel_role : Checks if Composer is already installed
*  => laravel_role : set .env file
*  => laravel_role : Composer update
*  => laravel_role : composer install
*  => laravel_role : php artisan steps
*  => laravel_role : php artisan steps
*  => laravel_role : php artisan steps
*  => laravel_role : php artisan steps
*  => laravel_role : php artisan steps
*  => laravel_role : php artisan steps

Мы видим, что несколько задач вносят изменения в запущенные экземпляры. Мы также можем видеть, что роль geerlingguy.mysql приводит к сбою нашей проверки на идемпотентность, но поскольку мы не тестируем эту роль, нам нужно убедиться, что наша проверка на идемпотентность игнорирует эту часть converge. Чтобы сделать это, мы можем добавить строку tags: molecule-idempotence-notest.

---
- name: Converge - DB
  hosts: mysql
  vars:
    mysql_databases:
      - name: "{{ lookup('env','DB_DATABASE') }}"
    mysql_users:
      - name: "{{ lookup('env','DB_USERNAME') }}"
        password: "{{ lookup('env','DB_PASSWORD') }}"
        host: site.laravel
        priv: "*.*:ALL"
  tasks:
    - name: "Setup MySQL DB"
      include_role:
        name: "geerlingguy.mysql"
  tags: molecule-idempotence-notest

- name: Converge - Site
  hosts: site
  tasks:
    - name: "Include laravel_role"
      include_role:
        name: "laravel_role"

Как и большинство тегов Ansible, мы можем использовать это в любой области. Здесь мы используем его, чтобы пропустить всю роль, но мы также могли бы использовать его для пропуска задач. С этим обновлением давайте повторим проверку идемпотентности. Molecule теперь проигнорирует первый раздел converge, который нацелен на контейнер базы данных, и выдаст нам результат, который выглядит следующим образом:

PLAY RECAP *********************************************************************
site                       : ok=20   changed=7    unreachable=0    failed=0    skipped=5    rescued=0    ignored=0
CRITICAL Idempotence test failed because of the following tasks:
*  => laravel_role : Checks if Composer is already installed
*  => laravel_role : set .env file
*  => laravel_role : Composer update
*  => laravel_role : composer install
*  => laravel_role : php artisan steps
*  => laravel_role : php artisan steps
*  => laravel_role : php artisan steps
*  => laravel_role : php artisan steps
*  => laravel_role : php artisan steps
*  => laravel_role : php artisan steps

Отлично, в итоговых данных нет хоста MySQL. Теперь, когда мы знаем, что наши проверки Molecule будут нацелены только на то, что мы хотим, нам нужно очистить остальную часть нашего кода. Преимущество выходных данных в том, что мы знаем, какие именно задачи вызывают наши проблемы с идемпотентностью. Мы начнем с самого начала задачи, которая устанавливает Composer.

Поскольку мы используем командный модуль для проверки наличия двоичного файла Composer, он всегда будет возвращать измененный. Что нас волнует, так это выходные данные, которые мы регистрируем, поэтому мы можем просто добавить параметр changed_when: false  к задаче Checks if Composer is already installed в php_install.yml.

- name: Checks if Composer is already installed
  command: "{{ composer_path }}"
  ignore_errors: true
  register: composer_installed
  changed_when: false

Остальные сбои идемпотентности происходят из deploy_site.yml. Если мы посмотрим, то увидим, что мы выполняем несколько задач, которые требуются только при первоначальном развертывании сайта. Чтобы исправить это, мы можем поместить эти задачи в блок с помощью инструкции when, которая проверяет, произошло ли клонирование git. Во-первых, мы хотим переместить задачу Clone git repository  из существующего блока в отдельную задачу и зарегистрировать выходные данные задачи. Помните, что нам нужно будет добавить параметры для запуска задачи от имени пользователя www-data  в строки 10-17. Как только мы переместим эту задачу, мы хотим изменить оставшийся блок, чтобы проверить зарегистрированный вывод на предмет измененного статуса в строке 56.

---
- name: create /var/www/ directory
  file: 
    dest: /var/www/
    state: directory
    owner: www-data
    group: www-data
    mode: 0700

- name: Clone git repository
  git:
    dest: /var/www/laravel
    repo: https://github.com/JeffreyWay/Laravel-From-Scratch-Blog-Project.git
    update: no
  register: repo
  become: true
  become_user: www-data

name: configure site
- block:
  - name: set .env file
    template:
      src: env.j2
      dest: /var/www/laravel/.env

  - name: set database.php conf file
    copy:
      src: database.php
      dest: /var/www/laravel/config/database.php
      mode: u+rw,g-rw,o-r

  - name: Composer update
    command:
      cmd: composer update
      chdir: /var/www/laravel

  - name: composer install
    command:
      cmd: composer install
      chdir: /var/www/laravel

  - name: php artisan steps
    command:
      cmd: php artisan {{ item }}
      chdir: /var/www/laravel
    with_items:
      - migrate --seed --force
      - storage:link
      - config:clear
      - key:generate --force
  become: true
  become_user: www-data
  notify:
      - restart php8.1-fpm
      - restart nginx
  when: repo is changed

Как только мы внесем эти изменения, мы сможем повторно запустить тест на идемпотентность. Теперь давайте запустим команду molecule idempotence и посмотрим, что мы получим. Если весь наш код обновлен правильно, мы должны увидеть следующий вывод:

PLAY RECAP *********************************************************************
site                       : ok=13   changed=0    unreachable=0    failed=0    skipped=10   rescued=0    ignored=0
INFO     Idempotence completed successfully

Отлично, мы на один шаг приблизились к точному и функциональному набору тестов Molecule.

Линтинг

Теперь мы уже знаем, что наш код Ansible функционален и идемпотентен, но является ли он чистым и соответствующим стандартам? Чтобы выяснить это, мы можем запустить наш код через линтер. Чтобы протестировать линтинг в Molecule, вам сначала нужно включить его в главном файле. Мы можем сделать это, добавив блок lint: в конец molecule.yml под блок verifier:

..
verifier:
  name: testinfra
lint: |
  set -e
  yamllint .
  ansible-lint

Вы заметите, что я использую здесь два разных линтера yamllint и ansible-lint. yamllint фокусируется на синтаксисе YAML и других методах, связанных с YAML, в то время как ansible-lint фокусируется на коде и поведении, ориентированных на Ansible. Есть третий вариант, который я бы порекомендовал, если вы используете testinfra, flake8, который поможет поддерживать ваши тесты на Python в соответствии со стандартами.

Как только мы добавили блок lint:, мы можем запустить molecule lint и посмотреть, где мы могли бы захотеть улучшить наш код.

INFO     Running default > lint
WARNING  Listing 30 violation(s) that are fatal
fqcn-builtins: Use FQCN for builtin actions.
handlers/main.yml:3 Task/Handler: restart php8.1-fpm
fqcn-builtins: Use FQCN for builtin actions.
handlers/main.yml:11 Task/Handler: restart nginx
meta-incorrect: Should change default metadata: company
meta/main.yml:1
meta-incorrect: Should change default metadata: license
meta/main.yml:1
fqcn-builtins: Use FQCN for builtin actions.
molecule/default/converge.yml:13 Task/Handler: Setup MySQL DB
fqcn-builtins: Use FQCN for builtin actions.
molecule/default/converge.yml:21 Task/Handler: Include laravel_role
fqcn-builtins: Use FQCN for builtin actions.
tasks/deploy_site.yml:2 Task/Handler: create /var/www/ directory
fqcn-builtins: Use FQCN for builtin actions.
tasks/deploy_site.yml:10 Task/Handler: Clone git repository
git-latest: Git checkouts must contain explicit version.
tasks/deploy_site.yml:10 Task/Handler: Clone git repository
no-handler: Tasks that run when changed should likely be handlers.
tasks/deploy_site.yml:19 Task/Handler: Configure the site then migrate and seed the database
fqcn-builtins: Use FQCN for builtin actions.
tasks/deploy_site.yml:21 Task/Handler: set .env file
risky-file-permissions: File permissions unset or incorrect.
tasks/deploy_site.yml:21 Task/Handler: set .env file
fqcn-builtins: Use FQCN for builtin actions.
tasks/deploy_site.yml:26 Task/Handler: set database.php conf file
fqcn-builtins: Use FQCN for builtin actions.
tasks/deploy_site.yml:32 Task/Handler: Composer update
no-changed-when: Commands should not change things if nothing needs doing.
tasks/deploy_site.yml:32 Task/Handler: Composer update
fqcn-builtins: Use FQCN for builtin actions.
tasks/deploy_site.yml:37 Task/Handler: composer install
no-changed-when: Commands should not change things if nothing needs doing.
tasks/deploy_site.yml:37 Task/Handler: composer install
fqcn-builtins: Use FQCN for builtin actions.
tasks/deploy_site.yml:42 Task/Handler: php artisan steps
no-changed-when: Commands should not change things if nothing needs doing.
tasks/deploy_site.yml:42 Task/Handler: php artisan steps
fqcn-builtins: Use FQCN for builtin actions.
tasks/nginx_install.yml:7 Task/Handler: install nginx
fqcn-builtins: Use FQCN for builtin actions.
tasks/nginx_install.yml:15 Task/Handler: Make sure a service unit is running
fqcn-builtins: Use FQCN for builtin actions.
tasks/nginx_install.yml:21 Task/Handler: copy over config
fqcn-builtins: Use FQCN for builtin actions.
tasks/php_install.yml:6 Task/Handler: install php and related packages
fqcn-builtins: Use FQCN for builtin actions.
tasks/php_install.yml:11 Task/Handler: Checks if Composer is already installed
fqcn-builtins: Use FQCN for builtin actions.
tasks/php_install.yml:20 Task/Handler: Download Composer Installation Script (if not already installed)
risky-file-permissions: File permissions unset or incorrect.
tasks/php_install.yml:20 Task/Handler: Download Composer Installation Script (if not already installed)
fqcn-builtins: Use FQCN for builtin actions.
tasks/php_install.yml:25 Task/Handler: Run Composer Installer (if not already installed)
fqcn-builtins: Use FQCN for builtin actions.
tasks/php_install.yml:30 Task/Handler: Copy Composer Executable to /usr/local/bin
fqcn-builtins: Use FQCN for builtin actions.
tasks/php_install.yml:38 Task/Handler: Remove phar
fqcn-builtins: Use FQCN for builtin actions.
tasks/php_install.yml:43 Task/Handler: Remove Install Script
You can skip specific rules or tags by adding them to your configuration file:
# .config/ansible-lint.yml
warn_list:  # or 'skip_list' to silence them completely
  - experimental  # all rules tagged as experimental
  - fqcn-builtins  # Use FQCN for builtin actions.
  - git-latest  # Git checkouts must contain explicit version.
  - meta-incorrect  # meta/main.yml default values should be changed.
  - no-changed-when  # Commands should not change things if nothing needs doing.
  - no-handler  # Tasks that run when changed should likely be handlers.
Finished with 29 failure(s), 2 warning(s) on 26 files.
WARNING  Retrying execution failure 2 of: s e t   - e 
 y a m l l i n t   . 
 a n s i b l e - l i n t
CRITICAL Lint failed with error code 2

Ого, да здесь масса потенциальных проблем, которые нужно решить. С чего нам следует начать? Давайте начнем с того, который имеет 21 случай, fqcn-builtins: Use FQCN for builtin actions. Эта ошибка означает, что мы не используем полное имя коллекции (FQCN) для многих наших задач Ansible. Нам нужно обновить все затронутые задачи до ansible.builtin.<MODULE_NAME>. Чтобы все было проще, я просто показываю ошибку и то, как должен выглядеть новый идентификатор задачи. 

handlers/main.yml:3 Task/Handler: restart php8.1-fpm
ansible.builtin.sysvinit:

handlers/main.yml:11 Task/Handler: restart nginx
ansible.builtin.sysvinit:

molecule/default/converge.yml:13 Task/Handler: Setup MySQL DB
ansible.builtin.include_role:

molecule/default/converge.yml:21 Task/Handler: Include laravel_role
ansible.builtin.include_role:

tasks/deploy_site.yml:2 Task/Handler: create /var/www/ directory
ansible.builtin.file:

tasks/deploy_site.yml:10 Task/Handler: Clone git repository
ansible.builtin.git:

tasks/deploy_site.yml:21 Task/Handler: set .env file
ansible.builtin.template:

tasks/deploy_site.yml:26 Task/Handler: set database.php conf file
ansible.builtin.copy:

tasks/deploy_site.yml:32 Task/Handler: Composer update
ansible.builtin.shell:

tasks/deploy_site.yml:37 Task/Handler: composer install
ansible.builtin.shell:

tasks/deploy_site.yml:42 Task/Handler: php artisan steps
ansible.builtin.shell:

tasks/nginx_install.yml:7 Task/Handler: install nginx
ansible.builtin.apt:

tasks/nginx_install.yml:15 Task/Handler: Make sure a service unit is running
ansible.builtin.sysvinit:

tasks/nginx_install.yml:21 Task/Handler: copy over config
ansible.builtin.copy:

tasks/php_install.yml:6 Task/Handler: install php and related packages
ansible.builtin.apt:

tasks/php_install.yml:11 Task/Handler: Checks if Composer is already installed
ansible.builtin.command:

tasks/php_install.yml:20 Task/Handler: Download Composer Installation Script (if not already installed)
ansible.builtin.get_url:

tasks/php_install.yml:25 Task/Handler: Run Composer Installer (if not already installed)
ansible.builtin.command:

tasks/php_install.yml:30 Task/Handler: Copy Composer Executable to /usr/local/bin
ansible.builtin.copy:

tasks/php_install.yml:38 Task/Handler: Remove phar
ansible.builtin.file:

tasks/php_install.yml:43 Task/Handler: Remove Install Script
ansible.builtin.file:

А теперь, когда они исправлены, давайте посмотрим на то, что осталось.

INFO     Running default > lint
WARNING  Listing 12 violation(s) that are fatal
meta-incorrect: Should change default metadata: company
meta/main.yml:1
meta-incorrect: Should change default metadata: license
meta/main.yml:1
git-latest: Git checkouts must contain explicit version.
tasks/deploy_site.yml:10 Task/Handler: Clone git repository
no-handler: Tasks that run when changed should likely be handlers.
tasks/deploy_site.yml:19 Task/Handler: Configure the site then migrate and seed the database
risky-file-permissions: File permissions unset or incorrect.
tasks/deploy_site.yml:21 Task/Handler: set .env file
no-changed-when: Commands should not change things if nothing needs doing.
tasks/deploy_site.yml:32 Task/Handler: Composer update
no-changed-when: Commands should not change things if nothing needs doing.
tasks/deploy_site.yml:37 Task/Handler: composer install
no-changed-when: Commands should not change things if nothing needs doing.
tasks/deploy_site.yml:42 Task/Handler: php artisan steps
risky-file-permissions: File permissions unset or incorrect.
tasks/php_install.yml:20 Task/Handler: Download Composer Installation Script (if not already installed)

Из оставшихся ошибок есть две, которые мы можем игнорировать: meta-incorrect, что означает, что файл в нашем каталоге ./meta по-прежнему содержит информацию по умолчанию, и no-changed-when , что означает, что конкретные задачи не являются идемпотентными. Поскольку нас не интересует метаинформация и дополнительные проверки и тесты на идемпотентность, мы можем пропустить их оба. Так как же нам пропустить эти проверки? Путем добавления файла .ansible-lint в каталог Molecule.

skip_list:  # or 'skip_list' to silence them completely
  - meta-incorrect  # meta/main.yml default values should be changed.
  - no-changed-when # Commands should not change things if nothing needs doing.

Сделав это, мы можем снова запустить molecule lint и увидеть, что у нас осталось всего 4 нарушения.

INFO     Running default > lint
WARNING  Listing 4 violation(s) that are fatal
git-latest: Git checkouts must contain explicit version.
tasks/deploy_site.yml:10 Task/Handler: Clone git repository
no-handler: Tasks that run when changed should likely be handlers.
tasks/deploy_site.yml:19 Task/Handler: Configure the site then migrate and seed the database
risky-file-permissions: File permissions unset or incorrect.
tasks/deploy_site.yml:21 Task/Handler: set .env file
risky-file-permissions: File permissions unset or incorrect.
tasks/php_install.yml:20 Task/Handler: Download Composer Installation Script (if not already installed)
Finished with 2 failure(s), 2 warning(s) on 26 files.

Первая ошибка git-latest. Это говорит нам, что мы должны привязать версию к нашей задаче git вместо того, чтобы предполагать основную ветвь. Исправить это так же просто, как добавить к задаче однострочную version: main .

- name: Clone git repository
  ansible.builtin.git:
    dest: /var/www/laravel
    repo: https://github.com/JeffreyWay/Laravel-From-Scratch-Blog-Project.git
    update: no
    version: main
  become: true
  become_user: www-data
  notify:
    - configure site
    - restart php8.1-fpm
    - restart nginx

Следующее нарушение no-handler. Эта ошибка дает нам знать, что у нас есть задача или серия задач, которые выполняются только при изменении. Мы должны использовать функцию обработчика для выполнения задач. Исправление этого нарушения требует ряда изменений, начиная с блока задач configure site из deploy_sites.yml в отдельный файл. Я начал с tasks/configure_site.yml.

- name: configure site
  block:
  - name: set .env file
    ansible.builtin.template:
      src: env.j2
      dest: /var/www/laravel/.env
      mode: '0755'

  - name: set database.php conf file
    ansible.builtin.copy:
      src: database.php
      dest: /var/www/laravel/config/database.php
      mode: u+rw,g-rw,o-r

  - name: Composer update
    ansible.builtin.command:
      cmd: composer update
      chdir: /var/www/laravel

  - name: composer install
    ansible.builtin.command:
      cmd: composer install
      chdir: /var/www/laravel

  - name: php artisan steps
    ansible.builtin.command:
      cmd: php artisan {{ item }}
      chdir: /var/www/laravel
    with_items:
      - migrate --seed --force
      - storage:link
      - config:clear
      - key:generate --force
  become: true
  become_user: www-data

Затем нам нужно внести дополнительное обновление в задачу git, добавив другой обработчик.

 notify:
    - configure site
    - restart php8.1-fpm
    - restart nginx

Наконец, нам нужно обновить handlers/main.yml для вызова tasks/configure_site.yml, чтобы действовать при срабатывании notify.

---
# handlers file for laravel_role
- name: configure site
  include_tasks: tasks/configure_site.yml

- name: restart php8.1-fpm
  ansible.builtin.sysvinit: 
    name: php8.1-fpm
    state: "{{ item }}"
  with_items:
    - stopped
    - started

- name: restart nginx
  ansible.builtin.sysvinit:
    name: nginx
    state: restarted

После этого наши задачи по настройке нашего сайта и нашей базы данных будут выполняться только тогда, когда задача git зарегистрирует изменение.

Последнее нарушение lint это risky-file-permissions  для двух файлов. Вкратце, мы не указывали разрешения для файлов, обрабатываемых Ansible, оставляя место для чрезмерно разрешающих или ограничительных разрешений для файлов. Это изменение легко устранить, добавив опцию mode: <PERMISSIONS> к файлам, которые мы обрабатываем.

tasks/configure_site.yml
- name: set .env file
  ansible.builtin.template:
    src: env.j2
    dest: /var/www/laravel/.env
    mode: '0755'

tasks/php_install.yml
- name: Download Composer Installation Script (if not already installed)
  ansible.builtin.get_url:
    url: https://getcomposer.org/installer
    dest: /tmp/composer-setup.php
    mode: '0755'

После того, как мы закончим вносить эти изменения, мы можем запустить molecule lint в последний раз и убедиться, что он не возвращает никаких нарушений.

Подводя итоги

Как только последняя секция будет завершена, мы сможем собрать все это вместе и запустить molecule test, позволяющий нам увидеть все эти изменения за один прогон.

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

Поток «Ansible: Infrastructure as Code» стартует 6 февраля.

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


  1. funca
    26.01.2023 19:38

    dest: /var/www/laravel/.env

    mode: '0755'

    Интересно зачем конфигурационному файлу, содержащему пароли, такие права?


  1. JuriM
    27.01.2023 00:32
    +1

    Не рассмотрена привязка молекулы к ансибл инвентори


  1. JuriM
    27.01.2023 00:35

    Yamllint можно не указывать отдельно, уже давно ansible-lint вызывает yamllint сам