пятница, 24 февраля 2012 г.

Управление конфигурацией в it инфраструктуре ч.2

Начнем воплощать в жизнь вторую часть. Ранее я писал скрипты которые работают с интерактивными приложениями или устройствами на expect'е. Трудность такой реализации это то что логику скрипта надо будет писать на TCL, или дробить задачу - писать логику на shell и из него вызывать кусочки на expect. Это не самый удобный путь. Ну а второе, почему мне не хотелось писать на expect'е - когда то давно я похвастался одному знакомому java программисту, что я пишу на tcl. На что он мне сказал -  "На нем пишут бородатые дядьки", отчасти это так.
Для python есть модуль реализующий функциональность expect - pyexpect. Соответственно скрипт я написал на python. Скрипт достаточно легко изменить под собственные нужды, используя тот же самый алгоритм. На python я ранее ничего не писал, поэтому буду рад услышать конструктивные замечания. Справедливости ради надо сказать, что для perl тоже есть аналогичный модуль. Ничто не запрещает реализовать все на нем.
Установим модули которые нам понадобятся:

[root@server ~]# yum install pexpect pysvn python-iniparse

Модуль pysvn будет использоваться для работы с репозиторием, а iniparse для разбора конфигурационного файла.
Что бы сократить размер приведенного здесь скрипта, я уменьшил количество видов устройств до двух, обладающих наиболее отличными друг от друга механизмами резервного копирования конфигурации. Это коммутаторы Brocade Silkworm (мне доступны коммутаторы двух моделей Brocade 5380 и входящие в HP C7000 - Brocade 4024) и пара кластеров IBM SAN Volume Controller(сокращенно svc). Опишу как выполняется резервное копирование конфигурации на этих устройствах. Резервирование конфигурации на коммутаторах Brocade можно выполнить командой (для версии Fabric OS: v6.2.2d):

RUMOWDRCENC005_LEFT:admin> configupload -all -scp host,user,path

где host, user, path - сервер, пользователь и путь на файловой системе сервера, на который будет записан конфигурационный файл. Передача конфигурационного файла осуществляется по протоколу scp.
Резервирование конфигурации кластера svc выполняется в два приема. Мне доступны два кластера с разной прошивкой - v5.1.0.1 и v6.3.0.1(один обновил, а второй еще нет). Сначала формируются файлы конфигурации:

IBM_2145:SVCMOS1P1:admin> svcconfig backup

которые затем надо забрать c svc (в отличие от резервирования конфигурации на коммутаторах Brocade):

[root@server ~]# scp -i ~/.ssh/svc.key svc.config.backup.xml admin@svc_ip:/tmp/svc.config.backup.*

Как вы заметили, аутентификация осуществляется по ключу. Конфигурационных файлов у svc -  три: svc.config.backup.sh, svc.config.backup.log и svc.config.backup.xml.
Дополнительно мы будем следующую информацию: состояние портов на свичах:

RUMOWDRCENC005_LEFT:admin> switchshow

и состояние пулов схд на svc:

IBM_2145:SVCMOS1P1:admin> svcinfo lsmdiskgrp

Листинг конфигурационного файла:

[confrepuser@server ~]$ cat config.ini 
[system]
host=192.168.1.100
password = cfgbackup
path = confrepo/devices/

[brocade1]
login = admin
password = password1
RUMOWDRCENC005_LEFT = Yes
RUMOWDRCENC005_RIGHT = yes

[brocade2]
login = admin
password = password2
BDMOS101L01 = True
BDMOS101R01 = 1
BDMOS201L01 = 0
BDMOS201R01 = False

[svc1]
login = admin
key = /home/confrepuser/.ssh/svc.key
svcmos1 = Yes
svcmos2 = No

Конфигурационный файл разбит на секции, обязательная из которых system. Параметры секции  system: host - ip или днс имя сервера на котором содержится рабочая копия репозитория; password - пароль пользователя от имени которого выполняется скрипт; path - путь на файловой системе, куда будут записываться конфигурационные файлы устройств.
Остальные секции именуются в соответствии с классом устройства (секции устройств), т.е. все секции, название которых начинается с "brocade" будут рассматриваться как секции содержащие коммутаторы Brocade. Абсолютно аналогично с svc. Параметры секций устройств логически состоят из двух частей, параметры используемые при аутентификации на устройстве (для примера: login - имя пользователя используемое при входе на устройство) и на параметры, название которых указывает на устройства (к примеру svcmos1 - название кластера svc, которое должно иметь соответствующую запись в днс или запись в файле /etc/hosts). Параметр указывающий на устройство является булевой переменной, положительное значение которого означает то, что с устройства будет выполнятся резервирование конфигурации.
Итак, листинг скрипта:

[confrepuser@server ~]$ cat geconfig.py 
#!/usr/bin/python
#-*- coding: utf-8 -*-

import pexpect,sys,pysvn,re,os
from iniparse import ConfigParser

# Check directory exist, and make if not.
def ensure_dir(dir):
    if not os.path.exists(dir):
       os.makedirs(dir)
       client.add(dir)
       client.checkin(dir, 'Add dir '+dir)

# Get brocade config
def getbrconf(login, password, brocade, dir):
    ssh_newkey = 'Are you sure you want to continue connecting'
    list_files = []
    child = pexpect.spawn("ssh "+login+"@"+brocade)
    #For debug purpose
    #child.logfile = sys.stdout

    i=child.expect([pexpect.EOF,ssh_newkey,'password:'],timeout=60)
    if i==0:
       print "Configuration file from", brocade,"is not received"
       return
    if i==1:
       child.sendline('yes')
       i=child.expect([ssh_newkey,'password:',pexpect.EOF],timeout=60)
    if i==2:
       child.sendline(password)

    child.expect (':'+login+'> ')
    child.sendline('configupload -all -scp '+host+','+username+','+devicepath+dir+'/'+brocade)
    child.expect ('password:')
    child.sendline(userpassword)
    child.expect (':'+login+'> ')
    # Get some state
    f = open(dir+'/'+brocade+'-state', 'w')
    child.sendline ('switchshow')
    child.expect (':'+login+'> ')
    f.write(child.before)
    child.sendline ('exit')
    list_files.extend(['./'+dir+'/'+brocade, './'+dir+'/'+brocade+'-state'])
    for file in list_files:
        if not client.info(file):
           # File added with "segmentation filed" bug!
           #client.add(file)
           child = pexpect.spawn('/usr/bin/svn add '+file)
           child.expect ('\r\n')

# Get ibm svc config
def getsvcconf(login, key, svc, dir):
    ssh_newkey = 'Are you sure you want to continue connecting'
    svc_files = ['svc.config.backup.sh', 'svc.config.backup.log', 'svc.config.backup.xml']
    list_files = []
    child = pexpect.spawn("ssh -i"+key+" "+login+"@"+svc)
    #For debug purpose
    #child.logfile = sys.stdout

    i=child.expect([pexpect.EOF,ssh_newkey,':'+login+'>'],timeout=60)
    if i==0:
       print "Configuration file from", svc,"is not received"
       return
    if i==1:
       child.sendline('yes')
    if i==2:
       child.sendline('svcconfig backup')
       child.expect (':'+login+'>')
       # Get some state
       f = open(dir+'/'+svc+'-state', 'w')
       child.sendline ('svcinfo lsmdiskgrp')
       child.expect (':'+login+'>')
       f.write(child.before)
       child.sendline ('exit')
       list_files.append('./'+dir+'/'+svc+'-state')
    # Get config from svc
    for file in svc_files:
        child = pexpect.spawn('/usr/bin/scp -i'+key+' '+login+'@'+svc+':/tmp/'+file+' '+dir+'/'+svc+'-'+file)
        # Expect new line
        child.expect ('\r\n')
        list_files.append('./'+dir+'/'+svc+'-'+file)
    # Check if file exist in svn and add file if not.
    for file in list_files:
        if not client.info(file):
           # File added with "segmentation filed" bug! 
           #client.add(file)
           child = pexpect.spawn('/usr/bin/svn add '+file)
           child.expect ('\r\n')


# Main program
# Read configuration file
cfgpr = ConfigParser()
cfgpr.read('config.ini')
client = pysvn.Client()

username = os.getenv('LOGNAME')
host = cfgpr.get('system','host')
userpassword = cfgpr.get('system','password')
devicepath = cfgpr.get('system','path')

# Get configurations
for section in cfgpr.sections():
    if re.match("brocade", section):
       ensure_dir(section)
       password = cfgpr.get(section,'password')
       login = cfgpr.get(section,'login')
       for option in cfgpr.options(section):
           if option != "password":
              if option != "login":
                 if cfgpr.getboolean(section, option):
                    getbrconf(login, password, option, section)
    if re.match("svc", section):
       ensure_dir(section)
       key = cfgpr.get(section,'key')
       login = cfgpr.get(section,'login')
       for option in cfgpr.options(section):
           if option != "key":
              if option != "login":
                 if cfgpr.getboolean(section, option):
                    getsvcconf(login, key, option, section)

# Update repo
client.checkin('./', 'Update repository')


В скрипте производится последовательный разбор конфигурационного файла с последующим вызовом соответствующей процедуры (т.е. или забираем конфигурационный файл с коммутатора - процедура getbrconf или с svc - getsvcconf). В целом, исходный текст скрипта достаточно подробно комментирован, так что я думаю разобраться в нем не составит труда. К сожалению модуль pysvn имеет ошибку, добавление файла в репозиторий заканчивается "Segmentation fault", поэтому добавление файлов реализовано средствами команды svn.
Создадим рабочую копию репозитария:

[confrepuser@server ~]$ svn co file:///var/www/svn/confrepo

Теперь для завершения задачи, достаточно поместить файлы config.ini и geconfig.py в ~/confrepo/devices и добавить задание в cron для запуска скрипта. Файл скрипта и конфигурационный файл так же можно поместить в репозиторий. Будьте осторожны, поскольку в config.ini находятся пароли!

суббота, 18 февраля 2012 г.

Вирусные заболевания

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

четверг, 9 февраля 2012 г.

Управление конфигурацией в IT инфраструктуре ч.1

Придя на нынешнее место работы, я был несколько удивлен. При большой и развитой it инфраструктуре механизма управления конфигурациями не было. Достаточно давно я использую rancid для хранения конфигурационных файлов оборудования cisco. После того как несколько лет назад я прочитал статью об использовании subversion, я стал использовать его для управления конфигурационными файлами на unix серверах. Так как rancid не поддерживает ряд оборудования, которое используется в настоящий момент (в основном это san оборудование), то возникла необходимость разработать решение под себя. Причем решение должно быть одно для всей инфраструктуры.
Для понимания какой конечный результат нужен, я сформулировал требования/положения которые я использовал в процессе разработки и эксплуатации решения:
  • Должно быть единое хранилище конфигурационной информации, включающее в себя конфигурационные файлы приложений, сетевого и san оборудования, а так же файлы скриптов
  • Для хранения конфигураций необходимо использовать систему контроля версий
  • В корне репозитория должны быть контейнеры отражающие классовую принадлежность конфигурационных файлов (т.е. к примеру servers - для серверов, devices - для устройств). Конфигурационные файлы серверов должны группироваться по принадлежности к серверу. Контейнер, содержащий конфигурационные файлы, должен именоваться в соответствии с полным именем сервера. Путь к конфигурационным файлам, находящимся в контейнере c именем сервера, должен соответствовать пути к файлу на файловой системе.  Конфигурационные файлы устройств должны группироваться по принадлежности к семейству, контейнер должен содержать имя семейства. Соответственно структура репозитория должна выглядеть следующим образом:
  /
   /servers/
      /name_of_server/
         /path_to_file/
            /file

         /path_to_file/
            /script_file
   /devices/
      /device_name/
         /device_name
  • Механизм получения конфигураций оборудования должен работать автоматически. Должен использоваться конфигурационный файл, содержащий все необходимое (название устройства, логин, пароль, признак то что с устройства надо снимать конфигурацию). Конфигурация должна быть получена рекомендованным производителем способом, для возможности корректного восстановления. Необходимо так же предусмотреть возможность получения информации о состоянии оборудования (версия ПО, состояние портов, заполненность пулов и т.п.)
  • Конфигурационные файлы с серверов помещаться в репозиторий будут вручную, с указанием целей произведенного изменения (через несколько лет краткие комментарии бывают иногда очень полезны). 
  • Необходим web интерфейс для анализа текущей конфигурации и произведенных изменений.
Легко заметить что процесс внедрения решения разделяется на две части. В первой мы произведем установку репозитария и рассмотрим основные аспекты его использования. А во второй напишем скрипт, который будет помещать в репозиторий конфигурационные файлы оборудования.
Используемое программное обеспечение: дистрибутив linux - rhel5,  в качестве системы контроля версий - subversion, httpd и websvn - для формирования web интерфейса. Полное имя сервера с используемое в примерах - server.domain. Подключите репозиторий epel, т.к ряд rpm пакетов содержится именно там.

Установим httpd, subversion и необходимые модули:

[root@server ~]# yum install mod_dav_svn websvn subversion httpd mod_ssl
[root@server ~]# chkconfig httpd on

Создадим самоподписанный сертификат для шифрования сессии между клиентом и сервером subversion:

[root@server ~]# openssl req -out /etc/pki/tls/certs/repos.crt -nodes -keyout /etc/pki/tls/private/repos.key -newkey rsa:2048 -x509 -days 3650  -subj "/C=RU/ST=Moscow/L=Moscow/O=Organization Name/CN=server.domain/emailAddress=email@domain"

Настроим модуль mod_dav_svn следующим образом:

[root@server ~]# grep -v '^#\|^$' /etc/httpd/conf.d/subversion.conf 
LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so
<Location /repos>
   DAV svn
   SVNParentPath /var/www/svn
      SSLRequireSSL
      AuthType Basic
      AuthName "Authorization Realm"
      AuthUserFile /etc/httpd/conf.d/svn-auth-conf
      Require valid-user
</Location>

Исправите путь к сертификату в файле ssl.conf:

[root@server ~]# grep "^SSLCert" /etc/httpd/conf.d/ssl.conf 
SSLCertificateFile /etc/pki/tls/certs/repos.crt
SSLCertificateKeyFile /etc/pki/tls/private/repos.key

Создадим файл паролей, который будет ограничивать доступ в репозиторий:

[root@server ~]# htpasswd -c /etc/httpd/conf.d/svn-auth-conf confrepuser
New password:
Re-type new password:
Adding password for user confrepuser

Добавим системного пользователя, от имени которого будет работать скрипт:

[root@server ~]# adduser confrepuser -c "Owner of repository"
[root@server ~]# echo "cfgbackup" | passwd --stdin confrepuser

Создадим репозиторий и запустим httpd:

[root@server ~]# mkdir /var/www/svn
[root@server ~]# cd /var/www/svn
[root@server ~]# svnadmin create confrepo
[root@server ~]# chown -R apache.confrepuser confrepo
[root@server ~]# chmod -R g+w ./confrepo
[root@server ~]# /etc/init.d/httpd start

Проверить работоспособность репозитория можно пройдя по ссылке  https://server.domain/repos/confrepo, введя логин/пароль вы должны увидеть страничку "confrepo - Revision 0: /". Если все получилось то перейдем к формировании структуры репозитория.

Создадим в репозитории корневые директории:

[root@server ~]# su - confrepuser
[confrepuser@server ~]$ svn mkdir file:///var/www/svn/confrepo/servers -m "Add dir"

Committed revision 1

Если вы сейчас обновите страничку, которую открывали для проверки, то увидите что ревизия теперь номер 1. Аналогично:

[confrepuser@server ~]$ svn mkdir file:///var/www/svn/confrepo/devices -m "Add dir"

Если указать переменную среды SVN_EDITOR с предпочитаемым редактором, то ключ "-m" можно не ставить, к примеру:

[confrepuser@server ~]$ export SVN_EDITOR=vim

Проиллюстрируем методику управления конфигурационными файлами на примере сервиса httpd, находящемся на сервере server2.domain. В соответствии с сформулируемыми ранее правилами создаем папки:

[confrepuser@server ~]$ svn mkdir --parents file:///var/www/svn/confrepo/servers/server2.domain/etc -m "Add dir"

Далее, на сервере server2.domain создадим рабочую копию каталога /etc непосредственно в корне файловой системы:

[root@server2 ~]# cd /
[root@server2 ~]# svn co https://server.domain/repos/confrepo/servers/server2.domain/etc

Error validating server certificate for 'https://server.domain:443':
 - The certificate is not issued by a trusted authority. Use the
   fingerprint to validate the certificate manually!
Certificate information:
 - Hostname: server.domain
 - Valid: from Feb  3 09:53:02 2012 GMT until Jan 31 09:53:02 2022 GMT
 - Issuer: Organization, Moscow, Moscow, RU
 - Fingerprint: 59:73:33:56:87:78:7f:06:f9:da:ae:de:c7:f9:19:ac:88:26:dd:42
(R)eject, accept (t)emporarily or accept (p)ermanently? p
...
Username: confrepuser
Password for 'confrepuser':
...
Store password unencrypted (yes/no)? no

Примите сертификат, введите логин и пароль. На вопрос "Сохранить пароль?" ответьте отрицательно, т.к. в случае утвердительного ответа пароль будет хранится в открытом виде, в файле находящемся в директории ~/.subversion/auth/svn.simple. Для того что бы в последующем этот вопрос не возникал добавьте следующий параметр:

[root@server2 ~]# echo "store-plaintext-passwords=no" >> /root/.subversion/servers

Добавим конфигурационные файлы в репозиторий, и запишем изменения (закоммитим):

[root@server2 ~]# svn add --parents /etc/httpd/conf/httpd.conf
[root@server2 ~]# svn add --parents /etc/httpd/conf.d/ssl.conf
[root@server2 ~]# svn commit /etc -m "Add apache's conf/ for server2.domain"

Итак, данные файлы теперь доступны в репозитории. Предположим что мы добавили новый хост в конфигурации httpd, произведем изменение и запишем изменение в репозиторий:

[root@server2 ~]# svn commit /etc -m "Add new vhost for server2.domain"
...
Sending        etc/httpd/conf/httpd.conf
Transmitting file data .
Committed revision 5.



Если необходимо вернуть конфигурацию к предыдущему состоянию:

[root@server2 ~]# svn co -r 4 https://server.domain/repos/confrepo/servers/server2.domain/etc/httpd/ /etc/httpd
...
U    /etc/httpd/conf/httpd.conf
Checked out revision 4.



опция "-r" указывает номер ревизии на который надо восстановить.
Если необходимо удалить файл или директорию из репозитория:

[root@server2 ~]# svn rm https://server.domain/repos/confrepo/servers/server2.domain/etc/httpd/conf/httpd.conf -m "remove file"

Развертывание конфигурационных файлов на сервер отличный от первоначального можно выполнить следующим образом:
  1. Создадите рабочую копию каталога содержащего конфигурационные файлы в домашней директории;
  2. Перепишите файлы в место назначения;
  3. Создадите новую директорию в репозитории с именем сервера, и поместите туда файлы.
Как вы уже заметили, web страница репозитария не отражает какие именно внесены изменения. Для наглядного работы с репозиторием воспользуемся уже установленным websvn. Приведите конфигурационный файл /etc/httpd/conf.d/websvn.conf виду:

[root@server ~]# grep -v '^#\|^$' /etc/httpd/conf.d/websvn.conf
Alias /websvn /usr/share/websvn/
<Directory /usr/share/websvn/>
   SSLRequireSSL
   Options MultiViews
   DirectoryIndex wsvn.php
   order allow,deny
   allow from all
   AuthType Basic
   AuthName "Subversion Repository"
   AuthUserFile /etc/httpd/conf.d/svn-auth-conf
   Require valid-user
</Directory>


Укажите нахождение репозитория в конфигурации websvn:


[root@server ~]# echo "\$config->addRepository('Organization's Repository', 'file:///var/www/svn/confrepo');" >> /etc/websvn/config.php
[root@server ~]# /etc/init.d/httpd reload


Рассказывать о websvn нет смысла, там все очень просто, просто перейдите по ссылке https://server.domain/repos/websvn. В качестве меры безопасности, вы можете сменить метод аутентификации на более стойкий (или можно просто использовать другой AuthUserFile).