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 .img.gz zawierający słowocombined.
- ZArchiverem rozpakowujemy plik i umieszczamy 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 .img z karty SD.
- 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:
opkg update && opkg install shadow-useradd
useradd -m uzytkownik
passwd 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
Aby usunąć automatyczne logowanie na konto root, wystarczy wyedytować plik /etc/config/system i zamienić opcję option ttylogin '0'
na option ttylogin '1'
.
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).
Ciekawy artykuł, polecam minimum 4GB przeznaczyć na dysk pod Alpine. Ponieważ od razu po instalacji zajmuje prawie 1,4 GB. Btw. też lubię htopa :)