Бэкап для Linux не пишет писем

    Всем привет!

    Сегодня хочу поведать о том, как управлять Veeam Agent for Linux с помощью командной строки, и о том, какие возможности она открывает в умелых руках программиста.

    На написание статьи меня подтолкнул комментарий к предыдущей статье. Перефразирую удивление пользователя: «Ну как же так? Cервер не пишет писем о том, что он забэкапился!». Причём, со слов аналитиков, он не один такой, иначе бы не появился тред на форуме. А раз люди пишут — значит, это кому нибудь нужно!

    В статье я поясню, почему этой функции в продукте нет. Но на этом мы не остановимся, мы эту функцию добавим! Мы ж программисты, так что напишем письмо и сгенерируем отчёт в виде html страницы.


    Кроме того, покажу наиболее полезные, на мой взгляд, команды, которые смогут облегчить труд администратора.

    Приготовьтесь: много кода, картинок нет.

    Для начала ответим на вопрос: «Почему Veeam Agent for Linux не пишет писем?»

    Ответы вам могут не понравиться, не обессудьте. А дело в том, что более-менее крупным enterprise пользователям это не нужно, и вот почему:

    • Во-первых, для работы с почтой нужно либо поставить smpt-сервер на локальную машину, либо пользоваться каким-то внутри сети. При самой простой реализации (команда mail) потребуется ставить пакет mailutils. А многие системные администраторы не захотят создавать на своём продакшн сервере потенуциальную уязвимость в виде сервиса, который может слать куда бы то ни было письма. Да и возможности может не быть по причине закрытости портов, независимости подсетей и прочее...
    • Во-вторых, так как пакета mailutils очень может не быть на системе (по первой причине), нет смысла и пытаться его использовать. Иначе можем получить функцию, которая вроде есть, но «из коробки» не работает, а значит, будет тред на форуме на тему типа: «Как настроить сервер так, чтобы письма-таки отсылались.»
    • Ну и в-третьих, вообще никакая дополнительная нотификация не нужна, так как более-менее крупные enterprise-заказчики используют Veeam Backup & Replication. Его консоль собирает информацию о всех бэкапах, которые были произведены на известные репозитории. Убедитесь сами.

    В версии Veeam Backup & Replication 9.5 Update 4 есть возможность пользоваться этим продуктом бесплатно, но с ограничением по обслуживаемым виртуальным/физическим машинам.
    Если у вас до 3-х (включительно) физических серверов — бесплатных функций VBR вам будет более чем достаточно.

    Если же у вас машин больше 3-х, платить за ПО нет возможности, а централизованно производить мониторинг своих серверов всё же хочется, то предлагаю дописать немного скриптов самостоятельно. Люблю потешить себя на python после рабочего для на С/С++.

    Скриптами мы будем оборачивать вызов команды veeamconfig. Команда veeamconfig обеспечивает доступ ко всему функционалу продукта. Бесспорно, псевдографический интерфейс, созданный с помощью библиотеки ncurses, намного приятнее для глаз, однако если нужно связать программы во что-то новое, то CLI — наше всё.

    Описание команд Veeam Agent for Linux справедливо для версии 3.0. На предыдущих версиях не проверял, так что могут быть отличия.

    CLI-интерфейс в Veeam Agent for Linux довольно удобный и неплохо документирован. Достаточно ввести veeamconfig --help, и вы получите список доступных команд:

    $sudo veeamconfig --help
    Veeam Agent for Linux
    (c) Veeam Software AG
    
      Usage: veeamconfig [command]
    
    Commands:
      repository               - Backup repositories management
      vbrserver                - Veeam Backup and Replication servers management
      job                      - Backup jobs management
      backup                   - Backups management
      point                    - Restore points management
      license                  - License management
      agreement                - End User License Agreement management
      config                   - Import/export configuration
      schedule                 - Jobs schedule configuration
      cloud                    - Cloud provider management
      mode                     - Operation mode
      session                  - Sessions management
      ui                       - User interface
      aap                      - Application-aware processing
      version, --version, -v   - Product version
      help, --help, -h         - Short help
    

    Для того, чтобы посмотреть, что каждая команда позволяет сделать, достаточно вызвать veeamconfig config --help. Получим:

    Veeam Agent for Linux
    (c) Veeam Software AG
    
      Usage: veeamconfig config [command]
    
    Commands:
      import             - Import repositories and jobs into database
      export             - Export repositories and jobs from database
      grabLogs           - Collect support logs bundle
      patchiso           - Create custom Veeam Recovery Media adding all hardware drivers from this system
      help, --help, -h   - Short help
    

    Тут, кстати, мы можем увидеть команду сбора логов grabLogs. Она позволит быстро собрать все необходимые логи для саппорта. Это на случай, если что-то пойдёт не так.

    Есть ещё интересная команда, которая появилась в версии 3.0:

    $ sudo veeamconfig agreement --help
    Veeam Agent for Linux
    (c) Veeam Software AG
    
      Usage: veeamconfig agreement [command]
    
    Commands:
      acceptEula                 - Accept Veeam End User License Agreements
      acceptThirdPartyLicenses   - Accept Veeam 3rd party License Agreement
      show                       - Show End User License Agreements acceptance status
      help, --help, -h           - Short help
    

    Дело в том, что начиная с версии 3.0 от пользователя требуется явно дать согласие с лицензионными соглашениями. Выглядит это примерно так:

    $ sudo veeamconfig job list
    I accept Veeam Software End User License Agreement:
    /usr/share/doc/veeam/EULA
    (yes/no | y/n):
    yes
    I accept the terms of the following 3rd party software components license agreements:
    /usr/share/doc/veeam/3rd_party
    (yes/no | y/n):
    

    Соответственно, работа ваших скриптов может быть нарушена. Чтобы не заходить на каждую машину и не выполнять эту процедуру вручную, были предусмотрены команды:

    veeamconfig agreement acceptEula
    veeamconfig agreement acceptThirdPartyLicenses
    

    Они позволяют принять лицензионные соглашения без лишних вопросов.

    Но мы отклонились от темы написания письма.

    Для задачи мониторинга состояния сервера нам потребуется команда veeamconfig session list. Выводит она что-то типа:

    Job name    Type    ID                                      State    Started at        Finished at     
    bj-home     Backup  {dbe48e88-3df7-4712-a472-09af8fed4e80}  Success  2018-12-05 15:43  2018-12-05 15:44
    bj-home     Backup  {c178a799-2935-4bd6-883b-b11278000076}  Success  2018-12-05 16:26  2018-12-05 16:26
    bj-home     Backup  {3405dad3-0016-4a00-933e-60ef66b30324}  Success  2018-12-06 06:00  2018-12-06 06:00
    

    Отлично, тут есть информация, когда сервер бэкапился и каков был успех. В принципе, уже можно собирать «выхлоп» в файл и слать письмом. Однако уже за год письмо может подрасти примерно на 365 строк. А выискивать в тексте State с ошибками может показаться утомительным. Поэтому распарсим этот «выхлоп» и получим нормальный список, с которым можно уже что-то делать.

    Целиком код смотреть тут

    class CSession:
        @staticmethod
        def List():
    return subproccall( ["veeamconfig", "session", "list"] )
    
    class CSessionInfoList(object):
        def __init__(self, list):
            self.list = list
    
        def List(self):
            return self.list
    
        @staticmethod
        def Get():
            text = CSession.List()
            lines = text.split("\n")
            list = [] # session info list
            for line in lines:
                if len(line) == 0:
                    continue
    
                words = line.split()
                if len(words) == 0:
                    continue
                if words[0] == "Job":
                    continue
                if words[0] == "Total":
                    continue
                try:
                    jobName = words[0]
                    type = words[1]
                    id = words[2]
                    state = words[3]
                    startTime = words[4] + " " + words[5]
                    finishTime = words[6] + " " + words[7]
                    list.append(CSessionInfo(id, type, jobName, state, startTime, finishTime))
                except:
                    print "Failed to parse [", line, "]"
            return CSessionInfoList(list)
    

    Ну а теперь сделаем письмо и отправим его себе.

    def SendMailsessions():
        print "---"
        print "Sending statistic to administrator:"
    
        sessions = veeamlpb.session.CSessionInfoList.Get()
    
        recipient = "dear.admin@company.com"
        subject = "VAL status notification"
        text = "Statistic:\n"
    
        inx = 0;
        successCount = 0
        warningCount = 0
        errorCount = 0
    
        for sessionInfo in sessions.List():
            if (sessionInfo.State() == "Success"):
                successCount += 1
            elif (sessionInfo.State() == "Warning"):
                warningCount += 1
            else:
                errorCount += 1
        text += str(successCount)+"/"+str(warningCount)+"/"+str(errorCount)+" Success/Warning/Error\n"
    
        text += "Last 10 session:\n"
        for sessionInfo in reversed(sessions.List()):
            if inx == 10:
                text += "...\n"
                break;
    
            text += str(inx)+" | "+sessionInfo.State()+" | "+sessionInfo.JobName()+" | "+sessionInfo.StartTime()+" / "+sessionInfo.FinishTime() + "\n"
            #text += 
            inx += 1
    
        text += "\n"
        text += "--------------------------------------------------------------------------------\n"
        text += "  Yours sincerely, Veeam Agent for Linux Monitor\n"
    
        print text
        os.system("echo '"+text+"' | mail -s '"+subject+"' "+recipient)
    

    В результате после установки mailutils можем получить письмо вида:

    Statistic:
    3/0/0 Success/Warning/Error
    Last 10 session:
    0 | Success | bj-home | 2018-12-06 06:00 / 2018-12-06 06:00
    1 | Success | bj-home | 2018-12-05 16:26 / 2018-12-05 16:26
    2 | Success | bj-home | 2018-12-05 15:43 / 2018-12-05 15:44
    
    --------------------------------------------------------------------------------
      Yours sincerely, Veeam Agent for Linux Monitor
    

    В письме выводятся только последние 10 сессий. При этом в самом начале письма выводится информация о числе успешных и не очень сессий. Достаточно глянуть на циферки в письме в начале рабочего дня, проверяя почту и потягивая кофеёк, чтобы понять, что ночные бэкапы прошли успешно.

    Если же вам нужно что-то понагляднее — можно запросить информацию о сессиях в xml-формате и передать её на свой сервер. Там объединить полученные данные в единую сводную таблицу, которая отобразит всю необходимую информацию в удобном или посильном для вас формате.
    XML-ку получаем парой строк:

    sessionList = veeamlpb.session.CSessionList()
    text = sessionList.ToXml()
    

    Сохраняем полученное в файлик

    sessionListFileName = "session_list.xml"
    print "Store XML to file: ",sessionListFileName
    sessionListXmlFile = open(sessionListFileName, "w")
    sessionListXmlFile.write(text)
    sessionListXmlFile.close()
    

    Далее полученную XML-ку отправляем на сервер. Возможен и альтернативный вариант — сервер собирает XML-ки с машин, которые бэкапятся. Кто инициатор — нам пока не важно. Важно, что на сервере собираются XML-ки со списками сессий со всех машин. Я выбрал певый вариант:

    hostname = os.uname()[1]
    target = "user@admin-desktop:/home/user"
    os.system("scp ./"+sessionListFileName+" "+target+"/backups/"+hostname+"/session_list.xml")
    

    Теперь на стороне сервера осталось обработать принятые данные и сделать красивую html-страничку.

    import veeamlpb
    import os
    import datetime
    import xml.etree.ElementTree as xml
    
    def main():
    
        hosts = []
        backupsDirectory = "/home/user/backups"
        for item in os.listdir(backupsDirectory):
            if item in [".", ".."]:
                continue
            if os.path.isdir(os.path.join(backupsDirectory,item)):
                hosts.append(item)
                print "item: ",item
    
        if len(hosts) == 0:
            return 0
    
        backupSessionMap = {}
        for host in hosts:
            print "found host: ", host
            sessionInfoFile = os.path.join(os.path.join(backupsDirectory,host), "session_list.xml")
            sessionList = veeamlpb.session.CSessionInfoList.FromXmlFile(sessionInfoFile)
            backupSessionMap[host] = sessionList
    
            for sessionInfo in sessionList.List():
                print "Session:",sessionInfo.ToString()
    
        html = xml.Element("html")
        body = xml.SubElement(html, "body", {"style":"background-color: #00b336;"})
    
        xml.SubElement(body,"h1").text = "Report at "+datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        xml.SubElement(body,"h2").text = "Statistic:"
        for host in hosts:
            sessionList = backupSessionMap[host]
            success=0
            warning=0
            error=0
            if len(sessionList.List()) == 0:
                continue
            
            for sessionInfo in sessionList.List():
                if sessionInfo.State() == "Success":
                    success +=1
                elif sessionInfo.State() == "Warning":
                    warning +=1
                else:
                    error +=1
    
            latestSessionInfo = sessionList.List()[-1]
            attr = {}
            if latestSessionInfo.State() == "Success":
                #attr["style"] = "background-color: #00b336;"
                attr["style"] = "background-color: #005f4b; color: white;"
            elif latestSessionInfo.State() == "Warning":
                attr["style"] = "background-color: #93ea20;"
            else:          
                attr["style"] = "background-color: #ba0200; color: white;"
                        
            xml.SubElement(xml.SubElement(body,"p"),"span", attr).text = \
                host + " - "+str(success)+"/"+str(warning)+"/"+str(error)+" Success/Warning/Error"
    
                
        for host in hosts:
            sessionList = backupSessionMap[host]
            
            xml.SubElement(body,"h2").text = host+":"
    
            tableStyle =xml.SubElement(body,"style")
            tableStyle.attrib["type"] = "text/css"
            tableStyle.text = "TABLE {border: 1px solid green;} TD{ border: 1px solid green; padding: 4px;}" 
            
            table = xml.SubElement(body,"table")
            thead = xml.SubElement(table, "thead")
            xml.SubElement(thead, "th").text = "Number"
            xml.SubElement(thead, "th").text = "State"
            xml.SubElement(thead, "th").text = "Job name"
            xml.SubElement(thead, "th").text = "Start at"
            xml.SubElement(thead, "th").text = "Complete at"
            
            tbody = xml.SubElement(table, "tbody")
            inx = 0
            for sessionInfo in reversed(sessionList.List()):
                if inx == 10:
                    break;
                tr = xml.SubElement(tbody,"tr")
                xml.SubElement(tr, "td").text = str(inx)
    
                attr ={}
                if sessionInfo.State() == "Success":
                    pass
                elif sessionInfo.State() == "Warning":
                    attr["style"] ="background-color: #93ea20;"
                else:  
                    attr["style"] ="background-color: #ba0200; color: white;" 
                xml.SubElement(tr, "td", attr).text = sessionInfo.State()
                
                xml.SubElement(tr, "td").text = sessionInfo.JobName()
                xml.SubElement(tr, "td").text = sessionInfo.StartTime()
                xml.SubElement(tr, "td").text = sessionInfo.FinishTime()
    
                inx += 1
    
        xml.ElementTree(html).write("summary.html", encoding='utf-8', method='html')
        return 0
    
    exit(main())
    

    В результате отчёт готов:



    У меня не было задачи сделать красивый продукт. Задача была показать, что вопрос сбора статистики можно решить на скриптах за один-два дня.

    В принципе, если развить изложенные здесь идеи, то можно сделать «Open Backup Monitor for Veeam Agent for Linux». На мой взгляд, хорошая тема для курсовой по Python, или, может, даже для диплома, или просто повод потренироваться в программировании над opensource-проектом. Согласитесь, лучше потренироваться в программировании, чем становиться эльфом 80-го уровня.

    Весь код можно найти на http://www.github.com/CodeImp/veeampy/. Качайте, пользуйтесь, дополняйте и форкайте на здоровье.

    Учтите, код распространяется по GPL-2 лицензии, может содержать ошибки и прочее. Всё как обычно в мире opensource. Так что прежде чем применять в продакшн — не забудьте погонять на тестовой лабе.
    Veeam Software
    109,00
    Продукты для резервного копирования информации
    Поделиться публикацией

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое