Skip to content
← Wszystkie wpisy
5 min czytania Dawid Skłodowski

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.