Powszechnym w projektach informatycznych jest napotykanie się na trudności, wynikające z braku aktualizacji projektów do obecnie stosowanych rozwiązań technologicznych, co znacząco utrudnia lub całkowicie uniemożliwia wprowadzanie w nich nowych funkcjonalności. Tak również było w przypadku projektu, który realizowaliśmy w listopadzie 2021 roku.
Nasz DevOps Engineer/SysAdmin Bartłomiej zmierzył się z dużym wyzwaniem, polegającym na utworzeniu skryptu do w pełni zdalnej i automatycznej aktualizacji 12-letniej dystrybucji Linuxa – Gentoo do najnowszego Debiana 11.
Jak tego dokonał? Dowiesz się czytając poniższy artykuł!
Problem
Klient dysponował urządzeniami, odpowiadającymi za odtwarzanie ścieżek audio. Zespół Fingoweb opracował nowoczesną aplikację opartą na Node.js, która łączy się z API napisanym w języku PHP działającym w chmurze AWS. Pobiera ona pliki muzyczne oraz harmonogram odtwarzania i samodzielnie rozpoczyna odtwarzanie kampanii. Niestety wszystkie urządzenia posiadały zainstalowaną dystrybucję Gentoo z okolic 2010/2011 roku.
Po przeprowadzonej analizie, okazało się że nie ma możliwości uruchomienia aplikacji na tak starym systemie, ze względu na przestarzały kernel oraz bibliotekę standardową C. Co więcej, brakowało wiele potrzebnych bibliotek do uruchomienia aplikacji oraz nie było skompilowanej obsługi ALSA w kernelu. Nie chcieliśmy ponosić kosztów wymiany wszystkich urządzeń, w związku z czym zdecydowaliśmy się na aktualizację systemu operacyjnego.
Rozwiązanie
Pierwszym krokiem był wybór odpowiedniego systemu operacyjnego. Po rozważeniu wszelkich za i przeciw, padło na jedną z najpopularniejszych dystrybucji Linuxa – Debiana 11.
Debian 11 jako jedna z niewielu dystrybucji, wspiera jeszcze architekturę i386, czego Gentoo nie robi już od 2018 roku. Miało to duże znaczenie ze względu na procesory zastosowane w urządzeniach klienta, oraz fakt, że nowy system musiał być skonfigurowany częściowo z poziomu istniejącego systemu – co z poziomu 32 bitowego systemu operacyjnego nie byłoby możliwości uruchamiania narzędzi skompilowanych na 64 bitowy system.
Ze względu na rozmieszczenie urządzeń w wielu lokalizacjach, cały proces instalacji systemu musiał zostać przeprowadzony zdalnie. Dostęp do urządzeń był możliwy dzięki SSH.
Podzieliłem proces instalacji na 3 główne etapy:
- Pobranie przygotowanych paczek z systemem – bez przerywania pracy istniejącej aplikacji;
- Odpowiednie przygotowanie partycji;
- Docelowa instalacja systemu.
Wykorzystałem fakt, że dysk który znajdował się w playerze, podzielony był na kilka partycji – w tym jedna duża partycja na której znajdują się wszystkie pliki muzyczne. Bez tego instalacja Debiana 11 z poziomu Gentoo byłaby niemożliwa.
Kolejnym wyzwaniem było utrzymanie połączenia z VPN oraz z siecią Internet po instalacji systemu. Urządzenie mogło być podpięte do internetu za pomocą sieci przewodowej Ethernet, sieci WiFi lub modemu LTE. Potrzebowałem stworzyć odpowiedni mechanizm, który potrafił odczytywać pliki konfiguracyjne, następnie je parsował oraz tworzył odpowiednią konfigurację dla NetworkManagera na docelowym systemie. Proces musiał być powtórzony dla WiFi, LTE oraz VPNa.
Niestety po przygotowaniu całej konfiguracji, okazało się że nie jest możliwe skorzystanie z nowego debootstrapa, którego zadaniem było zainstalowanie bazowego systemu Debian na nowej partycji z poziomu zainstalowanego już systemu. Nie dało się przygotować nowego systemu plików na czystej partycji, gdyż tak jak wcześniej, w przypadku Node.js binarki z nowego systemu wymagały nowszego ABI glibc, niż to, które było dostępne na działającym Gentoo.
W związku z tym wykonałem cykl aktualizacji: Debian 5->6->7->8->9->10->11 w pełni automatycznie i bez dostępu do Internetu.
Stworzyłem archiwum tar, z którego korzystał debootstrap do początkowej instalacji podstawowego systemu na nowej partycji. Po zainstalowaniu podstawowego systemu przez debootstrap musiałem zainstalować wszystkie wymagane pakiety za pomocą apta i tasksela.
Tasksel i APT:
DEBIAN_FRONTEND=noninteractive apt-get install -y
--force-yes $(aptitude search ~pstandard ~prequired ~pimportant -F%p)
Debian nie pobierał pakietów z repozytoriów zdalnych, lecz z wcześniej przygotowanych przeze mnie repozytoriów, które zostały pobrane w kroku pierwszym na dysk urządzenia. Dla każdej wersji systemu było przygotowane osobne repozytorium. Następnie po zainstalowaniu pakietów, skonfigurowałem bootloader, którego zadaniem było uruchomienie odpowiedniego systemu operacyjnego.
W celu automatyzacji kolejnych aktualizacji napisałem skrypty, które przeprowadzały ją samoczynnie po restarcie. Do tego celu został wykorzystany cron w Debianie, który może wykonywać komendy po reboocie. Za pomocą odpowiedniej komendy zostały dodane wiersze do crontaba. Po zakończeniu każdego etapu crontab był czyszczony, a następnie dodawana była linia która umożliwiała wystartowanie kolejnego etapu po restarcie urządzenia.
Dodatkowo z działania każdego etapu były zapisywane logi, które pomagały sprawdzić czy po zakończonej aktualizacji wszystko wykonało się pomyślnie.
cron:
crontab -r
(crontab -l ; echo "@reboot /root/upgrade_67 >> /root/upgrade_67.log 2>&1") | crontab -
Po wykonaniu wszystkich aktualizacji, dopiero w wersji Debiana 11 urządzenie uzyskiwało dostęp do sieci. Dodatkowo uruchomiono full-upgrade, aby zapewnić, że w systemie będą zainstalowane najnowsze pakiety. Po upgradzie automatycznie usuwane są stare partycje z Gentoo oraz aktualna partycja jest rozszerzana na 100% dysku za pomocą programów parted, growpart oraz resize2fs.
parted /dev/sda --script rm 2
parted /dev/sda --script rm 3
parted /dev/sda --script rm 5
parted /dev/sda --script rm 5
parted /dev/sda --script rm 5
growpart /dev/sda 4
growpart /dev/sda 5
partx -u /dev/sda
update-grub
grub-install /dev/sda
cat << EOF | debconf-set-selections
grub-pc grub-pc/install_devices multiselect /dev/sda
grub-pc grub-pc/install_devices_empty boolean false
grub-pc grub-pc/install_devices_disks_changed multiselect
EOF
Efekt
W grudniu 2021 roku przekazaliśmy klientowi gotową paczkę wraz z instrukcją instalacji nowego systemu. Zgodnie z wytycznymi, klient miał pobrać paczkę na urządzanie, rozpakować ją, a następnie wykonać 4 niezbędne komendy:
01_download – skrypt ten pobiera paczkę z pakietami potrzebnymi do instalacji oraz aktualizacji systemu – urządzenie zużywa wtedy około 1 GiB transferu internetowego.
02_stop_app – przed rozpoczęciem przygotowania dysku należy zamknąć wszystko co korzysta z partycji na której będzie przygotowywany Debian
03_prep_disk – jest to przygotowanie dysku do instalacji, skrypt przygotowuje partycję oraz narzędzia i rozpakowuje gotową paczkę repozytoriów
04_start_upgrade – skrypt aktualizujący system – proces zajmuje około 1h. Tutaj zużycie transferu jest na poziomie 50 MiB, tylko w przypadku aktualizacji jakiś pakietów w końcowym etapie instalacji.
Cały proces instalacji, rozpoczynając od uruchomienia upgrade’u do momentu, aż można się zalogować po SSH, zajmuje na docelowym urządzeniu max 2h. Po przejściu wszystkich etapów, na urządzeniu instaluje się zaktualizowany system Debian 11.
Dodatkowo, po instalacji i aktualizacji systemu, od razu jest instalowana aplikacja oparta na Node.js co pozwala klientowi na korzystanie w pełni z naszego rozwiązania.