Przeprowadzka serwera. Planowałem sekundy przerwy, wyszły dwie minuty
· · Nie jednym promptem · odcinek 7 · Read in English
Przeprowadzki nie lubi nikt. Pudła, taśma, a na końcu i tak okazuje się, że w szafie na strychu stało coś, o czym nikt nie pamiętał 😉
Z serwerami jest dokładnie tak samo. Tydzień temu przenieśliśmy z Klaudiuszem wszystko, co miałem na serwerze, na nową maszynę. Zakupomat poszedł na końcu. Zapowiadałem tę przeprowadzkę już przy Macu mini - "następny w kolejce serwer". No to opowiem, jak było.
Serwer, który rósł latami
Stary serwer to był mały VPS w OVH - 1 rdzeń, 2 GB pamięci. Przez lata zbierało się na nim wszystko - WordPress, kilka małych aplikacji, strony statyczne (w tym ta, na której to czytasz), przekierowania. Razem 13 domen i usług. Zakupomat dołączył do nich jako jeden z wielu.
I każda z tych rzeczy była postawiona "jak wtedy wypadało". Debian 11 już bez wsparcia, Python 3.9, PHP z zewnętrznego repozytorium, większość usług działająca jako ten sam użytkownik systemowy. A Apache w trybie, w którym każde otwarte połączenie to osobny proces. Dla Zakupomatu to ważne, bo każda otwarta aplikacja trzyma połączenie na żywo z serwerem (tak synchronizują się zmiany między telefonami domowników). Policzyliśmy, że sufit to mniej więcej 1000 rodzin aktywnych dziennie. Daleko? Rodzin przybywa szybciej, niż zakładałem 🙂
Bezpośredni impuls przyszedł z innej strony - łatka bezpieczeństwa PHP dla WordPressa. Załatałem i tego samego dnia powiedziałem: dość, przeprowadzamy się.
Nowy dom i ile kosztuje
Nowy serwer to OVH VPS-2 - 4 rdzenie (wirtualne, vCPU), 8 GB pamięci, 75 GB na dysku NVMe, Debian 13 postawiony od zera. Koszt - 38,40 zł netto miesięcznie, razem ze snapshotem. Za cztery razy więcej mocy niż dotąd. Dla porównania - stary serwer z automatyczną kopią kosztował 40 zł netto. Czyli więcej mocy za mniej pieniędzy. A to jeszcze nie koniec rachunku... (o tym niżej 😉)
Chciałem Warszawę. Warszawa była niedostępna dla całej linii tych serwerów, więc jest Mediolan. Dla użytkownika to 20-30 ms różnicy, czyli nic. Dane dalej w Unii, a kopie zapasowe dalej w Paryżu, czyli w innym miejscu niż serwer - tak jak powinno być.
Po przeprowadzce wszystkiego serwer się... nudzi. Obciążenie bliskie zeru, 1,2 z 7,6 GB pamięci zajęte. I dobrze, bo o to chodziło - Zakupomat ma mieć zapas na lata, a koszt ma zostać na poziomie, który nigdy nie zmusi mnie do reklam ani opłat od użytkowników.
Plan zamiast improwizacji
Zanim cokolwiek ruszyliśmy, Claude napisał plan w zgłoszeniu na GitHubie. Inwentaryzacja starego serwera, etapy, kto co robi i zasady, których trzymamy się do końca:
- stary serwer jest zapasem - niczego na nim nie kasujemy, powrót to zmiana jednego rekordu DNS,
- jedna usługa naraz - przygotowanie, test na nowym bez przełączania, zamrożenie zapisów, przełączenie, sprawdzenie,
- po przełączeniu stary serwer przekazuje ruch do nowego, więc nikt ze starym adresem w pamięci telefonu nie trafi w nieaktualne dane,
- hasła i klucze nigdy nie idą przez czat ani przez git,
- wszystko jako skrypty w repo, nic "ręcznie, bo szybciej",
- Zakupomat na końcu i tylko na moje wyraźne "wdrażaj".
Pierwszy krok, jeszcze przed jakimkolwiek przenoszeniem, był taki, o którym łatwo zapomnieć - skróciłem TTL w DNS. Najważniejsze było to dla Zakupomatu, który przenosiliśmy dopiero następnego dnia. TTL mówi telefonom, komputerom i operatorom, jak długo mogą pamiętać, pod jakim adresem jest serwer. U mnie była to godzina (3600 sekund), więc po zmianie adresu część ludzi przez godzinę pukałaby do starego serwera. Skróciłem do 5 minut (300 sekund) we wszystkich 7 domenach. Ale trzeba to zrobić z wyprzedzeniem - stara, godzinna wartość też jest zapamiętana i musi najpierw wygasnąć.
Pułapka w OVH - sama zmiana "domyślnego TTL" strefy niczego nie publikuje. Po pół godzinie dalej było 3600. Dopiero zapisanie edycji dowolnego rekordu wypchnęło zmianę. Warto sprawdzić, a nie wierzyć panelowi 😉
Podział był prosty. Wszystko, co w panelach (OVH, DNS, GitHub, menedżer haseł) i wszystkie decyzje - ja. Skrypty, porównania i sprawdzanie - Claude. A zgłoszenie było dziennikiem - Claude odhaczał kolejne kroki i dopisywał komentarze, co wyszło, a co nie.
Jedną decyzję podjąłem wbrew modzie. W planie stało "a najlepiej nginx". Zostałem przy Apache, tylko w nowocześniejszym trybie - 1024 wątki zamiast 150 procesów. Bo przy jednym procesie Pythona to on będzie następnym wąskim gardłem, a nie serwer WWW. A nginx to nie tylko przepisanie konfiguracji wszystkich stron. WordPress trzyma część swoich reguł (np. ładne adresy wpisów) w pliku .htaccess, który czyta Apache, a nginx go po prostu ignoruje. Bez żadnego błędu - strona niby działa, tylko część adresów po cichu przestaje. Na nginx przyjdzie czas razem z większą zmianą w backendzie. Jedna zmienna naraz.
Z tego samego powodu w dniu przeprowadzki najpierw wdrożyliśmy najnowszy kod Zakupomatu jeszcze na starym serwerze. Żeby wieczorem zmieniał się tylko serwer, a nie serwer i kod naraz.
Pierwszy wieczór: pięć usług w dziesięć minut... i wildcard
Pierwszego wieczoru poszło szybciej, niż się spodziewałem. System bazowy, kopie zapasowe, strony statyczne. A potem WordPress i cztery małe aplikacje - pięć usług w jakieś 10 minut, każda z przerwą od kilku sekund do minuty.
Tylko że wcześniej, przy stronach statycznych, przełączyliśmy w DNS rekord "wszystko inne" (tzw. wildcard). I BUM! Pięć usług, które według listy Claude'a miały własne rekordy, poszło razem z nim na nowy serwer, zanim nowy w ogóle wiedział o ich istnieniu. Z zewnątrz nie da się tego odróżnić - zapytanie o nazwę zwraca dokładnie to samo. Około 10 minut przerwy, naprawione bez ruszania DNS - nowy serwer przez chwilę przekazywał te nazwy do starego.
Lekcja, która trafiła do brainu: inwentaryzuj to, co serwer faktycznie obsługuje, a nie to, co jest zapisane w opisie. Lista nazw z konfiguracji serwera to prawda. Strefa DNS to tylko opis.
Drugi wieczór: Zakupomat i dwie minuty
Drugiego wieczoru, po moim "lecisz z przenosinami", Zakupomat przeszedł jednym skryptem. Zatrzymanie API na starym, ostatnia kopia bazy, import na nowym (217 rodzin), start, stary serwer jako pośrednik, kopie zapasowe od razu na nowym. Zakładana przerwa - sekundy.
Faktyczna - około 2 minut.
Na starym serwerze plik konfiguracji API był zwykłą kopią, a nie odnośnikiem do pliku, który poprawiał skrypt. Skrypt zmienił plik, którego serwer w ogóle nie czytał. Ten sam błąd co z wildcardem, tylko w innym miejscu - założenie, jak "powinno być", zamiast sprawdzenia, jak jest.
I zgadnijcie, kto akurat w tych dwóch minutach dodawał produkty do listy? Ja 😂 11 dodań dostało "Błąd serwera (HTTP 503)" i przepadło - kolejka offline w aplikacji obejmowała odhaczanie, ale nie dodawanie. Do tego komunikat, który nic nie mówi i nie podpowiada, co zrobić. Skończyło się dwoma zgłoszeniami - dodawanie też ma iść przez kolejkę, a komunikat ma mówić po ludzku i dawać przycisk "ponów". Przeprowadzka jako darmowy test odporności aplikacji 🙂
Pięć minut później pierwsze telefony łączyły się już bezpośrednio z nowym serwerem. Przez całą następną dobę przez stary przeszło 10 zapytań - to właśnie robota krótkiego TTL. Kolejne dni - już tylko skanery i boty.
Kopie, które widziały za dużo
Przy przeprowadzce postanowiłem, że kopie poza serwerem dostanie wszystko, nie tylko Zakupomat. Nowy kontener w Object Storage, nowy użytkownik z dostępem "tylko do swojego kontenera".
I wtedy wyszło, że ten "tylko do swojego" nie działa. Ani w nowym, ani w starym. W OVH pusta polityka dostępu nie znaczy "nic". Dostęp daje sama rola, a ona obejmuje wszystkie kontenery projektu. Klucz Zakupomatu od trzech tygodni widział wszystko.
Czy to był problem? Szczerze - do tej pory nie. Przez trzy tygodnie w tym miejscu nie było nic poza kopiami Zakupomatu, więc "wszystko" i "tylko Zakupomat" znaczyło to samo. Problem zaczął się dokładnie w momencie, w którym dołożyłem drugi kontener z kopiami innych usług. Od tej chwili szeroki dostęp miał znaczenie. A w decyzji w brainie od początku stało, że użytkownik jest "tylko do niego". Ładnie zapisane, nigdy nie sprawdzone.
Teraz każdy klucz ma politykę tylko na swój kontener i nie może kasować poprzednich wersji kopii. Nawet ktoś z kluczem z serwera nie usunie historii. Takiej izolacji nie można zapisać raz i zapomnieć - trzeba ją sprawdzić od strony, której ma bronić.
Druga rzecz to monitoring kopii. Do tej pory pilnował ich monitor w GitHub Actions, który co 15 minut pyta serwer o stan. Tylko że jak serwer leży, to nie ma kogo pytać... Dlatego kopie reszty usług pilnuje teraz Healthchecks - po każdej udanej kopii serwer daje mu znać, a jak znaku nie ma w dobę, dostaję maila. Złapie i leżący serwer, i wyłączony timer, i kopię, która po cichu przestała się robić.
A w szafie na strychu...
Przy okazji porządków zajrzałem też na drugi serwer. Tak, miałem drugi VPS. I okazało się, że od mniej więcej roku nie było na nim nic, czego ktokolwiek używał. Żadna domena na niego nie wskazywała, ostatnie odnowienie certyfikatu - wrzesień 2025. Stare aplikacje wyprowadziłem stamtąd jesienią zeszłego roku... a serwer został. I co miesiąc grzecznie przychodził rachunek - 21 zł netto, razem ze snapshotem. Przez rok to ponad 250 zł za nic 😮
Po przeprowadzce rachunek za serwery spadnie z 61 zł netto miesięcznie za dwa do 38 zł za jeden - cztery razy mocniejszy.
Wszystko z niego jest zarchiwizowane na Macu mini, usługi zatrzymane, a za miesiąc bezpieczeństwa rezygnuję. Ten sam los czeka stary serwer główny.
Było i drugie znalezisko. Kod jednej z małych aplikacji nie istniał na GitHubie - w repo był tylko "Initial commit", a działająca wersja leżała wyłącznie na serwerze. Pamiętacie z Maca mini? Czego nie ma w repo, nie istnieje. Teraz już jest.
Gdzie pomylił się agent, a gdzie ja
Uczciwie - Claude się mylił. Lista rekordów DNS bez wildcardu. Dwóch użytkowników bazy uznał za "martwych", a wdrożenia tych aplikacji robiły nimi zmiany w bazie. Założył, że aplikacja nie czyta pliku z konfiguracją sama, a czytała. Plik konfiguracji API, który okazał się kopią.
Tylko że każdą z tych pomyłek złapało sprawdzenie zaraz po kroku, a nie szczęście. Najbardziej opłaciło się jedno małe narzędzie - skrypt, który pyta stary i nowy serwer o te same adresy i porównuje odpowiedzi, zanim ktokolwiek dotknie DNS.
A ja? Ja przez rok płaciłem za pusty serwer i przez trzy tygodnie wierzyłem w izolację, której nie sprawdziłem 😉
Co z tego zostaje
- serwer z Debianem 13, każda usługa na własnym użytkowniku i z ograniczonymi uprawnieniami,
- kopie poza serwerem dla wszystkiego, z cotygodniowym testem odtworzenia z kontenera, a nie z dysku,
- repo z konfiguracją całego serwera - następna przeprowadzka zacznie się od skryptu, nie od pamięci,
- dwie lekcje w brainie: "inwentaryzuj to, co serwer faktycznie ładuje" i "izolacja zapisana w decyzji to nie izolacja, dopóki jej nie sprawdzisz",
- dwa zgłoszenia, które zrobią Zakupomat odporniejszym na następną przerwę.
Przeprowadzka zajęła dwa wieczory. Bez agenta zajęłaby mi pewnie dwa tygodnie... i pewnie nie zrobiłbym połowy tych porządków.
Jeśli w Twojej firmie jest serwer, o którym "wszyscy wiedzą, co na nim jest" - to warto to sprawdzić, zanim zaczniecie go przenosić. Inwentaryzacja z tego, co system faktycznie robi, a nie z dokumentacji, to połowa sukcesu każdej migracji. W ANSLAN zaczynamy właśnie od tego - odtwarzamy wiedzę o systemie, zanim cokolwiek ruszymy.
Zakupomat jest darmowy i bez reklam. Od tygodnia na nowym serwerze 🙂