Недавно выпала задача по развертке копий основного Zabbix-server на несколько машин, дабы хранить конфиги на разных серверах, да и еще всунуть это в CI/CD GitLab. Взял Ansible и начал писать плейбук.

Работа с установками на хосты Zabbix'а через Ansible

Сначала написал файл хостов с переменными, чтобы разделять установщики на разных семействах Linux(Debian, Ubuntu, RedHat):

[remote_zabbix:children]
os_family_Debian
os_family_RedHat
os_family_Ubuntu
py_script

## current_host - переменная, значение = самому хосту(да, хост два раза прописать)

[py_script]
#Скрипт, на котором будет запускаться скрипт для экспорта/импорта гита# current_host=

[os_family_Debian]
#Хост на ОС Debian# current_host=

[os_family_RedHat]
#Хост на ОС ReHat# current_host=

[os_family_Ubuntu]
#Хост на ОС Ubuntu# current_host=

[os_family_Debian:vars]
os_family_type=debian

[os_family_RedHat:vars]
os_family_type=redhat

[os_family_Ubuntu:vars]
os_family_type=ubuntu

Написал обычный пинг серверов:

- name: Test Connection
  hosts: remote_zabbix
  become: yes

  tasks:
    - name: Ping server
      ping:
  tags:
    - test_connection

После того, как я удостоверился, что хосты доступны, создал типичный шаблон для развертывания Zabbix (web, server и БД timescaledb на PostgreSQL) и вызвал его с ролью(run_docker-compose), где стопятся контейнеры со старым Zabbix'ом и БД, если они были подняты, и поднимаются заново в docker-compose:

- name: rm zabbix-containers
  ansible.builtin.shell: docker ps --filter name=zabbix* -aq | xargs docker stop | xargs docker rm
  ignore_errors: True
- name: rm timescale container
  ansible.builtin.shell: docker ps --filter name=timescale* -aq | xargs docker stop | xargs docker rm
  ignore_errors: True
- name: run zabbix-server
  ansible.builtin.shell: cd /home && docker-compose up -d

*Дабы не занимать много вашего времени и не делать длиннопост, прикреплю ссылку на репозиторий с файлами.

Далее я накатываю Zabbix-agent2, скачивая его пакеты с репозиториев, роль - install_agent2.service:

- name: Download agent2.service debian
  get_url:
    url: https://repo.zabbix.com/zabbix/6.2/debian/pool/main/z/zabbix-release/zabbix-release_6.2-4+debian10_all.deb
    dest: ./zabbix-release_6.2-4+debian10_all.deb
  when: os_family_type == "debian"

- name: Download agent2.service debian
  shell: | 
    dpkg -i ./zabbix-release_6.2-4+debian10_all.deb
    apt update
  when: os_family_type == "debian"

- name: Install agent2.service debian
  apt:
    name: zabbix-agent2
    update_cache: yes
  when: os_family_type == "debian"

- name: Download agent2 redhat
  yum:
    name: https://repo.zabbix.com/zabbix/6.2/rhel/7/x86_64/zabbix-release-6.2-3.el7.noarch.rpm
  when: os_family_type == "redhat"

- name: Yum clean all
  shell: yum clean all
  when: os_family_type == "redhat"

- name: Install agent2.service redhat
  yum:
    name: zabbix-agent2
  when: os_family_type == "redhat"

Накидываю уже шаблон для конфига агента вместо дефолтного файла конфигурации (тег configure_zabbix-agent2) и перезапустил службу этого агента ролью restart_agent2.service.

Скрипт для импорта/экспорта конфигурации

Далее наступила интересная часть, где я думал, как все-таки можно сделать импорт/экспорт конфигурации самого Zabbix, когда уже все поднято. Поискав в интернете, я уже почти расстроился, что придется делать либо запросами, либо писать самодельный API. Но, к счастью, я отыскал библиотеку для Python pyzabbix, где можно спокойно сделать экспорт конфига (как потом, спустя время, оказалось, что можно и импорт).

На вход два аргумента: host_from(откуда берем конфиг) и host_to(куда импортируем, подставляется в роли Ansible).

Написал функции для аутентификации (логин-пароль или токен):

# Auth Zabbix API to export
def zabbix_auth_from(host_from):
    zapi = ZabbixAPI("http://" + host_from)
    zapi.login("Admin", "zabbix")
    print("Connected to Zabbix API Version %s" % zapi.api_version())
    return zapi


# Auth Zabbix API to import
def zabbix_auth_to(url_link):
    zapi = ZabbixAPI('http://' + url_link + ':8080')
    zapi.login("Admin", "zabbix")
    print("Connected to Zabbix API Version %s" % zapi.api_version())
    return zapi

При успешном выполнении скрипта нам выведется в консоль отчет об успешном входе и выведет версию Zabbix:

Connected to Zabbix API Version %version%

Все хорошо, далее пишем функции для экспорта хостов, групп хостов и шаблонов:

# Func for make export files
def write_export(name, config, export_format):
    if config is not None:
        print("Writing %s.%s" % (name, export_format))
        with open("%s.%s" % (name, export_format), "w") as f:
            f.write(config)


# Func for export hosts from main zabbix
def export_hosts(zapi, namefile = "export_zabbix_hosts"):
    hosts_ids = []
    for item in zapi.host.get(output="extend"):
        hosts_ids.append(int(item['hostid']))

    config = zapi.configuration.export(
        format="yaml", prettyprint=True, options={"hosts": hosts_ids}
    )
    write_export(namefile, config, "yml")


# Func for export templates from main zabbix
def export_templates(zapi, namefile = "export_zabbix_templates"):
    hosts_ids = []
    for item in zapi.template.get(output="extend"):
        hosts_ids.append(int(item['templateid']))

    config = zapi.configuration.export(
        format="yaml", prettyprint=True, options={"templates": hosts_ids}
    )
    write_export(namefile, config, "yml")


# Func for export host groups from main zabbix
def export_host_groups(zapi, namefile = "export_zabbix_hostgroups"):
    hosts_groups_ids = []
    for item in zapi.hostgroup.get(output="extend"):
        hosts_groups_ids.append(int(item["groupid"]))

    config = zapi.configuration.export(
        format="yaml", prettyprint=True, options={"groups": hosts_groups_ids}
    )
    write_export(namefile, config, "yml") 

Далее мы просто вызываем поочередно функции экспорта, куда передаем объект с аутентифицированным Zabbix по API:

def export_zabbix_conf(zapi):
    export_templates(zapi)
    export_hosts(zapi)
    export_host_groups(zapi)

Все, что осталось - это импортировать созданные файлы экспорта в другие хосты Zabbix(здесь zapi - уже хост, на который копируем):

# Func for import config to copy of zabbix
def import_config(zapi, filename, rules):
    print("Import " + filename.split('_')[-1].split('.')[0])
    export_file = open(filename, "r").read()
    zapi.configuration['import'](format="yaml", source=export_file, rules=rules)


def import_zabbix_conf(zapi):
    import_config(zapi, "export_zabbix_templates.yml", { "templates": { "createMissing": True, "updateExisting": True }})
    import_config(zapi, "export_zabbix_hostgroups.yml", { "host_groups": {"createMissing": True }})
    import_config(zapi, "export_zabbix_hosts.yml", {"hosts": { "createMissing": True, "updateExisting": True }})

Все хорошо, скопировался конфиг, но остается последняя проблема - агент не видит свой же хост и нам нужно в настройках заббикса переименовать наш хост с "Zabbix Server" на наш DNS, IP (я переключил на чтение DNS, мне так удобнее было):

# Func for rename localhost on zabbix-server, for find it by zabbix-agent2
def rename_local_host(zapi, remote_host):
    try:
        hid=zapi.host.get(search={'host': remote_host})[0]['hostid']
    except:
        hid=zapi.host.get(search={'host': 'Zabbix server'})[0]['hostid']
    print('Change name')
    zapi.host.update(hostid=hid, host=remote_host, name=remote_host)
    print('Change DNS name')
    iid = zapi.hostinterface.get(hostids=hid)[0]['interfaceid']
    zapi.hostinterface.update(interfaceid=iid, dns=remote_host, useip=0, ip='сюда вписать наш IP')  

Внедрение в CI/CD GitLab

Ну здесь уже просто. Мы при создании заданий в плейбуке прописываем теги, которые уже вызываем в .gitlab-ci.yml:

image: ### Ansible 6.5.1 image

stages:
- install/update_zabbix
- configure_zabbix-agent2
- run_script

Install/update_Zabbix:
  stage: install/update_zabbix
  script:
    - ansible-playbook ansible/zabbix_playbook.yml -i ansible/hosts/hosts.txt -vvvv -t install/update_zabbix
  when: manual

Configure_Zabbix-Agent2:
  stage: configure_zabbix-agent2
  script:
    - ansible-playbook ansible/zabbix_playbook.yml -i ansible/hosts/hosts.txt -vvvv -t configure_zabbix-agent2
  when: manual

Export/import_zabbix_config:
  stage: run_script
  script:
    - ansible-playbook ansible/zabbix_playbook.yml -i ansible/hosts/hosts.txt -vvvv -t run_sript -t restart-agent2
  when: manual

Итоги

Мы смогли через Ansible развернуть много копий Zabbix-server и Zabbix-agent2 со своими настройками, а также скопировать конфиг с основного Zabbix на другие хосты с помощью одной библиотеки pyzabbix.

Репозиторий

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


  1. AlexGluck
    00.00.0000 00:00
    +3

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


  1. Tamerlan666
    00.00.0000 00:00
    +2

    Код ужасный, как и подход к решению задачи. Понимаю, что подход "раз, два и в продакшн" весьма распространен, но показывать такое посторонним людям вряд ли стоит...


    1. JuriM
      00.00.0000 00:00
      +2

      22 человека добавили это в закладки :)


      1. xface
        00.00.0000 00:00
        +2

        Чтобы не забыть почитать комментарии к статье :)


      1. denistu10
        00.00.0000 00:00

        Они просто извращенцы...


  1. walkingpylons
    00.00.0000 00:00

    Вообще непонятно, зачем решается такая задача и зачем здесь gitlab.

    `Server=172.127.0.6` - это мне у себя так оставить? В чём сложность была написать .j2 шаблон и генерить конфиг из переменных?

    Вопросы можно задавать практически бесконечно.

    Люди, добавившие это в закладки! Я искренне надеюсь, что вы сделали это, чтобы показывать коллегам the worst practices.


  1. TEMAndroid
    00.00.0000 00:00
    +1

    Чтобы установить deb можно использовать apt

    - name: Download agent2.service
          apt:
            deb: url