Szybki maintenance mode w Nginx

Czasami specyfika strony bądź serwisu sprawia, że regularnie trzeba wprowadzać go w tryb serwisowy. Zgodnie z wytycznymi SEO i specyfikacją kodów HTTP nalepiej zrobić to wysyłając kod 503, czyli Service Temporarily Unavailable. Poniżej pokażę pewien trick, który pozwala zrobić to szybko, sprawnie i nawet dla wielu domen jednocześnie.


Aby serwer Nginx wysyłał określony kod HTTP, inny niż standardowy w danej sytuacji, wystarczy w sekcji server konfiguracji podać linijkę:

return 503; //always returns 503 error code

takie rozwiązanie oznacza jednak, że włączanie i wyłączanie trybu serwisowego wiązałoby się z komentowaniem bądź usuwaniem linijki return w każdej sekcji server .

Jak zrobić to w nieco szybszy sposób? Z pomocą przy budowie takiego uniwersalnego rozwiązania przychodzi nam oferowany przez Nginx moduł geo, który pozwala na tworzenie rozbudowanych quasi-zmiennych, których wartość może być zależna od IP, a ich widoczność jest globalna, gdyż zmienne te definiuje się w sekcji http konfiguracji.

Opisany poniżej sposób zakłada, że konfiguracja Nginx zbudowana jest w co najmniej dwóch plikach tj. jednym z sekcją http i globalną konfiguracją oraz drugim zawierającym różne sekcje server, ale kilka zmian logiki (właściwie w dotyczących wyłącznie ostatniej części tego tekstu 😉 ) wystarczy, aby używać go dla konfiguracji Nginx zapisanej w jednym pliku.

Zgodnie ze wspomnianym wcześniej opisem zaczynamy od zdefiniowania zmiennej geo w sekcji http konfiguracji:

geo $maintenance {
       default 0;
}

zmiennej domyślnie nadajemy wartość 0. Ponieważ geo umożliwia nam zdefiniowanie różnych wartości (w ogólności nie tylko numerycznych) dla wielu adresów IP możemy rozszerzyć tę definicję np. tak:

geo $maintenance {
      default 0;
      10.1.0.0/16 0;
}

takie zbudowanie zmiennej $maintenance pozwoliłoby nam na wyłączenie (bądź nie włączenie w ogóle) trybu serwisowego strony np. dla dostępu z obrębu sieci VPN.

Kolejnym krokiem jest zmiana wybranych konfiguracji server tak, aby były „wrażliwe” na wartość zmiennej $maintenance  i w zależności od niej zwracały kod 503. W tym celu należy w sekcji server dodać warunek zwracający kod 503, gdy zmienna $maintenance  przyjmie żądaną wartość (w tym przypadku, dla czytelności i uproszczenia kodu jest to 1):

if ($maintenance) {
      add_header Retry-After 1800;
      return 503;
}

powyższa konstrukcja wewnątrz warunku zawiera jedną dodatkową linijkę:

add_header Retry-After 1800;

jest ona odpowiedzialna za wysyłanie wraz z kodem 503 nagłówka informującego, po jakim czasie (w sekundach) należy ponownie odwiedzić niedostępny zasób. Jest to trick przydatny pod kątem pozycjonerskim, gdyż przyspiesza reindeksowanie przez roboty strony zwracającej 503.

Kiedy już zaimplementujemy oba wymienione wyżej rozwiązania pozostaje nam odpowiedź na najważniejsze pytanie: jaka jest korzyść z tych działń? Główną zaletą tego rozwiązania jest właśnie szybkość jego uruchomienia. Wystarczy bowiem napisać skrypt (w dowolnym języku programowania), który za pomocą wyrażeń regularnych zmieni wartość zmiennej $maintenance na 1 (czy to domyślną, czy dla wybranego IP / zakresu IP) i wykona graceful restart Nginx. Po tym zabiegu wszystkie konfiguracje server z dodanym warunkiem zaczną zwracać kod 503, czyli wejdą w maintenance mode. Powrót do stanu normalnego następuje oczywiście analogicznie poprzez przypisanie 0 😉

Poniżej przykład takiej właśnie prostej funkcji w języku perl:

sub webServerMaint {
    my ( $self, $_conf, $_switcher ) = @_;    #switcher 1 - on, 0 - off

    #open config to read
    open(NGINXCONF, '<', $_conf) or die "Can't read file 'filename' [$!]\n";  
    my @nginxConf = <NGINXCONF>;
    close (NGINXCONF); 

    my $i=0;
    my $defPos = 0;

    #search for position of maintenance default value
    foreach $trafienie (@nginxConf) {	
	chomp $trafienie;
	if($trafienie =~ /geo \$maintenance/) {
	     $defPos = $i + 1;
	     last;
	}
	$i++;
    }

    #change maintenance default value according to given option
    if($_switcher == 1) {
	$maintanceStatus = $nginxConf[$defPos];
	$maintanceStatus =~ s/ 0/ 1/g;
	$nginxConf[$defPos] = $maintanceStatus;
    } elsif ($_switcher == 0) {
	$maintanceStatus = $nginxConf[$defPos];
	$maintanceStatus =~ s/ 1/ 0/g;
	$nginxConf[$defPos] = $maintanceStatus;
    }

    #rewrite config file
    open(NGINXCONF, '>', $_conf) or die "Can't read file 'filename' [$!]\n";  
    foreach $trafienie (@nginxConf) {	
	chomp $trafienie;
	print NGINXCONF $trafienie."\n";
    }
    close (NGINXCONF); 

    #reload nginx
    system("/etc/init.d/nginx", "reload");
    if ( $? == -1 ) {
      print "command failed: $!\n";
    } else {
      printf "command exited with value %d", $? ;
    }
}
Podziel się:

Mikołaj Niedbała

I'm a Poland based IT administrator, linux administrator and IT engineer creating professional IT infrastructure solutions based on Linux and virtual environments.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *