Vom Messwert zur Alarmierung

Vom Messwert zur Alarmierung

Unternehmen können heutzutage gar nicht von Überwachungsmaßnahmen ihrer Umgebung entkommen, da wir als Menschen den aktuellen Stand von hunderten von Geräten nicht im Griff haben können. Deshalb werden Tools und Equipment im Überwachungsbereich dauernd entwickelt. Aus dem vorherigen erwähnten Bedarf möchte ich ein paar Informationen über zwei Geräte teilen und zwar AKCP sensorProbe2+ und SMSEagle.

AKCP sensorProbe2+

Das AKCP sensorProbe2+ ist ein Messgerät, an das verschiedene Arten von Sensoren angeschlossen werden können. Beispielhaft sind Bewegungs-, Gas-, Schall-, Temperatur, Feuchtigkeit- und Spannungssensoren. Stecken Sie einen beliebigen Sensor in die sensorProbe2+ und eine Autosense-Funktion erkennt den Typ und konfiguriert ihn automatisch. Standardmäßig hat das SP2+ die IP-Adresse 192.168.0.100. Es ist über einen Webbrowser konfigurierbar – sogar die IP-Adresse lässt sich ändern unter Einstellungen  => IP-Einstellungen. Aber der angeschlossene PC muss im gleichen Netz wie der SP2+ sein, um die Verbindung herstellen zu können. Das Gerät kann in einem beliebigen Intervall sowohl seine Verfügbarkeit als auch sämtliche Messwerte durch SMS, Mail oder SNMP Trap mitteilen.

SMSEagle

SMSEagle ist ein SMS Gateway, das SMS und Mails schickt bzw. empfängt. Mails können in SMS umgewandelt werden und umgekehrt. Darüber hinaus gibt es eine eingebaute Überwachungsfunktion, die die Verfügbarkeit von Geräten und Ports überprüft (ICMP, SNMP, TCP, UDP) und beim Ausfall eine SMS oder Mail sendet. SMS können sogar weitergeleitet werden mit Nutzung von Filtern oder ohne. Zusätzlich können wir eine automatische Antwortregel konfigurieren. LDAP-Autentifizierung kommt auch als Feature mit.

Kombination von beiden Geräten:

Beide Geräte müssen nicht im gleichen Netzwerk sein, sondern es reicht, wenn beide einander anhand von einem SMTP-Server sehen. SMSEagle besitzt sogar einen integrierten SMTP-Server, der dafür benutzt werden kann. Dessen IP-Adresse habe ich beim SP2+ eingetragen. SMSEagle akzeptiert nur Mails, die an ihn gesendet sind und lehnt den Rest ab. In SMSEagle muss “Mail to sms” Plugin aktiviert werden, um eingehende Benachrichtigungsmails von SP2+ in SMS zu wandeln und an die gewünschte Nummer zu senden. Im SensorProbe2+ muss man eine Mail-Aktion unter Hauptmenü erstellen, in dem man Zieltelefonnumme@SMSEgale-FQDN als Zielmail einträgt. Zieltelefonnummer könnte man durch gespeicherte Kontaktnamen im Telefonbuch vom SMSEagle ersetzen, wenn es keine Leerzeichnen enthält. SP2+ akzeptiert nur FQDN als Zieladresse. Deswegen muss man folgende Konfigurationseinträge durchführen plus einen DNS-Eintrag für SMSEagle :

[ NXS Geräte unter]
/etc/postfix/main.cf

[NPE Geräte unter]
/mnt/nand-user/postfix/etc/postfix/main.cf

1. folgende Zeile finden “myhostname = localhost.localdomain” und ändern in “myhostname = yourdomain.com”

2. Konfiguration von  Postfix reloaden
postfix reload

Wir können eine Testmail von SP2+ schicken und im Eventslog nachschauen, ob sie versendet werden konnte , oder ob es eine Fehlermeldung gibt. Außerdem können wir unter “notification rules” einstellen, bei welchem erreichten Schwellenwert eine Mail geschickt werden soll. Hier haben wir fünf einstellbare Schwellenwerte Normal, High Warning, High Critical, Low Warning und Low Critical. Wir können auf der anderen Seite in den Maillog von SMSEagle unter /etc/var/log/mail.info, in Systemlog oder unter Einstellungen => sysinfo nachschauen, ob die gesendeten Mails vom SP2+ angekommen sind und als SMS weitergeschickt wurden.

Man kann SP2+ mit Icinga 2 verbinden, wofür man lediglich das entsprechende Plugin benötigt: check_sensorProbe2plus

Ansonsten helfen wir bei Fragen rund um die Hardware von AKCP und SMSEagle gerne weiter – wir sind erreichbar per Mail oder telefonisch unter der 0911 92885-44. Wer uns gerne bei der Arbeit ein bisschen über die Schulter schauen oder den Shop und die angebotenen Produkte verfolgen möchte, kann uns seit kurzem auch auf Twitter folgen – über @NetwaysShop twittert das NETWAYS Shop Team!

 

 

Afeef Ghannam
Afeef Ghannam
Junior Consultant

Afeef macht seit September 2017 eine Ausbildung zum Fachinformatiker für Systemintegration bei NETWAYS. Nachdem der gebürtige Syrer erfolgreich Deutsch gelernt hat, stellt er sich jetzt der Herausforderung Programmiersprache. Neben der IT schätzt er vielfältiges Essen. Sein Motto lautet يد واحدة لا تصفق لوحدها , was so viel bedeutet wie „Eine Hand wird nie allein klatschen“.
A journey with Vault – Teil 1

A journey with Vault – Teil 1

This entry is part of 1 in the series a journey with vault

Hello fellow blog readers!

Heute möchte ich euch auf die Reise mit Vault by Hashicorp mitnehmen.

Zunächst was ist Vault? Bei Vault handelt es sich stumpf gesagt, um einen Passwortspeicher. Vermutlich kommen da jetzt bei dem einen oder anderen Projekte wie Keypass oder Enpass in den Sinn. Die Richtung ist schon mal gut. Jedoch kennt auch jeder das Hauptproblem der oben genannten Lösungen nur zu gut.

Teamfähigkeit.

Das eine Projekt beherrscht es, andere nur teilweise oder vieleicht sogar garnicht. Frustrierend könnte man die Situation auch gerne umschreiben. Fakt ist auf jeden Fall das es sich bei Vault um eine Lösung handelt, die wirklich das Zeug dazu hat ein Teamfähiger Passwortspeicher zu sein. Wie so alles in der Welt haben Dinge leider ihren Preis. Man wird mit Teamfähigkeit gesegnet, aber Satan bestraft uns indirekt mit der Komplexität des ganzen Konstrukts, weswegen ich das Abenteuer Vault in eine kleine Serie verpacke. Genug Worte für den Einstieg, legen wir los mit dem neuen Abenteuer in der Hautprolle: Vault.

Part Uno widment sich der grundlegenden Inbetriebnahme von Vault.

Wie immer benutzte ich eine mit Vagrant provisionierte höchst aktuelle CentOS 7 Box der Marke Eigenbau mit VirtualBox als Provider.

Die Reise beginnt mit dem Download eines ZIP-Archivs welche das Vault binary enthält. Den legen wir einfach unter /tmp ab und entpacken ihn direkt nach /usr/local/bin

wget https://releases.hashicorp.com/vault/1.3.0/vault_1.3.0_linux_amd64.zip -P /tmp
unzip /tmp/vault_1.3.0_linux_amd64.zip -d /usr/local/bin
chown root. /usr/local/bin/vault

Damit das aufrufen von Vault direkt gelingt müssen wir unsere PATH Variable noch um /usr/local/bin ergänzen. Ich hab das ganze in meinem ~/.bash_profile hinterlegt:

PATH=$PATH:$HOME/bin:/usr/local/bin

Wenn alles korrekt ausgeführt wurde, können wir jetzt die Autocompletion nachziehen und anschließend die Shell neustarten:

vault -autocomplete-install
complete -C /usr/local/bin/vault vault
exec $SHELL

Um das ganze abzurunden lassen wir Vault als Daemon laufen.

Zunächst müssen wir es Vault gestatten mlock syscalls ohne der Notwendigkeit von root ausführen zu dürfen:

setcap cap_ipc_lock=+ep /usr/local/bin/vault

Danach legen wir einen nicht priviligierten Systembenutzer an, unter dem der Vault Daemon später laufen soll:

useradd --system --home /etc/vault.d --shell /bin/false vault

Jetzt kommt die systemd Unit:

touch /etc/systemd/system/vault.service

… mit folgenden Inhalt:

[Unit]
Description="HashiCorp Vault - A tool for managing secrets"
Documentation=https://www.vaultproject.io/docs/
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/vault.d/vault.hcl
StartLimitIntervalSec=60
StartLimitBurst=3

[Service]
User=vault
Group=vault
ProtectSystem=full
ProtectHome=read-only
PrivateTmp=yes
PrivateDevices=yes
SecureBits=keep-caps
AmbientCapabilities=CAP_IPC_LOCK
Capabilities=CAP_IPC_LOCK+ep
CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK
NoNewPrivileges=yes
ExecStart=/usr/local/bin/vault server -config=/etc/vault.d/vault.hcl
ExecReload=/bin/kill --signal HUP $MAINPID
KillMode=process
KillSignal=SIGINT
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
StartLimitInterval=60
StartLimitIntervalSec=60
StartLimitBurst=3
LimitNOFILE=65536
LimitMEMLOCK=infinity

[Install]
WantedBy=multi-user.target

Bevor wir den Daemon starten können, müssen wir ein paar Verzeichnisse sowie eine Konfigurationsdatei anlegen:
mkdir -pv /etc/vault.d/
mkdir -pv /usr/local/share/vault/data/
chown -R vault. /usr/local/share/vault/

touch /etc/vault.d/vault.hcl

Meine Konfigurationsdatei ist als Beispielhaft anzusehen. Sie behinhaltet das nötigste um den Vault Server grundsätzlich starten zu können. Diese sollte entsprechend an das eigene Szenario angepasst werden und unbedingt mit Zertifikaten ausgestattet sein!

storage "file" {
path = "/usr/local/share/vault/data"
}

ui = true

listener "tcp" {
address = "172.28.128.25:8200"
tls_disable = "true"
}

api_addr = "http://172.28.128.25:8200"
cluster_addr = "http://172.28.128.25:8201"

systemd neuladen und den Vault Daemon starten:

systemctl daemon-reload
systemctl start vault

Wenn uns alles geglückt ist, sollten wir unter der Adresse des Servers, mit Angabe des Ports 8200 nun die UI von Vault vorfinden. Damit geht es nun in der Bildstrecke weiter:

Das wars für den ersten Teil der Serie, im zweiten Teil werden wir uns den Aufbau von Vault genauer anschauen und uns der integration von SSH widment. Vault bietet nämlich viele Integrationsmöglichkeiten mit denen sich am Ende die Authentifizierung von sämtlichen Dienste Zentralisiert über Vault steuern lassen. Bis dahin wünsche ich wie immer viel Spass beim Basteln!

Photo from: https://devopscube.com/setup-hashicorp-vault-beginners-guide/

Max Deparade
Max Deparade
Consultant

Max ist seit Januar als Consultant bei NETWAYS und unterstützt tatkräftig unser Professional Services Team. Zuvor hat er seine Ausbildung zum Fachinformatiker für Systemintegration bei der Stadtverwaltung in Regensburg erfolgreich absolviert. Danach hat der gebürtige Schwabe, der einen Teil seiner Zeit auch in der Oberpfalz aufgewachsen ist ein halbes Jahr bei einem Managed Hosting Provider in Regensburg gearbeitet, ehe es ihn zu NETWAYS verschlagen hat. In seiner Freizeit genießt Max vor allem die Ruhe, wenn...

Text-Utils unter Linux – Wer kennt sie?

Bei aller Automatisierung, Systemmanagement oder Cloud und Log-Management  ist oben genannte Frage berechtigt.
Ich persönlich, stelle als Support-Engineer immer wieder fest, wie wichtig das eine oder andere Text-Util von Linux auf der Kommandozeile ist, deswegen möchte ich hier mal ein paar  Text-Utils und Anwendung vorstellen:

cat & split
Erklärung: Hier wird eine 100MB große Datei angelegt, die in 14MB große einzelne Dateien zerlegt wird und danach wieder zusammengefügt wird.
dd if=/dev/urandom of=test1.big count=100 bs=1M
split -b14m test1.big
cat x?? > test2.big
md5sum test.big test2.big

tac
Erklärung: Hier wird einen Liste der letzten installierten Pakete ausgeben und anschließend herumgedreht, damit das letzte installierte Paket unten in der Liste steht.
rpm -qa --last | tac

cut
Erklärung: Hier werden die ersten 25 Bytes aus Logfile messages vom Seiten-Anfang herausgeschnitten, setzt man das Minus vor die Zahl wird alles nach den 25 Byte herausgeschnitten
cut -b25- /var/log/messages | sort

cut & paste
Erklärung: Ausschneiden von Textausschnitte aus einer Datei, in einzelne Dateien kopiert und dann wieder zusammen in eine Datei zusammengeführt.
cut -d: -f1-3 /etc/passwd > spalte1-3
cut -d: -f4- /etc/passwd > spalte4-7

Wieder  zusammensetzen
paste -d: spalte1-3 spalte4-7

sort mit du
Erklärung: Hier wird mit du (disk usage) die Größe der Verzeichnisse ausgegeben und sortiert
du -mx / | sort -n
Hier ist der Beitrag Disk Usage (du) von mir, wenn jemand das Tool noch nicht kennen sollte.
Noch ein Beispiel:
sort -t : -k 3n /etc/passwd
Erklärung: Hier werden die User in der passwd nach der id sortiert aufsteigend sortiert, Trenner ist der Doppelpunkt.

column
Erklärung: Hier werden die IP-Routen schön in Spalten angezeigt um die Lesbarkeit zu verbessern.
ip r s | column -t

pr
Erklärung: Hier wird die Ausgabe des Textes zur besseren Lesbarkeit oder zum Ausdrucken aufbereitet und mit Seitenzahl angezeigt
wget -q -O - www.gnu.org/licenses/gpl-3.0.txt | pr | less

Alle Text-Utils können noch mehr, dazu bitte die Man-Pages zu Gemüte ziehen, oder vielleicht unsere Linux-Schulung im Bereich Trainings buchen.

In diesem Sinne Viel Spaß beim ausprobieren!

Johannes Carraro
Johannes Carraro
Support Engineer

Bevor Johannes bei NETWAYS anheuerte war er knapp drei Jahre als Systemadministrator in Ansbach tätig. Seit Februar 2016 verstärkt er nun unser Managed Services Team als Systems Engineer. In seiner Freizeit spielt Johannes E-Gitarre in einer Metalband, bastelt an Linux Systemen zuhause herum und ertüchtigt sich beim Tischtennisspielen im Verein, bzw. Mountainbiken, Inlinern und nicht zuletzt Skifahren.

Schneller LIKEn

Nein, hier soll es nicht um Twitter, Instagram oder Youtube gehen, sondern um Datenbankabfragen in PostgreSQL wie diese:

blog # SELECT * FROM kunden WHERE vorname LIKE 'Ann%';

Diese Abfragen sind recht häufig anzutreffen, man denke z.B. an Drop-Down-Boxen, die z.B. per AJAX mit Vorschlägen gefüllt werden, sobald drei oder mehr Buchstaben eingegeben wurden.

Das Spielfeld

Unsere Beispieldaten enthalten 1.000.000 zufällig generierte Kunden in dieser Form und mit dieser Verteilung von Vornamen, die mit ‘Ann’ beginnen:

blog # \d kunden
                               Table "public.kunden"
┌────────────┬─────────┬───────────┬──────────┬────────────────────────────────────┐
│   Column   │  Type   │ Collation │ Nullable │              Default               │
├────────────┼─────────┼───────────┼──────────┼────────────────────────────────────┤
│ id         │ integer │           │ not null │ nextval('kunden_id_seq'::regclass) │
│ vorname    │ text    │           │ not null │                                    │
│ nachname   │ text    │           │ not null │                                    │
│ strasse    │ text    │           │ not null │                                    │
│ hausnummer │ integer │           │ not null │                                    │
│ plz        │ text    │           │ not null │                                    │
│ ort        │ text    │           │ not null │                                    │
│ bundesland │ text    │           │ not null │                                    │
└────────────┴─────────┴───────────┴──────────┴────────────────────────────────────┘
Indexes:
    "kunden_pkey" PRIMARY KEY, btree (id)
Check constraints:
    "kunden_plz_check" CHECK (length(plz) = 5)

blog # vorname,count(*) FROM kunden WHERE vorname LIKE 'Ann%' GROUP BY vorname;
┌───────────┬───────┐
│  vorname  │ count │
├───────────┼───────┤
│ Anni      │   963 │
│ Annabella │   984 │
│ Anne      │   965 │
│ Annalena  │   971 │
│ Annika    │  1017 │
│ Anna      │  1011 │
│ Ann       │  1003 │
│ Annelie   │   976 │
│ Annemarie │  1001 │
│ Annabell  │   996 │
└───────────┴───────┘
(10 rows)

Ein erster Versuch – “vanilla”

Schauen wir doch mal, wie unsere PostgreSQL-Datenbank eine Suche nach ‘Ann%’ bearbeitet:

blog # EXPLAIN (ANALYSE,COSTS) SELECT * FROM kunden WHERE vorname LIKE 'Ann%';
┌────────────────────────────────────────────────────────────────────┐
│                           QUERY PLAN                               │
├────────────────────────────────────────────────────────────────────┤
│ Seq Scan on kunden  (cost=0.00..24667.00 rows=10244 width=65)      |
|      (actual time=0.019..90.620 rows=9887 loops=1)                 │
│   Filter: (vorname ~~ 'Ann%'::text)                                │
│   Rows Removed by Filter: 990113                                   │
│ Planning time: 0.100 ms                                            │
│ Execution time: 90.956 ms                                          │
└────────────────────────────────────────────────────────────────────┘
(5 rows)

Ein Seq Scan, also der gefürchtete Sequential-Scan aka. Full table scan; alle Datensätze werden gelesen und ‘vorname’ mit ‘Ann%’ verglichen. Das ist sehr ineffektiv.

Ein Index muss her!

Die Lösung ist offensichtlich: wenn solche Abfragen häufig vorkommen, muss ein Index her. Der wird den Vorgang beschleunigen:

blog # CREATE INDEX vorname_btree_vanilla ON kunden (vorname);
blog # EXPLAIN (ANALYSE,COSTS) SELECT * FROM kunden WHERE vorname LIKE 'Ann%';
┌───────────────────────────────────────────────────────────────────────┐
│                            QUERY PLAN                                 │
├───────────────────────────────────────────────────────────────────────┤
│ Seq Scan on kunden  (cost=0.00..24667.00 rows=10244 width=65)         |      
|      (actual time=0.011..105.340 rows=9887 loops=1)                   │
│   Filter: (vorname ~~ 'Ann%'::text)                                   │
│   Rows Removed by Filter: 990113                                      │
│ Planning time: 0.195 ms                                               │
│ Execution time: 105.768 ms                                            │
└───────────────────────────────────────────────────────────────────────┘
(5 rows)

Uhm, Moment mal… warum nimmt meine Datenbank nicht den Index zu Hilfe?!? Geht es denn mit einzelnen Werten?

 blog # EXPLAIN (ANALYSE,COSTS) SELECT * FROM kunden WHERE vorname IN ('Anna','Anne','Annelie');
┌────────────────────────────────────────────────────────────────────────────────────────┐
│                              QUERY PLAN                                                │
├────────────────────────────────────────────────────────────────────────────────────────┤
│ Bitmap Heap Scan on kunden  (cost=59.83..6892.21 rows=2909 width=65)                   |
|      (actual time=1.275..5.054 rows=2952 loops=1)                                      │
│   Recheck Cond: (vorname = ANY ('{Anna,Anne,Annelie}'::text[]))                        │
│   Heap Blocks: exact=2656                                                              │
│   ->  Bitmap Index Scan on vorname_btree_vanilla  (cost=0.00..59.10 rows=2909 width=0) |
|      (actual time=0.652..0.652 rows=2952 loops=1)                                      │
│         Index Cond: (vorname = ANY ('{Anna,Anne,Annelie}'::text[]))                    │
│ Planning time: 0.136 ms                                                                │
│ Execution time: 5.292 ms                                                               │
└────────────────────────────────────────────────────────────────────────────────────────┘
(7 rows)

Ja, da wird der Index genommen, und die Ausführung ist auch gleich um Größenordnungen schneller.

Was ist also das Problem?

“C” und seine Spätfolgen – Schei* encoding!

Das Geheimnis liegt – wie so häufig – in der Lokalisierung. Btree-Indexe sind (für Text-Daten) auf das C-Locale hin optimiert. Wenn aber die Datenbank (wie heutzutage üblich!) mit en_US.UTF8 oder de_DE.UTF8 initialisiert wurde, müssen wir dem Index bei der Erstellung mitteilen, dass wir pattern operator-Aktionen ausführen können wollen. PostgreSQL kommt mit einem ganzen Haufen dieser Operator Classes.

Für unser TEXT-Feld ‘vorname’ nehmen wir text_pattern_ops. Nach der Erstellung testen wir, ob der Index unsere LIKE-Anfrage beschleunigt und verifizieren, dass auch weiterhin die klassischen <=, == und >= Vergleichsoperatoren funktionieren:

blog # DROP INDEX vorname_btree_vanilla ;
blog # CREATE INDEX vorname_btree_opclass ON kunden (vorname text_pattern_ops);
blog # EXPLAIN (ANALYSE,COSTS) SELECT * FROM kunden WHERE vorname LIKE 'Ann%';
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                                     QUERY PLAN                                          │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ Bitmap Heap Scan on kunden  (cost=129.19..10234.85 rows=10244 width=65)                 |
|       (actual time=5.327..16.083 rows=9887 loops=1)                                     │
│   Filter: (vorname ~~ 'Ann%'::text)                                                     │
│   Heap Blocks: exact=6830                                                               │
│   ->  Bitmap Index Scan on vorname_btree_opclass  (cost=0.00..126.62 rows=5820 width=0) |
|       (actual time=3.524..3.524 rows=9887 loops=1)                                      │
│         Index Cond: ((vorname ~>=~ 'Ann'::text) AND (vorname ~<~ 'Ano'::text))          │
│ Planning time: 0.378 ms                                                                 │
│ Execution time: 16.650 ms                                                               │
└─────────────────────────────────────────────────────────────────────────────────────────┘
(7 rows)

blog # EXPLAIN (ANALYSE,COSTS) SELECT * FROM kunden WHERE vorname IN ('Anna','Anne','Annelie');
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                                        QUERY PLAN                                       │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ Bitmap Heap Scan on kunden  (cost=59.83..6892.21 rows=2909 width=65)                    |
|       (actual time=1.233..5.001 rows=2952 loops=1)                                      │
│   Recheck Cond: (vorname = ANY ('{Anna,Anne,Annelie}'::text[]))                         │
│   Heap Blocks: exact=2656                                                               │
│   ->  Bitmap Index Scan on vorname_btree_opclass  (cost=0.00..59.10 rows=2909 width=0)  |
|       (actual time=0.634..0.634 rows=2952 loops=1)                                      │
│         Index Cond: (vorname = ANY ('{Anna,Anne,Annelie}'::text[]))                     │
│ Planning time: 0.135 ms                                                                 │
│ Execution time: 5.246 ms                                                                │
└─────────────────────────────────────────────────────────────────────────────────────────┘
(7 rows)

Wunderbar! Und 17ms klingt auch gleich viel besser als 100ms.

Geht da noch was?

Jedes Kind weiß, dass Indexe nur Anfragen wie LIKE ‘Ann%’ beschleunigen können. Für LIKE ‘%nna%’ gibt es leider keine Hilfe von der Datenbank. Ist ja auch irgendwie klar, der Btree muss ja von links nach rechts aufgebaut werden…

EXPLAIN (ANALYSE,COSTS) SELECT * FROM kunden WHERE vorname LIKE '%nna%';
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                                         QUERY PLAN                                      │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ Seq Scan on kunden  (cost=0.00..24667.00 rows=19022 width=65)                           |
|      (actual time=0.014..110.607 rows=11993 loops=1)                                    │
│   Filter: (vorname ~~ '%nna%'::text)                                                    │
│   Rows Removed by Filter: 988007                                                        │
│ Planning time: 0.131 ms                                                                 │
│ Execution time: 111.006 ms                                                              │
└─────────────────────────────────────────────────────────────────────────────────────────┘
(5 rows)

Aber stimmt das? Gibt es wirklich keine Möglichkeit, solche Abfragen zu beschleunigen?

PostgreSQL ist schier unfassbar erweiterbar, und unter anderem kommt es von Haus aus mit einer Extension pg_trgm, die wiederum operator classes für GIN und GiST Indexe mitbringt.

blog # CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION
blog # CREATE INDEX vorname_gin_trgm ON kunden USING GIN (vorname gin_trgm_ops);
blog # EXPLAIN (ANALYSE,COSTS) SELECT * FROM kunden WHERE vorname LIKE '%nna%';
┌──────────────────────────────────────────────────────────────────────────────────────────┐
│                                           QUERY PLAN                                     │
├──────────────────────────────────────────────────────────────────────────────────────────┤
│ Bitmap Heap Scan on kunden  (cost=183.42..13123.52 rows=19022 width=65)                  |
|       (actual time=5.049..15.935 rows=11993 loops=1)                                     │
│   Recheck Cond: (vorname ~~ '%nna%'::text)                                               │
│   Heap Blocks: exact=7670                                                                │
│   ->  Bitmap Index Scan on vorname_gin_trgm  (cost=0.00..178.67 rows=19022 width=0)      |   
|       (actual time=3.014..3.014 rows=11993 loops=1)                                      │
│         Index Cond: (vorname ~~ '%nna%'::text)                                           │
│ Planning time: 0.253 ms                                                                  │
│ Execution time: 16.488 ms                                                                │
└──────────────────────────────────────────────────────────────────────────────────────────┘
(7 rows)

pg_trgm kann noch mehr

Eine vermeintlich nette Spielerei, aber – wenn man es denn kennt – in vielen Situationen hilfreich, ist die Ähnlichkeitssuche, die pg_trgm in Form von Funktionen und Operatoren mitbringt:

blog # SELECT vorname, count(*) FROM kunden WHERE vorname % 'Nick' GROUP BY vorname ORDER BY similarity(vorname, 'Nick') DESC;
┌─────────┬───────┐
│ vorname │ count │
├─────────┼───────┤
│ Nick    │   995 │
│ Nico    │  1047 │
│ Nicole  │   977 │
│ Nicolas │  1026 │
└─────────┴───────┘
(4 rows)

Und auch hier beschleunigt der GIN-Index die Abfrage signifikant:

blog # EXPLAIN ANALYSE SELECT vorname, count(*) FROM kunden WHERE vorname % 'Nick' GROUP BY vorname ORDER BY similarity(vorname, 'Nick') DESC;
┌──────────────────────────────────────────────────────────────────────────────────────────┐
│                                          QUERY PLAN                                      │
├──────────────────────────────────────────────────────────────────────────────────────────┤
│ Sort  (cost=24761.50..24763.07 rows=630 width=18)                                        |
|      (actual time=915.696..915.697 rows=4 loops=1) .                                     │
│   Sort Key: (similarity(vorname, 'Nick'::text)) DESC                                     │
│   Sort Method: quicksort  Memory: 25kB                                                   │
│   ->  GroupAggregate  (cost=24716.83..24732.20 rows=630 width=18)                        |
|      (actual time=915.305..915.689 rows=4 loops=1)                                       │
│         Group Key: vorname                                                               │
│         ->  Sort  (cost=24716.83..24719.33 rows=1000 width=6)                            |
|      (actual time=915.162..915.305 rows=4045 loops=1)                                    │
│               Sort Key: vorname                                                          │
│               Sort Method: quicksort  Memory: 286kB                                      │
│               ->  Seq Scan on kunden  (cost=0.00..24667.00 rows=1000 width=6)            |
|      (actual time=0.650..914.065 rows=4045 loops=1)                                      │
│                     Filter: (vorname % 'Nick'::text)                                     │
│                     Rows Removed by Filter: 995955                                       │
│ Planning time: 0.296 ms                                                                  |
│ Execution time: 915.737 ms                                                               │
└──────────────────────────────────────────────────────────────────────────────────────────┘
(13 rows)

blog # CREATE INDEX vorname_gin_trgm ON kunden USING GIN (vorname gin_trgm_ops);
blog # EXPLAIN ANALYSE SELECT vorname, count(*) FROM kunden WHERE vorname % 'Nick' GROUP BY vorname ORDER BY similarity(vorname, 'Nick') DESC;
┌──────────────────────────────────────────────────────────────────────────────────────────┐
│                                             QUERY PLAN                                   │
├──────────────────────────────────────────────────────────────────────────────────────────┤
│ Sort  (cost=3164.18..3165.75 rows=630 width=18)                                          |
|      (actual time=32.510..32.510 rows=4 loops=1)                                         │
│   Sort Key: (similarity(vorname, 'Nick'::text)) DESC                                     │
│   Sort Method: quicksort  Memory: 25kB                                                   │
│   ->  HashAggregate  (cost=3127.01..3134.88 rows=630 width=18)                           |
|      (actual time=32.497..32.503 rows=4 loops=1)                                         │
│         Group Key: vorname                                                               │
│         ->  Bitmap Heap Scan on kunden  (cost=75.75..3122.01 rows=1000 width=6)          |
|      (actual time=5.993..31.447 rows=4045 loops=1)                                       │
│               Recheck Cond: (vorname % 'Nick'::text)                                     │
│               Rows Removed by Index Recheck: 13010                                       │
│               Heap Blocks: exact=9174                                                    │
│               ->  Bitmap Index Scan on vorname_gin_trgm                                  |
|      (cost=0.00..75.50 rows=1000 width=0) (actual time=4.976..4.976 rows=17055 loops=1)  │
│                     Index Cond: (vorname % 'Nick'::text)                                 │
│ Planning time: 0.123 ms                                                                  │
│ Execution time: 32.563 ms                                                                │
└──────────────────────────────────────────────────────────────────────────────────────────┘
(13 rows)

Fazit

Dass PostgreSQL nicht von Haus aus alle LIKE-Anfragen per Index beschleunigt, sorgt gerne für Irritationen. Auf der anderen Seite öffnen sich, sobald man anfängt, sich mit dem Thema auseinanderzusetzen, ganz neue Möglichkeiten, die man in dieser Form bei anderen DBMS nicht findet. Und wir haben noch gar nicht über eine echte Volltextsuche gesprochen!

P.S.: pg_trgm kann kein Chinesisch!

Wenn nicht-alphanumerische Buchstaben in’s Spiel kommen (oder pg_trgm zu langsam ist…) sollte man einen Blick auf PGroonga werfen, das in diesen Bereichen glänzt.

Über den Author

Gunnar “Nick” Bluth hat seine Liebe zu relationalen Datenbanken Ende des letzten Jahrtausends entdeckt. Über MS Access und MySQL 3.x landete er sehr schnell bei PostgreSQL und hat nie zurückgeschaut, zumindest nie ohne Schmerzen. Er verdient seine Brötchen seit beinahe 20 Jahren mit FOSS (Administration, Schulungen, Linux, PostgreSQL). Gelegentlich taucht er auch tiefer in die Programmierung ein, so als SQL-Programmierer bei der Commerzbank oder in App-Nebenprojekten

OpenStack made easy – Snapshots erstellen, rotieren, einspielen

OpenStack made easy – Snapshots erstellen, rotieren, einspielen

This entry is part 4 of 4 in the series OpenStack made easy

Früher oder später gelangt wohl jede/r, die / der einen Server am Laufen hat einmal an den Punkt, dass es ihr / ihm die VM (oder Teile davon) irreversibel “zerreißt” – wodurch auch immer.
Wer sich im Vorfeld der Sicherung seiner Daten gewidmet hat, ist jetzt klar im Vorteil und hat signifikant niedrigere Adrenalinpegel zu erwarten – besonders, wenn die letzte Sicherung keine 24 Stunden her ist. Unsere OpenStack-Plattform bietet für die Sicherung Ihrer virtuellen Maschine(n) Funktionalität zum automatischen oder auch manuellen Erstellen von Schattenkopien (im Folgenden Snapshots genannt) an.

 > Automatischen Snapshot einrichten

Man wähle den Reiter Snapshots und setze ein Häkchen bei der automatisch zu sichernden VM und die Einstellung wird übernommen.

Et voilà! Ab sofort kümmert sich die OpenStack-App jede Nacht nach 00:00 Uhr darum, dass ein Snapshot dieser virtuellen Maschine erstellt wird. Wie im Bild erwähnt macht sie sieben Stück, beim achten Mal Snapshot-Erstellen löscht sie den ältesten, also den ersten, usw.
Ergo ist “7” ist der Rotations-Standardwert. Wer diesen gerne verändern möchte, d. h. weniger als eine Woche täglicher Snapshots vorhalten will, beispielsweise nur deren drei, kann das wie folgt tun: Zurück zum Reiter “LiveView” > Compute > Instanzen > Drop-down-Menü (ganz rechts neben der Instanz, die modifiziert werden soll) > “Aktualisiere Metadaten”.
Hier sieht man nun, dass unter “Existing Metadata” “nws_backup” existiert und auf “true” gesetzt ist.

Man schreibe unter “Available Metadata” neben das Feld “Custom” “rotation” hinein, füge es mit dem Pluszeichen der “Existing Metadata” hinzu, trage dort den Wert “3” ein und klicke auf “Speichern”.
Fertig.

¡¡¡ Im Fall von Datenbanken auf der zu sichernden VM !!!
Da Snapshots im laufenden Betrieb genommen werden, ist die Konsistenz der Datenbanken darin nicht garantiert. Ich empfehle die Einrichtung eines Cronjobs, der einen Dump der laufenden DBs oder anderer nicht-persistenter Daten anlegt. Gut wäre, wenn dieser vor 24:00 Uhr fertig ist und somit nahtlos mitgebackupt werden kann.

 > Manuellen Snapshot erstellen

Wer hingegen nur einen bestimmten Zustand – beispielsweise nach Durchführung aller Installationshandgriffe seiner Software-Landschaft – gesichert haben möchte, kann das manuell tun: Compute > Instanzen > Schattenkopie erstellen (neben der zu sichernden VM). Es ist dann noch ein aussagekräftiger Schattenkopiename zuzuteilen und Schattenkopie erstellen zu klicken.

Eine grüne Erfolgsmeldung wird hierauf oben rechts aufpoppen.
Hier sollte – wie oben beschrieben – ebenfalls auf Datenbanken geachtet werden.

 > Snapshot einspielen

Sollte es dann eben zur Havarie oder einem sonstigen Fall notwendiger Zurücksetzung kommen, läuft das Einspielen der Sicherung so ab, als würde man eine neue VM starten.
Compute > Instanzen > Instanz starten.

Details > Instanzname setzen

Quelle > Bootquelle auswählen:
Hier gibt es zwei Möglichkeiten, wo sich Ihr Snapshot befindet:

  • entweder > “Datenträger Snapshot” (i. F. v. VMs “mit Datenträger” bzw. “mit Volume”: Systemdatenträger in unserem Ceph-Storage, insgesamt dreifache Replikation an zwei Standorten)
  • oder > “Abbild” oder “Instanz Snapshot” (i. F. v. VMs “ohne Datenträger” bzw. “ohne Volume”: Systemdatenträger lediglich auf dem Hypervisor)

Variante, Netzwerke, Sicherheitsgruppen und Schlüsselpaar sind wie gehabt zu setzen.

Als Ergebnis wird man jetzt zwei ähnliche VMs haben:

Um den neuen Sollzustand zu verkomplettieren, bleibt, der zu verwerfenden Instanz die Floating-IP abzutrennen (Drop-down-Menü ganz rechts neben der Instanz) und diese der neuen zuzuweisen. Wenn sicher ist, dass die alte nicht mehr benötigt wird, kann diese dann auch gelöscht werden, um nicht weiterhin nutzlos Kosten zu verursachen.

Dieses Vorgehen eignet sich nicht nur zur Desaster-Recovery. Auch das Aufsetzen einer baugleichen VM oder das Testen größerer Patches abseits der produktiven Areale kann so bewerkstelligt werden.

> Snapshot als Datenträger einbinden

Für den Fall, dass man nur an die enthaltenen Daten heranwill, existiert auch die Möglichkeit, aus dem Snapshot einen Datenträger zu erstellen und diesen einer anderen laufenden VM anzuhängen.
Dies ist möglich über zwei Schritte:
Compute > Abbilder > Drop-down-Menü (ganz rechts neben dem zu benutzenden Abbild) > Datenträger erstellen > Datenträger erstellen.
Weiter über Datenträger > Datenträger > Drop-down-Menü (ganz rechts neben dem zu benutzenden Datenträger) > Anhänge verwalten.

Es ist noch die Instanz, mit der das Laufwerk verbunden werden soll auszuwählen und zu bestätigen.
Jetzt steht die Platte zur Verfügung, auf Ubuntu beispielsweise als /dev/sdb1 .

Martin Scholz
Martin Scholz
Systems Engineer

Martin sattelte unlängst vom sozialen Bereich auf die IT um und ist im Managed-Services-Support tätig. Praktischerweise nutzt ihm hier, dass er sich bereits vor geraumer Zeit Linux als User zugewandt hat. Privat ist er bekennende Couch-Potatoe. Es sei denn, er fühlt sich einmal wieder gedrängt, einen Marathon-Marsch zu unternehmen. Kein feliner oder kanider Passant ist vor seiner Kontaktaufnahme sicher.

SSH – Der Schlüssel zum Erfolg

Seal of ApprovalOder wie man das meiste aus SSH herauskitzelt.

SSH wird von den meisten Sysadmins genutzt, aber selten so wie es genutzt werden sollte.
Die meisten Administratoren verwenden das folgende Kommando Tag ein Tag aus.

ssh-keygen -t rsa -b 4096 -C "dummy_user@fqdn_dummy.com"

Zerlegen wir das Kommando mal in seine Einzelteile und versuchen zu verstehen was diese eigentlich tun.

Der Parameter '-t' gibt an welchen Key-Typ wir rsa/dsa/ecdsa/ecdsa-sk/ed25519 erstellen wollen. Hierbei wird von kleineren RSA-Keys unter 4096 Bits inzwischen abgeraten. DSA ist als unsicher deklariert und sollte auch seit Version 7 von OpenSSH auch nicht verwendet werden. ECDSA ist wegen der NSA ggf. problematisch zum nachlesen hier. So bleibt nur ED25519 welchen ich bei meiner täglichen Arbeit kaum sehe. Dies sollte aber heutzutage der De-Facto-Standard-Key sein, mit dem das obige Kommando aufgerufen wird.

'-b' für die Bits welche verwendet werden sollen. Wenn nun an dieser Stelle die Frage aufkommt mit welcher Bits-Anzahl ED25519 erstellt werden sollte, sei gesagt dass dieser eine festgelegte Länge hat und damit diesen Parameter ignoriert.

'-C' steht für Comment und dieser kann beliebig sein. Zumeist wird hier aber zur besseren Nachvollziehbarkeit von den Admins dieser Welt das Pattern Username@Hostname verwendet, damit man sehen kann für wen dieser Key erstellt wurde.

Im obigen Beispiel fehlt der Parameter '-N' welcher für new_passphrase bzw. das Passwort steht, wenn man den fehlenden Parameter '-N' mit "" ohne Zeichenkette angibt erhält man einen passwortlosen, privaten SSH-Schlüssel. Zusätzlich wird ein öffentlicher Schlüssel erstellt.

Verbessern wir das obige Kommando: ssh-keygen -t ed25519 -N'' -C 'dummy_user@fqdn_dummy.com'

Der Output des Beispiels:

ssh-keygen -t ed25519 -N'' -C'dummy_user@fqdn_dummy.com'
Generating public/private ed25519 key pair.
Enter file in which to save the key (/root/.ssh/id_ed25519): /root/.ssh/dummy
Created directory '/root/.ssh'.
Your identification has been saved in /root/.ssh/dummy.
Your public key has been saved in /root/.ssh/dummy.pub.
The key fingerprint is:
SHA256:14RQXbFyQCtEZfnGN8O7hV4h9hAEY0SYQN9Y0+9TRp8
root@fqdn_dummy.com The key's randomart image is:
+--[ED25519 256]--+
|      .oooO%O+o. |
|        .==o== ..|
|         oo.+o*.o|
|           + *+E=|
|        S . o.=+*|
|         .    .=o|
|             . .+|
|              .. |
|                 |
+----[SHA256]-----+
[root@fqdn_dummy.com .ssh]# ll
total 8
-rw-------. 1 root root 464 Nov 14 22:45 dummy
-rw-r--r--. 1 root root 108 Nov 14 22:45 dummy.pub

Damit hätten wir einen Standard-SSH-Schlüsselpaar, welches zumeist in den Unternehmen vergessen wird, wenn Mitarbeiter das Unternehmen verlassen oder die Abteilungen wechseln und auch durch automatisierte Deployments wird es oft als Karteileiche liegen gelassen.

Wie wäre es wenn wir ablaufende SSH-Schlüsselpaare hätten, welche nach einer gewissen Zeit ablaufen und zwar von selbst?

Was vielen nicht bewusst ist, ist dass dies SSH eigentlich schon von ganz allein kann, nur nutzt es kaum jemand.

Beginnen wir mit dem ersten Schritt und generieren uns eine CA (Certificate Authority).

ssh-keygen -t ed25519 -N '' -C 'ca' -f ca

Da diese CA unser Dreh- und Angelpunkt ist bedarf es Achtsamkeit, damit diese nie in falsche Hände gelangt.

Dank dieser CA können wir nun Zertifikate von Hosts und Benutzern signieren.

Als Beispiel nehmen wir einen Benutzer 'test' welcher sich per SSH an den Host namens ColdMoon anmelden soll.

Wir brauchen also für beide Zertifikate und einen SSH Schlüssel.

Bevor wir es vergessen müssen wir unsere vorher erstellte CA auf unserem SSH Server in der sshd_config als 'TrustedUserCAKeys /etc/ssh/ca.pub' eintragen.

Nun nehmen wir von unserem Benutzer 'test' auf seinem Laptop und den dort bereits erstellten öffentlichen Schlüssel mit dem wir ihm begrenzten Zugriff auf unsere Infrastruktur geben wollen in diesem Fall unser Rechner ColdMoon.

Im Vorfeld haben wir schon den Benutzer auf dem Rechner angelegt, und der Kollege 'test' hat uns seinen öffentlichen Schlüssel per Email geschickt.
Von unserem Vorgesetzten wissen wir das der Kollege 'test' nur 1 Woche ein Praktikum bei uns vornimmt und danach das Unternehmen wieder verlässt.

Auf gehts wir erstellen einen zeitlich begrenzten Zugang:

ssh-keygen -s ca -I Azubi_auf_Zeit -n test -V +1w -z 1 test.pub
Signed user key test-cert.pub: id "Azubi_auf_Zeit" serial 1 for test valid from 2019-11-13T09:00:00 to 2019-11-20T09:00:00

Das Resultat dieses Kommandos ist das wir ein signiertes 'test-cert.pub' erhalten mit eine Lebenszeit von 1 Woche.
Dieses senden wir umgehend unseren Praktikanten zu mit dem Hinweis das er sich mit dem folgenden Kommando nun an dem Rechner ColdMoon anmelden kann.

ssh -i ~/.ssh/test -i ~/.ssh/test-cert.pub test@ColdMoon -v

Ich habe mal den Verbose-Schalter genommen um zu zeigen wie das Zertifikat sich dann gegen ColdMoon anmeldet.

...
debug1: SSH2_MSG_EXT_INFO received
debug1: kex_input_ext_info: server-sig-algs=<rsa-sha2-256,rsa-sha2-512>
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Next authentication method: publickey
debug1: Offering public key: test ED25519 SHA256:4A3ab2/7dq0klERi9IevmSnTZd7dkOuuP8yrWCZ24gI explicit
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Offering public key: test ED25519-CERT SHA256:4A3ab2/7dq0klERi9IevmSnTZd7dkOuuP8yrWCZ24gI explicit
debug1: Server accepts key: test ED25519-CERT SHA256:4A3ab2/7dq0klERi9IevmSnTZd7dkOuuP8yrWCZ24gI explicit
debug1: Authentication succeeded (publickey).
Authenticated to 192.168.242.67 ([192.168.242.67]:22).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: network
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: Requesting authentication agent forwarding.
debug1: Sending environment.
debug1: Sending env LC_CTYPE = UTF-8
Last login: Thu Nov 14 11:00:14 2019 from 192.168.242.225
[test@coldmoon ~]$

Die Authentifizierung wird nun die gesamte Woche lang funktionieren in der der Praktikant Zugriff auf dem Rechner braucht, danach weil es nur zeitlich begrenzt ist nicht mehr. Damit ist für den Praktikanten kein Zugriff auf unserer Infrastruktur mehr möglich. Dies funktioniert auch mit anderen Hosts oder Services, welchen man nur für einen Zeitraum einen begrenzten Zugriff per SSH geben möchte.

Der Parameter '-V' gibt den Validity_Interval an, also den Zeitraum der Gültigkeit, und der Parameter '-z' eine Serialnumber, welche von uns frei gewählt werden kann.

Dies kann mit den üblichen Systemmitteln automatisiert werden oder per Ansible/Puppet auch auf Systeme verteilt werden.
Schöner ist es noch wenn es ohne viel manuelle Arbeit geht. Dies zeige ich in meinem nächsten Blogpost welcher Vault aus dem Hause Hashicorp zeigt.

Gruss

David

David Okon
David Okon
Support Engineer

Weltenbummler David hat aus Berlin fast den direkten Weg zu uns nach Nürnberg genommen. Bevor er hier anheuerte, gab es einen kleinen Schlenker nach Irland, England, Frankreich und in die Niederlande. Alles nur, damit er sein Know How als IHK Geprüfter DOSenöffner so sehr vertiefen konnte, dass er vom Apple Consultant den Sprung in unser Professional Services-Team wagen konnte. Er ist stolzer Papa eines Sohnemanns und bei uns mit der Mission unterwegs, unsere Kunden zu...