В этом посте я хотел бы поделиться своим опытом использования системы управления конфигурациями SaltStack, и, в частности, её применением в Masterless режиме при помощи salt-ssh компонента.
По сути, salt-ssh является аналогом системы Ansible.
salt-ssh '*-ec2.mydomain.com' test.ping
Будут затронуты следующие темы:
Почему SaltStack, ключевые особенности
Когда я несколько лет назад, вдоволь насытившись puppet (multiple environments, 100+ nodes), для нового проекта выбирал новую же систему управления конфигурациями, то Masterless режим работы был ключевым требованием. Но также хотелось сохранить возможность 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 умеет еще работать и в Agentless режиме. Что может быть реализовано при помощи локального запуска стейтов либо при помощи 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
