fsckup #3 - cannot create directory, too many links

Projektując serwis WWW lub zlecając firmie zewnętrznej jego napisanie, rzadko kiedy przewidujemy, co się stanie, gdy wzrośnie nam ruch np. 10-krotnie, kiedy skończy sie nam miejsce na dysku, albo w jaki sposób będziemy przechowywać ciągle przybywające dane od użytkowników. Każdy z nas lubi adrenalinę i pracę w stresie, dlatego nie zastanawia się nad możliwymi problemami które mogą wystąpić w przyszłości. Czasami mamy nawet spokój przez kilka miesiący, a szczęśliwcy latami. Oczywiście są sytuacje, których nie da się przewidzieć, ale warto przynajmniej spróbować zastanowić się, co może spowodować problemy wraz ze wzrostem ruchu.

Problem pojawił się... ;-/ i nie ma za bardzo jak zwalić winy na twórce kodu ;-) albo jeszcze inaczej, nie zawsze chodzi o błędnie zaprojektowane rozwiązania zaimplementowane w kodzie serwisu, czasami problem występuje w połączeniu - problemy implementacji + błędna konfiguracja systemu, albo... źle dobrany filesystem. Takie mieszanki wybuchowe mają opóźniony zapłon, bo kto myśli o wyborze filesystemu... ;-)

No właśnie, filesystem... jaki wybrać? xfs? reiserfs? ext3? a może ext4? nie będę opisywał żadnego z nich, nie będę też robił żadnych testów wydajnościowych... ale poruszę ciekawą kwestię związaną z ograniczeniami w systemie ext3 (który jest jeszcze bardzo popularny). Dla przedstawienia sytuacji, wyobraźmy sobie serwis służący do składowania obrazków od użytkowników, serwis jest otwarty więc każdy może wrzucić swój plik, no i z czasem robi się tego naprawdę dużo...

Nasze obrazki składujemy w następujący sposób:

/obrazki/2010-03-24-11-54/obrazek_1.jpg
/obrazki/2010-03-25-13-27/obrazek_2.jpg
/obrazki/2010-03-25-13-31/obrazek_3.jpg
/obrazki/2010-03-26-21-52/obrazek_4.jpg

przynajmniej do czasu kiedy pojawi nam się komunikat:

# mkdir 2010-03-27-16-52
mkdir: nie można utworzyć katalogu `2010-03-27-16-52': Za dużo dowiązań

Zamierzeniem takiego rozwiązania było zapisywanie obrazków w katalogach zawierających datę, godzinę i minutę... wszystko jest fajnie, wyszukanie obrazka dodanego o konkretnej dacie nie stanowi żadnego problemu, ale po stworzeniu 32000 takich katalogów, uśmiech z twarzy nam zniknie... 32000 to magiczna liczba która niektórym powinna śnić się po nocach ;-) Jest to maksymalna ilość podkatalogów w jednym katalogu. Nie da się tego limitu zmienić bez zmiany filesystemu na inny, najszybszym rozwiązaniem teraz jest rozproszenie katalogów do wielu podkatalogów np. tak:

/obrazki_links/2010/03/24/11/54/obrazek_1.jpg
/obrazki_links/2010/03/25/13/27/obrazek_2.jpg
/obrazki_links/2010/03/25/13/31/obrazek_3.jpg
/obrazki_links/2010/03/26/21/52/obrazek_4.jpg

oraz dla zachowania spójności stworzenie linków symbolicznych:

ln -s /obrazki_links/2010/03/24/11/54/ /obrazki/2010-03-24-11-54
ln -s /obrazki_links/2010/03/25/13/27/ /obrazki/2010-03-25-13-27
itd...

Dodatkowo wspomnę, że maksymalny rozmiar pliku możliwy do utworzenia na partycji ext3 to 2TB, więc warto pamiętać również o tym.

Jako wniosek wyciągnijmy, żeby przed wdrożeniem zapoznać się z limitami systemu plików na którym powstanie rozwiązanie, bo późniejsze migrowanie zajmie nam zdecydowanie więcej czasu niż mkfs. ;-)

Q&A - jak pobrać stronę bez używania przeglądarki?

Q&A - tak nazwaliśmy nową sekcję wpisów, każdy wpis będzie składał się z pytania i krótkiej odpowiedzi, wytłumaczenia, lub po prostu skryptu. Mamy nadzieję, że przypadnie Wam do gustu. ;-)

Czasami gdy musisz szybko sprawdzić, czy serwer zwraca dane, lub chcesz podejrzeć nagłówki jakie zwraca serwer możesz to zrobić bezpośrednio z konsoli, używając telnet'a lub netcat'a. Przeglądarka wysyła określony zestaw nagłówków na port 80 serwera i w odpowiedzi otrzymuje nagłówki serwera oraz kontent (zawartość strony). My zrobimy dokładnie to samo, ale bez używania wspomagaczy ;-)

Czytaj dalej

haproxy, czyli mieszania pakietami HTTP ciąg dalszy

Ostatnio pisałem o haproxy jako balanserze dla ruchu HTTP, dziś chciałbym uzupełnić poprzedni opis o coś co nazywane jest popularnie content switchingiem... czyli kierowanie ruchu HTTP na podstawie żądanego contentu/typu danych. Dla prostszego wyobrażenia sytuacji przygotowałem taki obrazek:

mamy jeden serwis w jednej domenie http://yoursite.pl/ i posiadamy tylko jeden adres IP, ale chcemy podawać content w zależności od żądanego pliku z różnych serwerów, apache sprawdzi się przy (.php .html), lighttpd lekki, szybki, zwinny, etc. doskonale poradzi sobie z obrazkami (.gif .jpg), a nginx'a chcemy użyć bo też jest modny, do podawania (.js .css), bez balancera w postaci haproxy który pracuje w 7 warstwie i potrafi analizować i wykonywać przeróżne akcje niczym ninja z filmów z dzieciństwa na nagłówkach HTTP byłoby to trochę bardziej skomplikowane. Zakładam, że mamy działające haproxy i pozostałe serwery httpd, jeśli tak to można troszkę pobawić się konfiguracją.

Zacznę od wyjaśnienia kilku pojęć:

  • frontend - zgodnie z dokumentacją, frontendem jest nasza usługa która przyjmuje połączenia od użytkowników i je proxy'uje dalej do backendów.
  • backend - to definicja serwerów które odbierają ruch od balancer'a i zwracają wyniki, w naszym przypadku backendami są serwery Apache, Lighttpd, Nginx.
  • acl - Access List, dzięki acl'kom możemy zmatchować dany request np. na podstawie src, dst, path_reg, path_end

Nasza konfiguracja haproxy będzie wyglądać następująco:

root@xen7:~# cat /etc/haproxy.cfg

global
log 127.0.0.1 local0
log 127.0.0.1 local1 notice
maxconn 4096
user haproxy
group haproxy

defaults
log global
mode http
option httplog
option dontlognull
retries 3
maxconn 2000
contimeout 5000
clitimeout 50000
srvtimeout 50000
option httpclose

frontend frontend_xen
bind 192.168.1.220:80
option forwardfor

acl acl_apache path_end .php .html
acl acl_lighttpd path_end .gif .jpg
acl acl_nginx path_end .css .js

use_backend backend_xen2 if acl_apache
use_backend backend_xen3 if acl_lighttpd
use_backend backend_xen4 if acl_nginx
default_backend backend_xen2

backend backend_xen2
mode http
balance roundrobin
server xen2 192.168.1.241:80 check

backend backend_xen3
mode http
balance roundrobin
server xen3 192.168.1.242:80 check

backend backend_xen4
mode http
balance roundrobin
server xen4 192.168.1.125:80 check

Po starcie daemona przeprowadzamy mały test:

$ lwp-request -Sde http://xen7/test.php | grep Server
Server: Apache/2.2.8 (Ubuntu)

$ lwp-request -Sde http://xen7/test.css | grep Server
Server: nginx/0.5.33

$ lwp-request -Sde http://xen7/test.jpg | grep Server
Server: lighttpd/1.4.19

Kluczową rolę odegrały definicje frontendu oraz backendów, ale bez ACL nie moglibyśmy powiedzieć balancer'owi jaki ruch gdzie kierować. Definiujemy więc 3 ACLe (acl_apache, acl_lighttpd, acl_nginx) gdzie warunkiem jest rozszerzenie:

acl acl_apache path_end .php .html
acl acl_lighttpd path_end .gif .jpg
acl acl_nginx path_end .css .js

składnia:
acl nazwa_acla sposób_matchowania warunek

Kolejny bardzo ważny element konfiguracji to wskazanie balancerowi gdzie ma kierować ruch po dopasowaniu do konkretnej acl'ki:

use_backend backend_xen2 if acl_apache
use_backend backend_xen3 if acl_lighttpd
use_backend backend_xen4 if acl_nginx
default_backend backend_xen2

Jeśli request pasuje do ACL acl_apache to kieruj do backend_xen2, jeśli pasuje do acl_lighttpd to kieruj do backend_xen3, itd... jeśli nie dopasuje do żadnej acl kieruje do default'owego backendu backend_xen2.

Bardzo proste do wdrożenia i daje nam ogromne możliwości zarządzania streamem HTTP, operowanie na rozszerzeniach to ułamek możliwości tego load balancer'a, będę starał się co jakiś czas, opisywać inne/pozostałe ciekawe przykłady wykorzystania haproxy.

A jakie Wy widzicie zastosowanie dla tego mechanizmu?

Odzyskanie usuniętego loga pracującego daemona

Czy zdarzyło się Wam przypadkiem usunąć plik loga pracującej aplikacji? Jeśli tak, ale daemon tej aplikacji wciąż pracuje, to można go przywrócić. Pokażę to na przykładzie.
Apache uruchomiony testowo na moim desktopie  loguje do trzech poniższych plików:

root@impact:~# ls -l /var/log/apache2/*.log
-rw-r----- 1 root adm 1008 2010-03-19 16:17 /var/log/apache2/access.log
-rw-r----- 1 root adm 998 2010-03-19 16:17 /var/log/apache2/error.log
-rw-r--r-- 1 root root 0 2009-11-25 13:53 /var/log/apache2/other_vhosts_access.log

Usuwam np. plik access.log:

root@impact:~# rm -v /var/log/apache2/access.log
usunięty `/var/log/apache2/access.log'

1. Szukam głównego PID'a procesu Apache:

root@impact:~# ps -ef | grep apache
root 3323 1 0 Mar17 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 3324 3323 0 Mar17 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 3327 3323 0 Mar17 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 24579 3323 0 08:13 ? 00:00:00 /usr/sbin/apache2 -k start

2. Sprawdzam deskryptory plików dla tego PID'a:

root@impact:~# ls -l /proc/3323/fd/ | grep apache
l-wx------ 1 root root 64 2010-03-19 23:37 2 -> /var/log/apache2/error.log
l-wx------ 1 root root 64 2010-03-19 23:37 7 -> /var/log/apache2/other_vhosts_access.log
l-wx------ 1 root root 64 2010-03-19 23:37 8 -> /var/log/apache2/access.log (deleted)

3. Przepisuję plik z deskryptora do pliku loga (można użyć również cp albo dd):

root@impact:~# cat /proc/3323/fd/8 > /var/log/apache2/access.log

I już skasowany plik jest na "swoim miejscu":

root@impact:~# ls -l /var/log/apache2/*.log
-rw-r--r-- 1 root root 1008 2010-03-19 23:40 /var/log/apache2/access.log
-rw-r----- 1 root adm 998 2010-03-19 16:17 /var/log/apache2/error.log
-rw-r--r-- 1 root root 0 2009-11-25 13:53 /var/log/apache2/other_vhosts_access.log

Warto pod koniec wspomnieć, że usunięcie plików logów na partycji z kończącym się miejscem nie zawsze oznacza faktyczne zwolnienie miejsca, jeśli deskryptory tych plików są nadal otwarte. Ale to już jeden z tematów na inny artykuł. :-)

no space left on device...

Regularnie usuwasz niepotrzebne pliki z dysku, wszystkie logi również rotujesz, ale czasami zdarza Ci się widzieć 0% wolnego miejsca na dysku pomimo tego, że żaden dostępny plik czy katalog nie zajmuje jakoś specjalnie dużo. Jednym słowem czary... ;-) ale, że już dawno wyrośliśmy ze świętego mikołaja i wróżek, to zdradzę tajemnicę, że to niezupełnie czary, a raczej  błąd w konfiguracji. Tak więc, usiądźcie wygodnie, weźcie kawę do ręki i lecimy.

W momencie kiedy czytasz/piszesz z/do pliku system operacyjny otwiera deskryptor i przypisuje plik do niego, tak więc mamy uchwyt do którego sięga podczas każdej operacji. Piszemy coś do pliku... piszemy... piszemy... i nagle plik usuwamy (rm -rf plik), system nie zamknie deskryptora ponieważ nie otrzymał takiego polecenia (np. funkcja systemowa close()), tak więc pomimo tego, że naszego pliku już nie ma na dysku to deskryptor nadal pozostał otwarty i nadal możemy z niego korzystać (pisać i czytać). Przekładając to na najczęstszą sytuację posłużę się syslogiem oraz logami. Widzimy, że mamy dużo zajętego w miejsca w /var/log/, wypadałoby więc skasować najstarsze logi, tak więc rm -rf /var/log/stare_logi.txt no i jest super, trochę miejsca się zwolniło i od razu nam lepiej. Po jakimś czasie problem się znów pojawia, z tym, że już nie ma czego kasować, du -sh /var/log/ nie pokazuje dużych wartości, a w pozostałych katalogach również pustki. Sytuacja trochę dziwna, ale wbrew pozorom częsta i łatwa do odtworzenia. Syslog jest daemonem który zapisuje otrzymane dane do plików, tak więc otwiera deskryptory do plików z logami, my usuwając pliki, nie zamykamy poprawnie deskryptorów, no i syslog nadal loguje jak gdyby nigdy nic do tych plików pomimo tego, że fizycznie ich nie widać.

Jak więc sprawdzić czy mamy takie pliki? Jest na to bardzo prosty sposób, pokażę na pewnym przykładzie:

Mamy więc taki przykładowy skrypt PERL'owy, otwieramy plik (plik.log), zostaje utworzony deskryptor do pliku, w nieskończonej pętli zapisujemy do pliku ciąg znaków "www.varlog.pl", a na STDOUT wypluwamy znak kropki i odczekujemy 1 milisekundę. Celowo wywołanie close(PLIK), czyli zamknięcie deskryptora znajduje się poza pętlą. Uruchamiamy skrypt, na ekranie pojawiają się nam kropki, a plik.log wraz z upływem czasu rośnie:

jamzed@katha:~/varlog$ while [ 1 ]; do ls -la plik.log; sleep 1; done
-rw-r--r-- 1 jamzed jamzed 184320 2010-03-19 21:28 plik.log
-rw-r--r-- 1 jamzed jamzed 196608 2010-03-19 21:28 plik.log
-rw-r--r-- 1 jamzed jamzed 212992 2010-03-19 21:28 plik.log
-rw-r--r-- 1 jamzed jamzed 225280 2010-03-19 21:29 plik.log

Skasujmy teraz plik.log (rm -rf plik.log),

jamzed@katha:~/varlog$ rm -rf plik.log
jamzed@katha:~/varlog$ ls -la plik.log
ls: cannot access plik.log: No such file or directory

Wszystko super, pozbyliśmy się pliku, ale nasz skrypt PERL'owy (space.pl) nadal pisze do plik.log, sprawdźcie sami, że wolnego miejsca na dysku robi się coraz mniej ;-)

Ok... w tym przypadku wiemy jaki to jest plik, oraz jaki proces trzyma deskryptor, wystarczy taki proces zabić, lub przerwać CTRL+C, wtedy deskryptor zostanie zamknięty a miejsce na dysku zwolni się, ale co w sytuacji kiedy nie znamy procesu, ani pliku... użyjmy lsof'a. Narzędzie lsof, listuje nasze aktywne deskryptory, przy okazji  informując nas który został usunięty ;-)

jamzed@katha:~/varlog$ lsof -n|grep deleted
space.pl  22817   jamzed    3w      REG    8,2  3235840 786475 /home/jamzed/varlog/plik.log (deleted)

No i wszystko pasuje, proces space.pl pisze do 3 deskryptora czyli pliku (plik.log), spróbujmy zatem skillować space.pl i sprawdźmy co się stanie z ilością wolnego miejsca:

/dev/sda2             63440968  33750780  26467552 57% /home

oraz po skillowaniu procesu (kill -9 22817)

/dev/sda2             63440968  33746392  26471940 57% /home

Zwolniły się 4MB czyli rozmiar naszego plik.log.

Tak więc, jak mówi stare admińskie przysłowie, nic w przyrodzie nie ginie, czyli pamiętajcie o restarcie albo wysyłaniu HUP'a (o ile nasza aplikacja obsługuje taki sygnał) do procesu po skasowaniu plików które miał otwarte ;-)

Apache 1.3.X (error: conflicting types for 'getline') [patch]

Podczas kompilowania Apache 1.3.42 oraz 1.3.41 na Ubuntu 9.10 zauważyłem pewien problem i po krótkiej analizie okazało się, że Apache Foundation używa własnej wersji funkcji getline() do obsługi danych wejściowych od użytkownika. Gdy kompilacja odbywa się przy użyciu gcc (version 4.4.1 Ubuntu 4.4.1-4ubuntu9) występuje konflikt funkcji, pomiędzy getline() od Apache oraz getline() zdefiniowaną w stdio.h (man 3 getline).

SYNOPSIS
#define _GNU_SOURCE
#include

ssize_t getline(char **lineptr, size_t *n, FILE *stream);
ssize_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream);

CONFORMING TO
Both getline() and getdelim() are GNU extensions.  They are available since libc 4.6.27.

Problem jest dokładnie z plikami:

  • src/support/htdigest.c
  • src/support/htpasswd.c
  • src/support/logresolve.c

Podczas kompilacji błąd objawia się komunikatem:

make[2]: Entering directory '/home/jamzed/lab/apache_1.3.42/src/support'
gcc -c  -I../os/unix -I../include   -DLINUX=22 -DHAVE_SET_DUMPABLE -DUSE_HSREGEX -DUSE_EXPAT -I../lib/expat-lite -DNO_DL_NEEDED '../apaci` htpasswd.c
htpasswd.c:101: error: conflicting types for 'getline'
/usr/include/stdio.h:651: note: previous declaration of 'getline' was here
make[2]: *** [htpasswd.o] Error 1
make[2]: Leaving directory '/home/jamzed/lab/apache_1.3.42/src/support'
make[1]: *** [build-support] Error 1
make[1]: Leaving directory '/home/jamzed/lab/apache_1.3.42'
make: *** [build] Error 2

Najszybszym rozwiązaniem jest zmiana nazwy funkcji getline w tych plikach na inną, jeśli komuś nie chce robić się tego ręcznie, przygotowałem diff'a w formie patch'a (patch zmienia nazwy funkcji na jmz_getline).

pobierz plik "apache-getline.patch"

Aplikacja patch'a:

# wget http://www.varlog.pl/wp-content/uploads/2010/03/apache-getline.patch_.gz
# gzip -d apache-getline.patch.gz
# cp apache-getline.patch apache_1.3.42/ #kopiujemy plik patch'a do katalogu z rozpakowanymi źródłami
# cd apache_1.3.42 #wchodzimy do katalogu ze źródłami
# patch -p0 < apache-getline.patch #wykonujemy polecenie patch -p0 <
patching file src/support/htpasswd.c
patching file src/support/htdigest.c
patching file src/support/logresolve.c

i możemy spokojnie kompilować dalej ;-)

Marcowe spotkanie OWASP – PHP [video]

Przygotowaliśmy filmy video z ostatniego spotkania OWASP Poland Local Chapter, które odbyło się 10 marca w Krakowie. Zachęcamy gorąco wszystkich do oglądania.

Niestety nie obyło się bez problemów technicznych i nie mieliśmy możliwości nagrania video w lepszej jakości, za co przepraszamy.

Krzysztof Kotowicz
SQL Injection: complete walktrough (not only) for PHP developers

Łukasz Pilorz
Secure PHP framework

Korzystając z okazji, chcemy przypomnieć, że kolejne krakowskie spotkanie polskiego oddziału OWASP odbędzie się prawdopodobnie już w maju, więcej informacji na liście dyskusyjnej.