Предполагается, что читающий знаком с saltstack (https://www.saltstack.com/), писал для него стейты, подключал пиллары или еще чего-нибудь). В этой заметке попробуем реализовать свой state через cmd.run - он будет изменять данные только в случае необходимости плюс покажет нам, что он будет менять в режиме test=true.
Задачка - пытаемся написать свой самописный state через использование cmd.run. Один из примеров использования - планируем через salt менять переменную mysql MAX_USER_CONNECTIONS для пользователей БД. Прямо точного стейта солта под этого функционал нет, обычно используют для этого модуль mysql.query.run (позволяет выполнять произвольные sql-команды).
Если опустить частности, то реализация через mysql.query.run может выглядеть так:
У нас на одном сервере может быть несколько рабочих инстансов БД, поэтому задаем такую структуру, pillar example:
mysql:
system:
server_pkg: percona-server-server-5.7
instances:
master1:
mysql_users:
cacti:
{{ credentials(password='*9D26') }}
databases_privileges:
"*.*":
present: SELECT, PROCESS, SUPER
max_user_connections: '20'
...
master1 - это название инстанса, у него есть пользователи (cacti и т.д.)
Стейт файл mysql.users (папка mysql/, файл users.sls в проекте salt):
{% for instance, data in mysql.instances.iteritems() %}
{% for user, properties in data.mysql_users.iteritems() %}
...
{% if properties.max_user_connections is defined %}
limit_number_connections-{{ instance }}-{{ user }}:
mysql_query.run:
- connection_default_file: {{ data.debian.debian_cnf }}
- database: 'mysql'
- query: {{ "ALTER USER " ~ user ~ "@'%' WITH MAX_USER_CONNECTIONS " ~ properties.max_user_connections ~ ';' }}
- output: "/tmp/salt_user_connections"
{% endif %}
{% endfor %}
В этом коде говорим, что для каждого инстанса mysql для каждого пользователя будет выполняться данный sql-запрос. Все бы хорошо, но неудобно то, что этот модуль будет исполняться каждый раз и каждый раз будет в терминале подсвечиваться что данный стейт выполнился, что он поменял значение перемнной mysql у пользователя, хотя может это и не требовалось (тоже самое будет и в режиме test=true), вдобавок пользователей может пару десятков => вывод в терминале будет захламлен информацией, можем пропустить что-то действительно важное, что может изменить state mysql.users.
Хочется сделать такую же логику, как и в стандартных стейтах солта - в test=true покажет, что он собирается изменить (в случае если это действительно требуется), в рабочем режиме - покажет что изменил, в последующем "прогоне" стейта не будет пытаться что-то менять. Все это также выражается в соответствующей подсветке терминала (желтым подсвечивает тогда, когда солт планирует изменить или уже поменял, зеленым - если ничего не требуется делать (расцветка может отличаться в зависимости от расцветки фона искомого терминала - он у меня белый, а не черный как обычно)).
Можно написать свой отдельный стейт именно под эту задачу (думаю это будет сложнее), а можно воспользоваться модулем cmd.run. Для этого нам понадобится stateful аргумент в cmd стейте. Документация солта на этот параметр.
Выполним 3 действия:
- напишем специальный shell скрипт
- копирование этого shell-скрипта на минион
- запуск скрипта в стейт-файле mysql.users через state cmd.run
Интересующая нас подсветка в терминале в стейте солта регулируется через простой протокол, на stdout подаем следующее:
echo "changed=yes comment='some comment1' - подсветит в терминале echo "changed=no comment="some comment2" - ничего не подсветит
Стало
{# копируем наш shell скрипт #}
{% set script_path = '/root/mysql-max-user-connections.sh' %}
copy-script-check_max_user_connections:
file.managed:
- name: {{ script_path }}
- source: "salt://mysql/files/mysql-max-user-connections.sh"
- user: root
- group: root
- mode: 700
...
{% if properties.max_user_connections is defined %}
run-script-mysql-max-user-connections-{{ instance }}-{{ user }}:
cmd.run:
- name: /root/mysql-max-user-connections.sh -i {{ instance }} -u {{ user }} -l {{ properties.max_user_connections }}
- stateful:
- test_name: /root/mysql-max-user-connections.sh -i {{ instance }} -u {{ user }} -l {{ properties.max_user_connections }} -t true
{% endif %}
Будет вызываться скрипт, который проверяет текущий max_user_connections, если он не отличается от заданного в пилларе, то ничего не делать; если отличается - применить изменения.
Соответственно, мы в нашем shell скрипте реализуем данную логику - передаем ему аргументами инстанс и пользователя, сверяем все, если надо - меняем параметр в БД, дополнительно в зависимости от варианта формируем нужный stdout, по которому salt будет понимать изменилось что-либо или нет.
Если бы просто написали
- stateful: True
то наш стейт cmd.run работал бы корректно только для варианта test=false (т.е. когда мы применяем изменения), для test=true подсветка в терминале говорила бы, что этот стейт будет менять переменную max_user_connections каждый раз. Чтобы и для test=true все работало, мы аргументу stateful передаем название скрипта, который будет исполняться; для удобства это тот же самый shell-скрипт, которому дополнительно передаем аргумент -t true.
Т.е. для salt-call state.sls mysql.users test=true будет вызываться скрипт
/root/mysql-max-user-connections.sh -i {{ instance }} -u {{ user }} -l {{ properties.max_user_connections }} -t true
Пример как это будет выглядеть после вызова (изменена настройка max_user_connections у пользователя cacti):
ID: run-script-mysql-max-user-connections-master1-cacti
Function: cmd.run
Name: /root/mysql-max-user-connections.sh -i master1 -u cacti -l 20
Result: None
Comment: Test mode: Will alter MAX_USER_CONNECTIONS for cacti in instance master1
Started: 15:17:21.734542
Duration: 23.457 ms
Changes:
Для salt-call state.sls mysql.users:
/root/mysql-max-user-connections.sh -i {{ instance }} -u {{ user }} -l {{ properties.max_user_connections }}
Что увидим в терминале:
ID: run-script-mysql-max-user-connections-apr-cacti_monitor
Function: cmd.run
Name: /tmp/mysql-max-user-connections.sh -i master1 -u cacti -l 20
Result: True
Comment: Alter MAX_USER_CONNECTIONS for cacti in instance master1
Started: 15:19:26.544489
Duration: 29.948 ms
Changes:
----------
changed:
yes
pid:
28250
retcode:
0
stderr:
stdout:
Наш shell скрипт:
#!/bin/bash
while getopts "i:u:l:t:" opt
do
case $opt in
i) instance=${OPTARG};;
u) user=${OPTARG};;
l) desire_user_limit=${OPTARG};;
t) test=${OPTARG};;
esac
done
sql_get_info="SELECT max_user_connections FROM mysql.user where User='$user' and Host='%';"
current_user_limit=`mysql --defaults-file=/etc/mysql/debian-$instance.cnf -s -N -e "$sql_get_info"`
if [ "$test" = "true" ]
then
if [ "$desire_user_limit" = "$current_user_limit" ]
then
echo "" ## This echos an empty line and is required
echo "changed=no comment='Test mode: MAX_USER_CONNECTIONS is actual for $user in instance $instance'"
else
echo "" ## This echos an empty line and is required
echo "changed=yes comment='Test mode: Will alter MAX_USER_CONNECTIONS for $user in instance $instance'"
fi
exit 0
else
if [ "$desire_user_limit" = "$current_user_limit" ]
then
echo "" ## This echos an empty line and is required
echo "changed=no comment='MAX_USER_CONNECTIONS is actual for $user in instance $instance'"
else
sql_edit_max_user_connections="ALTER USER $user@'%' WITH MAX_USER_CONNECTIONS $desire_user_limit;"
edit_max_user_connections=`mysql --defaults-file=/etc/mysql/debian-$instance.cnf -s -N -e "$sql_edit_max_user_connections"`
echo "" ## This echos an empty line and is required
echo "changed=yes comment='Alter MAX_USER_CONNECTIONS for $user in instance $instance'"
fi
fi
Bash скрипт для версии mysql 5.7 и выше (там синтаксис изменения переменной max_user_connections чуть другой, чем в 5.6 и ниже).
Вот так, набросали свой простенький стейт через готовый функционал saltstack, теперь мы меняем параметр БД только когда это нужно!