Wirtualizacja i konteneryzacja w Androidzie

Poniżej dzielę się moją metodą na uruchomienie kilku linuxowych maszyn wirtualnych oraz dowolnych kontenerów Dockera wewnątrz tych maszyn – to wszystko w telefonie z systemem Android. Dodatkowo opisuję również proces instalacji OpenWrt pod Androidem.

Oficjalnie docker dla Androida nie działa z powodu specyfiki androidowego krenela, ale można potestować Dockera w wirtualnej maszynie. Efekty pracy kontenerów można oglądać bezpośrednio w Androidzie, dzięki podwójnemu przekierowaniu portów – z kontenera do maszyny wirtualnej a następnie z maszyny wirtualnej do Androida. Rozwiązane jest oparte na programie do wirtualizacji i emulacji Limbo x86 PC Emulator, który to program z kolei jest oparty na QEMU. W Limbo (bez wsparcia KVM) bardzo responsywnie pracuje Alpine Linux, który posłuży nam jako host Dockera.

Instalacja Limbo x86 PC Emulator

W pierwszej kolejności instalujemy program Limbo x86 i w nim lekką dystrybucję Alpine Linux w wersji x86_64 (grupa VIRTUAL) – wykorzystując poniższe ustawienia maszyny wirtualnej:

Naciśnij tutaj, aby rozwinąć grafikę


Należy zwrócić szczególną uwagę na linię przekierowującą porty maszyny wirtualnej na Androida (ssh oraz poszczególnych serwerów WWW):
hostfwd=tcp::10022-:22,hostfwd=tcp::8080-:80,hostfwd=tcp::8081-:8081,hostfwd=tcp::8082-:8082
a od wersji 5.0 Limbo PC Emulatora używając formy:
tcp:10022:22,tcp:8080:80,tcp:8081:8081,tcp:8082:8082

Film

Nim przejdziemy do instalacji Dockera poniżej krótki filmik przedstawiający to rozwiązanie. Prezentuję uruchomienie maszyny wirtualnej Alpine w Limbo X86, uruchamianie kontenerów Dockera, testowanie pod Androidem zkonteneryzowanych tak serwerów WWW, uruchamianie dodatkowej maszyny wirtualnej z Debianem na ARM-owej wersji Limbo, łączenie się wirtualnej maszyny w Limbo x86 z maszyną w Limbo ARM, łączenie się z serwerem SSH kontenera z zewnętrznego komputera:



Instalacja Dockera

Po dostosowaniu Alpine do swoich potrzeb dodajemy do pliku /etc/apk/repositories jedną linię poniższym poleceniem:
echo 'http://dl-cdn.alpinelinux.org/alpine/latest-stable/community' >> /etc/apk/repositories
następnie wpisujemy:
apk update
apk add docker wget
service docker start

Docker działa poprawnie jeżeli w wyniku poniższego polecenia wyświetli się wersja serwera:
docker info | grep rver

Jeżeli docker wystartuje, to możemy dodać go do uruchamiania wraz z systemem
rc-update add docker boot

Jeżeli jednak z powodu ograniczeń telefonu lub tabletu Docker się nie uruchomi jako usługa (jak w moim przypadku), to należy skorzystać z mojego obejścia tworząc poniższy plik uruchomieniowy:

service cgroups start
sleep 5
rm /var/run/docker.pid 2> /dev/null 
rm /var/run/docker.sock 2> /dev/null
containerd >> /var/log/containerd.log 2>&1 &
sleep 15
dockerd --dns 8.8.8.8 --mtu=1500 >> /var/log/docker.log 2>&1 &

Ponieważ jest to również przyspieszony kurs Dockera, to dla lepszego zobrazowania ograniczymy się do podstawowych funkcji tego narzędzia. Po wcześniejszym zabezpieczeniu polecam wyłączenie demona ssh i logowanie się przez telnet, co nieco odciąży urządzenie (telned jest zawarty w Alpine w paczce busybox-extras).

Dodajemy grupę docker i dołączamy swojego użytkownika do tej grupy:
addgroup docker -g 101
adduser nazwa_użytkownika docker

Od tej pory możemy pracować z Dockerem bez używania konta roota – w wersjach produkcyjnych Dockera zalecane jest jednak korzystanie z polecenia sudo i pominięcie tego kroku.

Instalacja kontenerów

Pobieramy najnowszy image Debiana:
docker pull debian

Jeżeli pobieranie zakończy się komunikatem: TLS handshake timeout, to do parametrów uruchamiania dockerd dodajemy jeszcze:
--registry-mirror=http://f2d6cb40.m.daocloud.io

Uruchamiamy kontener, instalujemy serwer lighttpd i opuszczamy kontener:
docker run --name=www_srv -it ubuntu bash
apt update
apt install lighttpd
exit

Zatrzymujemy kontener, zapisujemy jego image, sprawdzamy czy nowy obraz zapisał się poprawnie i usuwamy zbędny kontener:
docker stop www_srv
docker commit www_srv www_srv
docker images
docker rm www_srv

W celu uporządkowania adresów IP tworzymy oddzielną sieć dla Dockera:
docker network create --subnet=172.18.0.0/16 dockernet

Sprawdzamy sieci:
docker network ls

Tworzymy współdzielone katalogi (busyboxowa wersja mkdir jest bardzo uproszczona i nie pozwala na używanie list):
mkdir -p ~/www/srv_1 && mkdir ~/www/srv_2
W katalogach tych umieszczamy dokument index.html – ja umieściłem placeholdery lighttpd, tylko zmieniłem im tyluł, aby łatwo było identyfikować ich pochodzenie w późniejszych testach.

Do testów instalujemy lighttpd również na wirtualce.

Przypisujemy port 80 kontenerów do portów 8081 i 8082 wirtualki oraz dyskową przestrzeń współdzieloną, a następnie uruchamiamy kontenery w tle i serwery lighttpd:

docker run --net dockernet -v ~/www/srv_1:/var/www/html --ip 172.18.0.3 --name=www_srv_1 -p 8081:80 -dit www_srv bash
docker run --net dockernet -v ~/www/srv_2:/var/www/html --ip 172.18.0.4 --name=www_srv_2 -p 8082:80 -dit www_srv bash
for i in 1 2; do docker exec www_srv_${i} service lighttpd start; done

Sprawdzamy procesy w uruchomionym kontenerze:
docker top www_srv_1

W celu inspekcji lub doinstalowania dodatkowego oprogramowania, możemy wejść do uruchomionego kontenera poleceniem:
docker exec -ti www_srv_1 bash

Możemy już uruchomić przeglądarkę WWW w Androidzie i sprawdzić jak wygląda strona http://localhost na portach od 8080 do 8082.

OpenWrt w systemie Android

OpenWrt to minimalistyczna dystrybucja Linuxa wykorzystywana głównie w routerach. Działa bardzo wydajnie w połączeniu z Limbo PC Emulator.

Opis instalacji:

  • Instalujemy aplikację ZArchiver.
  • Wchodzimy na stronę releases i odnajdujemy najwyższą, stabilną wersję OpenWrt.
  • Wchodzimy głębiej do podkatalogów targets -> x86 -> 64.
  • Pobieramy plik combined-ext4.img.gz.
  • ZArchiverem rozpakowujemy plik combined-ext4.img.gz i umieszczamy combined-ext4.img na karcie SD.
  • W Limbo x86 PC Emulator wybieramy:
    • User Interface Display: VNC
    • CPU/Board Architecture: x64, Machine Type: pc, CPU Model: core2duo, CPU Cores: 1, RAM Memory: 64, zaznaczone opcje: Enable MTTCG, Disable HPET, Disable TSC, Disable ACPI.
    • Disks Hard Disk A: wybieramy plik z karty SD: combined-ext4.img.
    • Boot Boot from: Hard Disk
    • Network Network: User, Network Card: e1000, DNS Server: 8.8.8.8, Host Forward: tcp:2222:22,tcp:8080:80
    • Advanced zaznaczone High Priority
  • Uruchamiamy maszynę i na końcu naciskamy klawisz ENTER, aby ujrzeć ekran powitalny OpenWrt i linię poleceń.
  • Uruchamiamy polecenie wykorzystujące dostarczony z Limbo serwer DHCP:
    uci set network.lan.proto=dhcp && uci commit
  • Zamykamy maszynę poleceniem poweroff (w Limbo nie działa reboot) i uruchamiamy ją ponownie.
  • Po restarcie czekamy nieco dłużej na nawiązanie połączenia z siecią.
  • W celu zapoznania się z interfejsem WWW i (przy okazji) aktywacji portu SSH, instalujemy pakiet luci:
    opkg update && opkg install luci
  • Ustalamy hasło roota poleceniem passwd.
  • Opuszczamy Limbo (maszyna pozostanie uruchomiona w tle), uruchamiamy przeglądarkę WWW i wchodzimy na stronę http://localhost:8080.
  • Logujemy się z ustalonym hasłem roota do interfejsu i wybieramy Network -> Firewall -> Traffic Rules -> na dole przycisk ADD, dodajemy w polach: Name: SSH, Destination port: 22 i naciskamy przycisk Save.
  • Od tej pory możemy logować się do OpenWrt dowolnym klientem SSH (polecam JuiceSSH lub ConnectBot) podając adres localhost i port 2222, ustawione wcześniej w sekcji Network w Limbo.

Instalacji dodatkowych pakietów (domyślnie niedostępnych) możemy dokonać dodając opcję –force-signature do polecenia update np.:
opkg update --force-signature && opkg install mc htop screen

Listę wszystkich dostępnych pakietów otrzymamy wydając polecenie:
opkg list

Ponieważ w maszynie wirtualnej OperWrt nie będzie spełniał roli routera, warto dodać obsługę użytkowników, gdyż domyślnie wszystkie operacje w OpenWrt wykonuje się z użytkownika root. Polecenia obsługi użytkowników nie są skompilowane w dostarczanej wersji BusyBox, więc po prostu ręcznie dodamy odpowiednie wpisy do wymaganych plików:

mkdir -p /home/uzytkownik
echo "uzytkownik:x:70000:70000:uzytkownik:/home/uzytkownik:/bin/ash" >> /etc/passwd
echo "uzytkownik:x:0:0:99999:7:::" >> /etc/shadow
echo "uzytkownik:x:70000:" >> /etc/group
passwd uzytkownik
chown uzytkownik /home/uzytkownik
opkg install sudo
echo "uzytkownik ALL=(ALL) ALL" >> /etc/sudoers

Od tej pory możemy logować się wybranym użytkownikiem, a operacje administracyjne wykonywać za pomocą polecenia sudo lub przełączając się na konto root poleceniem:
sudo -i

Szybkość działania

Jeżeli urządzenie z Androidem posiada jądro skompilowane ze wsparciem dla KVM, to prędkość działania będzie zbliżona do pracy maszyny wirtualnej na średniej klasy komputerze PC (o ile wirtualny system będzie zgodny z architekturą urządzenia) – wtedy też Limbo zapewnia prawdziwą wirtualizację zasobów. Jeżeli wsparcia brak, to Limbo przejdzie w tryb emulacji, który jest dużo wolniejszy, ale przy silnym androidowym urządzeniu nadal o akceptowalnej wydajności i responsywności. Jeżeli telefon lub tablet są wiekowe (lub tzw. wersją ekonomiczną), to Limbo można wykorzystać już tylko do zabawy z Alpine Linux (bez Dockera), OpenWrt lub ze starszymi wersjami Debiana.

Podsumowanie

Moje urządzenie (Gemini PDA) nie zwiera wsparcia dla KVM, ale i tak w połączeniu z OpenWrt, Alpine, Dockerem i odrobiną cierpliwości Limbo jest użytecznym oprogramowaniem. Im silniejsze urządzenie z Androidem, tym szybciej Limbo działa również w tym standardowym trybie emulacji. Program wymaga, by wszystkie obrazy były umieszczone na karcie pamięci. W trybie SDL renderowanie grafiki jest szybsze, ale aplikacja nie działa w tle. W trybie VNC renderowanie jest wolniejsze (co nie jest taki istotne w przypadku CLI), ale Limbo (i co za tym idzie – wirtualna maszyna) działa w tle nawet po wyłączeniu telefonu czy tabletu. Jeżeli nie chcemy oczekiwać na długie uruchomienie emulowanego systemu, to wirtualną maszynę można pauzować – wznowienie jej pracy zajmuje tylko kilka sekund. Docker na telefonie sprawdza się w prostych zastosowaniach programistycznych i administracyjnych. Polecam.

Więcej informacji o Limbo można znaleźć na stronie GitHub projektu oraz na nieistniejącej już oficjalnej stronie emulatora (w web.archive.org).

Jedna myśl na temat “Wirtualizacja i konteneryzacja w Androidzie

  1. Ciekawy artykuł, polecam minimum 4GB przeznaczyć na dysk pod Alpine. Ponieważ od razu po instalacji zajmuje prawie 1,4 GB. Btw. też lubię htopa :)

Dodaj komentarz

This site uses Akismet to reduce spam. Learn how your comment data is processed.