Panel produkcyjny drukarni, który opisujemy, składa arkusze do druku z projektów klientów, generuje PDF przez wkhtmltopdf albo Gotenberga i wysyła je na kontroler EFI Fiery w drukarni tunelem WireGuard z serwera w chmurze. To system drukarni, która sprzedaje spersonalizowane druki przez własny sklep i Allegro. Opisujemy jego architekturę, inwentaryzację przed przeniesieniem, porządki w archiwum S3 liczącym około 2,5 TB oraz backup i plan odtworzenia przed przeniesieniem na nowy serwer. Nie podajemy wyników sprzedażowych, bo nie były częścią tego zadania.
Jak działa panel produkcyjny drukarni internetowej?
Kod powstał w Laravelu 9 na PHP 8.1, z bazą MariaDB, i działa w kontenerach Docker. Zamówienie przechodzi przez kilka kroków:
- Klient zamawia w sklepie drukarni albo na Allegro. Zamówienia z Allegro pobiera cykliczne zadanie, a token OAuth2 odświeża osobna komenda.
- Klient personalizuje produkt w kreatorze, a projekt zapisuje się jako JSON w S3.
- Pracownik drukarni wybiera, czy arkusz idzie prosto na maszynę przez EFI Fiery, czy ma być pobrany jako PDF.
- Etykiety InPost i DPD panel generuje przez API przewoźników, a fakturę wystawia przez API SzybkaFaktura.
Produkty personalizowane mają listę gości. Na arkuszu SRA3 (320×450 mm, w pionie albo poziomie) mieści się kilka kopii tego samego wzoru, czyli użytków, i na każdym drukuje się inne imię. PDF powstaje w 600 DPI.
Jak drukować z aplikacji w chmurze na EFI Fiery w drukarni?
Kontroler EFI Fiery udostępnia REST API, ale stoi w sieci lokalnej drukarni, a panel w chmurze. Serwer łączy się z routerem drukarni tunelem WireGuard, a router przekazuje ruch do sieci lokalnej, w której jest Fiery. Panel zapisuje w bazie stan wydruku, więc pracownik widzi, które arkusze już poszły na maszynę.
W takim układzie najsłabszym punktem jest sam tunel. Gdy drukarnia zgłasza, że „panel nie drukuje”, pierwsze sprawdzenie to stan WireGuarda po obu stronach, a dopiero potem kod aplikacji. Dlatego usługa tunelu musi startować razem z systemem.
Dlaczego Gotenberg zamiast wkhtmltopdf?
Arkusze powstają z HTML, a panel ma dwa renderery PDF. Starszy to wkhtmltopdf, nowszy to Gotenberg, który renderuje w Chromium i działa jako osobny kontener z API HTTP. Wyboru dokonuje się parametrem w adresie, osobno dla pobierania PDF i dla wysyłki na Fiery, więc przejście na Gotenberga nie wymaga przełączania wszystkiego naraz.
Powody są dwa. wkhtmltopdf korzysta ze starego silnika WebKit i nie jest już rozwijany. W tym projekcie wiąże też architekturę serwera: używana paczka istnieje tylko dla amd64, więc tańsze serwery ARM odpadają, dopóki wkhtmltopdf zostaje w systemie.
Migracja ujawniła różnicę, którą łatwo przeoczyć. Przy wysyłce na Fiery użytki na arkuszu z Gotenberga miały inne odstępy niż przy pobieraniu PDF. Kod wstawiał do maski arkusza fragment HTML razem z tagiem <body>. Chromium widział zagnieżdżone body i nakładał domyślny margines 8 px, a wkhtmltopdf to ignorował. Pomogła zmiana saveHTML() tak, żeby zwracała cały dokument zamiast elementu body, w czterech miejscach tej metody.
Od czego zacząć przeniesienie takiego systemu?
Od inwentaryzacji. Na jednym serwerze pracowało 7 kontenerów: aplikacja, MariaDB, phpMyAdmin, Gotenberg, Nginx Proxy Manager z własną bazą i Portainer, a do serwera kierowało 21 domen. Spisaliśmy, co działa, jakie zadania cykliczne są potrzebne, które usługi muszą startować z systemem i które porty powinny być dostępne tylko z sieci wewnętrznej. Właściciel zdecydował, że zmiany konfiguracji wejdą razem z przeniesieniem na nowy serwer, a ten etap obejmuje dokumentację, archiwum i backup.
Masz ten sam problem? Nikt nie wie, co dokładnie działa na Twoim serwerze? Zrobimy inwentaryzację i plan przeniesienia, zanim cokolwiek wyłączymy. Opisz sytuację →
Jak uporządkować archiwum S3 z projektami klientów?
Bucket S3 miał 2,4–2,5 TB i około 930 tys. obiektów, bez reguł cyklu życia i bez wersjonowania. Największe pozycje:
| Zawartość | Rozmiar | Stan |
|---|---|---|
| Projekty klientów (szablony i JSON) | 1,07 TB | Aktualna lokalizacja |
| Projekty w starej lokalizacji | 508 GB | Kod nadal ma awaryjny odczyt stąd |
| Arkusze PDF zamówień | 479 GB | W użyciu |
| Pliki wstępnie generowane | 266 GB | Kod zapisujący wyłączony, nic ich nie używa |
| Zdjęcia tymczasowe | 50 GB | Nieużywane |
Baza miała 397 tys. odwołań do plików projektów, a w S3 było 344 tys. takich plików, więc około 52 tys. odwołań wskazywało na pliki, których już nie ma. Dlatego projektów nie dało się czyścić po samej dacie. Procedura porównuje zawartość S3 z bazą i dopiero na tej podstawie wybiera pliki do usunięcia.
Retencję ustalił właściciel: szablony i cliparty zostają, projekty klientów i arkusze PDF starsze niż 12 miesięcy idą do usunięcia, pliki tymczasowe w całości. Usunęliśmy około 320 GB śmieci i 623 739 projektów starszych niż rok, a archiwum zmalało z 2,4 TB do 264,9 GiB w 146 622 plikach. Wynik skopiowaliśmy 1:1 do Hetzner Object Storage, a rclone check nie wykazał żadnej różnicy.
Jak zrobić backup serwera, w którym kontenery zmieniano ręcznie?
Kontener aplikacji zmieniano ręcznie przez docker exec i jego warstwa zapisu urosła do 8,3 GB. Odbudowa z docker compose build dałaby inny system niż ten na produkcji. Dlatego archiwum obejmuje cały katalog danych Dockera (32 GB) i katalog aplikacji razem z node_modules i vendor, łącznie około 45 GB przed kompresją. Po spakowaniu pełne archiwum serwera ma 18,5 GB: aplikacja i dane Dockera, zrzut bazy, crontab i instrukcja odtworzenia. Odtworzenie polega na zatrzymaniu Dockera, rozpakowaniu archiwum i ponownym starcie, bez budowania obrazów.
Aplikację da się postawić w całości z kopii w Hetznerze, nawet bez dostępu do obecnego dostawcy chmury. Do przeniesienia zostały nowy serwer, zmiana DNS i nowy adres końcówki WireGuard po stronie drukarni. Docelowo panel trafi na tańszą maszynę.
Jak sprawdzić, czy backup sklepu naprawdę działa?
Sklep drukarni na WordPressie stoi na osobnym serwerze. Tam także przygotowaliśmy backup do Hetzner S3 i plan odtworzenia krok po kroku. Jeden duży plik ZIP nie wchodził w grę: na dysku było 41 GB wolnego miejsca przy około 76 GB danych. Bazy i konfiguracje trafiają do małego archiwum (451 MB) z 30-dniową retencją, a pliki synchronizuje przyrostowo rclone sync. Z kopii wypadło około 62 GB, m.in. 20 GB starych kopii z wtyczki UpdraftPlus, 8,6 GB cache i logi. Wolumen bazy nie jest kopiowany jako pliki, tylko jako zrzuty SQL, bo kopia plików działającej bazy jest niespójna.
Pierwszy test wykrył, że zrzut głównej bazy (5 GB) przerywa się na tabeli opcji WordPressa:
mysqldump: Error 2020: Got packet bigger than 'max_allowed_packet'Mniejsza baza zrzucała się poprawnie, a mysqldump | gzip zwraca kod wyjścia gzipa, więc bez testu skrypt raportowałby sukces, a najważniejszej bazy nie byłoby w kopii. Pomogły dwie zmiany: --max-allowed-packet=1G i sprawdzanie PIPESTATUS[0].
W kontenerach WordPressa też znaleźliśmy ręczne zmiany: limit procesów PHP-FPM podniesiony z 50 do 75 przez edycję pliku w działającym kontenerze i pakiety doinstalowane przez apt. Backup zapisuje teraz te pliki konfiguracyjne, listę pakietów i wynik docker diff, a plan odtworzenia ma gotowy fragment do Dockerfile. Odtworzenie mniejszej bazy do bazy testowej dało 95 tabel i 41 385 rekordów w tabeli wpisów, po czym bazę testową usunęliśmy. Pełny test odtworzenia na czystym serwerze wymaga osobnej maszyny i nie był częścią tego etapu.
Kto powinien utrzymywać taki system?
Panel drukarni łączy rzeczy, które rzadko zna jedna osoba: Laravel, renderowanie PDF, API maszyny drukującej, VPN do sieci zakładu, Docker i S3. W ASAPDevs prowadzimy takie systemy jako zewnętrzny dział IT. Rozwijamy aplikacje webowe. Przenosimy je na nowe serwery w ramach migracji, a potem utrzymujemy w modelu outsourcingu IT, razem z kopiami, aktualizacjami i monitoringiem tego, co w takim systemie najczęściej po cichu przestaje działać. Zaczynamy od inwentaryzacji i sprawdzonej kopii, bo bez nich każda zmiana na produkcji jest zakładem.
