Gitlab-CI и Ansible-lint

d83293edf85346cc80b3e4c830d3bea1.png

Всем привет! Мы продолжаем серию статей про DevOps и ищем наиболее эффективные способы управлять конфигурацией, делясь с вами опытом. В прошлых статьях мы рассматривали, как выстроить управление конфигурацией Ansible с помощью Jenkins и Serverspec, а теперь по вашим просьбам рассмотрим, как организовать управление конфигурацией с помощью GitLab-CI.

Ansible-lint — это утилита для проверки корректности синтаксиса плейбука и стиля кода, которую можно интегрировать в CI-сервис. В нашем случае мы внедряем её в gitlab-ci для проверки плейбуков на этапе принятия Merge-Request и выставления статуса проверок.
GitLab (GitLab Community Edition) — это opensource-проект, менеджер git-репозиториев, изначально разрабатывающийся как альтернатива платной корпоративной версии Github.

Установка GitLab CE описана в этой статье.

Устанавливаем gitlab-ci-multirunner

curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.rpm.sh | sudo bash
yum install gitlab-ci-multi-runner

Регистрируем runner

gitlab-ci-multi-runner register

Теперь нужно ответить на вопросы:

Running in system-mode.                            

Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/ci):
http://domain.example.com/ci
Please enter the gitlab-ci token for this runner:
your_token
Please enter the gitlab-ci description for this runner:
[domain.example.com]: 
Please enter the gitlab-ci tags for this runner (comma separated):
ansible
Registering runner... succeeded                     runner=
Please enter the executor: docker-ssh+machine, docker, docker-ssh, parallels, shell, ssh, virtualbox, docker+machine:
shell
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 

Токен и URL берем со страницы настроек проекта.

5a67dde9c76443149e8cab027b8003b7.png

1f47bd709d7f4902ad047b6d65f77492.png

Особенности настройки multi-runner.

Один раннер может обрабатывать несколько проектов. Для того, чтоб один раннер обрабатывал всё подряд, нужно просто зарегистрировать новый раннер, не указывая теги. Токен для shared-раннера берем в Admin Area:

77a2693ef7f242468e0527a34d9b5774.png

Можно пачку раннеров разнести по разным серверам.
И ещё: в gitlab, как и в Jenkins, есть такое понятие, как теги.
Проект с тегом ansible будет обрабатывать раннер, помеченный тегом ansible, а для других проектов, без метки или с другой меткой, этот раннер работать не будет.

Также в админке можно настроить соответствие раннера и проекта.

domain.example.com/admin/runners

812f1c2b4b9542478bd1268c08065e30.png

Установка Ansible-lint

Ansible-lint устанавливается через python-pip или из репозитория EPEL.

Первый способ:
Сначала устанавливаем python-pip, затем через него ставим ansible-lint:
sudo yum groupinstall @Development Tools
sudo yum install python-pip
sudo pip install --upgrade pip
sudo pip install ansible-lint
Второй способ
Всё просто — ставим epel-release и ставим ansible-lint через пакетный менеджер:
sudo yum install epel-release
sudo yum install ansible-lint

Настройка пайплайна.

Создаем в корне репозитория файл .gitlab-ci.yml. Важно соблюдать количество пробелов, иначе будет ошибка — yaml такое не прощает, ага.

stages:
[два пробела]- test

test_job:
[два пробела]stage: test
[два пробела]script:
[четыре пробела]- ansible-lint *.yml

[два пробела]tags:
[четыре пробела]- ansible

stages — обязательно описываем стадии сборки.

stages:
  - stagename

test_job — произвольное название джоба.
stage: test —обязательно описываем стадию test, указанную в секции stages.

jobname:
  stage: stagename

script — проводим тест в корне репозитория
tags — метка для раннера.

Название стадии stage: test может быть любым, например: converge, pre-test, post-test, deploy.
Название джоба test_job также может быть любым.

В GitLab есть встроенный линтер для пайплайнов. Проверить корректность синтаксиса пайплайна можно по URL domain.example.com/ci/lint

Вставляем пайплайн, жмём Validate.

ff9f0e7573f14569b7b3ffff7e314c30.png

При ошибке линтер будет ругаться и укажет на ошибку.

da1cfca65579414aa3464e5ed46a0e50.png

Должно быть stages, а не stage.

Таким образом ansible-lint будет автоматически при каждом коммите проверять все плейбуки с расширением .yml из корня репозитория.

У нас в репозитории две роли:

roles
├── monit
└── openssh

И два плейбука, накатывающих эти роли:

├── monit.yml
├── openssh.yml
├── README.md
└── roles

openssh.yml

---
- hosts: all
  user: ansible
  become: yes

  roles:
    - openssh

monit.yml

---
- hosts: all
  user: ansible
  become: yes

  roles:
    - monit

Следовательно, проверяя плейбук, присваивающий роль, мы проверяем все ее содержимое:

roles/openssh/tasks/
├── configure_iptables.yml
├── configure_monitoring.yml
├── configure_ssh.yml
└── main.ym

roles/monit/tasks/
├── configure_monit.yml
├── configure_monit_checks.yml
├── install_monit.yml
└── main.yml

Теперь, при коммите ansible-lint будет запущен автоматически и проверит все перечисленные в плейбуках роли.
Если попробовать что-нибудь закоммитить и перейти в веб-интерфейс (вкладка pipelines), то можно увидеть джоб в статусе failed.

ba0b3b1a22914a9cabb5f19d2f559bb4.png

Для того, чтоб отключить проверки lint’ом при пуше в репозиторий, достаточно вычистить в файле .gittab-ci.yml все стейджи, касающиеся запуска проверок ansible-lint.

Параметры проверки плейбуков также настраиваются:

╰─>$ ansible-lint --help
Usage: ansible-lint playbook.yml

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -L                    list all the rules
  -q                    quieter, although not silent output
  -p                    parseable output in the format of pep8
  -r RULESDIR           specify one or more rules directories using one or
                        more -r arguments. Any -r flags override the default
                        rules in /usr/local/lib/python2.7/dist-
                        packages/ansiblelint/rules, unless -R is also used.
  -R                    Use default rules in /usr/local/lib/python2.7/dist-
                        packages/ansiblelint/rules in addition to any extra
                        rules directories specified with -r. There is no need
                        to specify this if no -r flags are used
  -t TAGS               only check rules whose id/tags match these values
  -T                    list all the tags
  -v                    Increase verbosity level
  -x SKIP_LIST          only check rules whose id/tags do not match these
                        values
  --nocolor             disable colored output
  --exclude=EXCLUDE_PATHS
                        path to directories or files to skip. This option is
                        repeatable.
╰─>$ ansible-lint -L
ANSIBLE0002: Trailing whitespace
  There should not be any trailing whitespace
ANSIBLE0004: Git checkouts must contain explicit version
  All version control checkouts must point to an explicit commit or tag, not just "latest" 
ANSIBLE0005: Mercurial checkouts must contain explicit revision
  All version control checkouts must point to an explicit commit or tag, not just "latest" 
ANSIBLE0006: Using command rather than module
  Executing a command when there is an Ansible module is generally a bad idea
ANSIBLE0007: Using command rather than an argument to e.g. file
  Executing a command when there is are arguments to modules is generally a bad idea
ANSIBLE0008: Deprecated sudo
  Instead of sudo/sudo_user, use become/become_user.
ANSIBLE0009: Octal file permissions must contain leading zero
  Numeric file permissions without leading zero can behavein unexpected ways. See http://docs.ansible.com/ansible/file_module.html
ANSIBLE0010: Package installs should not use latest
  Package installs should use state=present with or without a version
ANSIBLE0011: All tasks should be named
  All tasks should have a distinct name for readability and for --start-at-task to work
ANSIBLE0012: Commands should not change things if nothing needs doing
  Commands should either read information (and thus set changed_when) or not do something if it has already been done (using creates/removes) or only do it if another check has a particular result (when)
ANSIBLE0013: Use shell only when shell functionality is required
  Shell should only be used when piping, redirecting or chaining commands (and Ansible would be preferred for some of those!)
ANSIBLE0014: Environment variables don't work as part of command
  Environment variables should be passed to shell or command through environment argument
ANSIBLE0015: Using bare variables is deprecated
  Using bare variables is deprecated. Update yourplaybooks so that the environment value uses the full variablesyntax ("{{your_variable}}").

Некоторые таски можно пропускать при проверке ansible-lint не очень любит модули command и shell, так как в идеологии ansible считается, что штатных core-модулей хватает для всех задач. На самом деле, это не всегда так.

К примеру, у нас в роли встречается таск, использующий модуль command:

- name: Installing monit
  command:
    yum -y install monit
  tags: monit

И если пролинтим плейбук с этой ролью, то ansible-lint сругнётся на то, что мы используем модуль command

╰─>$ ansible-lint monit.yml 
[ANSIBLE0002] Trailing whitespace
monit.yml:7
    - monit 

[ANSIBLE0012] Commands should not change things if nothing needs doing
/tmp/ansible-lint/roles/monit/tasks/install_monit.yml:8
Task/Handler: Installing monit

[ANSIBLE0006] yum used in place of yum module
/tmp/ansible-lint/roles/monit/tasks/install_monit.yml:8
Task/Handler: Installing monit

Чтобы этого избежать, можно пометить таск тегом skip_ansible_lint:

- name: Installing monit
  command:
    yum -y install monit
  tags: monit,skip_ansible_lint

Теперь при прогоне lint’а по плейбуку он не будет ругаться на используемый модуль command:

╰─>$ ansible-lint monit.yml 
[ANSIBLE0002] Trailing whitespace
monit.yml:7
    - monit 

Особенности Merge Request.

Забегая вперёд, пару слов о функционале проверок в MR.

По-умолчанию, merge-request принимается только при успешной проверке…
Отключить это можно в настройках проекта, в разделе Merge Requests:

9d407973c1bd41a28cbdbe6c253d8f24.png

Ansible-lint также можно запустить на локалхосте, не делая коммиты и не дожидаясь проверки CI-сервисом. Если у на десктопе Linux или OS X — просто установите его к себе.

Примеры коммитов с ошибкой, как это выглядит в gitlab.

1. Открываем файл во встроенном редакторе GitLab:

af9b8236a5884b418a1f18e499f1cbb3.png

2. Вносим изменения. Например, yaml очень чувствителен к пробелам, попробуем добавить лишний пробел в начале какой-нибудь строки:

ef2b8986abf6431c9f97ef8cc1edb8b1.png

Жмём Commit Changes, возвращаемся к изменённому файлу. Справа вверху появится пиктограмма со статусом проверки:

b14ca76bc45d4ea99e38f7e24206b0ea.png

Сейчас она в статусе Pending, так как проверка ещё не завершена.

Если ткнуть на пиктограмму — перейдем к статусу проверки нашего свежесделанного коммита:

485d36b2d35741a09037c32b3cd862a9.png

Он сейчас в статусе Failed, так как мы умышленно допустили ошибку в синтаксисе.
Если ткнуть на пиктограмму Failed, то мы сможем увидеть результат работы ansible-lint:

ce5c5db39d6c43d1b464ba3d9f697c89.png

Можно прилепить симпатичный баджик со статусом сборки в README.md

[![build status](http://domain.example.com/projectname/badges/master/build.svg)](http://domain.example.com/projectname/commits/master)

Взять его можно в настройках проекта, в разделе CI/CD Pipelines

ac8da41a388245c5945072fb6c19bd2a.png

Копипастим маркдаун и добавляем в README.md в корне проекта, коммитим, теперь статус проверки отображается на главной странице проекта:

4cf8dffc3f5641b59ef4b591356aafb4.png

Зеленый / Passed — если проверка прошла успешна.
Красный / Failed — если проверка завершилась с ошибкой.

Во вкладке Pipelines можно посмотреть статусы всех коммитов:

c01d70ae03084dab928d3109515dcb21.png

Таким образом, мы выстроили процесс контроля корректности синтаксиса при управлении конфигурацией.

Спасибо за внимание и всем удачной автоматизации!

Автор: DevOps-администратор Centos-admin — Виктор Батуев.

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

  • 20 сентября 2016 в 09:02

    –1

    Хм, а почему вы не используете модуль yum для такого?

© Habrahabr.ru