Teil von SELFHTML aktuell Teil von Artikel Teil von Serverkonfiguration Teil von SELFHTML Server Konfiguration

SELFHTML Server Konfiguration:
Backup-Konzept

nach unten Einleitung
nach unten Das Konzept
nach unten Das Backup-Script
nach unten Das Logrolling-Script

Einleitung

Das Backup-Konzept ist das wichtigste am ganzen Server, meiner Meinung nach. Denn im GAU-Fall dürfen nur so wenig Daten wie möglich verloren gehen. Deshalb ist es auch nicht sehr sinnvoll, auf dem eigenen Server (oder zumindest nur auf dem eigenen Server) zu sichern. Richtige Backups werden auf wenigstens zwei Rechner verteilt, oder sogar auf Band gesichert.

nach obennach unten

Das Konzept

Ich habe ein Script geschrieben, das <n> Generationen an Backups verwalten kann. Derzeit sind sieben eingestellt. Diese sieben Generationen werden lokal auf dem Server gespeichert, allerdings wird jede Nacht ein Backup auf einen anderen Server geschoben, wo allerdings nur fünf Generationen gesichert werden. Außerdem werden die Logfiles noch einmal extra gesichert — wir sind verpflichtet, die Logfiles wenigstens ein halbes Jahr aufzubewahren, um im Zeifelsfall der Polizei Daten herausgeben zu können. Deshalb liegt hier nocheinmal ein besonderes Augenmerk. Das mag sich vielleicht paranoid anhören, ist jedoch dringend notwendig.

nach obennach unten

Das Backup-Script

Das Backup-Script hat genau drei Aufgaben: Es muss den Verzeichnisbaum einlesen und die entsprechenden Dateien in ein Tar- Archiv packen, es muss die lokalen Backups verwalten und es muss die externen Backups verwalten. Dementsprechend komplex ist das Script:

#!/usr/bin/perl -w

use strict;
use vars qw($LIBPATH $CONFIGPATH $log $VERSION);

BEGIN {
  $VERSION    = 1.50;
  $LIBPATH    = "/home/cron/backup/";
  $CONFIGPATH = "/home/cron/backup/config/";
}

use lib $LIBPATH;
use XML::Simple;
use Logging;
use Compress::Zlib;
use Archive::Tar;

sub read_conf($);
sub read_tree($);
sub generation_management($);
sub create_date();

my $conf   = read_conf($CONFIGPATH."config.xml");
my $tar    = new Archive::Tar;
my @afiles = ();

# natuerlich werden alle Aktivitaeten geloggt!
$log  = new Logging($conf->{logfile}->{path}.$conf->{logfile}->{file});
$log->log("Startup");

$log->log("Starting generation management...");
generation_management($conf); # verwalte die lokalen Generationen
$log->log("Generation-Management endet...");

# Alle konfigurierten Dateien einlesen und in Archive speichern
foreach my $entry (@{$conf->{save}->{entry}})
  {
   my $tar  = new Archive::Tar;

   if(-d $entry->{content})
    {
     $tar->add_files(read_tree($entry->{content})) or die "Could not read: $!";
     $log->log("Archived directory ".$entry->{content});
    }
   else
    {
     $tar->add_files($entry->{content}) or die "Could not read: $!";
     $log->log("Archived file ".$entry->{content});
    }

   my $fname = $conf->{savefile}->{path}.$entry->{file};
   $fname =~ s/\{date\}/create_date()/e;

   $tar->write($fname,$conf->{savefile}->{compression});
   push @afiles,$fname);
  }

$log->log("Archive written to harddisk, compression level: ".$conf->{savefile}->{compression});

# jetzt das remote-Backup
my $tar = new Archive::Tar;
$tar->add_files(@afiles) or do {
  $log->log("Archive of archive could not be written!");
  die "Archive of archive could not be written!";
};

my $fname = $conf->{savefile}->{archive};
$fname =~ s/\Q{nr}\E/(localtime)[3] % $conf->{Generations}->{save_extern}/e;

$tar->write($fname,$conf->{savefile}->{compression}) or do {
  $log->log("Archive of archive could not be written!");
  die "Archive of archive could not be written!";
};

my $fname_remote = $fname;
$fname_remote =~ s!.*/!!;

$log->log(`$conf->{extern}->{scp} $fname $conf->{extern}->{path}/$fname_remote`);
$log->log("Transfered!");

unlink $fname;

$log->log("End");

#####################################################
#
# rekursive Funktion, um den Datei-Baum einzulesen
#
sub read_tree($) {
  my $aktdir = shift;
  local *DIR;
  my @flist  = ();

  opendir(DIR,$aktdir) or do {
    $log->log("Error reading directory '$aktdir': ".$!);
    die("Error reading directory '$aktdir': ".$!);
    };

  while(my $entry = readdir(DIR)) {
    next if $entry eq "." || $entry eq "..";

    if(-d $aktdir."/".$entry)	{
      push @flist, read_tree($aktdir."/".$entry);
      next;
	}


    push @flist,$aktdir."/".$entry;
  }

  closedir(DIR) and return @flist;

  $log->log("Error reading the tree: ".$!);
  die("Error reading the tree: ".$!);
}

#####################################################
#
# Konfiguration einlesen
#
sub read_conf($) {
  my $file = shift;
  my $conf = XMLin($file);

  $conf->{Generations}->{pattern} =~ s/\{date\}/[0-9]{4}\-[0-9]{2}-[0-9]{2}/;

  $conf->{logfile}->{path}  .= '/' unless $conf->{logfile}->{path}  =~ m!/$!;
  $conf->{savefile}->{path} .= '/' unless $conf->{savefile}->{path} =~ m!/$!;

  $conf->{save}->{entry} = [$conf->{save}->{entry}] unless ref $conf->{save}->{entry} eq "ARRAY";

  return $conf;
}

#####################################################
#
# Einheitliches Datums-Format erzeugen (muesste ISO-Norm sein)
#
sub create_date() {
  my ($day, $month, $year) = (localtime(time))[3,4,5];
  $month++;
  $year += 1900;

  return sprintf("%4d-%02d-%02d",$year,$month,$day);
}


#####################################################
#
# Generations-Management fuer die lokal gesicherten Generationen
#
sub generation_management($) {
  my $conf = shift;

  opendir DIR,$conf->{savefile}->{path} or die $!;

  foreach my $file (readdir DIR) {
    next if -d $conf->{savefile}->{path}.$file;
    unless($file =~ /$conf->{Generations}->{pattern}/) {
      $log->log("file ".$conf->{savefile}->{path}.$file." skipped");
      next;
    }


    foreach my $entry (@{$conf->{save}->{entry}}) {
      my $fn1   = $entry->{file};
      my $fn2   = $file;


      $fn1 =~ s/-\{date\}//;
      $fn2 =~ s/-[0-9]{4}-[0-9]{2}-[0-9]{2}//;
      next if $fn1 ne $fn2;

      my $delete = 1;
      for(my $i=0;$i<$conf->{Generations}->{save_local};$i++) {
        my $fname = $entry->{file};

        my $time  = time; $time -= $i * 24 * 60 * 60;
        my ($today, $now_month, $now_year) = (localtime($time))[3,4,5];
        $now_month++; $now_year += 1900;

        $fname =~ s/\{date\}/sprintf("%4d-%02d-%02d",$now_year,$now_month,$today)/e;
        $delete = 0 if $fname eq $file;
      }

      if($delete) {
        unlink $conf->{savefile}->{path}.$file;
        $log->log("Generation management: Deleting file $file");
      }
    }

  }

}

# eof

Die Konfigurations-Datei ist (mal wieder) eine XML-Datei:

config.xml:

<?xml version="1.0"?>
<!DOCTYPE config SYSTEM "backup.dtd">

<config>
  <savefile path="/usr/backup/" compression="9" archive="/usr/backup/archive-{nr}.tgz"/>
  <logfile  path="/var/log" file="backup.log"/>
  <extern scp="/usr/local/bin/scp" server="backup.primekom.net" path="~"/>

  <Generations save_local="7" save_extern="5" pattern="[\w\.-]+-{date}"/>

  <save>
   <entry file="alpentouren-{date}.tgz">/home/www/teamone.de/alpentouren</entry>
   <entry file="rhps-{date}.tgz">/home/www/teamone.de/rockyhorrorpictureshow</entry>
   <entry file="selfsuche-{date}.tgz">/home/www/teamone.de/selfsuche</entry>
   <entry file="selfaktuell-{date}.tgz">/home/www/teamone.de/selfaktuell</entry>
   <entry file="selfdeveloper-{date}.tgz">/home/www/teamone.de/selfdeveloper</entry>
   <entry file="selffan-{date}.tgz">/home/www/teamone.de/selffan</entry>
   <entry file="selfforum-{date}.tgz">/home/www/teamone.de/selfforum</entry>
   <entry file="selfhtml-{date}.tgz">/home/www/teamone.de/selfhtml</entry>
   <entry file="teamone-{date}.tgz">/home/www/teamone.de/www</entry>
   <entry file="selfhtml.fr-{date}.tgz">/home/www/selfhtml.com.fr/selfhtml</entry>
   <entry file="selfaktuell.fr-{date}.tgz">/home/www/selfhtml.com.fr/www</entry>
   <entry file="selfforum.fr-{date}.tgz">/home/www/selfhtml.com.fr/selfforum</entry>
  </save>
</config>

backup.dtd:
<!ELEMENT config (savefile,logfile,extern,Generations,save)>
   <!ELEMENT savefile EMPTY>
   <!ATTLIST savefile
             path        CDATA     #REQUIRED
             compression CDATA     #REQUIRED
             archive     CDATA     #REQUIRED
             >

   <!ELEMENT logfile EMPTY>
   <!ATTLIST logfile
             path        CDATA     #REQUIRED
             file        CDATA     #REQUIRED
             >

   <!ELEMENT extern EMPTY>
   <!ATTLIST extern
             scp         CDATA     #REQUIRED
             server      CDATA     #REQUIRED
             path        CDATA     #REQUIRED
             >

   <!ELEMENT Generations EMPTY>
   <!ATTLIST Generations
             save_local  CDATA     #REQUIRED
             save_extern CDATA     #REQUIRED
             pattern     CDATA     #REQUIRED
             >

   <!ELEMENT save (entry+)>
      <!ELEMENT entry (#PCDATA)>
      <!ATTLIST entry
                file     CDATA     #REQUIRED
                >

Tja, hier kann ich definitiv nicht sagen, ob es bugfrei ist. Ich weiss nur, es funktioniert so, wie ich es einsetze, wenn jemand Bugs findet — bitte sagt mir bescheid.

nach obennach unten

Das Logrolling-Script

Ich hatte ja bereits angemerkt, dass auf den Logs ein besonderes Augenmerk liegt, aus rein gesetzestechnischen Gründen. Deshalb werden sie auch nochmal extra gesichert, mit dem folgenden Script:

#!/usr/bin/perl -w

use vars qw($VERSION $CONFIG $TIME);

BEGIN
 {
  my ($mday,$month,$year,$hour,$min,$sec) = (localtime)[3,4,5,2,1,0];

  $year += 1900;
  $month++;

  $VERSION = '0.5';
  $CONFIG  = '/home/cron/logrolling/config/rolling.xml';
  $TIME    = $mday.'.'.$month.'.'.$year.'-'.$hour.':'.$min.':'.$sec;
 }

use strict;
use XML::Simple;

sub roll_it($);

my $config = XMLin($CONFIG);
roll_it $_ foreach (@{$config->{log}});
print `$config->{apache} restart`;

sub roll_it($)
 {
  my $entry   = shift;
  my $flag    = '';
  local *DIR;

  chdir $entry->{dir}                or die $entry->{dir},': ',$!;
  mkdir 'temp',oct(777);

  if(-f $entry->{archive})
   {
    print `tar -C $entry->{dir}/temp -xzf $entry->{archive}`;
   }

  opendir DIR,$entry->{dir}          or die $entry->{dir},': ',$!;

  foreach (readdir DIR)
   {
    next if -d $_;
    rename $_ => 'temp/'.$_.'.'.$TIME
                                        or die $entry->{dir},$_,': ',$!;
   }

  chdir 'temp';
  print `tar -czf $entry->{archive}  *`;
  print `rm *`;
  chdir '..';
  rmdir 'temp';
  closedir DIR;
 }

# eof

Ich benutze hier ganz aktiv Backpipes, weil Archive::Tar leider einen kleinen Bug beim einlesen von Archiven zu haben scheint.

weiter Seite Sicherheits-Konzept

zurück Seite Logfile-Auswertung

Teil von SELFHTML aktuell Teil von Artikel Teil von Serverkonfiguration Teil von SELFHTML Server Konfiguration

© 2007 bereichsübergreifende Seite Impressum