Серверы
  • Готовые серверы
  • Конфигуратор
  • Серверы с 1CPU
  • Серверы с 2CPU
  • 4 поколение AMD EPYC и Intel Xeоn
  • Серверы с AMD Ryzen и Intel Core i9
  • Серверы для хранения данных
  • Cерверы с портом 10 Гбит/c
  • GPU
  • Распродажа
  • VPS
    GPU
  • Выделенные серверы с GPU
  • Виртуальные серверы с GPU
  • Распродажа
    Маркетплейс
    Colocation
  • Размещение серверов в дата-центре в Москве
  • Размещение серверов в дата-центре в Амстердаме
  • Обслуживание серверов в других ЦОД
  • Кластеры
    Прокат
    Услуги
  • Аренда сетевого оборудования
  • Защита от DDoS атак
  • IPV4 и IPV6 адреса
  • Администрирование серверов
  • Уровни технической поддержки
  • Мониторинг сервера
  • Программное обеспечение
  • BYOIP
  • USB диск
  • IP-KVM
  • Трафик
  • Коммутация серверов
  • Поставки оборудования за рубежом
  • О нас
  • Работа в HOSTKEY
  • Панель управления серверами и API
  • Дата-центры
  • Сеть
  • Тест скорости
  • Специальные предложения
  • Отдел продаж
  • Для реселлеров
  • Партнерская программа
  • Гранты для специалистов по Data Science
  • Гранты для научных проектов и стартапов
  • Документация и Частые вопросы
  • Новости
  • Блог
  • Оплата
  • Документы
  • Сообщите о нарушении
  • Looking Glass
  • 04.01.2023

    Сбор логов при помощи Go

    server one
    HOSTKEY
    Арендуйте выделенные и виртуальные серверы с моментальным деплоем в надежных дата-центрах класса TIER III в Москве и Нидерландах. Принимаем оплату за услуги HOSTKEY в Нидерландах в рублях на счет российской компании. Оплата с помощью банковских карт, в том числе и картой МИР, банковского перевода и электронных денег.

    Автор: Александр Тряпкин, DevOps компании Hostkey

    Здравствуйте, уважаемые читатели! В этой статье я хочу поделиться своим опытом решения задачи сбора логов при помощи Go. Как начинающий DevOps, я выбрал для изучения и решения рабочих задач язык программирования Go. Для отправки syslog-логов доступна библиотeка syslog, но увы, она нам не подходит, поскольку данный пакет недоступен на Windows, а задача — сделать мультиплатформенный отправщик логов установки системы на удаленный syslog-сервер. Дополнительно есть потребность отправлять логи в кастомном формате, а именно — в json, для упрощения их последующей обработки. При этом важно, чтобы программа выполнялась одинаково на Linux и на Windows, не требовала установки, выполняла свою задачу и удалялась из системы, поэтому придется изобрести небольшой велосипед. Приступим.

    В качестве принимающей стороны мы будем использовать syslog-ng. Рассмотрим параметры, которые нам интересны в части сбора логов — от специфики параметров зависит, как мы будем их отправлять.

    Сначала указываем новый source для приема логов с удаленных серверов, и тут есть варианты — в зависимости от наших потребностей можно собирать логи по UDP, TCP, а также использовать TLS для шифрования и аутентификации. Наиболее интересным вариантом является TLS, но мы рассмотрим и другие методы — от простого к более сложному.

    1) UDP. Для сбора логов по UDP потребуется следующие параметры в конфигурации syslog-ng:

    source s_network {
    	network( ip("0.0.0.0") #IP, на котором принимать логи, 0.0.0.0 - на всех
    						 transport("udp")  );       };

    Порт по умолчанию — 514/UDP, документация предупреждает о необходимости увеличить UDP-буфер при высокой интенсивности отправки логов, иначе возможны потери сообщений. В случае потери пакетов логи также будут потеряны,так что это не оптимальный вариант.

    2) TCP. Вариант лишен вышеуказанных проблем и, согласно документации, применяется по умолчанию. Примерный конфиг следующий:

    source s_network {
    	network( ip("0.0.0.0") ); };

    3) TLS. Для использования этого протокола необходимо настроить сервер, в официальной документации есть достаточно подробная пошаговая инструкция. Пример:

    source s_remote_tls { 
    network ( ip ("0.0.0.0") port(6514) 
    transport("tls") 
    tls( key-file("/etc/syslog-ng/cert.d/serverkey.pem") cert-file("/etc/syslog-ng/cert.d/servercert.pem")
    ca-dir("/etc/syslog-ng/ca.d")
    peer-verify(yes)) ); };

    При таком варианте настройки мы будем принимать логи только от клиентов, прошедших аутентификацию. Иначе говоря, клиент использует действующий сертификат и логи приходят с IP-адреса или доменного имени, под который сертификат выпущен. Если нет задачи аутентифицировать пользователей, то можно указать peer-verify (no) и получить только шифрование.

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

    Для начала разберемся, как отправить syslog сообщение серверу так, чтобы он его принял и обработал. Из документации мы видим, что принимаемые сообщения должны соответствовать протоколу RFC3164 или RFC5424. Но поскольку это не окончательный вариант, попробуем отправить лог, используя RFC3164, который выглядит следующим образом:

    <30>Dec 25 21:55:36 19202.example.ru systemd[1]: Starting Cleanup of Temporary Directories...

    Теперь разберемся, что значит каждая из частей сообщения:

    • <30> — заголовок, содержащий информацию о severity и facility. Закодированную информацию можно расшифровать с помощью таблицы, в данном случае там содержится facility — system и severity — info.
    • Dec 25 21:55:36 — timestamp.
    • 19202.example.ru — hostname.
    • Systemd[1]: — тег сообщения, указывающий, какой программой было отправлено сообщение.
    • Starting Cleanup of Temporary Directories… — само сообщение.

    Попробуем отправить сообщение в таком формате в наш тестовый сервер syslog-ng, настроенный на прием логов по UDP. Для этого мы используем библиотеку net:

    	logsrv, err := net.ResolveUDPAddr("udp4", "141.105.70.24:514")
    	if err != nil {
    		log.Fatal(err)
    	}
    	logwriter, err := net.DialUDP("udp4", nil, logsrv)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer logwriter.Close()
    	_, err = logwriter.Write([]byte("<30>Dec 25 21:55:36 test-host go-logger: Hello Habr!"))
    	_, err = logwriter.Write([]byte("<30>Dec 25 21:55:36 test-host go-logger: This is test go-logger!"))
    	if err != nil {
    		log.Fatal(err)
    	}
    

    Выполнив код, мы видим, что сервер получил наши сообщения и обработал. Сообщения записаны в указанный файл:

    [root@19181 ~]# cat /var/log/test 
    Dec 25 21:55:36 test-host go-logger: Hello Habr! 
    Dec 25 21:55:36 test-host go-logger: This is test go-logger!

    Теперь отправим логи по TCP. Перенастраиваем сервер на получение логов по TCP и пробуем:

    tcpAddr, err := net.ResolveTCPAddr("tcp", "141.105.70.24:514")
    	if err != nil {
    		log.Fatal(err)
    	}
    	logwriter, err := net.DialTCP("tcp", nil, tcpAddr)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer logwriter.Close()
    	_, err = logwriter.Write([]byte("<30>Dec 25 21:55:36 test-host go-logger: Hello Habr!"))
    	_, err = logwriter.Write([]byte("<30>Dec 25 21:55:36 test-host go-logger: This is test go-logger!"))
    	if err != nil {
    		log.Fatal(err)
    	}
    
    Dec 25 21:55:36 test-host go-logger: Hello Habr!<30>Dec 25 21:55:36 test-host go-logger: This is test go-logger!

    Но что мы видим: что-то пошло не так, два сообщения соединены в одно, и второе сообщение не распарсилось. Когда мы отправляли логи по UDP, данная проблема не возникала, поскольку каждое сообщение уходит в своем пакете и обрабатывается отдельно. Решение на самом деле простое — я упустил, что каждое сообщение должно заканчиваться переносом строки \n. Правим и пробуем:

    	_, err = logwriter.Write([]byte("<30>Dec 25 21:55:36 test-host go-logger: Hello Habr!\n"))
    	_, err = logwriter.Write([]byte("<30>Dec 25 21:55:36 test-host go-logger: This is test go-logger!\n"))
    [root@19181 ~]# cat /var/log/test 
    Dec 25 21:55:36 test-host go-logger: Hello Habr! 
    Dec 25 21:55:36 test-host go-logger: This is test go-logger!

    Теперь все ок!

    Мы разобрались как отправить сообщение, пришло время применить это знание. Следует учитывать, что, если мы хотим отправить сообщения в формате json (в дальнейшем это сильно облегчит задачу по обработке логов), нам необходимо отключить парсинг в syslog-ng. Для этого достаточно добавить flags (no-parse) в source. Далее будем пробовать отправить логи по протоколу TLS и в json-формате уже в виде полноценной программы:

    package main
    
    import (
    	"bufio"
    	"crypto/tls"
    	"crypto/x509"
    	"encoding/json"
    	"fmt"
    	"io/ioutil"
    	"log"
    	"os"
    	"time"
    
    	"github.com/pborman/getopt/v2"
    )
    	
    // Задаем структуру нашего сообщения, тут мы не ограничены  протоколом syslog, отправляем только то, что нам необходимо или, наоборот, добавляем
    type message struct {
    	Time     string `json:"timestamp"`
    	Hostname string `json:"host"`
    	Programm string `json:"programm"`
    	Body     string `json:"message"`
    }
    	
    func main() { 
    	// Нужные параметры мы будем передавать в нашу программу посредством ключей, в этом нам поможет библиотека getopt
    	optSyslogSrv := getopt.StringLong("dest", 'd', "", "Remote syslog server with port ip:port, required")
    	optReadFromFile := getopt.StringLong("file", 'f', "", "Read log from file")
    	optProg := getopt.StringLong("prog", 'p', "go-logger", "Programm tag, optional,  default - go-logger")
    	optHost := getopt.StringLong("host", 'H', "", "Host override")
    	optHelp := getopt.BoolLong("help", 'h', "Display usage")
    	optVerb := getopt.BoolLong("verbose", 'v', "Display outgoing msgs")
    	optCa := getopt.StringLong("ca", 'c', "cacert.pem", "CA")
    	optCert := getopt.StringLong("cert", 'C', "clientcert.pem", "Cert")
    	optKey := getopt.StringLong("key", 'K', "", "clientkey.pem", "Key")
    
    	getopt.Parse()
    	// Если программа запущена с ключем -h --help или не задан необходимый параметр, выводим подсказку по использованию
    	if *optHelp || len(*optSyslogSrv) == 0 {
    		getopt.Usage()
    		os.Exit(0)
    	}
    
    	var hostname string
    	var scanner *bufio.Scanner
    
    	if len(*optHost) != 0 { // Если hostname указан ключем, берем информацию оттуда
    		hostname = *optHost
    	} else { // Иначе получаем из системы
    		hostname, _ = os.Hostname()
    	}
    	// Логи наша программа может брать либо из stdin будучи запущеной в pipeline “anyscript.sh | go-logger -d 127.0.0.1:514”, либо из файла. Если указан параметр, то берем из файла
    	if len(*optReadFromFile) != 0 {
    		file, err := os.Open(*optReadFromFile) //Открываем файл
    		if err != nil {
    			log.Fatal(err)
    		}
    		defer file.Close() //Запланируем закрытие файла по окончании
    		scanner = bufio.NewScanner(file) //Читаем файл 
    	} else {
    		scanner = bufio.NewScanner(os.Stdin) //Читаем stdin
    	}
    
    	msg := message{Hostname: hostname, Programm: *optProg} //Вносим данные в структуру
    	//TLS-часть отправки наших логов
    	caCert, _ := ioutil.ReadFile(*optCa) //Подгружаем CA сервера из файла
    	caCertPool := x509.NewCertPool()
    	caCertPool.AppendCertsFromPEM(caCert)
    
    	cert, err := tls.LoadX509KeyPair(*optCert, *optKey) //Подгружаем сертификат и закрытый ключ клиента из файлов
    	if err != nil {
    		log.Fatal(err)
    	}
    	tlsConf := &tls.Config{ //Создаем конфигурацию TLS
    		RootCAs:      caCertPool,
    		Certificates: []tls.Certificate{cert},
    	}
    
    	logwriter, err := tls.Dial("tcp", *optSyslogSrv, tlsConf) // Устанавливаем TLS-соединение
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer logwriter.Close() //Запланируем закрытие соединения по окончании
    
    	for scanner.Scan() { //Обрабатываем каждое полученное сканером сообщение
    		sendMsg := message{
    			Time:     time.Now().Format("2006-01-02T15:04:05.00-07:00"), //Время в нужном нам формате
    			Body:     scanner.Text(),                                    //Сообщение
    			Hostname: msg.Hostname,
    			Programm: msg.Programm,
    		}
    		data, err := json.Marshal(sendMsg) //Маршалим наш json
    		if err != nil {
    			log.Fatal(err)
    		}
    		if *optVerb { //Если задан параметр -v, то печатаем отправляемое сообщение
    			fmt.Println(string(data))
    		}
    		_, err = logwriter.Write(append(data, "\n"...)) //Отправляем сообщение добавив перенос строки
    		if err != nil {
    			log.Fatal(err)
    		}
    	}
    }

    Пробуем выполнить, передав в программу все те же две строки, и сервер получает наши логи:

    {"timestamp":"2022-12-26T00:19:23.54+03:00","host":"test-go-logger","programm":"go-logger","message":"Hello habr!"}
    {"timestamp":"2022-12-26T00:19:23.54+03:00","host":"test-go-logger","programm":"go-logger","message":"Lets test!"}

    Эта программа — одна из первых, написанных мною на Go. В процессе ее создания я разобрался, как работает протокол syslog, и освоил азы нового для меня языка программирования. Программа позволила унифицировать отправку логов на разных операционных системах, независимо от семейства, в тех местах, где нет возможности пользоваться syslog-ng. В настоящее время мы адаптируем созданную программу для дальнейшего применения в инфраструктуре нашей компании.

    Арендуйте выделенные и виртуальные серверы с моментальным деплоем в надежных дата-центрах класса TIER III в Москве и Нидерландах. Принимаем оплату за услуги HOSTKEY в Нидерландах в рублях на счет российской компании. Оплата с помощью банковских карт, в том числе и картой МИР, банковского перевода и электронных денег.

    Другие статьи

    17.04.2024

    Как выбрать правильный сервер c подходящими для ваших нейросетей CPU/GPU

    Рассказываем о наиболее важных компонентах, которые влияют на выбор сервера для искусственного интеллекта

    05.04.2024

    VPS, хостинг сайтов или конструктор? Где разместить сайт бизнесу?

    Давайте сравним размещение сайта на VPS, хостингах сайтов (общих хостингах) и в популярных конструкторах сайтов.

    21.03.2024

    Есть ли жизнь после Microsoft Teams и OneDrive?

    Ищем альтернативу облачным сервисам Microsoft. Чем заменить Microsoft Teams, OneDrive, Excel, Microsoft 365 и Azure

    07.03.2024

    Как AI помогает побороть монополию в спортивной рекламе и при чем тут GPU и выделенные серверы

    ИИ и AR-технологии позволяют адаптировать рекламу на спортивных соревнованиях под разные аудитории в реальном времени, используя облачные GPU-решения.

    14.02.2024

    От xWiki к static-HTML. Как мы документацию «переезжали»

    Выбор платформы для создания портала с внешней и внутренней документацией. Перенос документов с cWiki на Material for MkDocs

    HOSTKEY Выделенные серверы в Европе, России и США Готовые решения и индивидуальные конфигурации серверов на базе процессоров AMD, Intel, карт GPU, Бесплатной защитой от DDoS-атак и безлимитный соединением на скорости 1 Гбит/с 30
    4.3 48 48
    Upload