Klastrowanie sesji PHP z memcached

Klastrowanie to może zbyt dumnie powiedziane. Rozwiązanie to wyszukałem gdy chcąc skonfigurować dwa serwery apache do współpracy na rzecz jednego serwisu okazało się, że sejse trzymane są tylko przez jeden serwer a drugi nic o nich nie wie. To oczywiście nie pozwalało na prawidłowe działanie jakiegokolwiek serwisu korzystającego z sesji.

Pomysł jest taki, że zastępujemy domyśny mechanizm przechowywania sesji w plikach na dysku mechanizmem memcache. Ponieważ memcached działa jako usługa sieciowa, różne serwery mogą się odwoływać do puli memcached i odczytywać zapisane w niej dane. W przypadku sesji – nie jest ważne, kto ją utworzył – bo po jej wysłaniu do puli memcached staje się dostępna dla wszystkich klientów php z niej korzystających.

Jednym z pierwszych pytań nasuwających się do takiej kofiguracji jest: a co jeśli serwer memcached padnie? W chwili gdy wiele serwerów apache zależy od jednego serwera memcached jego awaria unieruchamia kaskadowo wszystkie.

Dlatego wykorzystałem konfigurację z dwoma serwerami memcached. Gdy php zapisuje dane w puli memcached dane są wysyłane do wszystkich podanych serwerów (w tym przypadku dwóch). A odczytywanie polega na odpytaniu pierwszego podanego serwera, a jeśli to się nie uda to drugiego. Układ nie jest idealny (jak mamy dwa działające serwery to aż się prosi o loadbalancing) ale zmniejsza prawdopodobieństwo, że awaria pojedynczego elementu położy wszystko.

Z moim problemem moża było sobie poradzić też inaczej, np. zmieniając mechanizm sesji na stronie na taki, który korzysta z bazy danych. O ile w przypadku jednego serwisu nie jest to duży kłopot, to już przy kilku/kilkunastu byłoby to już spore przedsięwzięcie.

Ważną zaletą tego rozwiązanie jest fakt, że nie są wymagane żadne zmiany w istniejących serwisach. Po zmianie mechanizmu w konfiguracji php wszystko powinno działać bez zmian.

Instalacja/Konfiguracja

Najpierw trzeba zainstalować i uruchomić memcached oraz rozszerzenie php5-memcachedo php’a, które da nam możliwość korzystania z niego. U mnie robi się to tak:

apt-get install memcached php5-memcache

Teraz trzeba by wyedytować konfigurację /etc/memcached.conf. Poniżej wycinek z tego pliku z opcjami, które należy ustawić:

# Maksymalna wartość pamięci w MB jaka może być
# wykorzystana przez demona.
# Warto dostosować do swoich potrzeb.
-m 128

# Interfejs, na którym nasłuchiwać będzie usługa.
# Ja dla wygody wybiorę wszystkie :)
-l 0.0.0.0

# można też dostosować port do nasłuchiwania
-p 11211

# użytkownika nieuprzywilejowanego
# (memcached domyślnie startuje jako root)
-u nobody

Wprowadzamy i zapisujemy zmiany. Trzeba zrestartować serwer memcached uruchamiając:

invoke-rc.d memcached restart

Do tego momentu musimy powtórzyć konfigurację na drugiej maszynie (bo inaczej po co klastrować sesje).

Teraz trzeba skonfiguraować php’a aby zamiast używać sesji zapisywanych do plików, korzystał z memcached. Edytujemy php.ini, u mnie akurat w lokalizacji:/etc/php5/apache2/php.ini. Odszukujemy opcje:

session.save_handler = files
;session.save_path = /var/lib/php5

I zamieniamy na:

session.save_handler = memcache
; adresy oczywiście należy dostosować do własnych ustawień
session.save_path = "tcp://localhost:11211, tcp://remotehost:11211"

W kolejnym kroku edytujemy plik konfiguracyjny rozszerzenia php’a dla memcache w /etc/php5/conf.d/memcache.ini dodając takie ustawienia:

extension=memcache.so

[memcache]
memcache.dbpath="/var/lib/memcache"
memcache.maxreclevel=0
memcache.maxfiles=0
memcache.archivememlim=0
memcache.maxfilesize=0
memcache.maxratio=0

; to jedyna wymagana opcja - resztę można dostosować pod siebie
; albo zostawić domyślnie
memcache.allow_failover=1

memcache.max_failover_attempts=20
memcache.default_port=11211
memcache.chunk_size=8192
memcache.hash_strategy=standard
memcache.hash_function="crc32"

Więcej po poszczególnych opcjach można się dowiedzieć z dokumentacji php.

Test

Jeżeli zrobiliśmy wszystko jak trzeba to sesje powinny zapisywać się z pamięci memcachedi dystrybuować na wszystkie wpisane w polu save_path serwery. Możemy to sprawdzić wykorzystując np. taki skrypt:

<?php
session_start();

print "Opcja save_handler: "
     . ini_get("session.save_handler") . "<br>";
print "Opcja save_path: "
     . ini_get("session.save_path") . "<br>";

if(isset($_SESSION['testowa'])) {
    print "Testowa sesja jest już ustawiona: " .
        $_SESSION['testowa'] . "<br>";
} else {
    $_SESSION['testowa'] = "i wygląda, że działa dobrze";
    print "Ustawiamy testową sesją wartością: " .
        $_SESSION['testowa'] . "<br>";
}
?>

Wystarczy odświeżyć skrypt kilka razy. Za pierwszym razem sesja zostanie ustawiona a kolejne odświeżenia będą już zwracały jej wartość.

Proponuję też wyłączyć jeden z serwerów memcached aby sprawdzić czy php poprawnie odwoła się do drugiego serwera.

RainbowDB

Pewnego popołudnia przeczytałem artykuł o tęczowych tabelach i pod jego wpływem zacząłem kombinować jak zrobić coś takiego ale swojego.

Do bazy wrzuciłem cały słownik z aspell’a dla języka polskiego (słowa z ogonkami i bez), angielskiego, kilka słowników z popularnymi hasłami, długą listę haseł z yourock i kilka innych. Sporo się nakombinowałem aby wygenerować dużo kombinacji szczególnie pod kątem polskich haseł (wszystkie: misiaczki, dupeczki, itp…) razem z różnymi popularnymi modyfikacjami (typu: misiaczki1, DUPECZKI, etc…). Na tym etapie miałem już wystarczająco dużo haseł aby bawić się dalej.

Zacząłem kombinować z różnymi długościami łańcuchów – niestety funkcja redukująca zmniejszała nieco entropię kolejnych hashy w łańcuchu, co dla długich łańcuchów mocno zmniejszało wydajność przeszukiwania bazy. Ostatecznie postawiłem na bardzo krótkie łańcuchy (a nuż się coś tafi a przynajmniej nie będzie mocno opuźniać wyszukiwań).

Obecnie baza ma ponad 200 milionów haseł i dla nich wygenerowane łańcuchy o długości 10 ogniw. Co daje teoretycznie możliwość sprawdzenia ok 2 miliardów hashy. Wydaje się to sporo ale w stosunku do innych baz dostępnych w sieci jest to kropla w morzu. A co do innych baz… pomyślałem, że skoro ktoś już zrobił taką bazę dobrze to można by to wykorzystać. I tak dorzuciłem funkcję “obsysania” innych bazy hashy w przypadku gdy moja danego hasha nie potrafi rozpoznać. Taki zabieg (którego implementacja zajęła mi 1 popołudnie) wdrożony dla kilku różnych wyszukiwarek znacznie poprawił efektywność rozpoznawania haseł.

Bazę postanowiłem udostępnić w postaci strony www – jak na razie ze stosunkowo liberalnymi zasadami korzystania (czyli bez ograniczeń na liczbę wyszukiwań, konieczności rejestracji itp).

Z bazy można korzystać pod poniższym linkiem:

RainbowDB

Wyłączyłem tą stronkę po dobrych dwóch/trzech latach działania – ruch jest przekierowany na nowy projekt Hybrid Rainbow DB, w którym oprócz tęczowych tablic zaimplementowałem hybrydową tęczową tablicę – nowa aplikacja sprawdza o wiele więcej haseł.

Włam na lokalne konto root’a

Jeżeli tu zaglądasz pewnie zdarzyło Ci się kiedyś, że przykładowo wygrzebujesz jakiś stary serwer i nie masz pojęcia co na nim było, ani do czego służyło, czy jeszcze działa… Albo jeszcze inaczej – serwer działał tak długo, że wszystkie osoby znające hasło na root’a przeszły na emeryturę lub zmarły… Nieistotne 🙂

Jest pewna prosta sztuczka, pozwalająca wbić się na konto root’a nie znając hasła – dając nam możliwość jego zmiany. Potrzebne dwa restarty ale za to nie trzeba korzystać z żadnychlive cd.

  1. Na początek zmuszamy serwer do restartu – mieć nadzieję, że maszyna obsługuje ACPI i delikatne wciśnięcie przycisku power subtelnie ją wyłączy. Jeśli to nie zadziała to kojarzą mi się tylko brzydkie rzeczy 🙂
  2. Gdy po restarcie załaduje się grub na domyślnej opcji bootowania wybieramy edycję wciskając “e“.
  3. Wybieramy linię zaczynającą się od kernel i znów wybieramy edycję wciskając “e“.
  4. Jeżeli znajduje się tam parametr ro to zastępujemy go rw i dopisujemy na końcu init=/bin/bash
  5. Wbijamy “enter” zapisując zmieniony wiersz.
  6. Bootujemy się z tak zmienionej konfiguracji wciskając “b“.
  7. Po chwili system zamiast wystartować init’a i uruchamiać usługi, ląduje w bash’u z uprawnieniami root’a. A skoro mamy root’a to możemy wpisać passwd i zmienić rootowi hasło 🙂
  8. Teraz już tylko reboot i startujemy system normalnie – hasło root’a powinno działać.

Niestety ta prosta sztuczka nie działa na wszystkich linux’ach – szczególnie tych wykorzystujących initramfs-tools. Na tych systemach trzeba ciut więcej pokombinować ale przynajmniej ma się jakiś punkt wyjścia.

MySQL – dostęp zdalny na szybko

Instalacja serwera MySQL na Debianie jest niezwykle prosta i sprowadza się do jednego polecenia:

sudo apt-get install mysql-server

Polecenie to zainstaluje i uruchomi usługę serwerową MySQL. W czasie instalacji będziemy proszeni o podanie hasła dla root’a (które oczywiście dobrze jest zapamiętać bądź zapisać).

Tak zainstalowana baza nasłuchuje na lokalnym porcie (localhost:3306) umożliwiająć dostęp wyłącznie root’owi. Jest to bardzo bezpieczna konfiguracja… Ale jeśli nie mamy zamiaru na tej samej maszynie instalować oprogramowania zarządzającego to nie zawsze jest to wygodne, tym bardziej gdy przykładowo mamy działającego phpmyadmin’a na jakimś serwerze www. W takim przypadku pierwszą rzeczą, którą robię jest udostępnienie dostępu zdalnego dla root’a. Warto zaznaczyć że uprawnienia root’a można nadać dowolnemu użytkownikowi (np. romanowi) co jest dużo bezpieczniejszą konfiguracją niż działanie bezpośrednio na koncie root’a (którego nazwa jest powszechnie znana).

Dostęp zdalny dla root’a

Aby umożliwić zdalne zalogowanie się do bazy z uprawnieniami root’a trzeba ustawić odpowiednie GRANT’y, robimy to tak:

mysql -u root -p

mysql> GRANT ALL PRIVILEGES ON *.* TO 'roman'@'%'
> IDENTIFIED BY 'haslo dla zdalnego roota' WITH GRANT OPTION;
mysql> FLUSH PRIVILEGES;
mysql> exit

Pierwsze polecenie połączy nas z bazą prosząc o hasło podane w czasie instalacji.

Kolejne to polecenia SQL’owe, które pozwalają użytkownikowi ‘roman’ łączącemu się z hosta ‘%’ (dowolnego) identyfikującemu się hasłem ‘haslo dla zdalnego roota’. Jeżeli chcemy ograniczyć dostęp do tylko jednego zdalnego adresu to zamiast znaku procenta wpisujemy ten adres IP. Tak dodany użytkownik ma też prawo nadawania uprawnień (GRANT OPTION). Polecenie FLUSH PRIVILEGES przeładowuje uprawnienia – umożliwiając logowanie z podanymi wcześniej uprawnieniami.

Pozostało nam zmienić ustawienia serwera tak aby nasłuchiwał nie tylko na localhoście. W tym celu edytujemy plik /etc/mysql/my.cnf:

sudo vim /etc/mysql/my.cnf

Odszukujemy następującą linię:

bind-address = 127.0.0.1

Linię tę możemy zakomentować co będzie skutkować nasłuchiwaniem przez serwer na wszystkich skonfigurowanych adresach IP (taki sam efekt da wpisanie w polu adresu 0.0.0.0). Można też wpisać tylko jeden adres IP w przypadku gdy na serwerze jest ich kilka i nie chcemy aby serwer był dostępny na wszystkich.

Ostatnim krokiem jest zrestartowania serwera MySQL aby zadziałały wprowadzone w pliku konfiguracyjnym zmiany. Można to zrobić tak:

invoke-rc.d mysql restart

Jeżeli właśnie założyłeś i udostępniłeś nowy serwer bazodanowy MySQL to oszczędź sobie pracy w przyszłości i od razu ustaw przechowywanie tabel InnoDB w osobnych plikach.