Недавно выпала задача по развертке копий основного 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)
Tamerlan666
00.00.0000 00:00+2Код ужасный, как и подход к решению задачи. Понимаю, что подход "раз, два и в продакшн" весьма распространен, но показывать такое посторонним людям вряд ли стоит...
walkingpylons
00.00.0000 00:00Вообще непонятно, зачем решается такая задача и зачем здесь gitlab.
`Server=172.127.0.6` - это мне у себя так оставить? В чём сложность была написать .j2 шаблон и генерить конфиг из переменных?
Вопросы можно задавать практически бесконечно.
Люди, добавившие это в закладки! Я искренне надеюсь, что вы сделали это, чтобы показывать коллегам the worst practices.
TEMAndroid
00.00.0000 00:00+1Чтобы установить deb можно использовать apt
- name: Download agent2.service apt: deb: url
AlexGluck
Увидев код волосы зашевелились в тех местах, которые должны быть без волос. Это башсибл, сопровождать такой код сплошное мучение и вся ide в ошибках, молекула не навешивается сходу. На Хабре уже много раз это обсуждалось и причины как не надо и почему, как надо и почему.