[Из песочницы] SaltStack: использование salt-ssh
В этом посте я хотел бы поделиться своим опытом использования системы управления конфигурациями SaltStack, и, в частности, её применением в Masterless режиме при помощи salt-ssh компонента.
По сути, salt-ssh является аналогом системы Ansible.
salt-ssh '*-ec2.mydomain.com' test.ping
Будут затронуты следующие темы:
- Почему SaltStack, ключевые особенности
- Базовые понятия SaltStack
- Salt-ssh установка и использование
Почему SaltStack, ключевые особенности
Когда я несколько лет назад, вдоволь насытившись puppet (multiple environments, 100+ nodes), для нового проекта выбирал новую же систему управления конфигурациями, то Master less режим работы был ключевым требованием. Но также хотелось сохранить возможность Master-Slave режима работы. Хотелось хорошей, обширной документации и гибкости. Хотелось уметь управлять облачными инфраструктурами.
А еще хотелось построить систему, в которой будут легко уживаться самые разные окружения. Все это удалось сделать при помощи salt-ssh.
Salt-ssh это компонент SaltStack, который, как и Ansible, использует ssh для соединения с удаленными машинами и не требует никаких предварительных настроек со стороны удаленных машин. Никаких агентов. Pure ssh!
Конечно, при выборе системы я рассматривал и Ansible. Но чаша весов тогда склонилась к SaltStack.
В отличие от Ansible, SaltStack использует Jinja2 как для обработки шаблонов, так и для построения логики.
Причем эту логику можно построить практически любыми способом. Что с одной стороны и хорошо и плохо. Хорошо т.к. даёт гибкость. Но плохо, т.к. нет одного стандартного способа и подхода к реализации. Мне даже кажется, что SaltStack больше представляет из себя конструктор в этом плане.
Также, рендеринг шаблонов и логики происходит на этапе запуска. Полученный пакет шаблонов, настроек и инструкций копируется на удаленный сервер и выполняется. По завершению выполнения salt-ssh выдает в консоль отчет, что было выполнено, а с чем произошли ошибки, если они возникли. Здесь отличие с ansible очень разительно. Последний выполняет задачи\плейбуки поочередно, в режиме shell-скрипта. Не скрою, что наблюдать за ходом выполнения ansible скриптов приятнее, но постепенно все это отходит на второй план, когда число хостов переваливает за несколько десятков. Также, в с равнении с ansible, SaltStack имеет более высокий уровень абстракции.
Как бы то ни было, и ansible и salt-ssh это два очень интересных инструмента, каждый из которых имеет свои плюсы и минусы.
Базовые понятия SaltStack
SaltStack — это система управления конфигурациями и инфраструктурой. Как на уровне отдельных серверов, так и в различных облачных платформах (SaltCloud). Также это система удаленного выполнения команд. Написана на python. Очень бурно развивается. Имеет множество самых разных модулей и возможностей, в том числе даже такие как Salt-Api и Salt-Syndic (master of masters или система, позволяющая строить иерархию мастер серверов, тобишь синдикат).
По умолчанию SaltStack подразумевает Master-Slave режим работы. Обмен сообщениями между нодами происходит через ZeroMQ протокол. Умеет горизонтально масштабироваться при помощи MultiMaster настроек.
Но самое приятное, Salt умеет еще работать и в Agent Less режиме. Что Может быть реализовано при помощи локального запуска стейтов либо при помощи salt-ssh, героя данного топика.
Salt master — процесс, работающий на машине, с которой происходит управление подключенными агентами. В случае с salt-ssh можно назвать мастером ту ноду, где лежат наши state и pillar данные
Salt minion — процесс, работающий на управляемых машинах, т.е. slave. В случае с salt-ssh minion это любой удаленный сервер
State — декларативное представление состояния системы (аналог playbooks в ansible)
Grains — статическая информация об удаленном minion (RAM, CPUs, OS, etc)
Pillar — переменые для одного или более minions
top.sls — центральные файлы, реализующие логику какие state и pillar данные какому minion назначить
highstate — все определенные state данные для minion
SLS — так называются все конфигурационные файлы для pillar\states в SaltStack, используется YAML
Одним из недостатков системы SaltStack является более высокий порог вхождения. Далее я покажу примеры, чтобы начать работать с этой замечательной системой было проще.
Salt-ssh установка и использование
Установка salt-ssh происходит тривиально.
На сайте https://repo.saltstack.com/ есть все необходимые репозитории и инструкции для подключения их в разнообразных системах.
Для установки нужен только сам salt-ssh.
sudo apt-get install salt-ssh
(На примере Deb систем)
Подготовка тестового окружения и Vagrant
Чтобы начать использовать salt-ssh нам достаточно установить его. Как минимум можно управлять своей локальной машиной, либо любым удаленным сервером, что гораздо наглядней.
В данном же примере, для тестов я буду использовать две виртуальные машины, созданные при помощи Vagrant. На одной из них будет установлен собственно сам salt-ssh, другая будет чистая, не считая подключенного публичного ключа с первой машины.
Сам Vagrantfile и необходимые salt states выложены в репозиторий https://github.com/skandyla/saltssh-intro.
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
# VM with salt-ssh
config.vm.define :"saltsshbox" do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.hostname = "saltsshbox"
config.vm.network "private_network", ip: "192.168.33.70"
config.vm.provider "virtualbox" do |vb|
vb.memory = "512"
vb.cpus = 2
end
config.vm.synced_folder ".", "/srv"
# Deploy vagrant insecure private key inside the VM
config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "~/.ssh/id_rsa"
# Install salt-ssh
config.vm.provision "shell", inline: <<-SHELL
wget -O - https://repo.saltstack.com/apt/ubuntu/14.04/amd64/latest/SALTSTACK-GPG-KEY.pub | sudo apt-key add -
sudo echo 'deb http://repo.saltstack.com/apt/ubuntu/14.04/amd64/latest trusty main' > /etc/apt/sources.list.d/saltstack.list
sudo apt-get update
sudo apt-get install -y salt-ssh
SHELL
end
# VM for testing
config.vm.define :"testserver" do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.hostname = "testserver"
config.vm.network "private_network", ip: "192.168.33.75"
config.vm.provider "virtualbox" do |vb|
vb.memory = "512"
end
# Deploy vagrant public key
config.vm.provision "shell", inline: <<-SHELL
curl https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub >> ~/.ssh/authorized_keys2 2>/dev/null
curl https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub >> /home/vagrant/.ssh/authorized_keys2 2>/dev/null
SHELL
end
end
Я предполагаю что аудитория знакома с Vagrant, но на всякий случай: Vagrant — это своееобразный фреймворк для систем виртуализации, предназначенный для упрощения процесса разработки и создания его воспроизводимым. Для запуска виртуальных машин нам понадобятся установленными сам Vagrant и Virtualbox.
Дальше клонируем репозиторий:
git clone https://github.com/skandyla/saltssh-intro
и в нем инициализируем Vagrant виртуальные машины:
vagrant up
После запуска последних, заходим в saltsshbox:
vagrant ssh saltsshbox
Вся дальнейшая работа будет вестись из данной виртуальной машины. По умолчанию SaltStack предполагает что мы будет действовать от имени root, поэтому сразу делаем:
vagrant@saltsshbox:~$ sudo -i
Понимание salt roster
Целевые хосты прописываются в файл /etc/salt/roster, однако, можно указать любой сторонний roster file. В некотором смысле можно провести аналогии с inventory файлами ansible. Roster файл, представляет собой YAML, с множеством разных опций. Ниже показаны несколько способов записи одного и того же хоста.
testserver:
host: 192.168.33.75
priv: /home/vagrant/.ssh/id_rsa
thesametestserver:
host: 192.168.33.75
user: vagrant
sudo: True
thesametestserver2:
host: 192.168.33.75
user: vagrant
passwd: vagrant
sudo: True
Теперь попробуем выполнить команду test.ping
применительно ко всем хостам, указанным в нашем ростере.
root@saltsshbox:~# salt-ssh -i --roster-file=/srv/saltstack/saltetc/roster_test '*' test.ping
Permission denied for host thesametestserver, do you want to deploy the salt-ssh key? (password required):
[Y/n] n
thesametestserver:
----------
retcode:
255
stderr:
Permission denied (publickey,password).
stdout:
testserver:
True
thesametestserver2:
True
Как видите, salt-ssh слегка ругнулся, что не может зайти на удаленный сервер и предложил туда развернуть ключ, но я отменил это. Остальные два сервера (по факту один под разными именами) ответили положительно. Произошло это потому что мы работает из-под root, для которого никаких ssh-ключей не определено. Поэтому можно просто добавить ключ через ssh-agent и снова повторить команду.
root@saltsshbox:~# eval `ssh-agent`; ssh-add /home/vagrant/.ssh/id_rsa
Agent pid 2846
Identity added: /home/vagrant/.ssh/id_rsa (/home/vagrant/.ssh/id_rsa)
root@saltsshbox:~# salt-ssh -i --roster-file=/srv/saltstack/saltetc/roster_test '*' test.ping
testserver:
True
thesametestserver:
True
thesametestserver2:
True
Теперь все хорошо! Мало того, можно запросто добавить ключ с паролем через ssh-agent. Но если же вы решите деплоить ключ, который предлагает сам salt, то брать его по умолчанию он будет вот по такому пути: /etc/salt/pki/master/ssh/salt-ssh.rsa
Здесь для теста я намеренно работал с отдельным roster файлом, чтобы показать интересные нюансы. Для дальнейшей же работы указывать roster нам будет не нужно, потому что он уже и так через symlink указан в требуемое место (/etc/salt/roster). Ключ -i необходим когда мы начинаем работать с новыми хостами, он просто запрещает StrictHostKeyChecking, давая возможность принять новый host key. Для дальнейшей работы он нам тоже будет не нужен.
root@saltsshbox:~# salt-ssh '*' test.ping
testserver:
True
Напомню, что по умолчанию salt смотрит на roster здесь: /etc/salt/roster в котором у нас сейчас определен только один хост.
Удаленное выполнение команд
Теперь когда мы убедились что наша машина с salt-ssh прекрасно видит указанный в roster тестовый сервер, поработаем с ним в стиле ad-hoc.
root@saltsshbox:~# salt-ssh testserver cmd.run "uname -a"
testserver:
Linux testserver 3.13.0-87-generic #133-Ubuntu SMP Tue May 24 18:32:09 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
cmd.run это по сути аналог ключа -a
в ansible.
Также можно использовать встроенные модуля saltstack, например:
salt-ssh testserver service.get_enabled
salt-ssh testserver pkg.install git
salt-ssh testserver network.interfaces
salt-ssh testserver disk.usage
salt-ssh testserver sys.doc
Последняя команда выдаст документацию об модулях и, главное, примеры их использования. Дополнительно можно посмотреть полный список доступных модулей Saltstack.
Salt Grains или факты о системе
Grains это мощный механизм, представляющий набор фактов об удаленной системе. В дальнейшем на основе Grains можно также строить различную логику.
Но для начала посмотрим как с ними начать работать:
root@saltsshbox:~# salt-ssh testserver grains.items
testserver:
----------
SSDs:
biosreleasedate:
12/01/2006
biosversion:
VirtualBox
cpu_flags:
- fpu
- vme
- de
- pse
- tsc
...
cpu_model:
Intel(R) Core(TM) i7-2620M CPU @ 2.70GHz
cpuarch:
x86_64
disks:
- sda
...
Вывод команды урезан.
К нужной ветви Grains можно обратиться, непосредственно указав её:
root@saltsshbox:~# salt-ssh testserver grains.get 'ip4_interfaces'
testserver:
----------
eth0:
- 10.0.2.15
eth1:
- 192.168.33.75
lo:
- 127.0.0.1
Либо даже более конкретно:
root@saltsshbox:~# salt-ssh testserver grains.get 'ip4_interfaces:eth1'
testserver:
- 192.168.33.75
Salt master файл и top.sls
Теперь пришла пора рассказать про еще один важный файл /etc/salt/master. Вообще он идет в комплекте с пакетом salt-master и определяет некоторые важные опции логирования и директории в которых salt будет искать наши states и pillar данные. По умолчанию для states определена директория /srv/salt. Но на практике часто более рационально использовать другую структуру, в том числе и для этих примеров.
/etc/salt/master:
state_verbose: False
state_output: mixed
file_roots:
base:
- /srv/saltstack/salt
pillar_roots:
base:
- /srv/saltstack/pillar
state_verbose и state_output это переменные, которые отвечают за отображение статуса выполнения на экране. На мой взгляд такая комбинация наиболее практична, но рекомендую поэкспериментировать.
file_roots и pillar_roots указывают пути к нашим state и pillar данным соотвественно.
Важно! этих путей может быть несколько. По принципу разных окружений, разных данных и т.д. и т.п., но это тема для отдельной статьи по настройке multi-environment среды, для начала нам же просто необходимо знать, куда нам положить наши state файлы, чтобы salt их нашел.
Далее, в каждой из этой директорий (file_roots и pillar_roots) сальт будет искать файлы top.sls, которые определяют уже дальнейшую логику обработки salt файлов.
В нашем случае:
/srv/saltstack/salt/top.sls:
base:
'*':
- common
- timezone
'testserver':
- chrony
Что означает, для всех хостов применить state common и timezone, а для testserver применить еще и chrony (сервис синхронизации времени).
Для pillar также необходим top.sls файл. Который будет определять в каком порядке и как будут назначаться переменные.
/srv/saltstack/pillar/top.sls:
base:
'*':
- timezone
'testserver':
- hosts/testserver
В нашем случае файл этот предельно простой, указано лишь подключить все переменные из файла timezone.sls и еще подключить переменые из файла hosts/testserver для нашего testserver, однако, за этой простотой скрывается мощная концепция, т.к. переменные можно назначить как угодно и на какое угодно окружение. Правда, перекрытие и слияние переменных (Variables Overriding and Merging) это отдельная тема, пока скажу что приоритетность задается сверху вниз. Т.е. если бы у нас здесь в файле hosts/testserver.sls содержались переменные с timezone, то они бы имели преимущество.
В файлах top.sls все указывается без расширения .sls.
Работа с salt states
Приступим к простенькому state:
/srv/saltstack/salt/packages.sls:
# Install some basic packages for Debian systems
{% if grains['os_family'] == 'Debian' %}
basepackages:
pkg.installed:
- pkgs:
- lsof
- sysstat
- telnet
{% endif %}
Как видно, здесь мы применили и jinja и grains и собственно сам pkg модуль.
Попробуем применить этот state в тестовом режиме:
root@saltsshbox:/srv/saltstack# salt-ssh testserver state.sls packages test=true
[INFO ] Fetching file from saltenv 'base', ** done ** 'packages.sls'
testserver:
Name: basepackages - Function: pkg.installed - Result: Differs
Summary for testserver
------------
Succeeded: 1 (unchanged=1)
Failed: 0
------------
Total states run: 1
И далее уже в реальном:
root@saltsshbox:/srv/saltstack# salt-ssh testserver state.sls packages
[INFO ] Fetching file from saltenv 'base', ** skipped ** latest already in cache 'salt://packages.sls'
testserver:
Name: basepackages - Function: pkg.installed - Result: Changed
Summary for testserver
------------
Succeeded: 1 (changed=1)
Failed: 0
------------
Total states run: 1
Salt Pillar или переменные
Следующее важное звено — это Pillar. Так в SaltStack называется переменные, или всё что задается со стороны мастера для удаленных систем. Частично они уже вам знакомы из описанного выше, поэтому сразу к делу.
Получить все pillar переменные, определенные для хоста:
root@saltsshbox:~# salt-ssh testserver pillar.items
testserver:
----------
chrony:
----------
lookup:
----------
custom:
# some custom addons
# if you need it
timezone:
----------
name:
Europe/Moscow
Также как и с Grains можно запросить отдельно взятую переменную:
salt-ssh testserver pillar.get 'timezone:name'
Использование state вместе с pillar
Рассмотрим следующий state:
/srv/saltstack/salt/timezone.sls:
{%- set timezone = salt['pillar.get']('timezone:name', 'Europe/Dublin') %}
{%- set utc = salt['pillar.get']('timezone:utc', True) %}
timezone_settings:
timezone.system:
- name: {{ timezone }}
- utc: {{ utc }}
Здесь мы задали переменную на основе данных из pillar. Причем в этой конструкции:
{%- set timezone = salt['pillar.get']('timezone:name', 'Europe/Dublin') %}
Europe/Dublin — это значение по умолчанию, если по каким-то причинам salt не сможет получить значение из Pillar.
root@saltsshbox:/srv/saltstack# salt-ssh testserver state.sls timezone
[INFO ] Fetching file from saltenv 'base', ** skipped ** latest already in cache 'salt://timezone.sls'
testserver:
Name: Europe/Moscow - Function: timezone.system - Result: Changed
Summary for testserver
------------
Succeeded: 1 (changed=1)
Failed: 0
------------
Total states run: 1
Real Life пример
И вот, наконец-то, мы добрались уже до реального жизненного примера. Рассмотрим state синхронизации времени — chrony. Находится он у нас здесь:
/srv/saltstack/salt/chrony/init.sls
Причем init.sls это индекс по умолчанию, salt ищет его автоматически, но можно использовать и любой другой файл.
Здесь мы введем еще одну типичную конструкцию для salt — это map.jinja.
/srv/saltstack/salt/chrony/map.jinja:
{% set chrony = salt['grains.filter_by']({
'RedHat': {
'pkg': 'chrony',
'conf': '/etc/chrony.conf',
'service': 'chronyd',
},
'Debian': {
'pkg': 'chrony',
'conf': '/etc/chrony/chrony.conf',
'service': 'chrony',
},
}, merge=salt['pillar.get']('chrony:lookup')) %}
Её назначение — это создать необходимый набор статических переменных под нашу систему, но с возможностью слияния с переменными из pillar, если таковые вдруг понадобится указать.
Дальше сам /srv/saltstack/salt/chrony/init.sls:
{% from "chrony/map.jinja" import chrony with context %}
chrony:
pkg.installed:
- name: {{ chrony.pkg }}
service:
- name: {{ chrony.service }}
- enable: True
- running
- require:
- pkg: {{ chrony.pkg }}
- file: {{ chrony.conf }}
{{ chrony.conf }}:
file.managed:
- name: {{ chrony.conf }}
- source: salt://chrony/files/chrony.conf.jinja
- template: jinja
- user: root
- group: root
- mode: 644
- watch_in:
- service: {{ chrony.service }}
- require:
- pkg: {{ chrony.pkg }}
Здесь отдельного внимания заслуживает шаблон salt://chrony/files/chrony.conf.jinja формата jinja.
/srv/saltstack/salt/chrony/files/chrony.conf.jinja:
# managed by SaltStack
{%- set config = salt['pillar.get']('chrony:lookup', {}) -%}
{%- set vals = {
'bindcmdaddress': config.get('bindcmdaddress','127.0.0.1'),
'custom': config.get('custom', ''),
}%}
### chrony conf
server 0.centos.pool.ntp.org iburst
server 1.centos.pool.ntp.org iburst
server 2.centos.pool.ntp.org iburst
server 3.centos.pool.ntp.org iburst
stratumweight 0
driftfile /var/lib/chrony/drift
rtcsync
makestep 10 3
bindcmdaddress {{ vals.bindcmdaddress }}
bindcmdaddress ::1
keyfile /etc/chrony.keys
commandkey 1
generatecommandkey
noclientlog
logchange 0.5
logdir /var/log/chrony
{% if vals.custom -%}
{{ vals.custom }}
{%- endif %}
В этом шаблоне мы также запрашиваем переменные из Pillar и обрабатываем их. Просмотреть как этот state воспринялся salt можно при помощи state.show_sls:
root@saltsshbox:/srv/saltstack# salt-ssh testserver state.show_sls chrony
[INFO ] Fetching file from saltenv 'base', ** done ** 'chrony/init.sls'
[INFO ] Fetching file from saltenv 'base', ** done ** 'chrony/map.jinja'
testserver:
----------
/etc/chrony/chrony.conf:
----------
__env__:
base
__sls__:
chrony
file:
|_
----------
name:
/etc/chrony/chrony.conf
|_
----------
source:
salt://chrony/files/chrony.conf.jinja
|_
----------
template:
jinja
|_
----------
user:
root
|_
----------
group:
root
|_
----------
mode:
644
|_
----------
watch_in:
|_
----------
service:
chrony
|_
----------
require:
|_
----------
pkg:
chrony
- managed
|_
----------
order:
10002
chrony:
----------
__env__:
base
__sls__:
chrony
pkg:
|_
----------
name:
chrony
- installed
|_
----------
order:
10001
service:
|_
----------
name:
chrony
|_
----------
enable:
True
- running
|_
----------
require:
|_
----------
pkg:
chrony
|_
----------
file:
/etc/chrony/chrony.conf
|_
----------
order:
10000
|_
----------
watch:
|_
----------
file:
/etc/chrony/chrony.conf
Дальше уже просто выполним его:
root@saltsshbox:/srv/saltstack# salt-ssh testserver state.sls chrony
testserver:
Name: chrony - Function: pkg.installed - Result: Changed
Name: /etc/chrony/chrony.conf - Function: file.managed - Result: Changed
Name: chrony - Function: service.running - Result: Changed
Summary for testserver
------------
Succeeded: 3 (changed=3)
Failed: 0
------------
Total states run: 3
Здесь salt докладывает о 3х выполненных state по суммарному колличеству задействованных модулей. Если выполнить повторно, то видно что никаких изменений не было произведено:
root@saltsshbox:/srv/saltstack# salt-ssh testserver state.sls chrony
testserver:
Summary for testserver
------------
Succeeded: 3
Failed: 0
------------
Total states run: 3
Сразу же можно и посмотреть как сформировался конфигурационный файл для chrony:
salt-ssh testserver cmd.run 'cat /etc/chrony/chrony.conf'
Финально стоит упомянуть еще команду state.highstate.
salt-ssh testserver state.highstate
Она применяет все прописанные state для нашего тестового сервера.
Заключение
Итак, мы узнали чтоже из себя представляет salt-ssh из комплекта SaltStack и как его использовать. Узнали ключевые особенности построения окружения, необходимого для работы salt-ssh. Настроили тестовую среду при помощи Vagrant. И планомерно провели эксперименты с фундаментальными концепциями SaltStack, такими как: Grains, States, Pillar. Также мы узнали как писать state от простого к сложному, дойдя до реальных примеров, позволяющих на своей базе уже строить дальнейшую автоматизацию.
На этом пока все. За бортом осталось еще множество интересных тем, но я надеюсь что эта информация поможет начать работать с этой замечательной системой управления конфигурациями.
Полезная информация:
best_practices
walkthrough
starting_states
pillar
formulas
tutorials