воскресенье, 29 сентября 2019 г.

Установка и настройка драйверов nvidia для X-сервера

Заменил в компьютере одну подержанную устаревшую материнскую плату на другую подержанную, устаревшую чуть менее. На прежней был встроенный видеоконтроллер от Intel, на новой - от Nvidia. Не задумываясь поставил свободный драйвер nouveau, однако столкнулся с проблемами: 1. изображение на экране изредка моргало, как будто кто-то периодически вставляет 25 кадр, 2. компьютер спонтанно зависал, предварительно заливая экран белым цветом в чёрные горизонтальные прерывистые полоски. Стал грешить на видеоконтроллер и его драйвер. Пытался заставить работать драйвер nvidia где-то с полчаса. Попытки увенчались успехом. Пока помнил последовательность настройки, записал всё в блокнот. Теперь из блокнота выбрасываю записи в блог, чтобы в следующий раз не ходить по всем граблям снова.

Для начала узнаем, видеконтроллер какого из производителей установлен в системе:
$ lspci | fgrep VGA
00:0d.0 VGA compatible controller: NVIDIA Corporation C61 [GeForce 7025 / nForce 630a] (rev a2)
В нашем случае это встроенный видеоконтроллер NVidia. Чтобы определить, какой драйвер следует использовать с этим видеоконтроллером, установим утилиту nvidia-detect и запустим её:
# apt-get install nvidia-detect
# nvidia-detect
Утилита должна сообщить имя пакета с драйвером видеоконтроллера. В моём случае это был пакет nvidia-legacy-304xx-driver. Установим этот пакет:
# apt-get install --install-recommends nvidia-legacy-304xx-driver
После установки пакета с драйвером будет собран модуль ядра, который нужно подгрузить. Сделать это можно при помощи следующей команды:
# modprobe nvidia-legacy-304xx
Вписываем модуль ядра nvidia-legacy-304xx в файл /etc/modules, чтобы при перезагрузке системы или включении компьютера этот модуль загружался автоматически.

Также нужно установить поддержку этого драйвера со стороны X-сервера. Для этого ставим пакет с именем xserver-xorg-video-nvidia-legacy-304xx:
# apt-get install xserver-xorg-video-nvidia-legacy-304xx
Свободный драйвер nouveau в X-сервере имеет более высокий приоритет, чем фирменный драйвер от NVidia. Поэтому, чтобы X-сервер использовал именно фирменный драйвер, нужно удалить из системы пакет, отвечающий за поддержку nouveau со стороны X-сервера:
# apt-get remove xserver-xorg-video-nouveau
Осталось перезапустить дисплейный менеджер, чтобы он запустил X-сервер, а X-сервер начал использовать новые драйверы. Я пользуюсь дисплейным менеджером lightdm, поэтому в моём случае это можно сделать следующими командами:
# systemctl stop lightdm
# systemctl start lightdm
По окончании настройки пакет nvidia-detect можно удалить, т.к. вероятность, что он вновь понадобится, довольно мала.
# apt-get purge nvidia-detect
P.S. Кстати, замена драйвера помогла решить проблему.

воскресенье, 22 сентября 2019 г.

Настройка BGP в Quagga

Понадобилось на работе настроить динамическую маршрутизацию по протоколу BGP, чтобы сэкономить время на настройке статических маршрутов. Поэкспериментировал для начала на виртуальных машинах на домашнем компьютере, подготовил для себя шпаргалку. На работе настраивал свои серверы в паре с сетевым администратором, который со своей стороны настраивал аппаратные маршрутизаторы. (Данила, если ты это читаешь, то передаю тебе привет!) Когда работа была выполнена, дополнил шпаргалку несколькими полезными разделами.

Через пару месяцев понадобилось вернуться к настройке BGP по ещё двум поводам. Во-первых, на одной из виртуальных машин в целях резервирования анонсы нужно было принимать сразу от двух соседних маршрутизаторов. Во-вторых, понадобилось настроить сервер, который сам в целях резервирования должен быть доступен через два соседних маршрутизатора. Конфигурацию этого сервера проверил и дополнил другой сетевой администратор. (Андрей, тебе тоже привет!) В результате добавил в шпаргалку ещё несколько пунктов.

По обыкновению выбрасываю отработанный материал в мусоркублог.

Установка пакетов

Для установки демона маршрутизации Quagga с поддержкой одного лишь протокола BGP достаточно установить только один пакет:
# apt-get install quagga-bgp
Вместе с этим пакетом будет установлен также пакет quagga-core, в котором находится демон zebra. Демон zebra отвечает за добавление маршрутов, полученных от демонов динамической маршрутизации, в системную таблицу маршрутизации.

Настройка демона zebra

Демон zebra выполняет роль прослойки между операционной системой и демонами динамической маршрутизации. Демоны динамической маршрутизации выступают в роли клиентов демона zebra и пользуются его API.

Первоначальная настройка демона zebra проста, нужно создать файл конфигурации /etc/quagga/zebra.conf с содержимым следующего вида:
hostname <hostname>
password <password>
enable password <secret_password>
После этого можно включать автозапуск демона при загрузке системы и запустить сам демон маршрутизации:
# systemctl enable zebra.service
# systemctl start zebra.service
Дальнейшее управление демоном, в том числе его конфигурирование, можно осуществлять при помощи команды telnet:
$ telnet 127.0.0.1 2601
Через telent доступен интерфейс командной строки, похожий на интерфейс командной строки устройств Cisco. После изменения настроек не забывайте сохранять их при помощи команды write.

Я настраивал демона на двух тестовых виртуальных машинах с именами bgp1 и bgp2.

Настройка демона bgpd

Демон bgpd реализует поддержку протокола динамической маршрутизации BGP.

Для первоначальной настройки демона bgpd нужно создать файл /etc/quagga/bgpd.conf.

На первой виртуальной машине содержимое файла конфигурации было примерно таким:
hostname bgp1
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.6
 network 169.254.253.0/24
 network 169.254.254.0/24
 neighbor 169.254.252.7 remote-as 64500
Эта виртуальная машина лишь анонсирует две сети на вторую виртуальную машину. Номера автономных систем на обеих виртуальных машинах одинаковы и взяты из диапазона 64496-64511 для примеров и документации. Поскольку используются одинаковые номера автономных систем, то в нашем случае будет использоваться вариант протокола iBGP - внутренний протокол пограничных шлюзов.

На второй виртуальной машине содержимое файла конфигурации было примерно следующим:
hostname bgp2
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.7
 neighbor 169.254.252.6 remote-as 64500
Эта виртуальная машина не анонсировала своих сетей, зато принимала анонсы от первой.

После первоначальной настройки можно на обеих виртуальных машинах включить автозапуск демонов bgpd при загрузке системы и запустить их:
# systemctl enable bgpd.service
# systemctl start bgpd.service
Подобно демону zebra, управление и настройку демона bgpd можно осуществлять при помощи команды telnet:
$ telnet 127.0.0.1 2605
Точно так же через telent доступен интерфейс командной строки, похожий на интерфейс командной строки устройств Cisco. После изменения настроек не забывайте сохранять их при помощи команды write.

Белый список маршрутов

Рассмотрим конфигурирование демона bgpd через telnet. Для примера настроим фильтрацию маршрутов на второй виртуальной машине. Для начала подключаемся через telnet:
$ telnet 127.0.0.1 2605
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

Hello, this is Quagga (version 1.1.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.


User Access Verification

Password:
Вводим пароль, который был указан в опции password.

Далее повышаем собственные привилегии при помощи команды enable. В ответ на запрос команды enable вводим пароль, который был указан в опции enable password.
bgp2> enable
Password: 
bgp2#
Далее переходим в режим настройки при помощи команды configure terminal:
bgp2# configure terminal 
bgp2(config)#
Настроим сначала список префиксов, который назовём PREFIX-LIST-FROM-BGP1:
bgp2(config)# ip prefix-list PREFIX-LIST-FROM-BGP1 seq 1 permit 169.254.254.0/24
bgp2(config)# ip prefix-list PREFIX-LIST-FROM-BGP1 seq 2 permit 169.254.253.0/24
bgp2(config)#
Теперь создадим на основе этого списка префиксов маршрутную карту с именем MAP-FROM-BGP1:
bgp2(config) # route-map MAP-FROM-BGP1 permit 10
bgp2(config-route-map)#
Добавим в маршрутную карту список префиксов PREFIX-LIST-FROM-BGP1:
bgp2(config-route-map)# match ip address prefix-list PREFIX-LIST-FROM-BGP1
bgp2(config-route-map)#
И покинем диалог настройки маршрутной карты:
bgp2(config-route-map)# exit
bgp2(config)#
Осталось немного. Переходим в режим настройки протокола динамической маршрутизации bgp:
bgp2(config)# router bgp 64500
bgp2(config-router)#
Добавим созданную маршрутную карту для фильтрации маршрутов, получаемых от соседа:
bgp2(config-router)# neighbor 169.254.252.6 route-map MAP-FROM-BGP1 in
bgp2(config-router)#
Покидаем режим настройки bgp:
bgp2(config-router)# exit
bgp2(config)#
Сохраняем изменения конфигурации на диск:
bgp2(config)# write
Configuration saved to /etc/quagga/bgpd.conf
bgp2(config)#
Осталось выйти из режима конфигурирования:
bgp2(config)# exit
bgp2#
В процессе настройки из любого режима можно проверять правильность каждой введённой команды, просматривая текущий файл конфигурации при помощи команды show running-config:
bgp2(config)# show running-config                          

Current configuration:
!
hostname bgp2
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.7
 neighbor 169.254.252.6 remote-as 64500
 neighbor 169.254.252.6 route-map MAP-FROM-BGP1 in
!
 address-family ipv6
 exit-address-family
 exit
!
ip prefix-list PREFIX-LIST-FROM-BGP1 seq 1 permit 169.254.254.0/24
ip prefix-list PREFIX-LIST-FROM-BGP1 seq 2 permit 169.254.253.0/24
!
route-map MAP-FROM-BGP1 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP1
!
line vty
!
end
Чтобы посмотреть текущую таблицу маршрутов, можно воспользоваться командой show ip bgp:
bgp2# show ip bgp 
BGP table version is 0, local router ID is 169.254.252.7
Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
              i internal, r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*>i169.254.253.0/24 169.254.252.6            0     100       0 i
*>i169.254.254.0/24 169.254.252.6            0     100       0 i

Displayed  2 out of 2 total prefixes
Если нужно применить новый или изменённый фильтр к уже принятым маршрутам, можно воспользоваться командой clear ip bgp такого вида:
bgp2# clear ip bgp 169.254.252.6 soft in
bgp2#
После этого все маршруты, не разрешённые маршрутной картой явным образом, должны пропасть из списка текущих маршрутов.

Чёрный список маршрутов

Цель настройки динамической маршрутизации заключается в том, чтобы перестать добавлять статические маршруты в систему вручную. Однако, если вместо этого придётся добавлять те же самые маршруты в белый список, то ручной работы не станет меньше. В большинстве случаев достаточно просто принимать все маршруты безо всякой фильтрации, но иногда может понадобиться не принимать маршруты до отдельных сетей. В таком случае поможет чёрный список маршрутов.

Не буду повторять предыдущий раздел, а расскажу кратко. Поскольку логика фильтрации обратная - нужно отбрасывать маршруты из списка, а не принимать их, то в маршрутной карте действие permit заменяется на deny:
bgp2(config)# route-map MAP-FROM-BGP1 deny 10
bgp2(config-route-map)# match ip address prefix-list PREFIX-LIST-FROM-BGP1
bgp2(config-route-map)# exit
bgp2(config)#
В списке префиксов действия permit не меняется. Чтобы отбросить анонсы сети 169.254.254.0/24, нужно поместить в список одну запись:
bgp2(config)# ip prefix-list PREFIX-LIST-FROM-BGP1 seq 1 permit 169.254.254.0/24
bgp2(config)#
Также может потребоваться не принимать анонсы маршрутов не только с точно совпадающим префиксом, но и анонсы маршрутов ко всем сетям, находящимся внутри указанной. Сделать это можно следующим образом:
bgp2(config)# ip prefix-list PREFIX-LIST-FROM-BGP1 seq 2 permit 169.254.253.0/24 le 32
bgp2(config)#
Выражение "le 32" означает, что условию удовлетворяют не только сети с префиксом 169.254.253.0/24, но и со всеми префиксами с длиной меньше 32 или равными 32. Например, если маршрутизатор BGP1 будет анонсировать маршруты к сетям 169.254.253.0/25 и 169.254.253.128/26, то оба анонса будут отброшены маршрутной картой.

Фильтрация исходящих анонсов

Сетевые администраторы рекомендуют создавать маршрутную карту не только для принимаемых маршрутов, но и для анонсируемых. Двойная защита позволяет застраховаться от непредусмотренных ошибок, допущенных при конфигурации одного из узлов.

Делается это аналогично фильтрации принимаемых маршрутов, с той лишь разницей, что ключевое слово in нужно заменить на out и прописать имя соответствующей маршрутной карты:
bgp2(config)# router bgp 64500
bgp2(config-router)# neighbor 169.254.252.6 route-map MAP-TO-BGP1 out
bgp2(config-router)# exit
bgp2(config)#
Если маршрутизатор должен только принимать анонсы, то такая маршрутная карта будет выглядеть следующим образом:
bgp2(config)# route-map MAP-TO-BGP1 deny 10
bgp2(config-router)# exit
bgp2(config)#

Применение политик без разрыва сеансов BGP

При изменении маршрутных карт и списков префиксов, чтобы новые политики фильтрации вступали в силу, нужно выполнять команду следующего вида:
bgp2# clear ip bgp 169.254.252.6 soft in
bgp2#
Эта команда разрывает сеанс BGP с соседним маршрутизатором и устанавливает его заново. При этом соседний маршрутизатор вновь анонсирует весь список маршрутов, который и подвергается фильтрации.

Чтобы не разрывать сеансы BGP только лишь для того, чтобы применить новые политики фильтрации маршрутов, сетевые администраторы рекомендуют включить для соседа режим мягкой переконфигурации:
bgp2(config)# router bgp 64500
bgp2(config-router)# neighbor 169.254.252.6 soft-reconfiguration inbound
bgp2(config-router)# exit
bgp2(config)#
В этом режиме сеанс BGP с соседним маршрутизатором не разрывается, а фильтрация применяется к уже принятому ранее списку маршрутов.

Описание соседей

Даже если в конфигурации BGP описан только один соседний маршрутизатор, лучше снабдить его описанием, чтобы в дальнейшем не приходилось ориентироваться только на IP-адреса и номера автономных систем. Сделать это можно следующим образом:
bgp2(config)# router bgp 64500
bgp2(config-router)# neighbor 169.254.252.6 description bgp1
bgp2(config-router)# exit
bgp2(config)#

Группировка настроек соседей

Рассмотрим случай, когда необходимо настроить соседство с двумя маршрутизаторами, настройки которых в целом должны быть идентичны. В примере ниже маршрутизатор с именем bgp3 и IP-адресом 169.254.252.10 соседствует с маршрутизаторами bgp1 и bgp2, имеющими IP-адреса 169.254.252.5 и 169.254.252.6 соответственно:
hostname bgp3
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.10
 neighbor 169.254.252.5 remote-as 64500
 neighbor 169.254.252.5 route-map PREFIX-LIST-FROM-BGP1 in
 neighbor 169.254.252.5 soft-reconfiguration inbound
 neighbor 169.254.252.5 description bgp1.stupin.su
 neighbor 169.254.252.6 remote-as 64500
 neighbor 169.254.252.6 route-map PREFIX-LIST-FROM-BGP2 in
 neighbor 169.254.252.6 soft-reconfiguration inbound
 neighbor 169.254.252.6 description bgp2.stupin.su
!
ip prefix-list PREFIX-LIST-FROM-BGP1 seq 1 permit 169.254.254.0/24
ip prefix-list PREFIX-LIST-FROM-BGP1 seq 2 permit 169.254.253.0/24
!
ip prefix-list PREFIX-LIST-FROM-BGP2 seq 1 permit 169.254.254.0/24
ip prefix-list PREFIX-LIST-FROM-BGP2 seq 2 permit 169.254.253.0/24
!
route-map MAP-FROM-BGP1 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP1
!
route-map MAP-FROM-BGP2 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP2
Настройки соседей по замыслу должны быть идентичными, но из приведённой выше конфигурации это не очевидно. Если конфигурацию будет читать другой системный администратор, то ему во-первых придётся прочитать довольно много, во-вторых первоначальный замысел может оказаться для него не очевидным. Он может изменить настройки одного из соседей, но забыть поправить подобным образом настройки второго соседа.

Первое, что приходит в голову - это объединить списки префиксов и маршрутные карты:
hostname bgp3
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.10
 neighbor 169.254.252.5 remote-as 64500
 neighbor 169.254.252.5 route-map PREFIX-LIST-FROM-BGP1_2 in
 neighbor 169.254.252.5 soft-reconfiguration inbound
 neighbor 169.254.252.5 description bgp1.stupin.su
 neighbor 169.254.252.6 remote-as 64500
 neighbor 169.254.252.6 route-map PREFIX-LIST-FROM-BGP1_2 in
 neighbor 169.254.252.6 soft-reconfiguration inbound
 neighbor 169.254.252.6 description bgp2.stupin.su
!
ip prefix-list PREFIX-LIST-FROM-BGP1_2 seq 1 permit 169.254.254.0/24
ip prefix-list PREFIX-LIST-FROM-BGP1_2 seq 2 permit 169.254.253.0/24
!
route-map MAP-FROM-BGP1_2 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP1_2
Конфигурация стала короче, замысел стал более понятным, но всё ещё есть возможность поменять настройки одного маршрутизатора, не меняя настройки второго. Настройки соседей можно сгруппировать:
hostname bgp3
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.10
 neighbor NEIGHBORS-BGP1_2 peer-group
 neighbor NEIGHBORS-BGP1_2 remote-as 64500
 neighbor NEIGHBORS-BGP1_2 route-map PREFIX-LIST-FROM-BGP1_2 in
 neighbor NEIGHBORS-BGP1_2 soft-reconfiguration inbound
 neighbor 169.254.252.5 peer-group NEIGHBORS-BGP1_2
 neighbor 169.254.252.5 description bgp1.stupin.su
 neighbor 169.254.252.6 peer-group NEIGHBORS-BGP1_2
 neighbor 169.254.252.6 description bgp2.stupin.su
!
ip prefix-list PREFIX-LIST-FROM-BGP1_2 seq 1 permit 169.254.254.0/24
ip prefix-list PREFIX-LIST-FROM-BGP1_2 seq 2 permit 169.254.253.0/24
!
route-map MAP-FROM-BGP1_2 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP1_2
Теперь оба соседа состоят в одной группе и их настройки объединены, так что будут меняться только одновременно, если специально не отделить их друг от друга. Получившийся файл конфигурации стал короче и нагляднее.

Ограничение доступа к терминалу

Т.к. для настройки демонов zebra и bpgd используется протокол telnet, то я бы не стал делать его доступным через внешние интерфейсы сервера. Трафик в telnet не шифруется, а если кому-то нужно поправить настройки, то пусть лучше зайдёт сначала на сервер по SSH, а потом уже подключается к терминалу zebra или quagga локально. Если бы мне пришла в голову мысль сделать терминал доступным на внешних интерфейсах, то я открыл доступ в пакетном фильтре только для необходимых IP-адресов. Тем не менее, параноики исходят из справедливости принципа Мерфи, поэтому стремятся защититься даже от тех ситуаций, которые кажутся почти невероятными. Мне порекомендовали защитить доступ к терминалу управления списками управления доступом, вот так:
access-list TERMINAL permit 127.0.0.1/32
access-list TERMINAL deny any
!
line vty
 access-class TERMINAL

Анонсирование адреса Loopback-интерфейса

Если BGP настраивается на сервере для того, чтобы сервер был доступен через два маршрутизатора, то IP-адрес сервера, анонсируемый соседям по протоколу BGP, настраивается не на физическом интерфейсе, а на так называемом Loopback-интерфейсе.

В Debian я не нашёл способа создать отдельный петлевой интерфейс, поэтому анонсируемый IP-адрес можно добавить на один-единственный петлевой интерфейс lo в качестве дополнительного IP-адреса. Сделать это можно при помощи команды следующего вида:
# ip addr add 169.254.251.1/32 dev lo
Чтобы дополнительный IP-адрес назначался интерфейсу lo при перезагрузке сервера, нужно в файле /etc/network/interfaces прописать аналогичные настройки:
auto lo:1
iface lo:1 inet static
  address 169.254.251.1
  netmask 255.255.255.255
После этого нужно:
  1. настроить демона, который реализует необходимый сервис, так чтобы он принимал входящие подключения только на этот IP-адрес,
  2. разрешить соответствующий трафик в пакетном фильтре,
  3. добавить IP-адрес в конфигурацию демона bgpd.
При выполнении последнего пункта стоит учесть правила хорошего тона и создать отдельную маршрутную карту на исходящие анонсы, которая будет разрешать демону bgpd анонсировать соседям только этот IP-адрес. Например, если сервер должен принимать от маршрутизаторов только маршруты по умолчанию, а анонсировать в их сторону только Loopback-адрес 169.254.251.1, то конфигурация может выглядеть следующим образом:
hostname server
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.251.1
 network 169.254.251.1/32
 neighbor NEIGHBORS-BGP1_2 peer-group
 neighbor NEIGHBORS-BGP1_2 remote-as 64500
 neighbor NEIGHBORS-BGP1_2 route-map PREFIX-LIST-FROM-BGP1_2 in
 neighbor NEIGHBORS-BGP1_2 route-map PREFIX-LIST-TO-BGP1_2 in
 neighbor NEIGHBORS-BGP1_2 soft-reconfiguration inbound
 neighbor 169.254.252.5 peer-group NEIGHBORS-BGP1_2
 neighbor 169.254.252.5 description bgp1.stupin.su
 neighbor 169.254.252.6 peer-group NEIGHBORS-BGP1_2
 neighbor 169.254.252.6 description bgp2.stupin.su
!
ip prefix-list PREFIX-LIST-FROM-BGP1_2 seq 1 permit 0.0.0.0/0
ip prefix-list PREFIX-LIST-TO-BGP1_2 seq 1 permit 169.254.251.1/32
!
route-map MAP-FROM-BGP1_2 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP1_2
!
route-map MAP-TO-BGP1_2 permit 10
 match ip address prefix-list PREFIX-LIST-TO-BGP1_2
На случай, если демон bgpd аварийно завершится, можно добавить в демоне zebra статический маршрут по умолчанию с низким приоритетом через одного из соседей:
ip route 0.0.0.0/0 169.254.252.5 250

Использованные материалы

воскресенье, 15 сентября 2019 г.

Управление конфигурациями с помощью Asnible

Настройка управляющей машины

На управляющей машине устанавливаем ansible:
# apt-get install ansible
В домашнем каталоге создаём каталог, в котором будем хранить все файлы для настройки удалённых компьютеров. Если нужно работать над проектом совместно с другими системными администраторами и отслеживать изменения, сделанные ими, то лучше сразу же создать в этом каталоге git-репозиторий:
$ mkdir playbooks
$ git init
Создадим файл конфигурации ansible.cfg и пропишем в него следующие настройки:
[defaults]
inventory = hosts
remote_user = ansible
private_key_file = .ssh/id_rsa
Сразу же сгенерируем ssh-ключ, который будем устанавливать на удалённые системы для их настройки при помощи ansible:
$ mkdir .ssh
$ ssh-keygen -f .ssh/id_rsa
$ chmod go= -R .ssh
Каталог .ssh помещать в git-репозиторий не стоит.

Создадим файл hosts, в котором будем отмечать узлы, управляемые при помощи ansible. Например, файл с одним-единственным узлом с именем mon может выглядеть следующим образом:
mon ansible_host=169.254.252.2

Настройка управляемой машины

На управляемой машине после настройки сети и установки OpenSSH-сервера необходимо установить несколько пакетов, которые будут использоваться ansible:
# apt-get install python2.7-minimal python2.7-stdlib python-minimal
Также установим sudo, чтобы при удалённой настройке можно было выполнять действия от имени пользователя root:
# apt-get install sudo
Добавим учётную запись специально для удалённой настройки:
# useradd -c 'Ansible account' -m ansible
Сразу же дадим возможность этой учётной записи использовать sudo для работы с правами пользователя root:
# visudo
Добавляем строчки:
Defaults:ansible !requiretty
ansible ALL=(root:ALL) NOPASSWD:ALL
Скопируем на удалённую машину SSH-ключ:
$ ssh-copy-id -i .ssh/id_rsa.pub ansible@169.254.252.2

Первый сценарий

Для первого сценария возьмём простейшую задачу - настройки файла /etc/resolv.conf на удалённом компьютере.

Создадим на управляющей машине в каталоге проекта файл сценария resolvconf.yml со следующим содержимым:
---
- name: Configure resolv.conf
  hosts: mon
  become: True
  tasks:
    - name: copy /etc/resolv.conf
      template:
        src: templates/resolv.conf
        dest: /etc/resolv.conf
        owner: root
        group: root
        mode: 0644
Создадим каталог templates для шаблонов файлов конфигурации:
$ mkdir templates
Создадим шаблон конфигурации templates/resolv.conf для файла /etc/resolv.conf управляемого компьютера:
{% if resolvconf.domain %}
domain {{ resolvconf.domain }}
{% endif %}
{% for ip in resolvconf.nameservers %}
nameserver {{ ip }}
{% endfor %}
Значения переменных хранятся в так называемом реестре ansible. Он представляет собой файл в формате YML и может находиться либо в каталоге host_vars, либо в каталоге group_vars. В каталоге host_vars хранятся настройки, специфичные для каждого отдельного управляемого узла. Внутри этого каталога создаётся фай, имя которого совпадает с указанным в файле hosts. В каталоге group_vars хранятся настройки, общие для определённой группы узлов. Внутри этого каталога создаётся файл, имя которого совпадает с именем группы, указанным в файле hosts.

Создадим каталог host_vars с переменными узлов:
$ mkdir host_vars
И внутри каталога создадим файл mon со следующим содержимым:
resolvconf:
  domain: stupin.su
  nameservers:
    - 169.254.252.1
Осталось выполнить наш первый простейший сценарий. Для этого воспользуемся командой ansible-playbook:
$ ansible-playbook resolvconf.yml

Переключение на непривилегированного пользователя

В прошлом сценарии имелась опция, которая заставляла выполнять действия от имени пользователя root:
become: True
В случае, если нужно выполнять действия от имени другого, непривилегированного пользователя, можно столкнуться с проблемами. Например, администратором баз данных PostgresSQL является системный пользователь postgres. Создать нового пользователя или базу данных можно только от его имени. Поэтому, задача такого вида:
- name: createuser -P <login>
  postgresql_user:
    name: "{{ item.login }}"
    password: "{{ item.password }}"
  with_items: "{{ postgresql_databases }}"
  when: postgresql_databases
Завершится такой ошибкой:
unable to connect to database: ВАЖНО:  пользователь "postgres" не прошёл проверку подлинности (Peer)
Однако и простого указания опций переключения на пользователя postgres оказывается недостаточно:
- name: createuser -P <login>
  become: True
  become_user: postgres
  postgresql_user:
    name: "{{ item.login }}"
    password: "{{ item.password }}"
  with_items: "{{ postgresql_databases }}"
  when: postgresql_databases
В этом случае попытка выполнить задачу завершается следующей ошибкой (отформатировано для наглядности):
Failed to set permissions on the temporary files
Ansible needs to create when becoming an unprivileged user (rc: 1, err:
  chown: изменение владельца '/tmp/ansible-tmp-1549203485.91-25063771984034/': Операция не позволена
  chown: изменение владельца '/tmp/ansible-tmp-1549203485.91-25063771984034/postgresql_user.py': Операция не позволена
).
For information on working around this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user
После доработки в соответствии с материалами по ссылке, указанной в тексте ошибки, задача приобретает следующий вид:
- name: createuser -P <login>
  become: yes
  become_user: postgres
  vars:
    ansible_ssh_pipelining: true
  postgresql_user:
    name: "{{ item.login }}"
    password: "{{ item.password }}"
  with_items: "{{ postgresql_databases }}"
  when: postgresql_databases
Но и этого оказывается недостаточно, т.к. возникает следующая ошибка:
failed: [mon] (item={u'login': u'zabbix', u'password': u'xxxxxxxxxxxxxxxx', u'name': u'zabbix'}) => {
    "failed": true, 
    "item": {
        "login": "zabbix", 
        "name": "zabbix", 
        "password": "xxxxxxxxxxxxxxxx"
    }, 
    "module_stderr": "sudo: a password is required\n", 
    "module_stdout": ""
}

MSG:

MODULE FAILURE
Как видно, ansible пытается переключиться на указанного пользователя при помощи sudo. Но, когда я настраивал sudo для ansible, то указал разрешение переключаться без указания пароля только на пользователя root:
ansible ALL=(root:ALL) NOPASSWD:ALL
Чтобы разрешить пользователю ansible переключаться ещё и на пользователя postgres, нужно добавить через команду visudo ещё одно разрешение:
ansible ALL=(postgres:ALL) NOPASSWD:ALL
После этого задача выполняется успешно.

Немедленный перезапуск сервиса при изменениях

Иногда отложенный перезапуск сервиса может таить в себе проблемы. Например, если в конфигурации сервиса изменился прослушиваемый им порт, новые настройки не вступят в силу, пока не будет достигнут конец сценария. В то же время, действия, выполняемые до вызова отложенного перезапуска сервиса, могут использовать новый порт для подключения к сервису, не зная, что сервис ещё не был перезапущен и его новые настройки ещё не вступили в силу.

Именно так получилось у меня при подготовке роли, настраивающей сервис PostgreSQL. После изменения номера порта может потребоваться создать новые базы данных или пользователей, но пока сервис не был перезапущен, подключиться к нему по новому порту не получится, а старый может быть не известен. В таком случае можно запомнить результаты выполненных действий в переменных при помощи выражения register:
- name: vim /etc/postgresql/9.6/main/pg_hba.conf
  template:
    src: etc/postgresql/9.6/main/pg_hba.conf
    dest: /etc/postgresql/9.6/main/pg_hba.conf
    owner: postgres
    group: postgres
    mode: 0640
  register: pg_hba 

- name: vim /etc/postgresql/9.6/main/postgresql.conf
  template:
    src: etc/postgresql/9.6/main/postgresql.conf
    dest: /etc/postgresql/9.6/main/postgresql.conf
    owner: postgres
    group: postgres
    mode: 0644
  register: postgresql
И затем проверить результаты этих действий и немедленно перезапустить сервис, если его конфигурация была изменена:
- name: systemctl restart postgresql
  service:
    name: postgresql
    state: restarted
  when: (pg_hba.changed or postgresql.changed)

Использованные материалы

P.S.

Для более-менее полноценного использования Ansible рекомендую изучить роли. Роль представляет собой совокупность сценария, настроек по умолчанию и используемых файлов и шаблонов. В сценарии можно будет указать список ролей, которые нужно применить к узлам, указанным в сценарии. Благодаря ролям становится возможным собирать сценарии из крупных строительных блоков - ролей.

воскресенье, 8 сентября 2019 г.

Настройка текстового терминала для использования в virsh

Ранее я описывал установку и настройку virt-manager - графической программы для администрирования виртуальных машин, работающих под управлением KVM, Xen и других гипервизоров. Для управления начинкой виртуальных машин, как правило, достаточно SSH. Однако, виртуальная машина может оказаться не доступной по SSH из-за неполадок в работе сети или ошибок в сетевых настройках самой виртуальной машины. В таком случае можно подключиться к консоли виртуальной машины, однако и virt-manager может оказаться не доступным. Вместо virt-manger можно попробовать воспользоваться virsh, но как попасть в текстовую консоль виртуальной машины, если она не была настроена заранее?

Чтобы подстелить соломки, можно заранее настроить текстовую консоль в каждой из виртуальных машин. В случае недоступности виртуальной машины по сети или отсутствии virt-manager на ближайшем компьютере достаточно будет подключиться по SSH к компьютеру, на котором работает гипервизор, на нём запустить virsh, а оттуда уже попасть в консоль виртуальной машины. Займёмся настройкой.

Настройка виртуальной машины

Запускаем от пользователя root команду virsh:
# virsh
В запущенной оболочке virsh проверяем, есть ли у виртуальной машины текстовый терминал (вместо <vm> нужно указать имя виртуальной машины):
ttyconsole <vm>
Если команда ничего не вывела, значит виртуального терминала нет и его нужно настроить. Для этого отредактируем конфигурацию виртуальной машины, выполнив в оболочке virsh команду:
edit <vm>
Если в файле есть секция console, имеющая такой вид:
<console type='pty'>
  <target type='virtio' port='0'/>
</console>
То её нужно привести её к следующему виду:
<serial type='pty'>
  <target port='0'/>
</serial>
<console type='pty'>
  <target type='serial' port='0'/>
</console>
Если же секцию console найти не удалось, то нужно вставить указанный фрагмент вовнутрь секции devices.

Дальше нужно остановить виртуальную машину и запустить её снова. Для этого в оболочке virsh можно выполнить следующие две команды:
shutdown <vm>
start <vm>
После пересоздания виртуальной машины, в ней появится последовательное устройство.

Настройка демона терминала getty

Когда виртуальная машина запустится, нужно зайти в неё через графическую консоль (или через SSH) и выполнить две команды, которые включат терминал на виртуальном последовательном порту:
# systemctl enable serial-getty@ttyS0.service
# systemctl start serial-getty@ttyS0.service
После этого появится возможность подключиться к этому терминалу через virsh, при помощи команды следующего вида:
console <vm>
Однако, этим терминалом можно будет пользоваться только после загрузки системы. Нельзя будет увидеть ни меню загрузчика, ни сообщения, выводимые ядром операционной системы в процессе его загрузки. Чтобы терминалом можно было воспользоваться до загрузки операционной системы, нужно настроить консоль в загрузчике и в ядре.

Настройка консоли в GRUB 2 и Linux

Поскольку загрузчик передаёт опции ядру, то для настройки консоли в загрузчике и в ядре Linux понадобится отредактировать только конфигурацию загрузчика.

Для настройки консоли в загрузчике, нужно в файле /etc/default/grub добавить или заменить следующие опции:
GRUB_TERMINAL="serial console"
GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"
Мне потребовалось поискать в интернете примеры настройки, при которых консоль на последовательном порту не вызывала бы отключение графической консоли. Указанный выше вариант позволяет пользоваться меню GRUB и через последовательную консоль, и через графическую.

Для настройки консоли в ядре Linux нужно в том же файле /etc/default/grub найти опцию GRUB_CMDLINE_LINUX и добавить в неё следующие опции ядра:
console=tty0 console=ttyS0,115200n8
В моём случае никаких опций ядру не передавалось, поэтому строчка приняла следующий вид:
GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8"

Теперь нужно применить изменения в конфигурации GRUB при помощи следующей команды:
# update-grub

Результат

Для того, чтобы убедиться в работоспособности консоли, можно подключиться к графической консоли и консоли на последовательном порту виртуальной машины и отправить её в перезагрузку. В моём случае меню загрузчика в обеих консолях выглядело следующим образом:



Ниже показан процесс загрузки системы на обеих консолях:

Использованные материалы

воскресенье, 1 сентября 2019 г.

Virt-manager для управления виртуальными машинами под управлением KVM и Xen

Для управления виртуальными машинам, работающими под управлением систем виртуализации KVM и Xen можно использовать программу с графическим интерфейсом, которая называется Virt-manager. Программа использует демон libvirt, установленный на компьютере с системой виртуализации. Для подключения Virt-manager к демону libvirt можно использовать SSH, что довольно удобно и безопасно. Как правило, демон SSH бывает уже установлен на компьютерах, доступных по сети, поэтому не нужно заботиться о настройке отдельного сетевого демона. Плюс к тому, SSH поддерживает аутентификацию пользователей и надёжное шифрование передаваемых данных.

Настройка демона libvirt для управления виртуальными машинами под управлением KVM (на примере Debian Stretch)

Проверим наличие аппаратной поддержки виртуализации:
$ egrep -c '(vmx|svm)' /proc/cpuinfo
Устанавливаем систему виртуализации qemu с поддержкой KVM и демон libvirt для управления виртуальными машинами, работающими под управлением KVM:
# apt-get install qemu-kvm libvirt-daemon-system
Добавляем пользователя, от имени которого будем заходить на сервер по SSH, в группу libvirt, чтобы он имел доступ к управлению виртуальными машинами:
# usermod -aG libvirt stupin
Пользователь получит доступ к Unix-сокету /var/run/libvirt/libvirt-sock

Установим netcat из OpenBSD, поддерживающий опцию -U для подключения к Unix-сокету. Это нужно для перенаправления подключения от SSH-клиента в Unix-сокет libvirt:
# apt-get install netcat-openbsd
Осталось включить и запустить демона:
# systemctl enable libvirtd
# systemctl start libvirtd

Настройка демона libvirt для управления виртуальными машинами под управлением Xen (на примере Debian Wheezy)

В моём случае Xen был развёрнут в операционной системе Debian Wheezy, где пакет с демоном libvirt называется libvirt-bin. Установим его:
# apt-get install libvirt-bin
Теперь, чтобы пользователь, от имени которого мы будем подключаться по SSH к серверу виртуализации, смог подключиться к демону libvirtd, нужно добавить его в группу libvirt:
# usermod -aG libvirt stupin
Пользователь получит доступ к Unix-сокету /var/run/libvirt/libvirt-sock

Для того, чтобы демон libvirt смог работать с системой виртуализации Xen, нужно включить HTTP-сервер, встроенный в xend. Демон libvirt будет выполнять роль посредника, транслируя входящие запросы в запросы к xend.

Настроим HTTP-сервер, ожидающий подключений на порту 8000 и адресе 127.0.0.1. Для этого нужно прописать в файл /etc/xen/xend-config.sxp следующие опции:
(xend-http-server yes)
(xend-address localhost)
(xend-port 8000)
Чтобы настройки xend вступили в силу, нужно перезапустить его (пример для Debian Wheezy):
# /etc/init.d/xen restart
Будьте осторожны - в конфигурации по умолчанию при перезапуске демона xend перезапускаются также и все виртуальные машины. Чтобы избежать этого, нужно при создании виртуальной машины прописать в её конфигурацию такие настройки:
on_xend_start = 'ignore'
on_xend_stop = 'ignore'
У меня эти настройки были прописаны в каждой виртуальной машине, поэтому я перезапускал xend без опасений.

Установка и использование Virt-manager

Устанавливаем на компьютере администратора пакет с GUI-интерфейсом для управления виртуальными машинами:
# apt-get install virt-manager
Теперь можно попробовать сделать то, ради чего всё и затевалось: подключиться к системе виртуализации из программы virt-manager. Ниже приведены снимки экрана, иллюстрирующие этот процесс.

Запуск virt-manager:

Добавление нового подключения:

Новое подключение к системе виртуализации Xen:

Настройки удалённого подключения к системе виртуализации Xen через SSH:

Подключение к системе виртуализации установлено, виден список виртуальных машин:

Теперь можно подключаться к консоли виртуальной машины и менять её настройки.

Типичные проблемы

Не установлен пакет netcat-openbsd

Не удалось подключиться к libvirt.

Для взаимодействия с удалённым узлом необходимо, чтобы версия netcat/nc поддерживала параметр -U.

Не установлен пакет libvirt-bin или libvirt-daemon-system

Не удалось подключиться к libvirt.

End of file while reading data: nc: unix connect failed: No such file or directory: Ошибка ввода/вывода

Убедитесь, что на удалённом узле запущен libvirtd.

Не настроен HTTP-сервер xend

Не удалось подключиться к libvirt.

unable to connect to 'localhost:8000': В соединении отказано

Убедитесь, что на удалённом узле запущен libvirtd.

Не установлен пакет gir1.2-spice-client-gtk-3.0

Ошибка подключения к графической консоли:
Error opening Spice console, SpiceClientGtk missing