No advertising, no support, no bug fixes, payment in advance.
— AT&T Unix Policy (1984)

The Phoenix Project

Im Urlaub laß ich ein wunderbares Buch. The Phoenix Project.

Das eBook handelt von Bill Parmer der von heute auf morgen zum Leiter der IT des ~4 Milliarden Dollar Unternehmens befördert wird, in dem er arbeitet.

Wer ITIL, ISO20000 oder ISO27001 kennt, kennt einige Konzepte die hinter Opetations und Development stecken. Das Buch verpackt all die schönen Best Practices und Vorgehensweisen zur Beherschung moderner IT Abteilungen in einen Roman. Eine nerdige, lustige und teils schockierende Story, in der Bill die völlig marode IT im Trial and Error Verfahren trotz immer neuer Rückschläge wieder auf Kurs bringt.

Ich denke es ist großartige Alternative den ganzen trockenen Content obiger Konzepte zu ersparen und trotzdem vermittelt zu bekommen um was es im Dev/Ops Bereich geht.

Jeder der was mit IT zu tun hat: Lesen. Oder gebt es euren Chefs, damit die es lesen.

Mehr BSD, Bye Uberspace

Ab und an passiert es, dass Dinge die einfach funktionieren anfangen mich zu langweilen. So geschehen mit Uberspace. Das CentOS, der Apache2.2, die Sache mit den Domains.

Im Grunde habe ich die 3 Dienste, die ich bei Uberspace nutze (Webserver für Blog, Mail, Domains) nun zu anderen Dienstleistern migriert.

Blog Webserver

Der erste “Dienstleister” bin ich gewissermaßen selbst. Die 1HE Dell Maschine die ich seit einiger Zeit bei meinem alten Arbeitgeber mit OpenBSD betreibe, hostet mit nginx den Blog. Selbige Kiste delivered auch devnull-as-a-service.com und coffeestat.org. Warum also eine extra Lösung.

Mails

Bei Uberspace hatte ich die Mglichkeit die Mails direkt von Qmail über Maildrop umzusortieren und dort meine Tools zu platzieren.

Mein neuer Mailprovider neomailbox.net hat seinen Firmensitz auf den Seychellen, Server in der Schweiz und setzt ebenfalls auf OpenBSD als OS. Nicht weil ich ernsthaft glaube, das ich das bräuchte, sondern weils mir gefällt.

Mit dem Wechsel ändert sich aber die Architektur meines Setups. Statt direkt am IMAP Server arbeiten zu können bin ich nun nur noch Konsument des IMAP Services. Blöd für maildrop-Regeln und Spamfiltering mit bogofilter.

Gerade beim Abschied von Bogofilter tat ich mich schwer, weils so gut und einfach ist. Aber dann entdeckte ich imapfilter.

imapfilter ist primär dazu gedacht Mails zwischen einem oder mehrere IMAP Accounts zu verschieben. Meine maildrop Regeln sind sehr leicht umgesetzt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
neomailbox = IMAP {
    server = 'neomailbox.net',
    username = 'user',
    password = 'pw',
    ssl = 'ssl3',
}

-- get set of mails from the last day
res = neomailbox.INBOX:is_newer(1)

-- move techmails to Tech/
tech = res:contain_to('tech@openbsd.org') +
       res:contain_cc('tech@openbsd.org') +
       res:contain_from('Cron Daemon <root@') +
       res:contain_from('Charlie Root <root@o0.n0q.org>') +
       res:contain_from('root@z0idberg.') +
       res:contain_from('noc.n0q.org')
tech:move_messages(neomailbox.Tech)

-- spam by vipul razor to Spam/ (offered by neomailbox.net)
-- header: X-SA-Status: Yes
spamvipul = res:contain_field('X-SA-Status', 'Yes')
spamvipul:move_messages(neomailbox.Spam)

Mit den Ergebnissen von vipul razor Antispam war ich aber nicht so 100%ig zufrieden, weshalb ich anfing zu googeln und fand was ich suchte.

Imapfilter piped jede Mail des letzten Tages zu dem lokal laufenden bogofilter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- bogofilter spam mails to Spam
res = neomailbox.INBOX:is_newer(1)
spam = Set {}
unsure = Set {}
  for _, mesg in ipairs(res) do
        mbox, uid = unpack(mesg)
        text = mbox[uid]:fetch_message()
        -- subject = mbox[uid]:fetch_field('subject')
        -- print(subject)
        flag = pipe_to('/usr/bin/bogofilter', text)
        if (flag == 0) then
          table.insert(spam, mesg)
        elseif (flag == 2) then
          table.insert(unsure, mesg)
        end
  end
neomailbox['INBOX']:move_messages(neomailbox['Spam'], spam)

H00ray!

Domains

Relativ unspektakulär, habe ich meine Domains zu inwx.de umgezogen. Preise und Webinterface sind okay. Und falls ich mal Lust habe eine Art DynDNS selbst zu bauen haben die auch gleich eine API.

Alles in allem kann ich mit dem neuen Setup gut Leben. Mehr OpenBSD, weniger Linux.

MongoDB 2.6 Sharded Cluster Performance

MongoDB Cluster wollen nach der Installation wie jede andere DB getestet werden. Performance, Konsistenz bei vielen Writes, usw. Gerade bei Sharding und Indexing über mehrere Knoten verteilt möchte man das schon ausprobieren. Sind die Documents wirklich gleichmässig verteilt?

Alle Zeiten der Auswertung und Interpretation der Ergebnisse spar ich mir jetzt. Der Fokus liegt ersteinmal auf dem “wie messen”.

Write

Der einfachste Weg war das Python Modul pymongo zu benutzen, welches über pip nachinstalliert werden kann.

1
2
3
4
5
6
7
8
import pymongo
m = pymongo.MongoClient('mongodb://user:password@localhost:27017/Database')
i = 0
doc = {'a': 1, 'b': 'foo'}

while (i < 5000000):
        m.Database.testcollection.insert(doc, manipulate=False, w=1)
        i = i + 1

Aufruf im Idealfall mit time python write.py, um auch wirklich die Zeit zu messen. Die 5 Mio erstellten Documents in der Collection testcollection, lassen sich nachher auch für Read-Tests weiterverwenden.

Read

Wie lange es dauert, alle 5 Mio Objekte aus der MongoDB auszulesen ist wahrscheinlich klar. Lange.

1
2
3
4
5
import pymongo
m = pymongo.MongoClient('mongodb://user:password@localhost:27017/Database')
r = m.Database.testcollection.find()
for doc in r:
        print doc["_id"]

Um das komplette Datenset auszugeben: time python readall.py > allids.txt

Read Random Documents

Alle Objekte sequenziell in einem Query ausgeben ist aber ein ziemlich exotischer Use-Case. Näher an der Realität sind kleine Queries die zufällige Dokumente abrufen (gerade wegen des Shardings). Da sowieso schon eine Liste aller ObjectIds existiert allids.txt hab ich dazu einfach ein Python Skript umgebaut dass ich schon hatte.

randompopulation.py wird eine Datei mit Input und die Anzahl der gewünschten Samples übergeben. Mithilfe von linecache ist das auch noch sehr effizient. Die nachfolgende modifizierte Version setzt auch gleich den MongoDB Query ab:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import random
import sys
import linecache
import pymongo
from bson.objectid import ObjectId

# configuration
population=sys.argv[1]
samplesize=int(sys.argv[2])

m = pymongo.MongoClient('mongodb://user:password@localhost:27017/Database')

# count lines of population file
def file_len(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1

# set length to value
length=file_len(population)

# get random number with max size
x=0
while (x < samplesize):
        y=(int(random.random() * length))
        r=linecache.getline(population, y).rstrip('\n')
        print list(m.Database.testcollection.find( { "_id": ObjectId(r) } ))
        x = x + 1

Und wirft 9000 zufällige Documents aus den angelegten Datensätzen aus.

1
2
3
4
5
6
7
8
9
10
11
$ time python choose-random-documents.py allids.txt 9000
[{u'a': 1, u'_id': ObjectId('5399a0620ab2ccca7276853b'), u'b': u'foo'}]
[{u'a': 1, u'_id': ObjectId('5399ab530ab2ccca728a2453'), u'b': u'foo'}]
[{u'a': 1, u'_id': ObjectId('5399b0160ab2ccca72aaaf91'), u'b': u'foo'}]
[{u'a': 1, u'_id': ObjectId('5399b60f0ab2ccca72cefcde'), u'b': u'foo'}]
[{u'a': 1, u'_id': ObjectId('5399a0780ab2ccca7277341d'), u'b': u'foo'}]
[{u'a': 1, u'_id': ObjectId('5399b56c0ab2ccca72cabd93'), u'b': u'foo'}]

real    0m6.355s
user    0m3.384s
sys     0m0.512s

Distributed Read / Write

Ein Host mit Queries ist natürlich auch witzlos. Schreiben und Lesen von mehreren Hosts! Für derartige Tasks packe ich gerne mal pssh aus.

$ pssh -h hostlist.txt -t 360 -l user -i 'python choose-random-documents.py allids.txt 25'

und selbes natürlich auch für die Write Tests

$ pssh -h hostlist.txt -t 360 -l user -i 'python write.py'

Nachdem ich in write.py noch ein paar Zeitstempel eingebaut habe, kann ich leicht die Schreibzeiten von den Clients visualisieren.

Die regelmäßigen Ausreißer beunruhigen etwas. Im Histogram visualisiert sieht das aber alles viel unproblematischer aus als im Dotchart. Es sind ja immerhin <50 Ausreißer bei 8 Mio Writes. Vertretbar.

Dataset Distribution

Nachdem alles geschrieben und gelesen ist, kann man sich auch mal anschauen wies in MongoDB aussieht. Status der Chunks anzeigen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mongos> sh.status()
{  "_id" : "Database",  "partitioned" : true,  "primary" : "rs0" }
 Database.testcollection
  shard key: { "_id" : "hashed" }
  chunks:
          rs0     8
          rs1     9
  { "_id" : { "$minKey" : 1 } } -->> { "_id" : NumberLong("-8278359716552185568") } on : rs0 Timestamp(2, 26)
  { "_id" : NumberLong("-8278359716552185568") } -->> { "_id" : NumberLong("-7260263158060599530") } on : rs0 Timestamp(2, 27)
  { "_id" : NumberLong("-7260263158060599530") } -->> { "_id" : NumberLong("-6016783570264293634") } on : rs0 Timestamp(2, 16)
  { "_id" : NumberLong("-6016783570264293634") } -->> { "_id" : NumberLong("-4611686018427387902") } on : rs0 Timestamp(2, 17)
  { "_id" : NumberLong("-4611686018427387902") } -->> { "_id" : NumberLong("-3654885303726982419") } on : rs0 Timestamp(2, 24)
  { "_id" : NumberLong("-3654885303726982419") } -->> { "_id" : NumberLong("-2474593789826765065") } on : rs0 Timestamp(2, 25)
  { "_id" : NumberLong("-2474593789826765065") } -->> { "_id" : NumberLong("-1237168844051948825") } on : rs0 Timestamp(2, 18)
  { "_id" : NumberLong("-1237168844051948825") } -->> { "_id" : NumberLong(0) } on : rs0 Timestamp(2, 19)
  { "_id" : NumberLong(0) } -->> { "_id" : NumberLong("960408942766083593") } on : rs1 Timestamp(2, 22)
  { "_id" : NumberLong("960408942766083593") } -->> { "_id" : NumberLong("2141950729934882470") } on : rs1 Timestamp(2, 23)
  { "_id" : NumberLong("2141950729934882470") } -->> { "_id" : NumberLong("3159510070215249954") } on : rs1 Timestamp(2, 20)
  { "_id" : NumberLong("3159510070215249954") } -->> { "_id" : NumberLong("3849612569857039248") } on : rs1 Timestamp(2, 30)
  { "_id" : NumberLong("3849612569857039248") } -->> { "_id" : NumberLong("4611686018427387902") } on : rs1 Timestamp(2, 31)
  { "_id" : NumberLong("4611686018427387902") } -->> { "_id" : NumberLong("5474895056408077106") } on : rs1 Timestamp(2, 28)
  { "_id" : NumberLong("5474895056408077106") } -->> { "_id" : NumberLong("6550645461419446020") } on : rs1 Timestamp(2, 29)
  { "_id" : NumberLong("6550645461419446020") } -->> { "_id" : NumberLong("7856429257149966918") } on : rs1 Timestamp(2, 14)
  { "_id" : NumberLong("7856429257149966918") } -->> { "_id" : { "$maxKey" : 1 } } on : rs1 Timestamp(2, 15)

Und auch wie es um die Verteilung der einzelnen Objekte steht (etwas gekürzt):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mongos> db.stats()
{
   "raw" : {
           "rs0/mongo01:27018,mongo02:27018" : {
                   "db" : "Database",
                   "collections" : 3,
                   "objects" : 3458713,
                   "avgObjSize" : 48.00009251996335,
                   "dataSize" : 166018544,
                   "storageSize" : 243331072,
           },
           "rs1/mongo03:27018,mongo04:27018" : {
                   "db" : "Database",
                   "collections" : 3,
                   "objects" : 3458108,
                   "avgObjSize" : 48.00009253614982,
                   "dataSize" : 165989504,
                   "storageSize" : 243331072,
   },
   "objects" : 6916821,
   "avgObjSize" : 48,
   "dataSize" : 332008048,
   "storageSize" : 486662144,
}

Sharded Cluster Visualisierung CC-NC-BY-SA MongoDB: http://docs.mongodb.org/manual/core/sharded-cluster-query-router/

mlmmj und OpenSMTPD unter Debian

Für die Mailingliste der k4cg zieht demnächst um. Weswegen ich mich mit einem dementsprechenden Setup auseinander setzen wollte.

mlmmj

Bisher läuft die ML mit mlmmj. Ich kannte das gute Stück vorher garnicht, macht aber einen sehr netten Eindruck. Einfach gestrickt, wenig Overhead, Plaintext Files ohne viel TamTam. Bei der Konfiguration kann man sich ohne Bedenken von mlmmj-make-ml leiten lassen.

1
2
$ sudo aptitude install mlmmj
$ mlmmj-make-ml

Nachdem die selbsterklärende Installation abgeschlossen ist, noch in /etc/aliases eine Pipe einfügen für den entsprechenden User.

1
k4cg:     "|/usr/bin/mlmmj-receive -L /var/spool/mlmmj/k4cg"

OpenSMTPD

Den aus dem OpenBSD Umfeld entstandenen OpenSMTPD wollte ich mir schon länger ansehen. Für Postfix läge die mlmmj Konfigurationsanleitung zwar bei, aber hat ja irgendwie auch jeder und ist für unsere Zwecke viel zu bloated.

1
$ sudo aptitude install opensmtpd

Die einzige Config, die es bei OpenSMTPD gibt, /etc/smtpd.conf liesst sich schön im Stil von pf.

/etc/smtpd.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# interfaces to listen
listen on localhost
listen on eth0

# if you edit the file, you have to run "smtpctl update table aliases"
table aliases file:/etc/aliases

# recieve mails only for mailinglist domain and from any host
accept from any for domain "k4cg.org" alias <aliases> deliver to mbox

# other local mailboxes
accept from local for local alias <aliases> deliver to mbox

# allow to sent out mails to subscribed users
accept from local for any relay

Habe etwas mit dem smtpd herumgespielt, gefällt mir richtig gut. Minimal gehalten und selbsterklärend. Danach noch das newaliases Pendant smtpctl update table aliases ausführen. Ansehen will man sich auch mal smtpctl monitor <3

Tests mit Swaks

Gerade bei Mailsetups sind die Testszenarien etwas unschön abzuarbeiten. Das Swiss-Army-Knife-for-SMTP swaks hilft einem, das Zeug nicht jedesmal selbst über telnet eintippern zu müssen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ swaks --server 56.78.90.46 --to k4cg+subscribe@k4cg.org --from noqqe@example.org
=== Connected to 56.78.90.46.
 -> MAIL FROM:<noqqe@example.org>
<-  250 2.0.0: Ok
 -> RCPT TO:<k4cg+subscribe@k4cg.org>
<-  250 2.1.5 Destination address valid: Recipient ok
 -> DATA
<-  354 Enter mail, end with "." on a line by itself
 -> Date: Wed, 28 May 2014 23:12:58 +0200
 -> To: k4cg+subscribe@k4cg.org
 -> From: noqqe@example.org
 -> Subject: test Wed, 28 May 2014 23:12:58 +0200
 -> X-Mailer: swaks v20130209.0 jetmore.org/john/code/swaks/
 ->
 -> This is a test mailing
 ->
 -> .
<-  250 2.0.0: ac3d1ccf Message accepted for delivery
 -> QUIT
<-  221 2.0.0: Bye

Nach Test für subscribe/unsubscribe sollte man ebenfalls überprüfen, ob man nicht unter Umständen ein OpenRelay konfiguriert hat.

1
2
3
4
5
6
$ swaks --server 56.78.90.46 --to irgendwer@gmail.com --from noqqe@example.org
[...]
 -> RCPT TO:<irgendwer@gmail.com>
<** 550 Invalid recipient
 -> QUIT
<-  221 2.0.0: Bye

Versteckte Prozessparameter in UNIX

Passwörter für Datenbanken beispielsweise sind Optionen die sich als Commandline Argument direkt im Aufruf mitgeben lassen. Bei MySQL oder MongoDB ist das angegebene Passwort aber in der Prozessliste durch xxxx ersetzt.

1
2
3
4
5
6
$ mysql -u noqqe -ppassw0rd -h localhost
$ ps auxfww
sshd: noqqe@pts/0
 \_ -bash
     \_ mysql -u noqqe -px xxxxx -h localhost
     \_ ps auxfww

Irgendwie blieb ich die Woche an dieser Tatsache hängen. Verstand ich nicht. Das OS bekommt doch den Aufruf des Programms und das Binary parst die bereits vorher übergebenen Paramter. Ist das Binary nur eine Art Wrapper, der einen neuen Prozess spawnt? Oder Linux Kernel API wie hidepid? Wer filtert hier?

Passwort Parameter in MySQL

Nachdem MySQL ja OpenSource ist, kann man ja mal etwas grepen im Source. Wurde schliesslich auch fündig.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case 'p':
  if (argument == disabled_my_option)
    argument= (char*) "";     // Don't require password
  if (argument)
  {
    char *start= argument;
    my_free(opt_password);
    opt_password= my_strdup(argument, MYF(MY_FAE));
    while (*argument) *argument++= 'x';     // Destroy argument
    if (*start)
      start[1]=0 ;
    tty_password= 0;
  }
  else
    tty_password= 1;
  break;

Das MySQL Client Binary wird also gestartet, initialisiert und die Variable argument, die aus dem Parameter-Parser von MySQL fällt, kopiert und direkt an der entsprechenden Speicheraddresse mit xen überschrieben.

Im Endeffekt eine coole Lösung, aha-Effekt war da. Bedeutet aber auch, dass beim Start des Programms für eine gewisse Zeit das Passwort in der Prozessliste steht. So lang bis der Argumentenparser an der entsprechenden Stelle angekommen ist und den Memory überschreibt. Ab jetzt also immer -p Parameter ganz am Anfang hinschreiben :P

Nachbau

Hört sich etwas nach zurecht gehackt an, fand ich. Dabei ist die Anpassbarkeit durchaus im C99 Standard vorgesehen.

The parameters argc and argv and the strings pointed to by the argv array shall be modifiable by the program, and retain their last-stored values between program startup and program termination.

C99 Standard

Ausprobieren lässt sich das eigentlich mit einfach ein bisschen C, welches ich mir via StackOverflow-Driven-Development zusammen geklaut habe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv) {

    int i = 0;
    int k=0;
    int len = 0;

    len = strlen(argv[0]);
    for(k=0;k<len;k++) {
        argv[0][k] = 'y';
    }

    len = strlen(argv[1]);
    for(i=0;i<len;i++) {
        argv[1][i] = 'x';
    }

    system("ps f");

    return 0;
}

Dabei kann auch der eigentliche Name des Programms überschrieben werden. Total evil-haxx0r.

1
2
3
4
5
6
7
$ gcc hide.c -o hide
$ ./hide tolorlerolero
  PID TTY      STAT   TIME COMMAND
12384 pts/1    Ss     0:01 -bash
23512 pts/1    S+     0:00  \_ yyyyyy xxxxxxxxxxxxx
23513 pts/1    S+     0:00      \_ sh -c ps f
23514 pts/1    R+     0:00          \_ ps f

Again what learned.

Packages zu R 3.1 migrieren

Die R Version 3.1.0 “Spring Dance” wurde released. Damit ändern sich auch die Modulpfade, sofern nicht global definiert.

Paketmigration einfach und pragmatisch

1
2
R> x <- list.files("~/R/x86_64-pc-linux-gnu-library/3.0/")
R> sapply(x, install.packages)

Macht mehr R.

nichtparasoup

In der k4cg laufen auf dem Beamer meistens irgendwelche Bilder von soup.io durch. Bisher mittels dem (fremdgehosteten) soupcache.

Da diese in letzter Zeit oft keine Bilder auswirft und wir das Stück Software aufgrund der Codebasis auch nicht selber hosten können/wollen, hab ich letzte Woche etwas eigenes geschrieben. nichtparasoup

Ums kurz zu machen, ein kleines Python Skript ersetzen Nodejs, MongoDB, Crawler-Shell-Skripte und viel zu viel JS-Code. Beim JavaScript-Part in der neuen Lösung geht großer Dank an Jan. Mit Webkram kenn ich mich überhaupt nicht aus.

Code und Anleitung auf Github: github.com/k4cg/nichtparasoup. Wer möchte kanns auch gerne ausprobieren und Rückmeldung geben.

Administration von Systemen

Admins. In der Systemadministration treffen Einstellungen aufeinander. Geschmacksrichtungen. Unterschiedliche Wissensstände. Glaubensrichtungen. Interessen. Vorlieben. Wer tut in einem Ops-Team was und warum. Was folgt ist ein Gedankenmodell.

Solutions

Lösungen. Eigentlich ist es das, worum es im Operations geht. Dem nachkommen von Anforderungen an ein System. Ob neue Komponente, Sicherheitslücke oder Setup-Aufbau, darauf lassen sich all diese Dinge herunterbrechen.

Die angewendeten Lösungen (Changes) lassen sich in maximal 2 der nachfolgenden Kategorien einordnen. Ähnlich wie beim CAP-Theorem.

  • Einfach: Der Change ist einfach, schnell umgesetzt und auch für Kollegen verständlich
  • Korrekt: Die Lösung kann je nach Bedarf zukünftig erweitert werden, ist sicher und auf Korrektheit überprüft
  • Risikofrei: Das entstehende Riskio für die Umgebung bei Anwendung der neuen Lösung ist möglichst gering.

Practices

“Es ging nicht anders”

Man hat keine Ahnung vom Thema. Die Lösung entspringt Google, einem Unix-Forum Post von 1997 oder im besten Fall einem Server Fault Thread mit 1,5 Upvotes.

Diese Praktik erzeugt meistens wenig Aufwand, geht schnell. Hinterlässt aber einen Gewissen Nachgeschmack. Fühlt sich dreckig an und sieht manchmal auch so aus. Meist auch die Variante in der sich andere Teamkollegen beim Drüberstolpern nachher die Augen auswaschen müssen.

“Best Practice”

Größtmögliche Korrektheit. Dieser Ansatz entspringt meistens entweder persönlichem Interesse des Admins an der Software oder entsprechende Schulung+Einarbeitung in das Thema.

Was in Erweiterbarkeit, leichter Administration und Stabilität endet muss mit dem Eingehen von Risken und Zeit bezahlt werden. Man ändert nicht einfach mal so die Templates der Standardrolle im LDAP oder die Architektur des Datenbankclusters. Danach hat man aber allerdings erstmal Ruhe und das angenehme Gefühl die Welt ein Stück besser gemacht zu haben.

“Hart overengineert”

Wenn auch das kleinste Ein-Server-Homepage-Setup von der Metzgerei um die Ecke bis zum Anschlag puppetisiert, hoch-skalierbar und für alle Erweiterungen bereit eingerichtet ist.

Der overengineerte Ansatz ist stabil. Allerdings schwierig im Team zu maintainen und beansprucht zu viel Zeit. Was in hohen Einmalkosten, verlorener Mühen und Nerven endet um für Eventualitäten gewappnet zu sein die wohl niemals eintreten werden.

Roles

Im Lauf eines Admin-Berufslebens begegnen einem auch allerhand Charaktere. Ich würde nicht von mir behaupten, ein Urgestein der Administration zu sein, dennoch lernt man so einiges kennen.

Der Noob-Op

Es spielt überhaupt keine Rolle ob wegen neuer (unbekannter) Technologie oder einfach nur Berufsanfänger. Beim Angehen eines Problems kommt die erstbeste Lösung her, die gefunden wird. Auch weil man oft garnicht in der Lage ist beurteilen zu können ob die Lösung schön, wartbar oder auch nur im Ansatz korrekt ist.

Der Realtitätsfremde

Es wird auch mal einfach ein C Programm geschrieben, gebaut und temporär eingesetzt, nachdem im Programm der Wahl ein Flag, eine Option oder ein Feature nicht so realisiert wie selbst erwartet.

Software wird selbst gepatched, statisch gebaut und auf Systeme verteilt. Meister für Problemlösungen, finden aber auch Lösungen die Betriebsführungstechnisch gesehen garkeine sind.

Der Engagierte Admin

Macht sich gerne länger Gedanken auch über temporäre Probleme oder niedriger Priorität die einfach zu lösen sind. Der Anspruch an die Correctness steht dabei an erster Stelle. Das Ergebnis will schliesslich hergezeigt werden können.

Alles dauert recht lang, funktioniert aber. Schöne Systemkonfiguration mit Kommentaren in den Configs bezahlt einem aber leider keinerr. Funktionieren muss es halt.

Der Hipster-Hacker

Nur Software einsetzen, die gerade irgendwo Trending auf Hackernews ist. Heisser Scheiss solls sein. Enden tut das alles in einer Infrastruktur aus pre-alpha Versionen die allesamt beim nächsten Update kaputtgehen. Ganz zu schweigen von “production-ready” Software.

Der resignierende Senior

Je älter oder erfahrener der Admin dieser Kategorie desto stärker nähert er sich dem Noob an.

Egal ob Faulheit, vergebene Bemühungen wegen der Vergänglichkeit des Systems, baldigem Eintritt ins Rentenalter oder einfach nur, um nicht der Einzige zu sein der sich mit dem System/ Konfiguration auskennt. Eingebaute Änderungen sind pragmatisch, einfach, zielorientiert und mit möglichst wenig Aufwand.

Abschliessend

Ich mag es Dinge in Schubladen zu sortieren und erwische mich oft an diesem Gedankenmodell schrauben. Ich weiss ehrlichgesagt nicht warum ich das verblogge. Der Sketch dazu liegt schon ewig in meinem Draftordner und über die Feiertage kam ich mal dazu.

Um Hinweise bei vergessenen Solutions, Roles oder Practices in den Kommentaren bitte ich sehr ;)

GNU Coreutils

Man stelle sich folgendes Szenario vor. Eine große CSV Datei enthält Datensätze. Eine weitere Datei enthält ~1,5mio IDs die ein Subset der Datensätze darstellen. Gewünscht ist ein File das alle Datensätze des Subsets enthält.

for-loop grep

Die gewohnte Pauschallösung für derartige Probleme. Ganz im Bash-Admin-Stil

1
2
3
$ time for x in $(cat idsubset.txt) ; do
>  grep ^$x dataset.csv
> done > result.csv

Nur leider kommen dabei ganze 1,5 Records pro Sekunde heraus, was alles in allem in über 2 Wochen Rechenzeit endet. IOwait enstand dabei nicht.

GNU parallel

16 Core-Maschine. Einfach härter parallel greppen. GNU parallel hatte ich 2012 einmal ausprobiert.

1
2
3
4
$ cat idsubset.txt | time parallel 'grep -m 1 ^{} dataset.csv' > result.csv
[...]
Command terminated by signal 2
13165.04user 56967.06system 1:23:04elapsed 1406%CPU (0avgtext+0avgdata 40816maxresident)k

Nach knapp 90 Minuten war das gute Stück bei ca. 80% des Files angekommen. Annehmbar, auch wenn die Cores und der RAM der Kiste damit gut beschäftigt waren.

join

Das effizienteste war allerdings join aus den GNU core utilities

1
2
3
4
5
6
7
$ sort idsubset.txt > sidsubset.txt
$ sort dataset.csv > sdataset.csv
$ time join sidsubset.txt sdataset.csv > result.txt
[...]
real    0m38.965s
user    0m36.290s
sys     0m0.991s

Fucking 38 Sekunden. Zwei Dinge sind zu beachten. Sortierung und Formatierung.

Das Field, das zusammengeführt werden soll muss in beiden Files über den gleichen Trenner identifizierbar sein. Zurecht-ge-sed-et©

Beide Files müssen alphabetisch sortiert sein, nicht numerisch. Das ist im wesentlichen dem Algorithmus geschuldet der in join verbaut ist. Linecounts anstelle von Fullscans bei jeder Iteration sind der Trick.

BigData Krams? Lolo. Fucking Coreutils.

Privacy++

“Machste halt mal kurz SSL am Webserver an”. So einfach ists halt leider nicht. Ich habe das so lange nicht in getan, weil weder Logins vorhanden sind noch geheimer Content publiziert wird. Wer möchte kann aber jetzt https://noqqe.de benutzten.

Folgende Dinge haben sich geändert:

  • Google Web Fonts now selfhosted (mit Clemens Skript)
  • Hart kodierte Links mit sed auf relative Links umgestellt. Siehe RFC3986
  • Flattr Button durch statische Variante ersetzt
  • Kein jQuery nachladen von extern mehr
  • Kein Github nachladen mehr
  • Kein Pinboard nachladen mehr
  • ~50MB verwaiste Uploads entfernt
  • Mit wget --spider tote Links entdeckt und korrigiert.
  • Extern gehostete Bilder aus früheren Posts in /uploads/ migriert
  • SSL noqqe.de Zertifikat erstellt
  • Isso Kommentarsystem auf comments.noqqe.de umgezogen und SSL
  • Piwik auf analytics.noqqe.de umgezogen und SSL

Gerade die letzten beiden Punkte waren garnicht so einfach. Für alles was ich sonst so hoste benutze ich meistens die Domain n0q.org. Nur leider möchte mir StartCom kein Zertifikat für diese Domain ausstellen, da sie fürchten ich könnte eine Fakesite für noq.org hosten. Valide Begründung aber doof für mich.