Админим с Буквой
5.52K subscribers
303 photos
8 videos
59 files
1.16K links
Канал о системном администрировании, DevOps и немного Инфобеза.

По всем вопросам обращаться к @bykva. Рекламу не размещаю.
Download Telegram
изолированные vars в каждом вызове ansible role


нерабочий вариант:

---
- hosts: webservers
roles:
- common
- role: foo_app_instance
vars:
app_port: 5000
- role: foo_app_instance
vars:
dir: '/opt/b'
app_port: 5001


рабочий вариант:

---
- hosts: webservers
roles:
- common
- role: foo_app_instance
app_port: 5000
- role: foo_app_instance
dir: '/opt/b'
app_port: 5001

Отличие первого варианта от второго в том что vars передает переменные на уровень хоста. А вторая запись, вызывает role_vars, и действует только в рамках роли.

#ansible
объединение значений элементов массива в ansible

myvar:
key1:
- value1
- value2
key2:
- value3
key3:
- value4
- value5
- value6

- name: Debug myvar_list
debug:
msg: >-
{{ myvar.values() | sum(start=[]) }}

TASK [Debug myvar_list]
ok: [localhost] =>
msg:
- value1
- value2
- value3
- value4
- value5
- value6


#ansible
ansible remove unmanaged files

на примере syslog-ng. сначала получаем список файлов в папке, потом удалем те, которые нами не поддерживаются.

- name: get files in syslog conf.d dir
shell: ls -1 /etc/syslog-ng/conf.d
register: syslog_existing_files
changed_when: false

- name: remove unmanaged syslog configs
file:
path: "/etc/syslog-ng/conf.d/{{ item }}"
state: absent
with_items: "{{ syslog_existing_files.stdout_lines }}"
when: item not in configs


Список поддерживаемых файлов находится в переменной configs, которая или задается заранее или формируется в момент раскатки конфигов на хост, если имена и количество конечных файлов на конечном хосте отлично от списка шаблонов подаваемых модулю

#ansible
jinja как не ставить последнюю запятую в цикле

дано
: переменная с количеством серверов
задача: составить строку вида

zookeeper.connect=zookeeper1:2181,zookeeper2:2181,zookeeper3:2181/kafka

в целом решается простым циклом for + 2 небольших нюанса:
1) вместо последней запятой стоит /kafka
2) необходимо убрать newline который добавляется на каждом проходе

Решение:

zookeeper.connect=
{%- for server in range(servers_count) -%}
zookeeper{{ server+1 }}:2181{% if not loop.last %},{% endif %}
{%- endfor %}/kafka

все довольно просто - запятая ставится конструкцией {% if not loop.last %},{% endif %}, а переносы строк убираются постановкой дефиса у открывающих и закрывающих тегов {% и %}.

з.ы. счет начинается с нуля, поэтому для красоты именования серверов инкрементирую переменную server.
з.з.ы еще подсказали вот такой способ:

zookeeper.connect={{ range(1, servers_count + 1) | map('regex_replace', '^(.*)$', 'zookeeper\\1:2181') | join(',') }}/kafka


#jinja #ansible #kafka #zookeeper
Размышления на тему питона в шапочных дистрах

это для тех кто стал задумываться о том как скрестить интерпретаторы в разных осях и не сойти с ума...

https://developers.redhat.com/blog/2018/11/14/python-in-rhel-8/

#ansible #python #centos
Jeff Geerling, автор большого количества ролей на Ansible Galaxy, в связи с коронавирусом выложил книги Ansible for DevOps и Ansible for Kubernetes бесплатно. Можно будет скачивать и все последующие обновления.
https://www.jeffgeerling.com/blog/2020/you-can-get-my-devops-books-free-rest-month

#ansible #kubernetes
Знания из серии "ходил мимо 100 раз и вдруг нежданчик"

Сегодня узнал что оказывается нельзя параметризировать просто так имена тасков в ансибле, например того же хендлера name: restart {{ service }} - просто так незя сделать. А вот почему:

Ansible task names need to be defined as "global vars" (vars which are defined via a method which creates only a single variable no matter how many hosts reference that variable later). Inventory variables don't satisfy that because inventory, by definition, defines variables for individual hosts. If you wish to parameterize the handler name, you could set it via an extra var at ansible-playbook invocation.

#ansible
извлекаем значение из переменной в ansible

случай довольно "тяжелый", тут бы в пору в дурку звонить, но понадобилось мне извлечь в переменную несколько значений из словаря, который в массиве, который в словаре, который в массиве. и сделать это всё по конкретным именам ключей, а вывод воплотить в конкретно оформленную строку. в общем наслаждайтесь.

Дано(для простоты оставлены только нужные для селекта ключи):

user_list:
- name: core
ssh_keys:
- name: main
type: "ecdsa-sha2-nistp256"
value: "AAAA..."

Нужно получить:

pubkey: "ecdsa-sha2-nistp256 AAAA....."

Как это в итоге получилось, для переменной в hostvars:

pubkey: "{% set core_main_ssh_params =
user_list |
selectattr('name', 'equalto', 'core') |
sum(attribute='ssh_keys', start=[]) |
selectattr('name', 'equalto', 'main') |
list | first -%}
{{ core_main_ssh_params.type }} {{ core_main_ssh_params.value }}"

#ansible #jinja #дичь
Конфиг prometheus сервера с хостами из consul шаблонизированный в jinja

global:
scrape_interval: 10s
scrape_timeout: 10s
remote_write:
- queue_config:
max_samples_per_send: 10000
max_shards: 30
url: {{ remote_write_url }}

rule_files:
{% for rulefile in (lookup('fileglob', '*.rules')).split(',') %}
- /etc/prometheus/{{ rulefile | basename }}
{% endfor %}

alerting:
alertmanagers:
- static_configs:
- targets:
{% for host in groups[alertmanager_group] %}
- {{ hostvars[host]['ansible_default_ipv4']['address'] }}:9093
{% endfor %}

{% set service_list=[] %}
{%- for k,v in consul_services.json.items() %}
{{ service_list.append(v.Service) }}
{%- endfor %}
scrape_configs:
{% for service in service_list | unique %}
- job_name: {{ service }}
consul_sd_configs:
{% for tag in ['env1','env2','env3'] %}
- server: {{ consul_url | replace("http://", "") }}
tags: [ {{ tag }} ]
services: [ {{ service }} ]
{% endfor %}
{% endfor %}


consul_services - переменная получаемая таском:

- name: Fetch consul services
uri:
url: "{{ consul_url }}/v1/agent/services?filter=prometheus+in+Tags"
body: json
register: consul_services
changed_when: false
check_mode: no


#consul #ansible #prometheus #jinja
Вооооот. А еще я наклепал лабу по linux hardening... По большей части это компиляция из известных ресурсов, моего там практически нет. В целом это выглядит как чистый хахреднинг, но на самом деле он лежит в разделе про ansible, потому что тут довольно неплохие задачи которые позволят прокачать свои умения в этом продукте. Так шо объединяем приятное с полезным - инфраструктура как код и сесурити, шобы злобный хацкер не майнил бетховенов на вашем железе!

https://github.com/bykvaadm/OS/tree/master/devops/ansible/lab4

#linux #hardening #security #ansible
Собрал стенд для обучения работы с hashicorp vault

Разворачивается через вагрант на локальных вм. Поднимается куб кластер в котором поднимается HA версия vault с бекендом на HA etcd.

НА - это не на, а аббревиатура. написано большими буквами потому что я не помню как пишется high availabilпыр-пыр-пыр. etcd используется вместо консула потому что почему бы и нет. разница в бэкенде не существенна при обучении работы с vault. (ну на самом деле, потому что консул в кубе можно развернуть только один кластер, а это значит что мы отдаем его только под секреты - ведь не шарить же его еще с кем-то если мы там храним секреты, а в данном случае кажется избыточным поднимать консул, ведь если у вас будет 20 нод, вы получите 20 реплик консула (так написан чарт), а у etcd по-прежнему останется 3. )

В данном репозитории ТОЛЬКО сам стенд. материалы для обучения продаются, как говорится, отдельно =)

https://github.com/bykvaadm/vault-on-kubernetes-stand

#virtualbox
#vagrant
#ansible
#kubernetes
#helm
#docker
#etcd
#ingress
#hashicorp
#vault
#обучение

З.Ы. поделились аналогичным стендом, на компоузах и с консулом. https://github.com/temalovepizza/vaultstuff
Используем несколько паролей в ansible-vault

Данный пост родился благодаря тому что документация ansible на этот счет нихрена не очевидна и мне пришлось потратить заметное количество времени на то чтобы сделать эту примитивную задачу, а именно - получить возможность использовать несколько разных паролей для разграничения доступа к sensitive data в ansible.

Дисклаймер: это мой личный путь, я описываю свой личный кейс, не надо писать в комментах "КОКОКО это не сработает при таких-то и таких-то условиях".

1) сохраняем файлы с паролями, например ~/.vault_prod ~/.vault_dev
2) рассказываем об этом ansible и помещаем, например, в .zshrc

export ANSIBLE_VAULT_IDENTITY_LIST=prod@~/.vault_prod, dev@~/.vault_dev


3) шифруем
- текст: ansible-vault encrypt_string TEXT --encrypt-vault-id prod
- файл: ansible-vault encrypt --encrypt-vault-id dev /path/to/file

4) расшифруем
При расшифровке (выкатке на сервера) указывать id не нужно - ансибл последовательно перебирает все указанные в ANSIBLE_VAULT_IDENTITY_LIST файлы и расшифровывает

#ansible
о структуре ansible репозитория (часть 1)

Казалось бы тема не сильно сложная, бери беспрактис от ансибла и работай - никаких проблем. Однакож есть несколько моментов, которые они не учитывают:
1. по любым примерам показывается что, мол, храните плейбуки в корне папки ансибла и всё ок. ну вот вам для примера 3 ямлика, красиво же.
2. кросс-инвентарные переменные. правда, я вроде не плохо гуглю, но на оффдоках не нашел ничего похожего.

В чем же проблема №1? а в том, что когда у вас несколько инвентарей, и вы пишете плейбуки которые по смыслу делают одно и то же но в разных инвентарях, вам их надо либо делать строго одинаковыми и в конечном итоге получать один файл (с возможными доработками в виде условий, которые в обычной жизни нахрен не нужны), либо делать несколько файлов для каждого инвентаря. Оба варианта очевидно в конечном итоге приводят к не очень хорошим результатам. В первом случае - это какая-то не нужная логика, которая усложняет поддержку и использование плейбука, а во втором случае у нас просто множится количество файлов. В моем опыте оказалась ситуация когда у коллег удавалось писать "абсолютный" плейбук для своих задач, но это им просто повезло - если ты пишешь плейбук для тестовой и для продовой инфры, сервера по сути одни и те же и способы ввыкладки сервиса тоже. а значит это по сути один и тот же инвентарь, который разделен логически на тест и прод. А вот мне повезло не так сильно и нужно было писать плейбуки под создание сервиса как на своих железках (которые мы можем приготовить как угодно) так и в облаке, а также на "территории" легасёвой инфраструктуры клиента. В таком случае сами понимате сделать 1 плейбук на всё невозможно. Стали рождаться плейбуки вида rolename_inventory_azaza, rolename_inventory_ololo, rolename_inventory_pururum итп. Ситуацию осложняло то, что не все придерживались практики (я никого не виню - не было никакой договорённости) указывать имя инвентаря в имени плейбука.
В конечном итоге ситуация пришла к тому, что в корневом каталоге у нас оказались порядка 50 плейбуков, вперемешку со служебными файлами - gitlab-ci, Dockerfile, ansible.cfg, ansible-lint итп итп. Лично мне стало уже просто неудобно этим оперировать репозиторием. Не критично, не тяжело или как-то сложно, а именно неудобно. плейбуки перестали помещаться в один столбец в pycharm, а понять из названия сразу к какому инвентарю относится плейбук стало тоже непонятно - нужно было заходить внутрь, смотреть группу по которой катался плейбук, грепать ее по инвентарям... короче гемор! При этом в текущем состоянии репозиторий не то чтобы был плох, нет, им вполне можно было пользоваться, но я понимал, что команда у нас растет, а репозиторию меньше года и что дальше будет только хуже. В итоге назрел план небольшой революции и перекроя репозитория. конечный результат - после истории №2.
#ansible
о структуре ansible репозитория (часть 2)

Проблема №2: кросс инвентарные переменные. А вот это нужно для того чтобы можно было из любого инвентаря воспользоваться нужной общей переменной. Например вы создаете виртуалку и хотите чтобы после ее разворота можно было сразу обратиться по доменному имени. Вы добавляете роль управления днс именами в плейбук... а как авторизоваться? как хороший пацан вы написали универсальную роль и не захардкодили в ней параметр с токеном. Или другой пример - база данных пользователей, если вы катаете их ансиблом (хейтеры, отстаньте), то чтобы катать их на разные инвентари и не дублировать, нужно либо иметь один плейбук который будет только что и делать что настраивать пользователей и вы будете запускать его с лимитами по нужным хостам..... ну, да, звучит ужасно. Либо - хранить где-то снаружи ямл с ключами и хешами юзеров. Общие ссл сертификаты. адрес консула. Ну и итд итп, это много где используется. С самого начала такой файл был организован как group_vars/all.yml в корне ансибла. По сути этой бы проблемы не было, если бы не способ решения первого пункта. Для справки как это вообще работало: запускаемый плейбук читает свою директорию. и у него нет никаких проблем залезть в соседнюю с ним папку group_vars и забрать all.yaml а потом сходить в inventory/i_name/ и там прочитать group_vars, host_vars... и так мы получали все нужные переменные при запуске....

А теперь к решению. Для того чтобы сделать красиво я рассмотрел вариант убрать все плейбуки по инвентарям в папку playbooks/i_name. Сказано - сделано. и в этот момент мгновенно перестали работать роли и кросс-инвентарные переменные. Решение вопроса было осуществлено с помощью симлинка. т.е. мы взяли и слинковали в каждом инвентаре кросс-инвентарный файл с переменными. а в ansible.cfg нужно добавить вот эти строки:

[defaults]
roles_path = roles:~/.ansible/roles:/etc/ansible/roles

добавление ~/.ansible/roles нужно для того чтобы подцеплялись galaxy роли.
А конечная структура вышла вот такой:

.
├── inventories
│ ├── global.yml
│ ├── inventory1
│ │ ├── group_vars
│ │ │ ├── all
│ │ │ │ ├── global.yml -> ../../../global.yml
│ │ │ │ └── local.yml
│ │ │ ├── group1.yml
│ │ │ ├── ...
│ │ │ └── groupN.yml
│ │ ├── host_vars
│ │ │ ├── host1.yml
│ │ │ ├── ...
│ │ │ └── hostN.yml
│ │ └── hosts
│ └── inventory2
│ ├── group_vars
│ │ ├── all
│ │ │ ├── global.yml -> ../../../global.yml
│ │ │ └── local.yml
│ │ ├── group1.yml
│ │ ├── ...
│ │ └── groupN.yml
│ ├── host_vars
│ │ ├── host1.yml
│ │ ├── ...
│ │ └── hostN.yml
│ └── hosts
├── playbooks
│ ├── inventory1
│ │ ├── play1.yml
│ │ ├── ...
│ │ └── playN.yml
│ └── inventory2
│ ├── play1.yml
│ ├── ...
│ └── playN.yml
├── roles
│ ├── role1
│ ├── ...
│ └── roleN
├── ansible.cfg
├── tabpy.yml
├── test.yaml
├── wireguard.yml
└── zookeeper.yml

Запуск плейбука осуществляется из директории с ансиблом такой командой:

ansible-playbook -i inventories/inventory1/hosts playbooks/inventory1/play1.yml -bD [--tags, ...]
Строка полностью влезает в консоль и не переносится на вторую, что вполне сохраняет удобство. Кроме того такой подход к хранению плейбуков является обратно-совместимым. вы можете оставить часть плейбуков в корневой директории.

Остается до сих пор проблема того, что в нашем кросс-инвентарном файле может быть довольно много переменных. На текущий момент в качестве решения я думаю о том чтобы сделать symlink кросс-платформенных переменных не на файл global.yml, а на директорию, all -> all. в директори можно хранить множество разделенных по смыслу файлов и все они будут подсасываться во время работы. а файл специфичных для инвентаря переменных вынести по классике в group_vars/all.yml. Но это - тема для будущей революции =)

#ansible
Делаем резервные копии postgres на s3 (minio)

В гисте приложен кусочек плейбука ansible который настраивает резервное копирование через mc клиент. скрипт умеет выгружать в bucket по-отдельности каждую базу из списка, оценивать размер и время совершения последнего копирования каждой базы и отдавать эту информацию в формате prometheus, а также подчищать из s3 устаревшие копии.

https://gist.github.com/bykvaadm/434a4eef5392528c9c0e4788937301bc

#postgres
#docker
#patroni
#ansible
#minio
#prometheus
Когда нужно ролью ansible положить некоторое количество файлов, можно это сделать методом создания директорий, а потом каждый файл класть по-отдельности. Но это подход, который не слишком-то удобен и требует переписывать каждый раз код. Для того чтобы автоматизировать этот процесс можно пользоваться фильтрами with_filetree\fileglob:

- name: ensure www dirs
file:
path: "/var/www/{{ item.path }}"
state: directory
owner: root
group: root
mode: 0755
with_filetree:
- files/www
when: item.state == 'directory'

- name: ensure apple files
copy:
src: "{{ item.src }}"
dest: "/var/{{ item.src | regex_search('www/(vhost1|vhost2)/.*') }}"
with_filetree:
- files/www
when: item.state == 'file'


первый таск создаст все директории с учетом вложенных, какие бы не были в files/www, а второй таск положит все файлы какие бы там не были, сохраняя расположение файлов в дереве каталогов.

#ansible
Еще в догонку по ансиблу - о булевых выражениях. Сейчас будет ооочень очевидная вещь, но True и true - это разные вещи. Почему-то раньше я дико тупил и все мои булевые проверки были такими:

{% if anyvar | bool %}
или
when: anyvar | bool
или еще хуже
when: anyvar | bool == "true"


хотя на самом деле всё очень просто. true - строка. True\False - bool.

поэтому объявляем переменную мы так:

anyvar: True


и проверку делаем так:

when: anyvar
{% if anyvar %}


#ansible #очевидное