Archives pour l'étiquette Perl

Le début: web access log logstash redis elasticsearch kibana

Gérer et monitorer les web access log avec logstash redis elasticsearch et kibana.

 

Voila une belle série de buzz word non ? Trêve de plaisanterie ! Je vais dans une série de note expliquer la création d’une pile applicative ELK (logstash elasticsearch kibana) que j’ai monté pour surveiller et navigué dans le flux des access log sur site www.closermag.fr

Acte 1 scène 1: Le défi des web access log

A l’origine sont les access log de plusieurs serveurs apaches ainsi que de plusieurs serveur varnish. Plusieurs défis sont à relever  :

  1. Généré des access log complet dans un format facilitant leur utilisation par la chaîne applicative souhaité
  2. La plupart des serveurs étant des machines virtuelles il est nécessaire de ne pas écrire sur disque. Le disque est le sous système le plus lent d’un serveur. Dans le cas d’une machine virtuelle, quelques soit l’approche ou la technologie, c’est encore pire.
  3. Le routage des flux de log apache comme varnish et leur concaténation dans un ensemble unique.

Acte 1 scène 2 : Les apaches

Pour les serveurs apaches la gestion de log et de leur format est un chose parfaitement documenté, je ne vais pas revenir dessus. Les access log devant être finalement enregistré dans elasticsearch dont la structure de stockage et sa hiérarchie étant basé sur le format JSON pour quoi ne pas les générer directement dans ce format et les router tel quel ?

Voici dont le format de log apache créé sur cette base et utilisé. Plusieurs itération et correction ont été intégré.

LogFormat "{ \
 \"@timestamp\": \"%{%Y-%m-%dT%H:%M:%S%z}t\", \
 \"@version\": \"1\", \
 \"vips\":\"%v\", \
 \"tags\":[\"apache\"], \
 \"message\": \"%h %l %u %t \\\"%r\\\" %>s %b\", \
 \"clientip\": \"%{X-Forwarded-For}i\", \
 \"duration\": %D, \
 \"status\": %>s, \
 \"request\": \"%U%q\", \
 \"urlpath\": \"%U\", \
 \"urlquery\": \"%q\", \
 \"bytes\": %B, \
 \"method\": \"%m\", \
 \"referer\": \"%{Referer}i\", \
 \"useragent\": \"%{User-agent}i\" \
 }" log_apache_json

Notez que :

  1. Le champs tags indiquant le type de la source
  2. Le champs clientip prend les ip d’origine de la requête et non ceux des lodbalancers ou reverse proxy en amont
  3. Que la duré de réponse est fourni via le champs duration
  4. Que le volume de donnée de la réponse est fourni par le champ bytes

Nous générons donc les access log directement dans un format facilitant leur utilisation par la chaîne d’application souhaité.

Acte 1 scène 3 : Le routage

Pour ne pas toucher aux disques le mieux est encore de transmettre directement les logs au démon syslog. Avec nginx c’est facile avec apache un peu moins.

Il est possible de transmettre les log apache à syslog avec l’utilitaire logger. Seulement logger à quelque limitation et comme une trame de log au format JSON est longue et dépasse parfois 1024 bits … logger la coupe en deux et envois deux trames syslog pour une trame access log. Il faut donc trouver autre chose.

Perl

Dans l’échange sur serverfault concernant les limitations de logger la solution est donné : Sys::Syslog. Il suffit de créé soit même un nouvel utilitaire capable de prendre en charge un trame dépassant les 1024 bits.

#!/usr/bin/perl
use Sys::Syslog qw (:DEFAULT setlogsock);
 
setlogsock('unix');
 
$ident = $ARGV[0];
@facilityandpriority = split(/\./, $ARGV[1]);
$facility = @facilityandpriority[0];
$priority = @facilityandpriority[1];
 
# open our log socket
openlog($ident, '', $facility);
 
# log all our input
while () {
 syslog($priority, $_);
}
 
# close the log socket
closelog;

Vhost et rsyslog

Nous avons le format de log et l’utilitaire de couplage avec syslog. Il reste à coller les bout ensemble.

Dans le vhost ont appel le format de log créé et on l’envois vers l’utilitaire. On fait le choix d’une catégorie et d’un priorité syslog, ici local0 et notice

CustomLog "||/usr/local/bin/to_rsyslog.pl closermag.fr local0.notice" log_apache_json

Dans la configuration rsyslog on précise que tout ce qui porte l’étiquette local0.notice est à envoyé à un serveur logmaster.

local0.notice @logmaster:514
& stop

Je précise que après l’envois au serveur logmaster le message syslog ne doit pas poursuivre et traverser les autres règles de traitement avec l’instruction & stop . Attention cette syntaxe est pour la version 8 de rsyslog que j’utilise. Comme la trame ne passe pas au travers des autres règles elle n’est pas enregistré dans un fichier, localement, sur disque.

Tail mais en couleur

Tail en couleur

Comment surveiller des log, par exemple, en mettant en évidence par la couleur une chaine de caractère en particulier ? J’ai trouver la solution en furetant sur dralnux.com.

Pour mettre juste la chaine de caractère en couleur rouge


tail -n 500 -f /var/log/syslog | perl -pe 's/CHAINE/\e[1;31m$&\e[0m/g'

Pour mettre toute la ligne contenant la chaine de caractère recherché en couleur rouge


tail -n 500 -f /var/log/syslog | perl -pe 's/.*\bsqueeze\b.*/\e[1;31m$&\e[0m/g'

Erreur Perl avec APT : warning : Setting locale failed.

Suite a l’annonce d’un Important bug de sécurité sur noyau 2.6.17 à 2.6.24 sur linuxfr.org je colle le nez sur mes serveurs pour faire un brin de ménage.

Ménage avec apt-get remove

J’en profite pour virer de vieux paquet et la, sur un serveur de test et bibouille, c’est le drame !!

apt-get remove --purge linux-image<span style="color: #000000;">-2.6</span><span style="color: #000000;">-686</span> Reading package lists... Done Building dependency tree... Done The following packages will be REMOVED: linux-image<span style="color: #000000;">-2.6</span><span style="color: #000000;">-686</span><span style="color: #000000; font-weight: bold;">*</span> <span style="color: #000000;">0</span> upgraded, <span style="color: #000000;">0</span> newly installed, <span style="color: #000000;">1</span> to remove and <span style="color: #000000;">45</span> not upgraded. Need to get 0B of archives. After unpacking 8192B disk space will be freed. Do you want to <span style="color: #7a0874; font-weight: bold;">continue</span> <span style="color: #7a0874; font-weight: bold;">&#91;</span>Y<span style="color: #000000; font-weight: bold;">/</span>n<span style="color: #7a0874; font-weight: bold;">&#93;</span>? y <span style="color: #c20cb9; font-weight: bold;">perl</span>: warning: Setting locale failed. <span style="color: #c20cb9; font-weight: bold;">perl</span>: warning: Please check that your locale settings: LANGUAGE = <span style="color: #7a0874; font-weight: bold;">&#40;</span><span style="color: #7a0874; font-weight: bold;">unset</span><span style="color: #7a0874; font-weight: bold;">&#41;</span>, LC_ALL = <span style="color: #7a0874; font-weight: bold;">&#40;</span><span style="color: #7a0874; font-weight: bold;">unset</span><span style="color: #7a0874; font-weight: bold;">&#41;</span>, LANG = <span style="color: #ff0000;">&quot;fr_FR.UTF-8&quot;</span> are supported and installed on your system. <span style="color: #c20cb9; font-weight: bold;">perl</span>: warning: Falling back to the standard locale <span style="color: #7a0874; font-weight: bold;">&#40;</span><span style="color: #ff0000;">&quot;C&quot;</span><span style="color: #7a0874; font-weight: bold;">&#41;</span>. locale: Cannot <span style="color: #000000; font-weight: bold;">set</span> LC_CTYPE to default locale: No such <span style="color: #c20cb9; font-weight: bold;">file</span> or directory locale: Cannot <span style="color: #000000; font-weight: bold;">set</span> LC_MESSAGES to default locale: No such <span style="color: #c20cb9; font-weight: bold;">file</span> or directory locale: Cannot <span style="color: #000000; font-weight: bold;">set</span> LC_ALL to default locale: No such <span style="color: #c20cb9; font-weight: bold;">file</span> or directory <span style="color: #7a0874; font-weight: bold;">&#40;</span>Reading database ... <span style="color: #000000;">43097</span> files and directories currently installed.<span style="color: #7a0874; font-weight: bold;">&#41;</span> Removing linux-image<span style="color: #000000;">-2.6</span><span style="color: #000000;">-686</span> ...

dpkg-reconfigure locales

J’ai du testé et bibouiller un peu trop sur ce serveur et maintenant plus moyen de le maintenant à jour. C’est pas la première fois que j’ai ce message et d’habitude le solutionne la chose avec un bonne reconfiguration des LOCALES

dpkg-reconfigure locales <span style="color: #000000; font-weight: bold;">//</span> &nbsp; Malheureusement l<span style="color: #ff0000;">'utilisation de __dpkg-reconfigure locales__ débouche sur le même message d'</span>erreur. __Perl__ ne comprend pas ma langue et ne trouve pas les variables nécessaire pour cela. &nbsp; Pas grave on va revenir a la langue universel, au fondamentaux de <span style="color: #c20cb9; font-weight: bold;">perl</span> &nbsp; <span style="color: #000000; font-weight: bold;">///</span><span style="color: #7a0874; font-weight: bold;">&#91;</span>bash<span style="color: #7a0874; font-weight: bold;">&#93;</span> <span style="color: #7a0874; font-weight: bold;">export</span> <span style="color: #007800;">LC_ALL=</span>C

Parfait, je retente la configuration des locales

dpkg-reconfigure locales <span style="color: #000000; font-weight: bold;">/</span>usr<span style="color: #000000; font-weight: bold;">/</span>sbin<span style="color: #000000; font-weight: bold;">/</span>dpkg-reconfigure: locales is not installed

Et la j’ai l’air con 😀

J’installe les locales, je les reconfigure, en sifflant d’un air dégagé et je reprend mes activités.

Script de sauvegarde (perl)

Petit script de sauvegarde en perl utilisé dans le projet d’architecture réseau internet / intranet en environnement GNU / Linux Debian concrétisant la fin de ma formation Administrateur Système et Réseau au GRETA SUD TERTIAIRE 93

Le principe

Désirant faire des sauvegardes régulières de la configuration des serveur du réseau j’ai décider d’écrire un petit script perl pour me facilité la vie. Je suis parti du principe que je ne sauvegardais que les fichiers de configuration, ce qui représente que quelque centaine de ko.

L’organisation

La sauvegarde doit être souple pour juste sélectionner un fichier comme un répertoire en entier. L’archivage ce fait dans le répertoire /var/backups et je garde toujours une trace de la sauvegarde précédente. Enfin j’utilise évidement le cron pour faire une sauvegarde régulières.

Le fichier ini.

Mon script prend la liste des fichiers et répertoires à sauvegardé dans un fichier d’ini. Ce fichier à la syntaxe suivante :
- /etc/network/interface
Pour désigner un fichier a sauvegarder isolément, pas de compression.
- /etc/
Pour désigner un répertoire a sauvegarder en entier au format tar.gz
- /home@
Pour désigner un répertoire racine dont chaque sous répertoire doit être sauvegarder en entier et isolément au format tar.gz.

Voici un exemple de fichier d’ini :


# backlist.ini
#
# Liste des fichiers et rep a sauvegarder

/etc/network/interfaces
/etc/network/options
/home/karles/Desktop/gts93.greta.fr/dev_script/
/tmp@

Le script

A mon humble avis le script est suffisamment bien commenté pour être facile à comprendre :


#!/usr/bin/perl

######################################################################################
## ##
## Name : tovarbackup.pl ##
## Version : 0.1 ##
## Author : Charles Christian CROIX ##
## Thank to : Larry Wall ##
## ##
######################################################################################

#################
# APPEL MODULES #
#################
# use diagnostics;
# use strict;

##################
# VARIABLES PROG #
##################
$backlist="./backuplist.ini"; ## fichier listant les rep et file a sauvegarder
$backlogfile="./tovarbackup.log"; ## fichier log
$backupdir="/var/backups"; ## ou seront stocker les fichiers a sauvegarder /!\ / a la fin
$iWidth=60; ## Largeur de l'affichage fonction sub center

########################
# Sous Prog & Fonction #
########################

sub CENTER($$) {
my ($Temp,$Width) = @_;
my $Len = ($Width - length($Temp)) / 2;
return " " x int($Len), $Temp, " " x (int($Len) + (($Len != int($Len))? 1:0));
}

sub CALDATE() {
# Commande recuperant l'heure et la date systeme
# ATTENTION VARIABLE DATE & TIME DIFF EN Fr et Us
my ($month,$day) = @_;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); ## Commande recuperant l'heure et la date systeme
$month = $mon + 1;
$month = sprintf ("%02d",$month);
$day = sprintf ("%02d",$mday);
$hour = sprintf ("%02d",$hour);
$min = sprintf ("%02d",$min);
$sec = sprintf ("%02d",$sec);
$time = "$hour.$min.$sec";
$year += 1900;
$date = "$day.$month.$year";
$datestamp = "$day/$month/$year";
$timestamp = "$hour.$min.$sec";
}

sub DOLOG($$) {
my ($file,$log)=@_;
# ecriture dans fichier log
# $file : fichier log
# $log : texte
local *LOG;
open (LOG, ">>$file") or warn "\n * Peux pas ouvrir $file \n * $!";
print LOG $log;
close LOG;
}

sub BACKLIST() {
# Appel du fichier INI donnant la liste des fichiers et rep a sauvegarder
open (BACKLIST,$backlist) || die "\U\n HOULA ouverture backlist.ini impossible \n $!";
@backlist = <BACKLIST> ;
close (BACKLIST);
}
sub FIN() {
print "\n\t+", "=" x ($iWidth), "+\n";
print "\t|", CENTER("FIN DU SCRIPT tovarbackup.pl", $iWidth), "|\n";
print "\t+", "=" x ($iWidth), "+\n";
EXIT;
}

sub ENTETE() {
print "\n";
print "\t+", "=" x ($iWidth), "+\n";
print "\t|", CENTER("** PERL tovarbackup.pl **", $iWidth), "|\n";
print "\t|", CENTER("Version 0.1 ", $iWidth), "|\n";
print "\t|", CENTER("(c) Charles Christian CROIX", $iWidth), "|\n";
print "\t|", CENTER("Licence: GPL", $iWidth), "|\n";
print "\t|", CENTER("Syntaxe : tovarbackup.pl ", $iWidth), "|\n";
print "\t|", CENTER( $date , $iWidth), "|\n";
print "\t|", CENTER( $time , $iWidth), "|\n";
print "\t+", "=" x ($iWidth), "+\n"
}

##############################
# #
# TRAITEMENT PRINCIPALE #
# #
##############################

CALDATE();
ENTETE();
BACKLIST();

DOLOG $backlogfile,"\n[$datestamp] [$timestamp] --";
DOLOG $backlogfile,"\n[$datestamp] [$timestamp] LANCEMENT ET INIT de tovarbackup";

# pour chaque ligne du ficier ini
foreach $line (@backlist) {
chomp $line;
@mots = split(/\s+/,$line);
next if (@mots[0] eq "#");
next if (@mots[0] eq "");

# recup du caractere de fin de ligne
$dirornot = substr (@mots[0],-1,1);

# recupe du nom de ficher / rep en fin de path
@filename = split(/\//,$mots[0]);
$nb_element_filename = @filename;
$nb_element_filename--;

###############################
# test si c'est un repertoire #
###############################
if ($dirornot eq "/") {
print "\n\t $mots[0] est un repertoire";
# on garde tjrs la version precedente
if (-e "$backupdir/$filename[$nb_element_filename].tar.gz") {
if (-e "$backupdir/$filename[$nb_element_filename].old.tar.gz") {
unlink ("$backupdir/$filename[$nb_element_filename].old.tar.gz")
}
system("mv $backupdir/$filename[$nb_element_filename].tar.gz $backupdir/$filename[$nb_element_filename].old.tar.gz ");
}
# appel systeme pour le tar
system("tar cvfz $backupdir/$filename[$nb_element_filename].tar.gz $mots[0] 2>&-, >&-");
DOLOG $backlogfile,"\n[$datestamp] [$timestamp] TAR REPERTOIRE $mots[0]";
}

######################################
# test si c'est un repertoire racine #
######################################
elsif ($dirornot eq "@") {
print "\n\t $mots[0] est un repertoire racine";
# suppression du carractere @
$mots[0]=substr($mots[0], 0, -1);
print "\n\t $backupdir$mots[0]";
# creation du rep racine dans le rep de backup
unless (-e "$backupdir$mots[0]") {
mkdir "$backupdir$mots[0]";
DOLOG $backlogfile,"\n[$datestamp] [$timestamp] CREATION de $backupdir$mots[0]";
}
# recup de la liste des fichier dans le rep racine
@list_sousrep = glob ("$mots[0]/*");
DOLOG $backlogfile,"\n[$datestamp] [$timestamp] TAR.GZ des sous repertoire de $mots[0] ";
foreach $sousrep (@list_sousrep) {
#si c'est un rep on le tar
if (-d $sousrep) {
# on garde tjrs la version precedente
if (-e "$backupdir$sousrep.tar.gz") {
if (-e "$backupdir$sousrep.old.tar.gz") {
unlink ("$backupdir$sousrep.old.tar.gz");
DOLOG $backlogfile,"\n[$datestamp] [$timestamp] SUPPRESSION de $backupdir$sousrep.old.tar.gz";
}
system("mv $backupdir$sousrep.tar.gz $backupdir$sousrep.old.tar.gz ");
DOLOG $backlogfile,"\n[$datestamp] [$timestamp] DEPLACEMENT de $backupdir$sousrep.tar.gz vers $backupdir$sousrep.old.tar.gz ";
}
print "\n\t - $sousrep";
system("tar cvfz $backupdir/$sousrep.tar.gz $sousrep 2>&-, >&-");
DOLOG $backlogfile,"\n[$datestamp] [$timestamp]\t TAR.GZ de $sousrep";
}
}
}

############################################
# et bien c'est ni un rep ni un rep racine #
# donc c'est un fichier #
############################################
else {
print "\n\t $mots[0] est un fichier ";
# on garde tjrs la version precedente
if (-e "$backupdir/$filename[$nb_element_filename]") {
if (-e "$backupdir/$filename[$nb_element_filename].old") {
unlink ("$backupdir/$filename[$nb_element_filename].old");
DOLOG $backlogfile,"\n[$datestamp] [$timestamp] SUPPRESSION DU FICHIER $backupdir/$filename[$nb_element_filename].old";
}
system("mv $backupdir/$filename[$nb_element_filename] $backupdir/$filename[$nb_element_filename].old ");
DOLOG $backlogfile,"\n[$datestamp] [$timestamp] DEPLACEMENT DU FICHER $backupdir/$filename[$nb_element_filename] vers $backupdir/$filename[$nb_element_filename].old ";
}
else {
}
# copie simple
system("cp $mots[0] $backupdir ");
DOLOG $backlogfile,"\n[$datestamp] [$timestamp] COPIE DU FICHER $mots[0]";
}
print "\n";
}

FIN();

Pour faire mieux

Pour faire mieux je prendrais soins de prévoir la possibilité de choisir entre la compression d’un répertoire ou sa simple copie. J’inclurais également la gestion des fichiers présent dans un répertoire racine car pour le moment seul les sous répertoires sont pris en compte. Les messages sur STDOUT devrait également être optionnel.