Kapitel 4. Nützliche Linux-Dienstprogramme

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Die Kommandozeile und ihre Werkzeuge waren einer der Hauptgründe, warum sich Alfredo zu Beginn seiner Karriere mit Linux-Servern verbunden fühlte. Einer seiner ersten Aufträge als Systemadministrator in einem mittelständischen Unternehmen bestand darin, sich um alles zu kümmern, was mit Linux zu tun hatte. Die kleine IT-Abteilung konzentrierte sich auf die Windows-Server und -Desktops, und die Verwendung der Kommandozeile war ihnen zutiefst zuwider. Irgendwann sagte der IT-Manager zu ihm, dass er grafische Benutzeroberflächen (GUIs), die Installation von Dienstprogrammen und Werkzeuge im Allgemeinen verstehe, um Probleme zu lösen: "Ich bin kein Programmierer, wenn es keine grafische Benutzeroberfläche gibt, kann ich sie nicht benutzen", sagte er.

Alfredo wurde als Auftragnehmer eingestellt, um bei den wenigen Linux-Servern des Unternehmens auszuhelfen. Zu dieser Zeitwar Subversion (SVN) der letzte Schrei in Sachen Versionskontrolle, und die Entwickler waren auf diesen einen SVN-Server angewiesen, um ihre Arbeit zu veröffentlichen. Anstelle des zentralen Identitätsservers, der von zwei Domänencontrollern bereitgestellt wurde, wurde ein textbasiertes Authentifizierungssystem verwendet, das einen Benutzer einem Hash zuordnete, der das Passwort repräsentierte. Das bedeutete, dass die Benutzernamen nicht unbedingt mit denen des Domänencontrollers übereinstimmten und dass die Passwörter beliebig sein konnten. Oft bat ein Entwickler darum, das Passwort zurückzusetzen, und jemand musste die Textdatei mit dem Hash bearbeiten. Ein Projektmanager bat Alfredo, die SVN-Authentifizierung in den Domänencontroller (Microsofts Active Directory) zu integrieren. Die erste Frage, die er stellte, war, warum die IT-Abteilung das nicht schon längst getan hatte. "Sie sagen, es sei nicht möglich, aber Alfredo, das ist eine Lüge, SVN kann sich in Active Directory integrieren."

Er hatte noch nie einen Authentifizierungsdienst wie Active Directory benutzt und verstand kaum etwas von SVN, aber er war fest entschlossen, dies zum Laufen zu bringen. Alfredo machte sich daran, alles über SVN und Active Directory zu lesen, bastelte an einer virtuellen Maschine mit einem SVN-Server herum und versuchte, diese Authentifizierung zum Laufen zu bringen. Er brauchte etwa zwei Wochen, um sich über alle Einzelheiten zu informieren und sie zum Funktionieren zu bringen. Am Ende war er erfolgreich und konnte das System in die Produktion bringen. Er fühlte sich unglaublich stark; er hatte sich einzigartiges Wissen angeeignet und war nun in der Lage, die volle Verantwortung für dieses System zu übernehmen. Der IT-Manager und der Rest der Abteilung waren überglücklich. Alfredo versuchte, sein neu erworbenes Wissen mit anderen zu teilen, aber er bekam immer eine Ausrede zu hören: "keine Zeit " , "zu beschäftigt " , "andere Prioritäten" und "vielleicht ein anderes Mal - vielleicht nächste Woche".

Eine treffende Beschreibung für Technologen ist: Wissensarbeiter. Deine Neugier und dein unermüdliches Streben nach Wissen werden dich und die Umgebungen, an denen du arbeitest, immer besser machen. Lass dich niemals von einem Kollegen oder einer Kollegin (oder einer ganzen IT-Abteilung, wie in Alfredos Fall) davon abhalten, deine Systeme zu verbessern. Wenn es eine Möglichkeit gibt, etwas Neues zu lernen, dann nimm sie wahr! Das Schlimmste, was passieren kann, ist, dass du dir Wissen angeeignet hast, das du vielleicht nicht oft nutzen wirst, das aber deine berufliche Laufbahn verändern könnte.

Linux verfügt zwar über Desktop-Umgebungen, aber seine wahre Stärke liegt im Verständnis und in der Nutzung der Kommandozeile und letztlich in deren Erweiterung. Wenn es keine vorgefertigten Tools gibt, um ein Problem zu lösen, entwickeln erfahrene DevOps-Leute ihre eigenen. Dieser Gedanke, Lösungen zu finden, indem man die wichtigsten Teile zusammensetzt, ist unglaublich mächtig und das ist es auch, was bei diesem Job passiert ist, bei dem es sich produktiv anfühlte, Aufgaben zu erledigen, ohne Software von der Stange installieren zu müssen, um Dinge zu beheben.

In diesem Kapitel gehen wir einige gängige Muster in der Shell durch und stellen einige nützliche Python-Befehle vor, die die Interaktion mit einer Maschine verbessern sollen. Wir finden, dass das Erstellen von Aliasen und Einzeilern am meisten Spaß bei der Arbeit macht, und manchmal sind sie so nützlich, dass sie als Plug-ins oder eigenständige Software landen.

Disk Utilities

Es gibt verschiedene Dienstprogramme, mit denen du Informationen über Geräte in einem System erhalten kannst. Viele von ihnen haben überlappende Funktionen, und einige haben eine interaktive Sitzung, um mit Festplattenoperationen umzugehen, wie fdisk und parted.

Es ist wichtig, die Festplattendienstprogramme gut zu beherrschen, nicht nur um Informationen abzurufen und Partitionen zu manipulieren, sondern auch um die Leistung genau zu messen. Vor allem die Leistung ist eine der schwierigsten Aufgaben, die es zu bewältigen gilt. Die beste Antwort auf die Frage "Wie messe ich die Leistung eines Geräts?" lautet: Es kommt darauf an, denn es ist schwierig, die gewünschte Kennzahl zu ermitteln.

Leistung messen

Wenn wir in einer isolierten Umgebung mit einem Server arbeiten müssen, der keinen Zugang zum Internet hat oder den wir nicht kontrollieren und daher keine Pakete installieren können, würde das Tool dd (das auf allen gängigen Linux-Distributionen verfügbar sein sollte ) helfen, einige Antworten zu finden. Wenn möglich, solltest du es mit iostat kombinieren, um den Befehl, der das Gerät angreift, von dem Befehl zu trennen, der den Bericht liefert.

Wie ein erfahrener Leistungsingenieur einmal sagte, kommt es darauf an, was gemessen wird und wie. Zum Beispiel ist dd ein Single-Thread-System und kann nicht mehrere zufällige Lese- und Schreibvorgänge durchführen; außerdem misst es den Durchsatz und nicht die Ein- und Ausgabevorgänge pro Sekunde (IOPS). Was misst du? Durchsatz oder IOPS?

Vorsicht

Ein Wort der Warnung zu diesen Beispielen. Sie können dein System zerstören, also folge ihnen nicht blind und verwende Geräte, die gelöscht werden können.

Dieser einfache Einzeiler lässt dd laufen, um einige Zahlen für ein brandneues Gerät( in diesem Fall/dev/sdc ) zu erhalten:

$ dd if=/dev/zero of=/dev/sdc count=10 bs=100M
10+0 records in
10+0 records out
1048576000 bytes (1.0 GB, 1000 MiB) copied, 1.01127 s, 1.0 GB/s

Er schreibt 10 Datensätze von 100 Megabyte mit einer Rate von 1 GB/s. Das ist der Durchsatz. Ein einfacher Weg, um mit dd IOPS zu erhalten, ist die Verwendung von iostat . In diesem Beispiel wird iostat nur auf dem Gerät ausgeführt, das mit dd überlastet wird, wobei das Flag -d nur dazu dient, das Gerät zu informieren, und das Intervall eine Sekunde beträgt:

$ iostat -d /dev/sdc 1

Device             tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
sdc            6813.00         0.00   1498640.00          0    1498640

Device             tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
sdc            6711.00         0.00   1476420.00          0    1476420

Die Ausgabe iostat wiederholt sich jede Sekunde, bis eine Ctrl-C ausgegeben wird, um den Vorgang abzubrechen. Die zweite Spalte in der Ausgabe ist tps, was für Transaktionen pro Sekunde steht und dasselbe ist wie IOPS. Eine schönere Art, die Ausgabe zu visualisieren und das Durcheinander zu vermeiden, das ein sich wiederholender Befehl verursacht, ist, das Terminal bei jedem Durchlauf zu löschen:

$ while true; do clear && iostat -d /dev/sdc && sleep 1; done

Genaue Tests mit fio

Wenn dd und iostat nicht ausreichen, ist das am häufigsten verwendete Tool für Leistungstests fio . Es kann dabei helfen, das Leistungsverhalten eines Geräts in einer lese- oder schreiblastigen Umgebung zu verdeutlichen (und sogar die Anteile von Lese- und Schreibvorgängen anzupassen).

Die Ausgabe von fio ist ziemlich ausführlich. Das folgende Beispiel hebt die IOPS bei Lese- und Schreibvorgängen hervor:

$ fio --name=sdc-performance --filename=/dev/sdc --ioengine=libaio \
  --iodepth=1 --rw=randrw --bs=32k --direct=0 --size=64m
sdc-performance: (g=0): rw=randwrite, bs=(R) 32.0KiB-32.0KiB,
(W) 32.0KiB-32.0KiB, (T) 32.0KiB-32.0KiB, ioengine=libaio, iodepth=1
fio-3.1
Starting 1 process

sdc-performance: (groupid=0, jobs=1): err= 0: pid=2879:
   read: IOPS=1753, BW=54.8MiB/s (57.4MB/s)(31.1MiB/567msec)
...
   iops        : min= 1718, max= 1718, avg=1718.00, stdev= 0.00, samples=1
  write: IOPS=1858, BW=58.1MiB/s (60.9MB/s)(32.9MiB/567msec)
...
   iops        : min= 1824, max= 1824, avg=1824.00, stdev= 0.00, samples=1

Die im Beispiel verwendeten Flags geben dem Auftrag den Namen sdc-performance, verweisen direkt auf das Gerät /dev/sdc (erfordert Superuser-Berechtigungen), verwenden die native Linux-Bibliothek für asynchrone E/A, setzen iodepth auf 1 (Anzahl der gleichzeitig zu sendenden sequentiellen E/A-Anfragen) und definieren zufällige Lese- und Schreibvorgänge von 32 Kilobyte für die Puffergröße unter Verwendung von gepufferter E/A (kann auf 1 gesetzt werden, um ungepufferte E/A zu verwenden) auf eine 64-Megabyte-Datei. Das ist ein ziemlich langer Befehl!

Das Tool fio bietet eine Vielzahl zusätzlicher Optionen, die in fast allen Fällen, in denen genaue IOPS-Messungen erforderlich sind, hilfreich sind. Zum Beispiel kann es den Test auf viele Geräte gleichzeitig ausdehnen, ein I/O-Warm-up durchführen und sogar I/O-Schwellenwerte für den Test festlegen, wenn eine bestimmte Grenze nicht überschritten werden soll. Schließlich können die vielen Optionen in der Befehlszeile mit INI-Dateien konfiguriert werden, so dass die Ausführung von Aufträgen gut geskriptet werden kann.

Trennwände

In der Regel verwenden wir fdisk mit seiner interaktiven Sitzung zum Erstellen von Partitionen, aber in manchen Fällen funktioniert fdisk nicht gut, z. B. bei großen Partitionen (zwei Terabyte oder mehr). In diesen Fällen solltest du auf parted zurückgreifen.

Eine kurze interaktive Sitzung zeigt, wie man eine primäre Partition mit fdisk erstellt, mit dem Standard-Startwert und einer Größe von vier Gibibytes. Am Ende wird der Schlüssel w gesendet, um die Änderungen zu schreiben:

$ sudo fdisk /dev/sds

Command (m for help): n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Partition number (1-4, default 1):
First sector (2048-22527999, default 2048):
Using default value 2048
Last sector, +sectors or +size{K,M,G} (2048-22527999, default 22527999): +4G
Partition 1 of type Linux and of size 4 GiB is set

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.

parted erreicht das Gleiche, allerdings mit einer anderen Schnittstelle:

$ sudo parted /dev/sdaa
GNU Parted 3.1
Using /dev/sdaa
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) mklabel
New disk label type? gpt
(parted) mkpart
Partition name?  []?
File system type?  [ext2]?
Start? 0
End? 40%

Zum Schluss beendest du das Programm mit der Taste q. Für die programmatische Erstellung von Partitionen auf der Kommandozeile ohne interaktive Eingabeaufforderungen erreichst du das gleiche Ergebnis mit ein paar Befehlen:

$ parted --script /dev/sdaa mklabel gpt
$ parted --script /dev/sdaa mkpart primary 1 40%
$ parted --script /dev/sdaa print
Disk /dev/sdaa: 11.5GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number  Start   End     Size    File system  Name     Flags
 1      1049kB  4614MB  4613MB

Abrufen von spezifischen Geräteinformationen

Wenn bestimmte Informationen zu einem Gerät benötigt werden, sind entweder lsblk oder blkid gut geeignet. fdisk arbeitet nicht gerne ohne Superuser-Rechte. Hier fdisk listet die Informationen über das Gerät /dev/sda auf:

$ fdisk -l /dev/sda
fdisk: cannot open /dev/sda: Permission denied

$ sudo fdisk -l /dev/sda

Disk /dev/sda: 42.9 GB, 42949672960 bytes, 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x0009d9ce

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    83886079    41942016   83  Linux

blkid ist insofern ähnlich, als dass es auch Superuser-Rechte benötigt:

$ blkid /dev/sda

$ sudo blkid /dev/sda
/dev/sda: PTTYPE="dos"

lsblk ermöglicht es, Informationen ohne höhere Berechtigungen zu erhalten, und liefert unabhängig davon die gleiche Informationsausgabe:

$ lsblk /dev/sda
NAME   MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda      8:0    0  40G  0 disk
└─sda1   8:1    0  40G  0 part /
$ sudo lsblk /dev/sda
NAME   MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda      8:0    0  40G  0 disk
└─sda1   8:1    0  40G  0 part /

Dieser Befehl, der das -p Flag für Low-Level-Device-Probing verwendet, ist sehr gründlich und sollte dir genügend Informationen über ein Gerät liefern:

$ blkid -p /dev/sda1
UUID="8e4622c4-1066-4ea8-ab6c-9a19f626755c" TYPE="xfs" USAGE="filesystem"
PART_ENTRY_SCHEME="dos" PART_ENTRY_TYPE="0x83" PART_ENTRY_FLAGS="0x80"
PART_ENTRY_NUMBER="1" PART_ENTRY_OFFSET="2048" PART_ENTRY_SIZE="83884032"

lsblk hat einige Standardeigenschaften, nach denen du suchen kannst:

$ lsblk -P /dev/nvme0n1p1
NAME="nvme0n1p1" MAJ:MIN="259:1" RM="0" SIZE="512M" RO="0" TYPE="part"

Du kannst aber auch bestimmte Flaggen setzen, um eine bestimmte Eigenschaft abzufragen:

lsblk -P -o SIZE /dev/nvme0n1p1
SIZE="512M"

Der Zugriff auf eine Eigenschaft auf diese Weise macht es einfach, Skripte zu erstellen und sogar von der Python-Seite aus zu konsumieren.

Netzwerk-Dienstprogramme

Die Netzwerk-Tools werden immer besser, da immer mehr Server miteinander verbunden werden müssen. Viele der Dienstprogramme in diesem Abschnitt befassen sich mit nützlichen Einzeilern wie Secure Shell (SSH)-Tunneling, aber einige andere gehen ins Detail, wenn es darum geht, die Netzwerkleistung zu testen, z. B. mit dem Apache Bench Tool.

SSH-Tunneling

Hast du schon einmal versucht, einen HTTP-Dienst zu erreichen, der auf einem entfernten Server läuft, der nur über SSH erreichbar ist? Diese Situation tritt ein, wenn der HTTP-Dienst aktiviert ist, aber nicht öffentlich benötigt wird. Das letzte Mal haben wir das bei einer RabbitMQ-Produktionsinstanz gesehen, bei der das Management Plug-in aktiviert war, das einen HTTP-Dienst auf Port 15672 startet. Der Dienst ist nicht öffentlich zugänglich, und das aus gutem Grund: Es besteht keine Notwendigkeit, ihn öffentlich zugänglich zu machen, da er nur selten genutzt wird, und außerdem kann man die Tunneling-Funktionen von SSH nutzen.

Dazu wird eine SSH-Verbindung mit dem entfernten Server hergestellt und der entfernte Port (in meinem Fall 15672) an einen lokalen Port auf dem Ursprungsrechner weitergeleitet. Der entfernte Rechner hat einen eigenen SSH-Port, was den Befehl etwas komplizierter macht. So sieht es aus:

$ ssh -L 9998:localhost:15672 -p 2223 adeza@prod1.rabbitmq.ceph.internal -N

Es gibt drei Flaggen, drei Zahlen und zwei Adressen. Zerlegen wir den Befehl, um zu verdeutlichen, was hier vor sich geht. Das Flag -L signalisiert, dass die Weiterleitung aktiviert ist und ein lokaler Port (9998) an einen Remote-Port (RabbitMQs Standardwert 15672) gebunden werden soll. Als Nächstes gibt das -p Flag an, dass der benutzerdefinierte SSH-Port des Remote-Servers 2223 ist, und dann werden der Benutzername und die Adresse angegeben. Schließlich bedeutet -N, dass wir nicht zu einer entfernten Shell gelangen und die Weiterleitung vornehmen sollen.

Wenn der Befehl korrekt ausgeführt wird, scheint er zu hängen, aber er ermöglicht es dir, http://localhost:9998/ aufzurufen und die Anmeldeseite für die entfernte RabbitMQ-Instanz zu sehen. Ein nützliches Flag beim Tunneln ist -f: Es schickt den Prozess in den Hintergrund, was hilfreich ist, wenn die Verbindung nicht nur vorübergehend ist, so dass das Terminal bereit und sauber ist, um weitere Arbeit zu erledigen.

Benchmarking von HTTP mit Apache Benchmark (ab)

Wir lieben es, die Server, mit denen wir arbeiten, auf Herz und Nieren zu prüfen, um sicherzustellen, dass sie mit der Last richtig umgehen, vor allem bevor sie in die Produktion gehen. Manchmal versuchen wir sogar, eine seltsame Wettlaufsituation auszulösen, die unter hoher Last auftreten kann. Das Apache Benchmark Tool (ab in der Kommandozeile) ist eines dieser kleinen Tools, das dich mit nur ein paar Flags schnell ans Ziel bringt.

Mit diesem Befehl werden jeweils 100 Anfragen an eine lokale Instanz, auf der Nginx läuft, erstellt, insgesamt also 10.000 Anfragen:

$ ab -c 100 -n 10000 http://localhost/

Das ist ziemlich brutal für ein System, aber dies ist ein lokaler Server, und die Anfragen sind nur ein HTTP GET. Die detaillierte Ausgabe von ab ist sehr umfangreich und sieht wie folgt aus (der Kürze halber gekürzt):

Benchmarking localhost (be patient)
...
Completed 10000 requests
Finished 10000 requests

Server Software:        nginx/1.15.9
Server Hostname:        localhost
Server Port:            80

Document Path:          /
Document Length:        612 bytes

Concurrency Level:      100
Time taken for tests:   0.624 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      8540000 bytes
HTML transferred:       6120000 bytes
Requests per second:    16015.37 [#/sec] (mean)
Time per request:       6.244 [ms] (mean)
Time per request:       0.062 [ms] (mean, across all concurrent requests)
Transfer rate:          13356.57 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    3   0.6      3       5
Processing:     0    4   0.8      3       8
Waiting:        0    3   0.8      3       6
Total:          0    6   1.0      6       9

Diese Art von Informationen und wie sie präsentiert werden, ist enorm. Auf einen Blick kannst du schnell erkennen, ob ein Produktionsserver Verbindungen abbricht (im Feld Failed requests ) und wie hoch die Durchschnittswerte sind. Es wird eine GET Anfrage verwendet, aber mit ab kannst du auch andere HTTP-Verben verwenden, wie z. B. POST, und sogar eine HEAD Anfrage stellen. Mit dieser Art von Tool musst du vorsichtig sein, da es einen Server leicht überlasten kann. Im Folgenden findest du realistischere Zahlen von einem HTTP-Dienst in der Produktion (der Kürze halber gekürzt):

...
Benchmarking prod1.ceph.internal (be patient)

Server Software:        nginx
Server Hostname:        prod1.ceph.internal
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256
Server Temp Key:        ECDH P-256 256 bits
TLS Server Name:        prod1.ceph.internal

Complete requests:      200
Failed requests:        0
Total transferred:      212600 bytes
HTML transferred:       175000 bytes
Requests per second:    83.94 [#/sec] (mean)
Time per request:       1191.324 [ms] (mean)
Time per request:       11.913 [ms] (mean, across all concurrent requests)
Transfer rate:          87.14 [Kbytes/sec] received
....

Jetzt sehen die Zahlen anders aus, es trifft auf einen Dienst mit aktiviertem SSL, und ab listet auf, welche Protokolle das sind. Mit 83 Anfragen pro Sekunde könnte es besser laufen, aber dies ist ein API-Server, der JSON produziert und normalerweise nicht so viel Last auf einmal bekommt, wie gerade erzeugt wurde.

Belastungstests mit molotov

Das Molotov-Projekt ist ein interessantes Projekt, das auf Lasttests ausgerichtet ist. Einige seiner Funktionen ähneln denen von Apache Benchmark, aber da es ein Python-Projekt ist, bietet es eine Möglichkeit, Szenarien mit Python und dem Modul asyncio zu schreiben.

So sieht das einfachste Beispiel für molotov aus:

import molotov

@molotov.scenario(100)
async def scenario_one(session):
    async with session.get("http://localhost:5000") as resp:
        assert resp.status == 200

Speichere die Datei als load_test.py, erstelle eine kleine Flask-Anwendung, die sowohl POST als auch GET Anfragen an ihre Haupt-URL bearbeitet, und speichere sie als small.py:

from flask import Flask, redirect, request

app = Flask('basic app')

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        redirect('https://www.google.com/search?q=%s' % request.args['q'])
    else:
        return '<h1>GET request from Flask!</h1>'

Starte die Flask-Anwendung mit FLASK_APP=small.py flask run und führe dann molotov mit der zuvor erstellten Datei load_test.py aus:

$ molotov -v -r 100 load_test.py
**** Molotov v1.6. Happy breaking! ****
Preparing 1 worker...
OK
SUCCESSES: 100 | FAILURES: 0 WORKERS: 0
*** Bye ***

Einhundert Anfragen auf einem einzigen Worker liefen gegen die lokale Flask-Instanz. Das Tool glänzt wirklich, wenn die Lasttests erweitert werden, um mehr pro Anfrage zu tun. Es verfügt über Konzepte, die den Unit-Tests ähneln, wie z. B. Setup, Teardown und sogar Code, der auf bestimmte Ereignisse reagieren kann. Da die kleine Flask-Anwendung mit einer POST umgehen kann, die auf eine Google-Suche umleitet, füge ein weiteres Szenario in die Datei load_test.py_ ein. Diesmal änderst du die Gewichtung so, dass 100% der Anfragen eine POST ausführen:

@molotov.scenario(100)
async def scenario_post(session):
    resp = await session.post("http://localhost:5000", params={'q': 'devops'})
    redirect_status = resp.history[0].status
    error = "unexpected redirect status: %s" % redirect_status
    assert redirect_status == 301, error

Führe dieses neue Szenario für eine einzelne Anfrage aus, um das Folgende zu zeigen:

$ molotov -v -r 1 --processes 1 load_test.py
**** Molotov v1.6. Happy breaking! ****
Preparing 1 worker...
OK
AssertionError('unexpected redirect status: 302',)
  File ".venv/lib/python3.6/site-packages/molotov/worker.py", line 206, in step
    **scenario['kw'])
  File "load_test.py", line 12, in scenario_two
    assert redirect_status == 301, error
SUCCESSES: 0 | FAILURES: 1
*** Bye ***

Eine einzige Anfrage (mit -r 1) reichte aus, um das Ganze fehlschlagen zu lassen. Die Assertion muss so aktualisiert werden, dass sie auf 302 statt auf 301 prüft. Sobald dieser Status aktualisiert ist, ändere die Gewichtung des Szenarios POST in 80, damit weitere Anfragen (mit GET) an die Flask-Anwendung gesendet werden. So sieht die Datei am Ende aus:

import molotov

@molotov.scenario()
async def scenario_one(session):
    async with session.get("http://localhost:5000/") as resp:
        assert resp.status == 200

@molotov.scenario(80)
async def scenario_two(session):
    resp = await session.post("http://localhost:5000", params={'q': 'devops'})
    redirect_status = resp.history[0].status
    error = "unexpected redirect status: %s" % redirect_status
    assert redirect_status == 301, error

Führe load_test.py für 10 Anfragen aus, um die Anfragen zu verteilen, zwei für eine GET und den Rest mit einer POST:

127.0.0.1 - - [04/Sep/2019 12:10:54] "POST /?q=devops HTTP/1.1" 302 -
127.0.0.1 - - [04/Sep/2019 12:10:56] "POST /?q=devops HTTP/1.1" 302 -
127.0.0.1 - - [04/Sep/2019 12:10:57] "POST /?q=devops HTTP/1.1" 302 -
127.0.0.1 - - [04/Sep/2019 12:10:58] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [04/Sep/2019 12:10:58] "POST /?q=devops HTTP/1.1" 302 -
127.0.0.1 - - [04/Sep/2019 12:10:59] "POST /?q=devops HTTP/1.1" 302 -
127.0.0.1 - - [04/Sep/2019 12:11:00] "POST /?q=devops HTTP/1.1" 302 -
127.0.0.1 - - [04/Sep/2019 12:11:01] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [04/Sep/2019 12:11:01] "POST /?q=devops HTTP/1.1" 302 -
127.0.0.1 - - [04/Sep/2019 12:11:02] "POST /?q=devops HTTP/1.1" 302 -

Wie du siehst, ist molotov mit reinem Python leicht erweiterbar und kann an andere, komplexere Bedürfnisse angepasst werden. Diese Beispiele kratzen nur an der Oberfläche dessen, was das Tool leisten kann.

CPU-Dienstprogramme

Es gibt zwei wichtige CPU-Utilities: top und htop. top ist heute in den meisten Linux-Distributionen vorinstalliert, aber wenn du in der Lage bist, Pakete zu installieren, ist htop fantastisch zu bedienen und wir ziehen seine anpassbare Oberfläche top vor. Es gibt noch ein paar andere Tools, die eine CPU-Visualisierung und vielleicht sogar eine Überwachung ermöglichen, aber keines davon ist so vollständig und so weit verbreitet wie top und htop. Es ist zum Beispiel durchaus möglich, die CPU-Auslastung mit dem Befehl ps zu ermitteln:

$ ps -eo pcpu,pid,user,args | sort -r | head -10
%CPU   PID USER     COMMAND
 0.3   719 vagrant  -bash
 0.1   718 vagrant  sshd: vagrant@pts/0
 0.1   668 vagrant  /lib/systemd/systemd --user
 0.0     9 root     [rcu_bh]
 0.0    95 root     [ipv6_addrconf]
 0.0    91 root     [kworker/u4:3]
 0.0     8 root     [rcu_sched]
 0.0    89 root     [scsi_tmf_1]

Der Befehl ps benötigt einige benutzerdefinierte Felder. Das erste ist pcpu, das die CPU-Auslastung angibt, gefolgt von der Prozess-ID, dem Benutzer und schließlich dem Befehl. Das führt zu einer umgekehrten Sortierung, da die CPU-Auslastung standardmäßig von weniger zu mehr geht und du die höchste CPU-Auslastung an der Spitze haben musst. Da der Befehl diese Informationen für jeden einzelnen Prozess anzeigt, filtert er die 10 besten Ergebnisse mit dem Befehl head .

Aber der Befehl ist ein ziemlicher Zungenbrecher, an den man sich nur schwer erinnern kann und der nicht ständig aktualisiert wird. Selbst wenn du einen Alias hast, bist du mit top oder htop besser dran. Wie du sehen wirst, haben beide umfangreiche Funktionen.

Prozesse mit htop ansehen

Das Tool htop ist genau wie top (ein interaktiver Prozessbetrachter), aber es ist plattformübergreifend (funktioniert unter OS X, FreeBSD, OpenBSD und Linux), bietet Unterstützung für bessere Visualisierungen (siehe Abbildung 4-1) und ist angenehm zu bedienen. Unter https://hisham.hm/htop findest du einen Screenshot von htop, der auf einem Server läuft. Einer der größten Nachteile von htop ist, dass alle Shortcuts, die du vielleicht von top kennst, nicht kompatibel sind. Du musst also dein Gehirn neu programmieren, um sie zu verstehen und für htop zu verwenden.

pydo 0401
Abbildung 4-1. htop läuft auf einem Server

Die Informationen in Abbildung 4-1 sehen sofort anders aus und fühlen sich anders an. CPU, Speicher und Swap werden oben links angezeigt und bewegen sich, wenn sich das System ändert. Mit den Pfeiltasten kannst du nach oben oder unten und sogar von links nach rechts scrollen und so den gesamten Befehl des Prozesses sehen.

Willst du einen Prozess beenden? Bewege dich mit den Pfeiltasten zu ihm oder drücke /, um den Prozess schrittweise zu suchen (und zu filtern), und drücke dann k. In einem neuen Menü werden alle Signale angezeigt, die an den Prozess gesendet werden können, z. B. SIGTERM anstelle von SIGKILL. Es ist möglich, mehr als einen Prozess zum Töten zu "markieren". Drücke die Leertaste, um den ausgewählten Prozess zu markieren und ihn mit einer anderen Farbe hervorzuheben. Du hast einen Fehler gemacht und möchtest die Markierung aufheben? Drücke erneut die Leertaste. Das ist alles sehr intuitiv.

Ein Problem mit htop ist, dass es viele Aktionen gibt, die den Tasten F zugeordnet sind, und du vielleicht keine hast. F1 steht zum Beispiel für Hilfe. Die Alternative ist, wenn möglich die entsprechenden Zuordnungen zu verwenden. Um das Hilfemenü aufzurufen, benutze die Taste h; um das Setup aufzurufen, benutze Shift s anstelle von F2.

Die t (auch hier, wie intuitiv!) aktiviert (schaltet) die Prozessliste als Baum. Die wahrscheinlich am häufigsten verwendete Funktion ist die Sortierung. Drücke > und es erscheint ein Menü, in dem du die Art der Sortierung auswählen kannst: PID, Benutzer, Speicher, Priorität und CPU-Prozent sind nur einige davon. Es gibt auch Tastenkombinationen, um direkt (ohne Menüauswahl) nach Speicher (Shift i), CPU (Shift p) und Zeit (Shift t) zu sortieren.

Zum Schluss noch zwei unglaubliche Funktionen: Du kannst strace oder lsof direkt in dem ausgewählten Prozess ausführen, solange diese installiert und für den Benutzer verfügbar sind. Wenn die Prozesse Superuser-Rechte benötigen, meldet htop dies und verlangt, dass sudo als privilegierter Benutzer ausgeführt wird. Um strace in einem ausgewählten Prozess auszuführen, verwendest du die Taste s; für lsof verwendest du die Taste l.

Wenn entweder strace oder lsof verwendet wird, sind die Such- und Filteroptionen auch mit dem Zeichen / verfügbar. Was für ein unglaublich nützliches Tool! Hoffentlich werden eines Tages auch andere Zuordnungen alsF möglich sein, auch wenn die meiste Arbeit mit den alternativen Zuordnungen erledigt werden kann .

Tipp

Wenn htop über die interaktive Sitzung angepasst wird, werden die Änderungen in einer Konfigurationsdatei gespeichert, die sich normalerweise in ~/.config/htop/htoprc befindet. Wenn du dort Konfigurationen definierst und sie später in der Sitzung änderst, überschreibt die Sitzung alles, was zuvor in der htoprc-Datei definiert wurde.

Arbeiten mit Bash und ZSH

Alles beginnt mit der Anpassung. Sowohl Bash als auch ZSH werden in der Regel mit einem "Dotfile" ausgeliefert , einer Datei mit einem vorangestellten Punkt, die Konfigurationsdaten enthält, aber standardmäßig ausgeblendet wird, wenn Verzeichnisinhalte aufgelistet werden, und die sich im Heimatverzeichnis des Benutzers befindet. Bei der Bash ist das die Datei .bashrc und bei der ZSH die Datei .zshrc. Beide Shells unterstützen mehrere Ebenen, die in einer vordefinierten Reihenfolge geladen werden, die in der Konfigurationsdatei für den Benutzer endet.

Wenn ZSH installiert wird, wird normalerweise keine .zshrc erstellt. So sieht eine Minimalversion davon in einer CentOS-Distribution aus (alle Kommentare wurden der Kürze halber entfernt):

$ cat /etc/skel/.zshrc
autoload -U compinit
compinit

setopt COMPLETE_IN_WORD

In der Bash gibt es ein paar zusätzliche Punkte, aber nichts Überraschendes. Zweifellos wirst du an den Punkt kommen, an dem du dich über ein Verhalten oder eine Sache, die du auf einem anderen Server gesehen hast und die du nachmachen willst, extrem ärgerst. Wir können ohne Farben im Terminal nicht leben, also muss in jeder Shell die Farbe aktiviert sein. Ehe du dich versiehst, steckst du tief in den Konfigurationen und willst einen Haufen nützlicher Aliase und Funktionen hinzufügen.

Bald darauf kommen die Texteditor-Konfigurationen hinzu, und das Ganze fühlt sich auf verschiedenen Rechnern oder wenn neue hinzukommen unhandlich an, und all diese nützlichen Aliase sind nicht eingerichtet, und es ist unglaublich, aber niemand hat irgendwo die Farbunterstützung aktiviert. Jeder hat einen Weg gefunden, dieses Problem auf eine nicht übertragbare, ad hoc Weise zu lösen: Alfredo benutzt irgendwann ein Makefile, und seine Kollegen benutzen entweder gar nichts oder ein Bash-Skript. Ein neues Projekt namens Dotdrop bietet viele Funktionen, um all diese Dotfiles in Ordnung zu bringen, z. B. Kopieren, Symlinking und getrennte Profile für Entwicklungs- und andere Rechner - sehr nützlich, wenn du von einem Rechner auf einen anderen umziehst.

Du kannst Dotdrop für ein Python-Projekt verwenden. Obwohl du es über die regulären Tools virtualenv und pip installieren kannst, ist es empfehlenswert, es als Submodul in dein Repository mit Dotfiles einzubinden. Falls du das noch nicht getan hast, ist es sehr praktisch, alle deine Dotfiles in der Versionskontrolle zu haben, damit du die Änderungen verfolgen kannst. Die Dotfiles von Alfredo sind öffentlich zugänglich und er versucht, sie so aktuell wie möglich zu halten.

Unabhängig davon, was verwendet wird, ist es eine gute Strategie, die Änderungen über die Versionskontrolle zu verfolgen und sicherzustellen, dass alles immer auf dem neuesten Stand ist.

Anpassen der Python-Shell

Du kannst die Python-Shell mit Helfern anpassen und nützliche Module in eine Python-Datei importieren, die dann als Umgebungsvariable exportiert werden muss. Ich bewahre meine Konfigurationsdateien in einem Repository namens dotfiles auf. In meiner Shell-Konfigurationsdatei( bei mir$HOME/.zshrc ) definiere ich also den folgenden Export:

export PYTHONSTARTUP=$HOME/dotfiles/pythonstartup.py

Um das auszuprobieren, erstellst du eine neue Python-Datei mit dem Namen pythonstartup.py (sie kann aber jeden beliebigen Namen haben), die so aussieht:

import types
import uuid

helpers = types.ModuleType('helpers')
helpers.uuid4 = uuid.uuid4()

Öffne nun eine neue Python-Shell und gib die neu erstellte pythonstartup.py an:

$ PYTHONSTARTUP=pythonstartup.py python
Python 3.7.3 (default, Apr  3 2019, 06:39:12)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> helpers
<module 'helpers'>
>>> helpers.uuid4()
UUID('966d7dbe-7835-4ac7-bbbf-06bf33db5302')

Das Objekt helpers ist sofort verfügbar. Da wir die Eigenschaft uuid4 hinzugefügt haben, können wir es als helpers.uuid4() aufrufen. Wie du vielleicht schon gemerkt hast, werden alle Importe und Definitionen in der Python-Shell verfügbar sein. Dies ist eine bequeme Möglichkeit, das Verhalten zu erweitern, das in der Standard-Shell nützlich ist.

Rekursives Globbing

Rekursives Globbing ist in ZSH standardmäßig aktiviert, aber in der Bash (Version 4 und höher) muss es unter shopt eingestellt werden. Rekursives Globbing ist eine coole Einstellung, die es dir ermöglicht, einen Pfad mit der folgenden Syntax zu durchlaufen:

$ ls **/*.py

Dieses Snippet würde jede Datei und jedes Verzeichnis rekursiv durchgehen und jede einzelne Datei auflisten, die auf .py endet. So aktivierst du es in Bash 4:

$ shopt -s globstar

Suchen und Ersetzen durch Eingabeaufforderungen

Vim hat eine nette Funktion in seiner Such- und Ersetzungsmaschine, die eine Eingabeaufforderung enthält, um die Ersetzung durchzuführen oder zu überspringen. Das ist besonders nützlich, wenn du nicht genau den regulären Ausdruck findest, den du brauchst, aber einige andere naheliegende Übereinstimmungen ignorieren willst. Wir kennen uns mit regulären Ausdrücken aus, aber wir haben versucht, keine Experten darin zu sein, denn es wäre sehr verlockend, sie für alles zu verwenden. Meistens willst du eine einfache Suche und Ersetzung durchführen und nicht mit dem Kopf gegen die Wand schlagen, um die perfekte Regex zu finden.

Das Flag c muss am Ende des Befehls angehängt werden, um die Eingabeaufforderung in Vim zu aktivieren:

:%s/original term/replacement term/gc

Das bedeutet: Suche nach dem ursprünglichen Begriff in der ganzen Datei und ersetze ihn durch den Ersatzbegriff, aber bei jeder Eingabeaufforderung kann man sich entscheiden, ihn zu ändern oder zu überspringen. Wenn eine Übereinstimmung gefunden wird, zeigt Vim eine Meldung wie diese an:

replace with replacement term (y/n/a/q/l/^E/^Y)?

Der gesamte Bestätigungs-Workflow mag albern erscheinen, aber er ermöglicht es dir, die Beschränkungen für den regulären Ausdruck zu lockern oder sogar gar keinen zu verwenden, um ein einfacheres Vergleichen und Ersetzen zu erreichen. Ein kurzes Beispiel dafür ist eine kürzliche API-Änderung in einem Produktionswerkzeug, bei der das Attribut eines Objekts für eine Callable geändert wurde. Der Code gab True oder False zurück, um anzugeben, ob Superuser-Berechtigungen erforderlich waren oder nicht. Die tatsächliche Ersetzung in einer einzelnen Datei würde wie folgt aussehen:

:%s/needs_root/needs_root()/gc

Die zusätzliche Schwierigkeit besteht darin, dass needs_root auch in Kommentaren und Doku-Strings vorkommt. Daher war es nicht einfach, einen regulären Ausdruck zu finden, der die Ersetzung in einem Kommentarblock oder in einem Teil eines Doku-Strings überspringt. Mit dem c Flag kannst du einfach Y oder N eingeben und weitermachen. Es wird überhaupt kein regulärer Ausdruck benötigt!

Wenn rekursives Globbing aktiviert ist (shopt -s globstar in Bash 4), durchläuft dieser leistungsstarke Einzeiler alle übereinstimmenden Dateien, führt die Suche durch und ersetzt das Element entsprechend der Eingabeaufforderung, wenn das Muster in den Dateien gefunden wird:

vim -c "bufdo! set eventignore-=Syntax | %s/needs_root/needs_root()/gce" **/*.py

Es gibt hier viel zu entpacken, aber das obige Beispiel sucht rekursiv nach allen Dateien, die auf .py enden, lädt sie in Vim und führt das Suchen und Ersetzen mit Bestätigung nur dann durch, wenn es eine Übereinstimmung gibt. Wenn es keine Übereinstimmung gibt, wird die Datei übersprungen. Das set eventignore-=Syntax wird verwendet, weil Vim sonst die Syntaxdateien nicht lädt, wenn er auf diese Weise ausgeführt wird; wir mögen die Syntaxhervorhebung und erwarten, dass sie funktioniert, wenn diese Art der Ersetzung verwendet wird. Der nächste Teil nach dem | -Zeichen ist die Ersetzung mit dem Bestätigungsflag und dem e -Flag, das dazu beiträgt, Fehler zu ignorieren, die einen reibungslosen Arbeitsablauf mit Fehlern verhindern würden.

Tipp

Es gibt zahlreiche andere Flags und Variationen, mit denen du den Ersetzungsbefehl erweitern kannst. Um mehr über die speziellen Flags beim Suchen und Ersetzen in Vim zu erfahren, wirf einen Blick auf :help substitute, insbesondere auf den Abschnitt s_flags.

Mach den komplizierten Einzeiler leichter zu merken mit einer Funktion, die zwei Parameter (Such- und Ersetzungsbegriffe) und den Pfad benötigt:

vsed() {
  search=$1
  replace=$2
  shift
  shift
  vim -c "bufdo! set eventignore-=Syntax| %s/$search/$replace/gce" $*
}

Nenne es vsed, als eine Mischung aus Vim und dem Tool sed, damit du es dir leichter merken kannst. Im Terminal sieht es übersichtlich aus und ermöglicht es dir, Änderungen an mehreren Dateien einfach und sicher vorzunehmen, da du jede Ersetzung akzeptieren oder ablehnen kannst:

$ vsed needs_root needs_root() **/*.py

Entfernen von temporären Python-Dateien

Pythons pyc, und neuerdings auch seine pycache Verzeichnisse, können manchmal im Weg sein. Dieser einfache Einzeiler mit dem Alias pyclean verwendet den Befehl find, um pyc zu entfernen, und sucht dann nach pycache Verzeichnisse und löscht sie rekursiv mit dem eingebauten Delete-Flag des Tools:

alias pyclean='find . \
    \( -type f -name "*.py[co]" -o -type d -name "__pycache__" \) -delete &&
    echo "Removed pycs and __pycache__"'

Auflistung und Filterung von Prozessen

Das Auflisten von Prozessen, um zu sehen, was auf einem Rechner läuft, und das anschließende Filtern, um eine bestimmte Anwendung zu überprüfen, gehört zu den Dingen, die du mindestens mehrmals am Tag machen wirst. Es ist nicht verwunderlich, dass jeder eine Variation der Flags oder der Reihenfolge der Flags für das Tool ps hat (wir verwenden normalerweise aux). Das ist etwas, das du so oft am Tag machst, dass sich die Reihenfolge und die Flaggen in deinem Gehirn festsetzen und es schwer ist, es anders zu machen.

Ein guter Ausgangspunkt für die Auflistung der Prozesse und einiger Informationen, wie z. B. der Prozess-IDs, ist Folgendes:

$ ps auxw

Dieser Befehl listet alle Prozesse mit den BSD-üblichen Flags auf (Flags, denen kein Bindestrich vorangestellt ist -), unabhängig davon, ob sie ein Terminal (tty) haben oder nicht, und gibt den Benutzer an, dem der Prozess gehört. Außerdem wird der Benutzer, dem der Prozess gehört, mit angegeben. Außerdem wird mehr Platz für die Ausgabe zur Verfügung gestellt (w ).

In den meisten Fällen filterst du mit grep, um Informationen über einen bestimmten Prozess zu erhalten. Wenn du zum Beispiel überprüfen willst, ob Nginx läuft, leitest du die Ausgabe in grep und gibst nginx als Argument an:

$ ps auxw | grep nginx
root     29640  1536 ?        Ss   10:11   0:00 nginx: master process
www-data 29648  5440 ?        S    10:11   0:00 nginx: worker process
alfredo  30024   924 pts/14   S+   10:12   0:00 grep nginx

Das ist toll, aber es ist ärgerlich, dass der Befehl grep enthalten ist. Das ist besonders ärgerlich, wenn es außer dem grep keine Ergebnisse gibt:

$ ps auxw | grep apache
alfredo  31351  0.0  0.0   8856   912 pts/13   S+   10:15   0:00 grep apache

Es wird kein apache Prozess gefunden, aber die visuelle Darstellung könnte dich zu der Annahme verleiten, dass dies der Fall ist, und die doppelte Überprüfung, ob es sich tatsächlich nur um grep handelt, das wegen des Arguments eingeschlossen wurde, kann ziemlich schnell ermüdend werden. Eine Möglichkeit, dieses Problem zu lösen, besteht darin, eine weitere Pipe zu grep hinzuzufügen, um sich selbst aus der Ausgabe zu filtern:

$ ps auxw | grep apache | grep -v grep

Wenn du dich immer daran erinnern musst, das zusätzliche grep hinzuzufügen, kann das genauso lästig sein, also hilft ein Alias:

alias pg='ps aux | grep -v grep | grep $1'

Der neue Alias filtert die erste Zeile von grep heraus und lässt nur die interessante Ausgabe übrig (falls vorhanden):

$ pg vim
alfredo  31585  77836 20624 pts/3    S+   18:39   0:00 vim /home/alfredo/.zshrc

Unix Zeitstempel

Es ist sehr einfach, den weit verbreiteten Unix-Zeitstempel in Python zu erhalten:

In [1]: import time

In [2]: int(time.time())
Out[2]: 1566168361

Aber in der Shell kann es ein bisschen komplizierter sein. Dieser Alias funktioniert unter OS X, wo es die BSD-Version des Tools date gibt:

alias timestamp='date -j -f "%a %b %d %T %Z %Y" "`date`" "+%s"'

OS X kann mit seinen Werkzeugen sehr umständlich sein, und es kann verwirrend sein, sich nie daran zu erinnern, warum sich ein bestimmtes Dienstprogramm (wie in diesem Fall date ) völlig anders verhält. In der Linux-Version von date funktioniert ein viel einfacherer Ansatz auf die gleiche Weise:

alias timestamp='date "+%s"'

Python mit Bash und ZSH mischen

Es ist uns nie in den Sinn gekommen, Python mit einer Shell wie ZSH oder Bash zu kombinieren. Das widerspricht zwar dem gesunden Menschenverstand, aber es gibt ein paar gute Beispiele, die du fast täglich nutzen kannst. Generell gilt die Faustregel, dass 10 Zeilen Shell-Skript die Obergrenze sind. Alles, was darüber hinausgeht, ist ein Fehler, der nur darauf wartet, dass du Zeit verschwendest, weil die Fehlermeldungen dir nicht weiterhelfen.

Zufallsgenerator für Passwörter

Die Anzahl der Konten und Passwörter, die du von Woche zu Woche brauchst, wird immer größer, selbst für Wegwerfkonten, für die du Python verwenden kannst, um robuste Passwörter zu generieren. Erstelle einen nützlichen, zufälligen Passwortgenerator, der den Inhalt in die Zwischenablage sendet, damit du ihn einfach einfügen kannst:

In [1]: import os

In [2]: import base64

In [3]: print(base64.b64encode(os.urandom(64)).decode('utf-8'))
gHHlGXnqnbsALbAZrGaw+LmvipTeFi3tA/9uBltNf9g2S9qTQ8hTpBYrXStp+i/o5TseeVo6wcX2A==

Die Übertragung auf eine Shell-Funktion, die eine beliebige Länge annehmen kann (nützlich, wenn eine Website die Länge auf eine bestimmte Zahl beschränkt), sieht so aus:

mpass() {
    if [ $1 ]; then
        length=$1
    else
        length=12
    fi
    _hash=`python3 -c "
import os,base64
exec('print(base64.b64encode(os.urandom(64))[:${length}].decode(\'utf-8\'))')
    "`
    echo $_hash | xclip -selection clipboard
    echo "new password copied to the system clipboard"
}

Jetzt generiert die Funktion mpass standardmäßig 12-stellige Passwörter, indem sie die Ausgabe zerschneidet und den Inhalt der generierten Zeichenfolge an xclip sendet, damit sie in die Zwischenablage kopiert und einfach eingefügt werden kann.

Hinweis

xclip ist in vielen Distributionen nicht standardmäßig installiert, du musst also sicherstellen, dass es installiert ist, damit die Funktion richtig funktioniert. Wenn xclip nicht verfügbar ist, funktioniert auch jedes andere Dienstprogramm, das die Zwischenablage des Systems verwalten kann.

Existiert mein Modul?

Finde heraus, ob ein Modul existiert, und wenn ja, erhalte den Pfad zu diesem Modul. Dies ist nützlich, wenn du es für andere Funktionen wiederverwendest, die diese Ausgabe zur Verarbeitung verwenden können:

try() {
    python -c "
exec('''
try:
    import ${1} as _
    print(_.__file__)
except Exception as e:
    print(e)
''')"
}

Verzeichnisse in den Pfad eines Moduls ändern

"Wo befindet sich dieses Modul?" wird oft gefragt, wenn man Bibliotheken und Abhängigkeiten debuggt oder sogar im Quellcode von Modulen herumstöbert. Die Art und Weise, wie Python Module installiert und verteilt, ist nicht ganz einfach, und in den verschiedenen Linux-Distributionen sind die Pfade völlig unterschiedlich und haben eigene Konventionen. Du kannst den Pfad eines Moduls herausfinden, wenn du es importierst und dann print verwendest:

In [1]: import os

In [2]: print(os)
<module 'os' from '.virtualenvs/python-devops/lib/python3.6/os.py'>

Das ist unpraktisch, wenn du nur den Pfad brauchst, damit du in ein anderes Verzeichnis wechseln und dir das Modul ansehen kannst. Diese Funktion versucht, das Modul als Argument zu importieren, es auszudrucken (das ist eine Shell, also tut return nichts für uns) und dann in das Verzeichnis zu wechseln:

cdp() {
    MODULE_DIRECTORY=`python -c "
exec('''
try:
    import os.path as _, ${module}
    print(_.dirname(_.realpath(${module}.__file__)))
except Exception as e:
    print(e)
''')"`
    if  [[ -d $MODULE_DIRECTORY ]]; then
        cd $MODULE_DIRECTORY
    else
        echo "Module ${1} not found or is not importable: $MODULE_DIRECTORY"
    fi
}

Für den Fall, dass der Paketname einen Bindestrich enthält und das Modul einen Unterstrich verwendet, fügen wir noch etwas hinzu:

    module=$(sed 's/-/_/g' <<< $1)

Wenn die Eingabe einen Bindestrich enthält, kann die kleine Funktion dies im Handumdrehen lösen und uns an das gewünschte Ziel bringen:

$ cdp pkg-resources
$ pwd
/usr/lib/python2.7/dist-packages/pkg_resources

Konvertierung einer CSV-Datei in JSON

Python verfügt über einige integrierte Funktionen, die dich überraschen werden, wenn du dich noch nie mit ihnen beschäftigt hast. Python kann sowohl JSON als auch CSV-Dateien nativ verarbeiten. Es sind nur ein paar Zeilen nötig, um eine CSV-Datei zu laden und ihren Inhalt als JSON auszugeben. Verwende die folgende CSV-Datei(addresses.csv), um den Inhalt zu sehen, wenn JSON in der Python-Shell gedumpt wird:

John,Doe,120 Main St.,Riverside, NJ, 08075
Jack,Jhonson,220 St. Vernardeen Av.,Phila, PA,09119
John,Howards,120 Monroe St.,Riverside, NJ,08075
Alfred, Reynolds, 271 Terrell Trace Dr., Marietta, GA, 30068
Jim, Harrison, 100 Sandy Plains Plc., Houston, TX, 77005
>>> import csv
>>> import json
>>> contents = open("addresses.csv").readlines()
>>> json.dumps(list(csv.reader(contents)))
'[["John", "Doe", "120 Main St.", "Riverside", " NJ", " 08075"],
["Jack", "Jhonson", "220 St. Vernardeen Av.", "Phila", " PA", "09119"],
["John", "Howards", "120 Monroe St.", "Riverside", " NJ", "08075"],
["Alfred", " Reynolds", " 271 Terrell Trace Dr.", " Marietta", " GA", " 30068"],
["Jim", " Harrison", " 100 Sandy Plains Plc.", " Houston", " TX", " 77005"]]'

Portiere die interaktive Sitzung auf eine Funktion, die dies auf der Kommandozeile erledigen kann:

csv2json () {
	python3 -c "
exec('''
import csv,json
print(json.dumps(list(csv.reader(open(\'${1}\')))))
''')
"
}

Das ist viel einfacher, als sich an alle Aufrufe und Module zu erinnern:

$ csv2json addresses.csv
[["John", "Doe", "120 Main St.", "Riverside", " NJ", " 08075"],
["Jack", "Jhonson", "220 St. Vernardeen Av.", "Phila", " PA", "09119"],
["John", "Howards", "120 Monroe St.", "Riverside", " NJ", "08075"],
["Alfred", " Reynolds", " 271 Terrell Trace Dr.", " Marietta", " GA", " 30068"],
["Jim", " Harrison", " 100 Sandy Plains Plc.", " Houston", " TX", " 77005"]]

Python Einzeiler

Im Allgemeinen gilt es nicht als gute Praxis, eine lange, einzelne Python-Zeile zu schreiben. Der PEP 8-Leitfaden rät sogar davon ab, Anweisungen mit einem Semikolon zu verbinden (es ist möglich, Semikolons in Python zu verwenden!). Aber schnelle Debug-Anweisungen und Aufrufe eines Debuggers sind in Ordnung. Sie sind schließlich nur vorübergehend.

Debugger

Einige Programmierer da draußen schwören auf die Anweisung print() als beste Strategie zum Debuggen von laufendem Code. In manchen Fällen mag das gut funktionieren, aber meistens verwenden wir den Python-Debugger (mit dem Modul pdb ) oder ipdb, der IPython als Backend verwendet. Wenn du einen Haltepunkt einrichtest, kannst du in den Variablen herumstochern und den Stack rauf und runter gehen. Diese einzeiligen Anweisungen sind so wichtig, dass du sie auswendig lernen solltest:

Setze einen Haltepunkt und wechsle in den Python-Debugger (pdb):

import pdb;pdb.set_trace()

Setze einen Haltepunkt und wechsle zu einem Python-Debugger, der auf IPython (ipdb) basiert:

import ipdb;ipdb.set_trace()

Obwohl es sich technisch gesehen nicht um einen Debugger handelt (du kannst dich im Stack nicht vorwärts oder rückwärts bewegen), kannst du mit diesem Einzeiler eine IPython-Sitzung starten, wenn die Ausführung sie erreicht:

import IPython; IPython.embed()
Hinweis

Jeder scheint ein Lieblings-Debugger-Tool zu haben. Wir finden, dass pdb zu grob ist (keine Autovervollständigung, keine Syntaxhervorhebung), deshalb gefällt uns ipdb besser. Sei nicht überrascht, wenn jemand mit einem anderen Debugger daherkommt! Letztendlich ist es nützlich zu wissen, wie pdb funktioniert, denn das ist die Grundlage, die man braucht, um unabhängig vom Debugger gut zurechtzukommen. In Systemen, die du nicht kontrollieren kannst, solltest du pdb direkt verwenden, da du keine Abhängigkeiten installieren kannst; das mag dir vielleicht nicht gefallen, aber du kannst dich trotzdem zurechtfinden.

Wie schnell ist dieses Snippet?

Python verfügt über ein Modul, mit dem du einen Code mehrmals ausführen und Leistungskennzahlen erhalten kannst. Viele Nutzerinnen und Nutzer wollen wissen, ob es effiziente Wege gibt, eine Schleife zu bearbeiten oder ein Wörterbuch zu aktualisieren, und es gibt viele sachkundige Leute, die das Modul timeit lieben, um die Leistung zu beweisen.

Wie du wahrscheinlich schon gesehen hast, sind wir Fans von IPython, und seine interaktive Shell verfügt über eine "magische" Spezialfunktion für das Modul timeit. "Magische" Funktionen haben das Zeichen % vorangestellt und führen eine bestimmte Operation innerhalb der Shell aus. Ein beliebtes Thema in Bezug auf die Leistung ist die Frage, ob das Verstehen von Listen schneller ist als das einfache Anhängen an eine Liste. Die beiden folgenden Beispiele verwenden das Modul timeit, um das herauszufinden:

In [1]: def f(x):
   ...:     return x*x
   ...:

In [2]: %timeit for x in range(100): f(x)
100000 loops, best of 3: 20.3 us per loop

In der Standard-Python-Shell (oder dem Interpreter) importierst du das Modul und rufst es direkt auf. Der Aufruf sieht in diesem Fall ein bisschen anders aus:

>>> array = []
>>> def appending():
...     for i in range(100):
...         array.append(i)
...
>>> timeit.repeat("appending()", "from __main__ import appending")
[5.298534262983594, 5.32031941099558, 5.359099322988186]
>>> timeit.repeat("[i for i in range(100)]")
[2.2052824340062216, 2.1648171059787273, 2.1733458579983562]

Die Ausgabe ist etwas merkwürdig, aber das liegt daran, dass sie von einem anderen Modul oder einer Bibliothek verarbeitet werden soll und nicht für die menschliche Lesbarkeit gedacht ist. Die Durchschnittswerte bevorzugen das Listenverständnis. So sieht es in IPython aus:

In [1]: def appending():
   ...:     array = []
   ...:     for i in range(100):
   ...:         array.append(i)
   ...:

In [2]: %timeit appending()
5.39 µs ± 95.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [3]: %timeit [i for i in range(100)]
2.1 µs ± 15.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Da IPython timeit als speziellen Befehl ausgibt (beachte das Präfix mit %), ist die Ausgabe für Menschen lesbar und hilfreicher und erfordert nicht den seltsamen Import wie in der Standard-Python-Shell.

strace

Die Fähigkeit, herauszufinden, wie ein Programm mit dem Betriebssystem interagiert, ist von entscheidender Bedeutung, wenn Anwendungen die interessanten Teile nicht protokollieren oder überhaupt nicht protokollieren. Die Ausgabe von strace kann grob sein, aber wenn du die Grundlagen verstehst, wird es einfacher zu verstehen, was in einer problematischen Anwendung vor sich geht. Einmal versuchte Alfredo zu verstehen, warum der Zugriff auf eine Datei verweigert wurde. Diese Datei befand sich in einem Symlink, der alle Rechte zu haben schien. Was war da los? Es war schwierig, das herauszufinden, wenn man sich nur die Logs ansah, da diese nicht besonders hilfreich waren, wenn es darum ging, die Berechtigungen für den Zugriff auf Dateien anzuzeigen.

strace diese beiden Zeilen in die Ausgabe aufgenommen:

stat("/var/lib/ceph/osd/block.db", 0x7fd) = -1 EACCES (Permission denied)
lstat("/var/lib/ceph/osd/block.db", {st_mode=S_IFLNK|0777, st_size=22}) = 0

Das Programm legte die Eigentumsrechte für das übergeordnete Verzeichnis fest, das zufällig ein Link war, und für block.db, das in diesem Fall ebenfalls ein Link zu einem Blockgerät war. Das Blockgerät selbst hatte die richtigen Berechtigungen, wo lag also das Problem? Es stellte sich heraus, dass der Link im Verzeichnis ein Sticky Bit hatte, das andere Links daran hinderte, den Pfad zu ändern - einschließlich des Blockgeräts. Das Tool chown hat ein spezielles Flag (-h oder --no-dereference), um anzuzeigen, dass die Änderung der Besitzverhältnisse auch die Links betreffen sollte.

Diese Art der Fehlersuche wäre ohne etwas wie strace schwierig (wenn nicht sogar unmöglich). Um es auszuprobieren, erstelle eine Datei namens follow.py mit folgendem Inhalt:

import subprocess

subprocess.call(['ls', '-alh'])

Es importiert das Modul subprocess, um einen Systemaufruf durchzuführen. Er gibt den Inhalt des Systemaufrufs an ls aus. Anstelle eines direkten Aufrufs mit Python kannst du dem Befehl strace voranstellen und sehen, was passiert:

$ strace python follow.py

Das Terminal sollte mit einer Menge Ausgaben gefüllt sein, und wahrscheinlich wird das meiste davon sehr fremd aussehen. Zwinge dich, jede Zeile durchzugehen, unabhängig davon, ob du verstehst, was vor sich geht. Einige Zeilen werden leichter zu unterscheiden sein als andere. Es gibt viele read und fstat Aufrufe; du siehst die tatsächlichen Systemaufrufe und was der Prozess bei jedem Schritt macht. Es gibt auch open und close Operationen für einige Dateien und es gibt einen bestimmten Abschnitt, in dem einige stat Aufrufe zu sehen sind:

stat("/home/alfredo/go/bin/python", 0x7ff) = -1 ENOENT (No such file)
stat("/usr/local/go/bin/python", 0x7ff) = -1 ENOENT (No such file)
stat("/usr/local/bin/python", 0x7ff) = -1 ENOENT (No such file)
stat("/home/alfredo/bin/python", 0x7ff) = -1 ENOENT (No such file)
stat("/usr/local/sbin/python", 0x7ff) = -1 ENOENT (No such file)
stat("/usr/local/bin/python", 0x7ff) = -1 ENOENT (No such file)
stat("/usr/sbin/python", 0x7ff) = -1 ENOENT (No such file)
stat("/usr/bin/python", {st_mode=S_IFREG|0755, st_size=3691008, ...}) = 0
readlink("/usr/bin/python", "python2", 4096) = 7
readlink("/usr/bin/python2", "python2.7", 4096) = 9
readlink("/usr/bin/python2.7", 0x7ff, 4096) = -1 EINVAL (Invalid argument)
stat("/usr/bin/Modules/Setup", 0x7ff) = -1 ENOENT (No such file)
stat("/usr/bin/lib/python2.7/os.py", 0x7ffd) = -1 ENOENT (No such file)
stat("/usr/bin/lib/python2.7/os.pyc", 0x7ff) = -1 ENOENT (No such file)
stat("/usr/lib/python2.7/os.py", {st_mode=S_IFREG|0644, ...}) = 0
stat("/usr/bin/pybuilddir.txt", 0x7ff) = -1 ENOENT (No such file)
stat("/usr/bin/lib/python2.7/lib-dynload", 0x7ff) = -1 ENOENT (No such file)
stat("/usr/lib/python2.7/lib-dynload", {st_mode=S_IFDIR|0755, ...}) = 0

Dieses System ist ziemlich alt, und python in der Ausgabe bedeutet python2.7, also stöbert es im Dateisystem herum, um die richtige ausführbare Datei zu finden. Er geht einige durch, bis er /usr/bin/python erreicht, das ein Link ist, der auf /usr/bin/python2 verweist, was wiederum ein weiterer Link ist, der den Prozess zu /usr/bin/python2.7 schickt. Dann ruft er stat auf /usr/bin/Modules/Setup auf, von dem wir als Python-Entwickler noch nie etwas gehört haben, um dann zum Modul os weiterzugehen.

Es geht weiter zu pybuilddir.txt und lib-dynload. Was für ein Trip. Ohne strace hätten wir wahrscheinlich versucht, den Code zu lesen, um herauszufinden, wohin er als Nächstes führt. Aber strace macht das alles viel einfacher, indem es alle interessanten Schritte auf dem Weg mit nützlichen Informationen für jeden Aufruf aufführt.

Das Tool hat viele Flags, die einen Blick wert sind; zum Beispiel kann es sich an eine PID anhängen. Wenn du die PID eines Prozesses kennst, kannst du strace anweisen, eine Ausgabe darüber zu erstellen, was genau mit dem Prozess passiert.

Eines dieser nützlichen Flags ist -f; es verfolgt die Kindprozesse, die vom Startprogramm erstellt werden. In der Python-Beispieldatei wird ein Aufruf an subprocess gemacht, der ls aufruft. Wenn der Befehl an strace geändert wird, um -f zu verwenden, wird die Ausgabe umfangreicher und enthält Details über diesen Aufruf.

Wenn follow.py im Home-Verzeichnis ausgeführt wird, gibt es einige Unterschiede mit dem -f Flag. Du kannst Aufrufe an lstat und readlink für die Dotfiles sehen (von denen einige symlinked sind):

[pid 30127] lstat(".vimrc", {st_mode=S_IFLNK|0777, st_size=29, ...}) = 0
[pid 30127] lgetxattr(".vimrc", "security.selinux", 0x55c5a36f4720, 255)
[pid 30127] readlink(".vimrc", "/home/alfredo/dotfiles/.vimrc", 30) = 29
[pid 30127] lstat(".config", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0

Es werden nicht nur die Aufrufe dieser Dateien angezeigt, sondern auch die PID wird in der Ausgabe vorangestellt, was dabei hilft, zu erkennen, welcher (untergeordnete) Prozess was tut. Ein Aufruf von strace ohne das Flag -f würde zum Beispiel keine PID anzeigen.

Um die Ausgabe im Detail zu analysieren, kann es hilfreich sein, sie in einer Datei zu speichern. Das ist mit dem Flag -o möglich:

$ strace -o output.txt python follow.py

Übungen

  • Definiere, was IOPS ist.

  • Erkläre, was der Unterschied zwischen Durchsatz und IOPS ist.

  • Nenne eine Einschränkung bei fdisk für die Erstellung von Partitionen, die parted nicht hat.

  • Nenne drei Tools, die Festplatteninformationen liefern können.

  • Was kann ein SSH-Tunnel leisten? Wann ist er nützlich?

Fallstudie Frage

  • Erstelle mit dem Tool molotov einen Lasttest, der eine JSON Antwort von einem Server mit dem HTTP-Status 200 testet.

Get Python für DevOps now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.