pixel
Select Page

NETWAYS Blog

Der NETWAYS Support Collector

Dem ein oder anderen unserer Support Kunden ist unser neuer Support Collector vielleicht schon über den Weg gelaufen. Aber was ist das überhaupt? Und was bringt er?

Der NETWAYS Support Collector ist eines unserer neuesten Kreationen. Inspiriert von, dem mehr verbreiteten, icinga2-diagnostics ist die Aufgabe des Support Collectors, Daten über laufende Systeme und deren Komponenten zu sammeln.
So ist es möglich mittels eines einzelnen Aufrufes alle essentiellen Daten über das System zu sammeln.

Anhand dieser Daten können beispielsweise Support Abläufe effizienter gemacht werden oder sogar aussagekräftige Statistiken erstellt werden.

Der Support Collector kann neben den vorstellbar gängigen Daten wie Icinga 2 und Icinga Web 2 weit aus mehr.
Der aktuelle Rahmen, welcher durch das Tool abgedeckt wird, ist folgender:

  • Allgemeine System Informationen
  • Icinga 2
  • Icinga Web 2
  • Icinga Director
  • Mysql / MariaDB
  • PostgreSQL
  • Ansible
  • Puppet
  • InfluxDB
  • Grafana
  • Graphite

Für den User ist es selber wählbar, welche “Module” durch den Support Collector alle gesammelt werden sollen. Standartmäßig werden alle “Module” gesammelt, welche auf dem System gefunden werden.

Um den Sicherheitsaspekt zu beachten, werden alle Passwörter / IP Adressen / Token innerhalb der gesammelten Daten entfernt, bevor diese zu einen ZIP verpackt werden.
Die generierte ZIP Datei kann dann durch Support Kunden an unseren Support weitergeleitet werden, sobald ein Support Fall eintrifft.

Wer selber einen Blick auf den Support Collector werfen möchte, kann dies in dem GitHub Repository machen oder sich das Tool mit den durch uns bereit gestellten Paketen auf packages.netways.de/extra installieren.
Die –help Übersicht liefert einige Konfigurations Möglichkeiten, welche optional mitgegeben werden können.

Tobias Bauriedel
Tobias Bauriedel
Systems Engineer

Tobias ist ein offener und gelassener Mensch, dem vor allem der Spaß an der Arbeit wichtig ist. Bei uns hat er seine Ausbildung zum Fachinformatiker für Systemintegration abgeschlossen und arbeitet nun im NETWAYS Professional Services - Team Operations und entwickelt nebenbei Projekte für die NPS. In seiner Freizeit engagiert er sich ehrenamtlich aktiv bei der Freiwilligen Feuerwehr als Atemschutzgerätetrager und Maschinist, bereist die Welt und unternimmt gerne etwas mit Freunden.

Icinga Plugins in Golang

Golang ist an sich noch eine relativ junge Programmiersprache, ist jedoch bei vielen Entwicklern und Firmen gut angekommen und ist die Basis von vielen modernen Software Tools, von Docker bis Kubernetes.

Für die Entwicklung von Icinga Plugins bringt die Sprache einige hilfreiche Konzepte mit. Golang baut fertige Binaries, Plugins können also zentral kompiliert und ohne große Abhängigkeiten verteilt werden. Alle Abhängigkeiten werden im Rahmen vom Bauprozess abgedeckt, die einzige Unterscheidung liegt in der Ziel Architektur, also Linux, Windows, Mac oder ähnliches, sowie ob 32 oder 64 bit.

Viele Funktionen und Packages (vergleichbar mit Libraries) kommen entweder direkt mit Golang mit oder können leicht aus der Open Source Community verwendet werden. Mit dem Package go-check von uns haben wir eine kleine Basis geschaffen, um leichter Plugins schreiben zu können, ohne sich zu sehr im Code wiederholen zu müssen.

Als ganz einfaches Go Plugin hier ein Beispiel eine “main.go” Datei:

package main

import (
	"github.com/NETWAYS/go-check"
)

func main() {
	config := check.NewConfig()
	config.Name = "check_test"
	config.Readme = `Test Plugin`
	config.Version = "1.0.0"

	_ = config.FlagSet.StringP("hostname", "H", "localhost", "Hostname to check")

	config.ParseArguments()

	// Some checking should be done here, when --help is not passed

	check.Exitf(check.OK, "Everything is fine - answer=%d", 42)
}

Alles was man noch tun muss, ist das Plugin zu kompilieren oder direkt auszuführen:

go build -o check_test main.go && ./check_test --help
go run main.go

Ein guter Einstieg in Go findet man über die Dokumentation, die Tour und vor allem in dem man sich umschaut, was die Community an Packages zu bieten hat.

Natürlich bleibt die Frage, wie überwache ich das Ding was mir wichtig ist, wofür es aber noch kein Plugin gibt. Gerade dort helfen wir von NETWAYS mit unseren Consulting und Entwicklungsleistungen.  Beispiele unserer Go Plugins findet man auf GitHub unter der NETWAYS Organisation.

 

C++Go. Halb C++ – halb Go.

Scherz beiseite, die Go-Entwickler bieten noch keine Möglichkeit, C++-Bibliotheken ohne weiteres anzusprechen. Aber es geht ja auch mit weiteres. Das weitere besteht darin, dass C++-Funktionen mittels C-Bibliotheken gewrapped werden können. Und in meinem letzten Blogpost zu diesem Thema habe ich bereits erklärt, wie C-Bibliotheken in Go angesprochen werden können. Sprich, es braucht nur eine hauchdünne C-Schicht zwischen C++ und Go.

Multilingual++ in der Praxis

Wie auch letztes mal habe ich schon mal was vorbereitet – eine Schnittstelle für die Boost.Regex-Bibliothek. Diese findet sich in diesem GitHub-Repo und besteht aus folgenden Komponenten:

  • Ein Struct, das boost::basic_regex<char> wrapped
  • Eine C-Bibliothek, die den C++-Teil wrapped
  • Die Go-Bibliothek, die die C-Bibliothek verwendet

Wrapper-Struct

Dieses Struct ist Notwendig, da die Größe von boost::basic_regex<char> zwar C++ bekannt ist, aber nicht Go. Das Wrapper-Struct hingegen hat eine feste Größe (ein Zeiger).

libcxx/regex.hpp

#pragma once

#include <boost/regex.hpp>
// boost::basic_regex
// boost::match_results
// boost::regex_search

#include <utility>
// std::forward
// std::move

template<class Char>
struct Regex
{
	template<class... Args>
	inline
	Regex(Args&&... args) : Rgx(new boost::basic_regex<Char>(std::forward<Args>(args)...))
	{
	}

	Regex(const Regex& origin) : Rgx(new boost::basic_regex<Char>(*origin.Rgx))
	{
	}

	Regex& operator=(const Regex& origin)
	{
		Regex copy (origin);
		return *this = std::move(copy);
	}

	inline
	Regex(Regex&& origin) noexcept : Rgx(origin.Rgx)
	{
		origin.Rgx = nullptr;
	}

	inline
	Regex& operator=(Regex&& origin) noexcept
	{
		this->~Regex();
		new(this) Regex(std::move(origin));
		return *this;
	}

	inline
	~Regex()
	{
		delete this->Rgx;
	}

	template<class Iterator>
	bool MatchesSomewhere(Iterator first, Iterator last) const
	{
		boost::match_results<Iterator> m;
		return boost::regex_search(first, last, m, *(const boost::basic_regex<Char>*)this->Rgx);
	}

	boost::basic_regex<Char>* Rgx;
};

Eine C-Bibliothek, die den C++-Teil wrapped

Die folgenden Funktionen sind zwar waschechte C++-Funktionen, aber dank dem extern "C" werden sind sie in der Bibliothek als C-Funktionen sichtbar und können von Go angesprochen werden.

libcxx/regex.cpp

#include "regex.hpp"
// Regex

#include <boost/regex.hpp>
using boost::bad_expression;

#include <stdint.h>
// uint64_t

#include <utility>
using std::move;

extern "C" unsigned char CompileRegex(uint64_t pattern_start, uint64_t pattern_end, uint64_t out)
{
	try {
		*(Regex<char>*)out = Regex<char>((const char*)pattern_start, (const char*)pattern_end);
	} catch (const boost::bad_expression&) {
		return 2;
	} catch (...) {
		return 1;
	}

	return 0;
}

extern "C" void FreeRegex(uint64_t rgx)
{
	try {
		Regex<char> r (move(*(Regex<char>*)rgx));
	} catch (...) {
	}
}

extern "C" signed char MatchesSomewhere(uint64_t rgx, uint64_t subject_start, uint64_t subject_end)
{
	try {
		return ((const Regex<char>*)rgx)->MatchesSomewhere((const char*)subject_start, (const char*)subject_end);
	} catch (...) {
		return -1;
	}
}

libcxx/regex.h

#pragma once

#include <stdint.h>
// uint64_t

unsigned char CompileRegex(uint64_t pattern_start, uint64_t pattern_end, uint64_t out);

void FreeRegex(uint64_t rgx);

signed char MatchesSomewhere(uint64_t rgx, uint64_t subject_start, uint64_t subject_end);

Go-Bibliothek

Diese spricht letztendlich die C-Funktionen an. Dabei übergibt sie die Zeiger als Ganzzahlen, um gewisse Sicherheitsmaßnahmen von CGo zu umgehen. Das Regex-Struct entspricht dem Regex-Struct aus dem C++-Teil.

regex.go

package boostregex2go

import (
	"io"
	"reflect"
	"runtime"
	"unsafe"
)

/*
#include "libcxx/regex.h"
// CompileRegex
// FreeRegex
// MatchesSomewhere
*/
import "C"

type OOM struct {
}

var _ error = OOM{}

func (OOM) Error() string {
	return "out of memory"
}

type BadPattern struct {
}

var _ error = BadPattern{}

func (BadPattern) Error() string {
	return "bad pattern"
}

type Regex struct {
	rgx unsafe.Pointer
}

var _ io.Closer = (*Regex)(nil)

func (r *Regex) Close() error {
	C.FreeRegex(rgxPtr64(r))
	return nil
}

func (r *Regex) MatchesSomewhere(subject []byte) (bool, error) {
	defer runtime.KeepAlive(subject)
	start, end := bytesToCharRange(subject)

	switch C.MatchesSomewhere(rgxPtr64(r), start, end) {
	case 0:
		return false, nil
	case 1:
		return true, nil
	default:
		return false, OOM{}
	}
}

func NewRegex(pattern []byte) (*Regex, error) {
	rgx := &Regex{}

	defer runtime.KeepAlive(pattern)
	start, end := bytesToCharRange(pattern)

	switch C.CompileRegex(start, end, rgxPtr64(rgx)) {
	case 0:
		return rgx, nil
	case 2:
		return nil, BadPattern{}
	default:
		return nil, OOM{}
	}
}

func bytesToCharRange(b []byte) (C.uint64_t, C.uint64_t) {
	sh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	return C.uint64_t(sh.Data), C.uint64_t(sh.Data + uintptr(sh.Len))
}

func rgxPtr64(p *Regex) C.uint64_t {
	return C.uint64_t(uintptr(unsafe.Pointer(p)))
}

Fazit++

Wenn etwas abgedrehtes mal nicht zu gehen scheint, dann gebe ich doch nicht auf, sondern ich mache es einfach noch abgedrehter. Impossible is nothing.

Wenn Du auch lernen willst, wie man unmögliches möglich macht, komm auf unsere Seite der Macht.

Alexander Klimov
Alexander Klimov
Senior Developer

Alexander hat 2017 seine Ausbildung zum Developer bei NETWAYS erfolgreich abgeschlossen. Als leidenschaftlicher Programmierer und begeisterter Anhänger der Idee freier Software, hat er sich dabei innerhalb kürzester Zeit in die Herzen seiner Kollegen im Development geschlichen. Wäre nicht ausgerechnet Gandhi sein Vorbild, würde er von dort aus daran arbeiten, seinen geheimen Plan, erst die Abteilung und dann die Weltherrschaft an sich zu reißen, zu realisieren - tut er aber nicht. Stattdessen beschreitet er mit der...

Generics waren gestern. Lang lebe Golangs Reflection!

Vor einiger Zeit habe ich die Programmiersprache Golang und all ihren Nutzen für die Entwickler vorgestellt. Zugegeben, eine in der Konkurrenz sehr verbreitete Funktionalität besitzt Go nicht: Generics. Und diese Funktionalität is noch dazu sehr gefragt. Allein das Gesamtbild der Reaktionen (Smileys) auf den Vorschlag, Generics in Go v2 zu integrieren, sagt mehr als 1000 Worte. Aber es geht auch anders…

Problem

Aktuell arbeite ich an einem (streng geheimen) Programm, das u.a. mit SQL-Datenbanken kommunizieren soll. Die grundsätzliche Infrastruktur dafür bringt Go von Haus aus mit. Jedoch kann es etwas mühselig sein, bei jeder Abfrage dieselbe Routine (samt Fehlerbehandlung) durchzukauen:

package blog

import (
	"database/sql"
)

type Employee struct {
	GivenName, FamilyName string
}

func GetEmployees(db *sql.DB) ([]Employee, error) {
	rows, errQuery := db.Query("SELECT given_name, family_name FROM employee")
	if errQuery != nil {
		return nil, errQuery
	}

	defer rows.Close()

	employees := []Employee{}

	for {
		if rows.Next() {
			row := Employee{}

			if errScan := rows.Scan(&row.GivenName, &row.FamilyName); errScan != nil {
				return nil, errScan
			}

			employees = append(employees, row)
		} else if errNext := rows.Err(); errNext == nil {
			break
		} else {
			return nil, errNext
		}
	}

	return employees, nil
}

Außerdem hat sich in einem vergangenen Projekt herausgestellt, dass (zumindest in Transaktionen) erst nach rows.Close() die nächste Datenbank-Operation beginnen kann. Dies verpflichtete fast schon dazu, den Code ab defer db.Close() bei jeder Abfrage so oder so ähnlich zu schreiben. Letztendlich löste das Team das Problem mit folgender Funktion:

func FetchAll(db *sql.DB, query string, args ...interface{}) ([][]interface{}, error)

Diese erledigte die oben gezeigte Routinearbeit und verringerte damit den Aufwand pro Abfrage deutlich:

func GetEmployees(db *sql.DB) ([]Employee, error) {
	rows, errFetchAll := FetchAll(db, "SELECT given_name, family_name FROM employee")
	if errFetchAll != nil {
		return nil, errFetchAll
	}

	employees := []Employee{}

	for _, row := range rows {
		employees = append(employees, Employee{row[0].(string), row[1].(string)})
	}

	return employees, nil
}

Jedoch war nun jede Spalte jeder Zeile des Ergebnisses ein interface{}, das erstmal in den richtigen Datentyp umgewandelt werden musste. Dafür wiederum musste die neue Funktion zusätzlich den Spaltentyp beim Datenbanktreiber erfragen, um immer die (hinter dem interface{} versteckten) Datentypen zurückzugeben, die die konkrete Abfrage erwartet. Andernfalls hätten wir uns auf die Standard-Datentypen der Datenbanktreiber verlassen müssen.

Lösung

Nun übernehme ich also den Code Schritt für Schritt in das neue Projekt und frage mich: Geht das nicht auch einfacher? Ja, mit sog. Reflection:

package blog

import (
	"database/sql"
	"reflect"
)

func FetchAll(db *sql.DB, rowType interface{}, query string, args ...interface{}) (interface{}, error) {
	rows, errQuery := db.Query(query, args...)

	if errQuery != nil {
		return nil, errQuery
	}

	defer rows.Close()

	blankRow := reflect.ValueOf(rowType)
	res := reflect.MakeSlice(reflect.SliceOf(blankRow.Type()), 0, 0)
	idx := -1
	scanDest := make([]interface{}, blankRow.NumField())

	for {
		if rows.Next() {
			res = reflect.Append(res, blankRow)
			idx++

			row := res.Index(idx)

			for i := range scanDest {
				scanDest[i] = row.Field(i).Addr().Interface()
			}

			if errScan := rows.Scan(scanDest...); errScan != nil {
				return nil, errScan
			}
		} else if errNext := rows.Err(); errNext == nil {
			break
		} else {
			return nil, errNext
		}
	}

	return res.Interface(), nil
}

Diese Funktion erwartet einen zusätzlichen Parameter, rowType. Dessen eigentlicher Typ hinter interface{} (Employee) bestimmt den Typ einer Zeile des Abfrage-Ergebnisses. Das komplette Ergebnis ist logischerweise eine Slice aus Zeilen ([]Employee). Mit Hilfe von Funktionen aus dem reflect-Paket arbeitet FetchAll() zur Laufzeit mit dem konkreten Datentyp Employee, fast so als wäre er mittels Generics zur Kompilierzeit bekannt:

  • reflect.ValueOf(rowType) analysiert rowType und kapselt ihn als Wert vom Typ Employee
  • reflect.ValueOf(rowType).Type() steht für Employee
  • reflect.SliceOf(Employee) steht für []Employee
  • reflect.MakeSlice([]Employee, 0, 0) steht für make([]Employee, 0, 0)
  • reflect.ValueOf(rowType).NumField() zählt die Felder des Structs Employee

Ja, richtig, rowType muss ein Struct sein, sonst stürzt das Programm spätestens bei reflect.ValueOf(rowType).NumField() ab. Jedes Feld des Structs steht nämlich für eine Spalte des Abfrage-Ergebnisses. Genau das wird in der darauf folgenden Schleife wie folgt bewerkstelligt:

  • res = reflect.Append(res, reflect.ValueOf(rowType)) steht für res = append(res, Employee{})
  • res.Index(idx) steht für res[idx]
  • res[idx].Field(0) steht für res[idx].GivenName
  • res[idx].GivenName.Addr() steht für &res[idx].GivenName

Und .Interface() holt letztendlich den Zeiger auf das Struct-Feld aus der Reflection-Versenkung, damit rows.Scan() die entsprechende Spalte des Abfrage-Ergebnisses darin speichert. Am Ende verbirgt sich hinter res tatsächlich ein []Employee, das mit res.Interface() in ein interface{} gekapselt, um es zurückzugeben. Damit bestimmt GetEmployees() den Zeilen-Typ im voraus und schrumpft auf ein vernünftiges Minimum:

func GetEmployees(db *sql.DB) ([]Employee, error) {
	rows, errFetchAll := FetchAll(db, Employee{}, "SELECT given_name, family_name FROM employee")
	if errFetchAll != nil {
		return nil, errFetchAll
	}

	return rows.([]Employee), nil
}

Fazit

Nachdem ich zuletzt schon eine C-Bibliothek in Go wiederverwendet habe, spare ich schon zum zweiten mal in Folge Code und damit Zeit. Sprich, wir arbeiten jetzt noch ein bisschen effizienter (als sowieso schon) an euren Projekten. Bestelle noch heute!

Alexander Klimov
Alexander Klimov
Senior Developer

Alexander hat 2017 seine Ausbildung zum Developer bei NETWAYS erfolgreich abgeschlossen. Als leidenschaftlicher Programmierer und begeisterter Anhänger der Idee freier Software, hat er sich dabei innerhalb kürzester Zeit in die Herzen seiner Kollegen im Development geschlichen. Wäre nicht ausgerechnet Gandhi sein Vorbild, würde er von dort aus daran arbeiten, seinen geheimen Plan, erst die Abteilung und dann die Weltherrschaft an sich zu reißen, zu realisieren - tut er aber nicht. Stattdessen beschreitet er mit der...

Oida, geht das nicht schneller?!

Findet 99 Linux Befehle

Ich hatte etwas downtime, also habe ich ein kleines Programm geschrieben das Wortsalate erstellt: wordsalad, Code hier auf Github. Ist noch nicht ganz fertig, aber wie ihr sehen könnt tut es schon seinen Dienst. Es nimmt eine Liste an Wörtern und versteckt sie in einem Suchbild. Der Algorithmus hierfür ist: Schmeiß das Wort irgendwo aufs Spielfeld, wenn es klappt gehe zum nächsten. Wenn nicht versuche das ganze nochmal, bis zu 200 mal pro Wort. Sollte sich so kein freier Platz für das Wort finden, mach das ganze Spielfeld neu und starte von Null – bis zu 2000 mal. Nicht besonders intelligent aber wofür haben wir sonst Prozessoren im Gigahertz Bereich!

Die Hertz sind machen nur ein Teil der Geschwindigkeit aus, ein anderer ist die Anzahl der Threads. Parallelisierung erlaubt es modernen Programmen ihre Arbeit doppelt, vier mal, acht mal oder noch schneller zu verrichten (theoretisch zumindest). Aber was bietet Go in dieser Hinsicht? Concurrency. Und was ist das? “Irgendwas mit Multi-threadding und Parallelisierung” dachte ich anfangs. Sogenannte Goroutinen können einfach gestartet werden und haben mit channels eine simple und mächtige Form der Synchronisierung und Kommunikation.

Findet 40 Staaten in denen die USA einen Coup herbeigeführt haben

Aber ich lag falsch, Concurrency ist eben nicht Parallelisierung.

Meine Vermutung Concurrency sei nur ein anderes Wort für Parallelisierung kam von der Bedeutung und Verwendung außerhalb der IT Welt. “Concurrence” bedeutet Kooperation, Zustimmung und eben auch “zur gleichen Zeit stattfinden”, gemeint ist hierbei aber wohl die erste Bedeutung. Goroutinen laufen eben nicht gleichzeitig ab, sie wechseln sich ab. Sollte eine routine mal länger nichts zu tun haben, weil sie etwa darauf wartet etwas von der Platte zu lesen oder schreiben, kann eine andere arbeiten. So laufen sie nicht gleichzeitig, sondern miteinander ab – sehr praktisch für schnelle Prozessoren.

Findet 30 Österreichische Schimpfwörter

Aber man hat eben mehrere Prozessoren, sollen die sich die übrigen etwa langweilen während einer arbeitet? Nein, go verwendet einen Prozessorpool der goroutinen wenn möglich parallel laufen lässt. So hat man das beste beider Welten, es ist einfacher mit Concurrency zu designen und die Sprache kümmert sich im Hintergrund darum das es möglichst schnell passiert.

Sollte euch das Thema weiter interessieren, kann ich diesen Talk von Rob Pike empfehlen.

Viel Spaß beim Suchen 🙂