Salt
В данном практическом занятии познакомимся с базовым использованием saltstack.
Vagrant
Для работы с salt будем использовать следующий Vagrantfile
:
Vagrant.configure("2") do |config|
config.vm.define "master" do |c|
c.vm.box = "ubuntu/lunar64"
c.vm.hostname = "master"
c.vm.network "private_network", type: "dhcp"
c.vm.provision "shell", inline: <<-SHELL
curl -fsSL -o /etc/apt/keyrings/salt-archive-keyring-2023.gpg \
https://repo.saltproject.io/salt/py3/ubuntu/22.04/amd64/SALT-PROJECT-GPG-PUBKEY-2023.gpg
echo "deb [signed-by=/etc/apt/keyrings/salt-archive-keyring-2023.gpg arch=amd64] https://repo.saltproject.io/salt/py3/ubuntu/22.04/amd64/latest jammy main" \
| tee /etc/apt/sources.list.d/salt.list
apt-get update -q
apt-get install -yq libnss-mdns salt-master salt-minion
systemctl enable --now salt-master.service
SHELL
end
config.vm.define "minion1" do |c|
c.vm.box = "ubuntu/lunar64"
c.vm.hostname = "minion1"
c.vm.network "private_network", type: "dhcp"
c.vm.provision "shell", inline: <<-SHELL
curl -fsSL -o /etc/apt/keyrings/salt-archive-keyring-2023.gpg \
https://repo.saltproject.io/salt/py3/ubuntu/22.04/amd64/SALT-PROJECT-GPG-PUBKEY-2023.gpg
echo "deb [signed-by=/etc/apt/keyrings/salt-archive-keyring-2023.gpg arch=amd64] https://repo.saltproject.io/salt/py3/ubuntu/22.04/amd64/latest jammy main" \
| tee /etc/apt/sources.list.d/salt.list
apt-get update -q
apt-get install -yq libnss-mdns salt-minion
echo 'master: master.local' > /etc/salt/minion.d/master.conf
systemctl restart salt-minion.service
systemctl enable salt-minion.service
SHELL
end
config.vm.define "minion2" do |c|
c.vm.box = "ubuntu/lunar64"
c.vm.hostname = "minion2"
c.vm.network "private_network", type: "dhcp"
c.vm.provision "shell", inline: <<-SHELL
curl -fsSL -o /etc/apt/keyrings/salt-archive-keyring-2023.gpg \
https://repo.saltproject.io/salt/py3/ubuntu/22.04/amd64/SALT-PROJECT-GPG-PUBKEY-2023.gpg
echo "deb [signed-by=/etc/apt/keyrings/salt-archive-keyring-2023.gpg arch=amd64] https://repo.saltproject.io/salt/py3/ubuntu/22.04/amd64/latest jammy main" \
| tee /etc/apt/sources.list.d/salt.list
apt-get update -q
apt-get install -yq libnss-mdns salt-minion
echo 'master: master.local' > /etc/salt/minion.d/master.conf
systemctl restart salt-minion.service
systemctl enable salt-minion.service
SHELL
end
end
Команды будут выполняться на машине master
из под пользователя root
.
Для этого после команды vagrant ssh
можно выполнить команду sudo -i
.
Basic Usage
Masterless
После установки пакетов на машине master
появится ряд утилит для работы
с saltstack. Для выполнения функций локально на миньоне можно воспользоваться
командой salt-call
. Чтобы при этом не производилось обращений к мастеру
можно воспользоваться опцией --local
. Таким образом можно вызвать, например,
функцию network.ping
без использования мастера:
# salt-call --local network.ping 1.1.1.1
local:
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=63 time=25.9 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=63 time=23.8 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=63 time=23.6 ms
64 bytes from 1.1.1.1: icmp_seq=4 ttl=63 time=24.0 ms
--- 1.1.1.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 23.608/24.338/25.946/0.940 ms
Переданная функция здесь состоит из имени модуля network
и имени функции ping
.
Documentation
Документацию по функции можно получить передав ее имя функции sys.doc
:
# salt-call --local sys.doc network.ping
local:
----------
network.ping:
Performs an ICMP ping to a host
Changed in version 2015.8.0
Added support for SunOS
CLI Example:
salt '*' network.ping archlinux.org
New in version 2015.5.0
Return a True or False instead of ping output.
salt '*' network.ping archlinux.org return_boolean=True
Set the time to wait for a response in seconds.
salt '*' network.ping archlinux.org timeout=3
Также можно получить и информацию по всему модулю:
# salt-call --local sys.doc network | head -100
local:
----------
network.active_tcp:
Return a dict containing information on all of the running TCP connections (currently linux and solaris only)
Changed in version 2015.8.4
Added support for SunOS
CLI Example:
salt '*' network.active_tcp
network.arp:
Return the arp table from the minion
Changed in version 2015.8.0
Added support for SunOS
CLI Example:
salt '*' network.arp
network.calc_net:
А список доступных модулей и функций можно получить вызовом sys.list_modules
и sys.list_functions
соответственно:
# salt-call --local sys.list_modules | head
local:
- aliases
- alternatives
- archive
- artifactory
- baredoc
- bcache
- beacons
- bigip
- btrfs
# salt-call --local sys.list_functions | head
local:
- aliases.get_target
- aliases.has_target
- aliases.list_aliases
- aliases.rm_alias
- aliases.set_target
- alternatives.auto
- alternatives.check_exists
- alternatives.check_installed
- alternatives.display
А также можно просто запустить функцию sys.doc
без аргументов, чтобы получить
документацию по всем функциям выполняемых модулей.
Grains
Salt позволяет получить информацию о системе миньона с помощью механизма называемого
grains. Список параметров, которые возможно получить можно посмотреть вызовом
функции grains.ls
:
# salt-call --local grains.ls | head
local:
- biosreleasedate
- biosvendor
- biosversion
- boardname
- cpu_flags
- cpu_model
- cpuarch
- cwd
- disks
Информацию же можно посмотреть функцией grains.items
для всех параметров или
grains.item
с указанием конкретного:
# salt-call --local grains.item id
local:
----------
id:
master
Minions
Keys
После запуска машин minion1
и minion2
они должны были подключиться к мастеру,
но на этом этапе еще нельзя вызывать команды на них, так как не были подтверждены
их ключи. Для просмотра списка ключей можно воспользоваться командой salt-key -L
:
# salt-key -L
Accepted Keys:
Denied Keys:
Unaccepted Keys:
minion1
minion2
Rejected Keys:
Как видно ключи миньонов находятся в Unaccepted Keys
. Для того чтобы принять
ключи миньонов можно воспользоваться командами salt-key -a
для конкретного ключа
или salt-key -A
, чтобы принять все ключи в разделе Unaccepted Keys
:
# salt-key -a minion1
The following keys are going to be accepted:
Unaccepted Keys:
minion1
Proceed? [n/Y]
Key for minion minion1 accepted.
# salt-key -A
The following keys are going to be accepted:
Unaccepted Keys:
minion2
Proceed? [n/Y]
Key for minion minion2 accepted.
Execution
Теперь выполнять функции на миньонах можно с помощью команды salt
:
# salt '*' grains.item id
minion2:
----------
id:
minion2
minion1:
----------
id:
minion1
Здесь мы получили grains
с информацией о minion_id
, вторым аргументом команды salt
указывается на каких миньонах должна выполниться команда. *
- означает выполнение на
всех миньонах. Задавать цели для исполнения функции можно различными способами,
например явно указывать имя миньона или с опцией -G
можно указать его grains
:
# salt minion1 grains.item ip4_interfaces:enp0s8
minion1:
----------
ip4_interfaces:enp0s8:
- 192.168.56.42
# salt -G 'ip4_interfaces:enp0s8:0:192.168.56.42' grains.item id
minion1:
----------
id:
minion1
State
Salt также позволяет описывать состояния в sls
(Salt State) файлах в yaml
формате.
По-умолчанию используется каталог /srv/salt
, в котором находится верхнеуровневый файл
состояния - top.sls
, который описывает каким миньонам в каких состояниях необходимо
находиться. Создадим директорию /srv/salt
и файл top.sls
со следующим содержимым:
base:
'*':
- nginx
Что означает, что ко всем миньоном будет применено состояние описанное в файле nginx.sls
в этой же директории. Опишем этот файл:
nginx_pkg: # идентификатор состояния
pkg.installed: # функция состояни
- name: nginx # аргументы функции
Данный файл описывает состояние с установленным пакетом nginx
на миньонах, для
применения состояния можно воспользоваться командой salt
с функцией state.apply
.
Для запуска без внесения изменений для проверки производимых операций можно добавить
аргумент test=True
:
# salt '*' state.apply test=True
minion2:
----------
ID: nginx_pkg
Function: pkg.installed
Name: nginx
Result: None
Comment: The following packages would be installed/updated: nginx
Started: 21:52:28.189534
Duration: 101.366 ms
Changes:
----------
nginx:
----------
new:
installed
old:
Summary for minion2
------------
Succeeded: 1 (unchanged=1, changed=1)
Failed: 0
------------
Total states run: 1
Total run time: 101.366 ms
minion1:
----------
ID: nginx_pkg
Function: pkg.installed
Name: nginx
Result: None
Comment: The following packages would be installed/updated: nginx
Started: 21:52:28.275752
Duration: 95.124 ms
Changes:
----------
nginx:
----------
new:
installed
old:
Summary for minion1
------------
Succeeded: 1 (unchanged=1, changed=1)
Failed: 0
------------
Total states run: 1
Total run time: 95.124 ms
Данная команда выводит информацию о том какие изменения будут применены. Попробуем применить состояние:
# salt '*' state.apply
minion1:
----------
ID: nginx_pkg
Function: pkg.installed
Name: nginx
Result: True
Comment: The following packages were installed/updated: nginx
Started: 21:57:15.678622
Duration: 12706.167 ms
Changes:
----------
fontconfig-config:
----------
new:
2.14.1-3ubuntu3
old:
fonts-dejavu-core:
----------
new:
2.37-6
old:
...
Summary for minion2
------------
Succeeded: 1 (changed=1)
Failed: 0
------------
Total states run: 1
Total run time: 13.452 s
Добавим собственную конфигурацию nginx
в /srv/salt/default.conf
:
server {
listen 80;
server_name localhost;
location / {
root /var/www/html;
index index.html;
}
}
А также добавим состояние для применения данной конфигурации и перезапуска сервиса
при ее изменении в файл nginx.sls
:
nginx_pkg: # идентификатор состояния
pkg.installed: # функция состояни
- name: nginx # аргументы функции
nginx_service:
service.running:
- name: nginx
- reload: True
- watch:
- file: /etc/nginx/sites-available/default
file.managed:
- name: /etc/nginx/sites-available/default
- source: salt://default.conf
И применим новое состояние:
# salt '*' state.apply
minion2:
----------
ID: nginx_pkg
Function: pkg.installed
Name: nginx
Result: True
Comment: All specified packages are already installed
Started: 22:16:23.382729
Duration: 25.124 ms
Changes:
...
minion1:
----------
ID: nginx_pkg
Function: pkg.installed
Name: nginx
Result: True
Comment: All specified packages are already installed
Started: 22:16:23.467122
Duration: 25.565 ms
Changes:
----------
ID: nginx_service
Function: file.managed
Name: /etc/nginx/sites-available/default
Result: True
Comment: File /etc/nginx/sites-available/default updated
Started: 22:16:23.495544
Duration: 42.962 ms
Changes:
----------
diff:
---
+++
@@ -1,91 +1,9 @@
-##
-# You should look at the following URL's in order to grasp a solid understanding
-# of Nginx configuration files in order to fully unleash the power of Nginx.
-# https://www.nginx.com/resources/wiki/start/
-# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
-# https://wiki.debian.org/Nginx/DirectoryStructure
-#
-# In most cases, administrators will remove this file from sites-enabled/ and
-# leave it as reference inside of sites-available where it will continue to be
-# updated by the nginx packaging team.
-#
-# This file will automatically load configuration files provided by other
-# applications, such as Drupal or Wordpress. These applications will be made
-# available underneath a path with that package name, such as /drupal8.
-#
-# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
-##
+server {
+ listen 80;
+ server_name localhost;
-# Default server configuration
-#
-server {
- listen 80 default_server;
- listen [::]:80 default_server;
-
- # SSL configuration
- #
- # listen 443 ssl default_server;
- # listen [::]:443 ssl default_server;
- #
- # Note: You should disable gzip for SSL traffic.
- # See: https://bugs.debian.org/773332
- #
- # Read up on ssl_ciphers to ensure a secure configuration.
- # See: https://bugs.debian.org/765782
- #
- # Self signed certs generated by the ssl-cert package
- # Don't use them in a production server!
- #
- # include snippets/snakeoil.conf;
-
- root /var/www/html;
-
- # Add index.php to the list if you are using PHP
- index index.html index.htm index.nginx-debian.html;
-
- server_name _;
-
- location / {
- # First attempt to serve request as file, then
- # as directory, then fall back to displaying a 404.
- try_files $uri $uri/ =404;
- }
-
- # pass PHP scripts to FastCGI server
- #
- #location ~ \.php$ {
- # include snippets/fastcgi-php.conf;
- #
- # # With php-fpm (or other unix sockets):
- # fastcgi_pass unix:/run/php/php7.4-fpm.sock;
- # # With php-cgi (or other tcp sockets):
- # fastcgi_pass 127.0.0.1:9000;
- #}
-
- # deny access to .htaccess files, if Apache's document root
- # concurs with nginx's one
- #
- #location ~ /\.ht {
- # deny all;
- #}
+ location / {
+ root /var/www/html;
+ index index.html;
+ }
}
-
-
-# Virtual Host configuration for example.com
-#
-# You can move that to a different file under sites-available/ and symlink that
-# to sites-enabled/ to enable it.
-#
-#server {
-# listen 80;
-# listen [::]:80;
-#
-# server_name example.com;
-#
-# root /var/www/example.com;
-# index index.html;
-#
-# location / {
-# try_files $uri $uri/ =404;
-# }
-#}
----------
ID: nginx_service
Function: service.running
Name: nginx
Result: True
Comment: Service reloaded
Started: 22:16:23.561905
Duration: 100.347 ms
Changes:
----------
nginx:
True
Summary for minion1
------------
Succeeded: 3 (changed=2)
Failed: 0
------------
Total states run: 3
Total run time: 168.874 ms
Jinja2
Как в передаваемых файлах так и в самих файлах состояний sls
можно использовать шаблоны,
в качестве движка шаблонизации по-умолчанию используется jinja2
. Добавим в файл
nginx.sls
передачу на миньоны файла index.html
из шаблона:
nginx_pkg: # идентификатор состояния
pkg.installed: # функция состояни
- name: nginx # аргументы функции
nginx_service:
service.running:
- name: nginx
- reload: True
- watch:
- file: /etc/nginx/sites-available/default
file.managed:
- name: /etc/nginx/sites-available/default
- source: salt://default.conf
nginx_index:
file.managed:
- name: /var/www/html/index.html
- source: salt://index.html
- template: jinja
Сам же файл index.html
добавим в директорию /srv/salt
со следующим содержимым:
hello from {{ grains['id'] }}
Как видно в шаблоне мы используем переменную grains
, содержащая данные, которые мы
наблюдали при вызове функции grains.items
. Применим новое состояние:
# salt '*' state.apply
minion1:
----------
...
----------
ID: nginx_index
Function: file.managed
Name: /var/www/html/index.html
Result: True
Comment: File /var/www/html/index.html updated
Started: 22:29:12.440232
Duration: 45.531 ms
Changes:
----------
diff:
New file
mode:
0644
Summary for minion2
------------
Succeeded: 4 (changed=1)
Failed: 0
------------
Total states run: 4
Total run time: 193.739 ms
# curl minion1.local
hello from minion1
# curl minion2.local
hello from minion2
Pillars
Помимо того, что мы можем использовать переменные в jinja
шаблонах с самих миньонов
через переменную grains
, мы также можем передавать переменные с мастера на миньоны
через механизм pillars
. Для этого по умолчанию используется директория
/srv/pillar
, в которой также должен находиться файл top.sls
, указывающий каким
миньонам какие переменные необходимо отправлять. Создадим директорию и опишем файл
top.sls
:
base:
'*':
- data
Таким образом на все миньоны будут отправляться pillars
из файла data.sls
в этой же директории. Зададим содержимое этого файла следующим образом:
html:
- name: 123
target: minion1
value: 321
- name: 2
target: minion2
value: hello
Теперь попробуем шаблонизировать наш файл состояния nginx.sls
используя эти переменные:
nginx_pkg: # идентификатор состояния
pkg.installed: # функция состояни
- name: nginx # аргументы функции
nginx_service:
service.running:
- name: nginx
- reload: True
- watch:
- file: /etc/nginx/sites-available/default
file.managed:
- name: /etc/nginx/sites-available/default
- source: salt://default.conf
nginx_index:
file.managed:
- name: /var/www/html/index.html
- source: salt://index.html
- template: jinja
{% for file in pillar['html'] %}
{% if file['target'] == grains['id'] %}
nginx_html_{{ file['name'] }}:
file.managed:
- name: /var/www/html/{{ file['name'] }}
- contents: {{ file['value'] }}
{% endif %}
{% endfor %}
Как видно в шаблоне используется цикл и проверка условия, так что на первом миньоне
должен появиться путь /123
с содержимым 321
, а на втором путь /2
с содержимым
hello
. Применим новое состояние:
# salt '*' state.apply
minion2:
----------
...
----------
ID: nginx_html_2
Function: file.managed
Name: /var/www/html/2
Result: True
Comment: File /var/www/html/2 updated
Started: 22:54:42.659035
Duration: 2.291 ms
Changes:
----------
diff:
New file
Summary for minion2
------------
Succeeded: 5 (changed=1)
Failed: 0
------------
Total states run: 5
Total run time: 107.908 ms
minion1:
----------
...
----------
ID: nginx_html_123
Function: file.managed
Name: /var/www/html/123
Result: True
Comment: File /var/www/html/123 updated
Started: 22:54:42.682458
Duration: 2.967 ms
Changes:
----------
diff:
New file
Summary for minion1
------------
Succeeded: 5 (changed=1)
Failed: 0
------------
Total states run: 5
Total run time: 116.716 ms
# curl minion1.local/123
321
# curl minion2.local/2
hello