Uruchamianie węzła Bitcoina w Dockerze
Historia z okopów o pakowaniu bitcoind w kontener: początkowe pobieranie bloków, które uczy pokory, dlaczego blockchain musi żyć w wolumenie, zamykanie interfejsu RPC i czego pełny węzeł uczy o długo działających usługach ze stanem.
Chcieliśmy pełnego węzła Bitcoina — własnego, nie API od kogoś trzeciego — by
odpytywać blockchain wprost i rozumieć sieć od środka, a nie przez czyjąś
abstrakcję. Docker właśnie osiągnął 1.0, kontenerizowaliśmy wszystko inne, więc plan
był oczywisty: spakować bitcoind w obraz i uruchomić jak każdą inną usługę. Plan
był słuszny. Wykonanie nauczyło nas wiele o tym, w czym kontenery są dobre, w czym
nie i jak długo działające oprogramowanie ze stanem naprawdę się zachowuje. Oto
historia z okopów.
Po co pełny węzeł i po co bitcoind
Pełny węzeł pobiera i waliduje cały blockchain oraz sam egzekwuje każdą regułę
konsensusu. Alternatywa — zaufanie hostowanemu API — jest wygodna i dokładnie tą
zależnością, którą chcieliśmy usunąć: węzeł, który wszystko weryfikuje lokalnie, nie
zawdzięcza swoich odpowiedzi nikomu. bitcoind (demon Bitcoin Core) to implementacja
referencyjna, wystawia interfejs JSON-RPC, który mogliśmy wołać z Ruby, i jest
pojedynczym długo działającym procesem — co na papierze czyni go idealnym
obywatelem Dockera.
Pierwszy Dockerfile i fałszywe poczucie postępu
Sam obraz jest niczym niezwykłym: bazowy system, binarka bitcoind, plik konfiguracji, wystaw porty, uruchom demona.
FROM ubuntu:14.04
RUN apt-get update && apt-get install -y bitcoind && rm -rf /var/lib/apt/lists/*
COPY bitcoin.conf /root/.bitcoin/bitcoin.conf
EXPOSE 8333 8332
CMD ["bitcoind", "-printtoconsole"]
docker build -t btc-node .
docker run -d --name btc btc-node
Zbudował się, wystartował, logi przewijały się połączeniami z peerami i napływającymi blokami. Wyglądał na skończony. Nie był nawet blisko — obraz to były łatwe 10%, a każda trudna lekcja wciąż czaiła się przed nami.
Początkowe pobieranie bloków uczy pokory
Pierwszy zderzeniem z rzeczywistością jest początkowe pobieranie bloków (IBD): zupełnie nowy węzeł musi pobrać i zwalidować cały łańcuch od bloku genesis, zanim będzie użyteczny. W 2014 to mniej więcej dwadzieścia gigabajtów, które napływają wolno, bo twój węzeł nie tylko pobiera — weryfikuje każdy blok i każdy podpis, co jest pracą związaną z CPU i dyskiem. Na skromnej instancji chmurowej zajmuje to nie minuty, lecz większość dnia, czasem więcej.
Wypadły z tego od razu dwie porażki. Po pierwsze, synchronizacja jest na tyle ciężka dla I/O, że głodzi wszystko inne na małej maszynie — węzeł z radością przypnie dysk. Po drugie, i boleśniej: kilka razy zabiliśmy i zrestartowaliśmy kontener podczas konfiguracji (jak się robi z kontenerami), a przez sposób, w jaki to skonfigurowaliśmy, częściowy łańcuch nie przetrwał. Ponowne pobieranie dwudziestu gigabajtów, bo zrestartowałeś kontener, to rodzaj błędu, który popełniasz dokładnie raz. Wskazał wprost na prawdziwą lekcję.
Stan nie może żyć w kontenerze
To jest sedno. Zapisywalna warstwa kontenera jest z założenia efemeryczna — usuń kontener i jej nie ma. To funkcja dla bezstanowych serwerów aplikacji i katastrofa dla węzła, którego całą wartością jest dwudziestogigabajtowy zbiór danych, który budował przez dzień. Blockchain musi żyć w wolumenie, na hoście, poza cyklem życia kontenera:
docker run -d --name btc \
-v /data/bitcoin:/root/.bitcoin \
-p 8333:8333 \
btc-node
Teraz dane łańcucha żyją w /data/bitcoin na hoście. Kontener staje się naprawdę
jednorazowy: zabij go, przebuduj obraz, zaktualizuj bitcoind, wystartuj świeży
kontener wskazujący na ten sam wolumen, a podejmie dokładnie tam, gdzie skończył.
Dyscyplina, która się tu wykrystalizowała, to ta, którą teraz stosujemy wszędzie —
kontener jest procesem; wolumen jest stanem, a mają zupełnie różne cykle życia.
Mylenie ich to najczęstszy sposób, w jaki ludzie się parzą, uruchamiając
oprogramowanie ze stanem w Dockerze. Dane chcą też być na szybkim, odpowiednio
dużym dysku, bo węzeł będzie rósł w nieskończoność; „20 GB” to podłoga, nie sufit.
Zamykanie interfejsu RPC
Powodem, dla którego zbudowaliśmy węzeł, był jego interfejs JSON-RPC, i jest to
zarazem rzecz najpewniej mogąca cię okraść, jeśli będziesz nieostrożny. bitcoind
chętnie przyjmie komendy RPC — w tym, na węźle z włączonym portfelem, takie, które
ruszają środki — więc interfejs nigdy nie może być wystawiony na otwarty internet.
# bitcoin.conf
server=1
rpcuser=prawdziwa_nazwa
rpcpassword=długie_losowe_hasło
rpcallowip=127.0.0.1 # albo konkretna wewnętrzna podsieć — nigdy 0.0.0.0
Subtelność specyficzna dla kontenera gryzie tutaj: wewnątrz kontenera rpcallowip i
bindowanie współgrają z przestrzenią sieciową Dockera, więc łatwo myśleć, że
związałeś RPC z localhostem, faktycznie wystawiając je na cokolwiek, co dosięgnie
sieci kontenera. Reguła, przy której zostaliśmy: publikuj port P2P (8333), by
węzeł mógł rozmawiać z peerami, ale nigdy nie publikuj portu RPC (8332) na host ani
świat. Dosięgaj RPC tylko z wnętrza sieci Dockera — z kontenera aplikacji w tej
samej prywatnej sieci — by interfejs ruszający pieniędzmi nigdy nie był routowalny z
zewnątrz. Traktuj poświadczenia RPC jak sekrety, którymi są.
Czego pełny węzeł uczy o usługach ze stanem
Zdejmij specyfikę Bitcoina, a ten projekt był skondensowaną lekcją uruchamiania dowolnej długowiecznej usługi ze stanem w kontenerze:
- Oddzielaj proces od jego danych, bezwzględnie. Wolumeny to nie dodatek po fakcie; dla oprogramowania ze stanem są projektem. Zdecyduj, co jest efemeryczne, a co cenne, zanim cokolwiek uruchomisz.
- Uwzględnij koszt zimnego startu. Usługa potrzebująca dnia, by stać się użyteczną, zmienia sposób, w jaki robisz wszystko — deploye, failover, skalowanie. Nie restartujesz beztrosko węzła w trakcie synchronizacji, a każda automatyzacja musi o tym wiedzieć.
- Kontener nie zmienia twojej postawy bezpieczeństwa. Łatwo poczuć, że „to jest w kontenerze” to forma izolacji; dla wystawionego na sieć, obsługującego pieniądze demona nie jest. Interfejs RPC potrzebował dokładnie tej samej troski, co na gołym serwerze.
- Jednorazowy kontener, trwałe dane to cel. Wygrana, gdy już tam dotarliśmy, była realna: upgrade bitcoind stał się „zbuduj nowy obraz, podmień kontener, zachowaj wolumen” — trzydziestosekundową operacją bez ponownej synchronizacji, na infrastrukturze, która robi code review i odtwarza się jak kod.
Węzeł działa od tamtej pory szczęśliwie, przeżywając restarty, przebudowania obrazu i upgrade bitcoind bez ponownego pobierania choć jednego bloku. Dotarcie tam kosztowało nas jedną przypadkową dwudziestogigabajtową ponowną synchronizację i twarde spojrzenie na to, gdzie naprawdę żyje nasz stan — tania cena za lekcję, którą zastosowaliśmy do każdej bazy i usługi ze stanem, jaką od tamtej pory skonteneryzowaliśmy. Docker nie uczynił uruchomienia węzła Bitcoina trywialnym. Uczynił je odtwarzalnym, co na długowiecznym kawałku infrastruktury okazuje się rzeczą, która się liczy.