Ускорение выполнения команды ipfs add
В двух предыдущих постах я рассказал о том, что такое ipfs и как развернуть сайт в ipfs и, помимо этого, для эксперимента настроил раздачу своего блога через ipfs: ipfs.romka.eu.
Сайт обслуживается дешёвой виртуальной машиной, а, как оказалось, ipfs довольно прожорлив до ресурсов процессора, особенно при выполнении команды ipfs add
. Несколько раз хостер просто молча прибивал мою виртуалку из-за превышения ею каких-то лимитов.
В моём случае ipfs работает в докер-контейнере, который запускается через docker compose
. Поэтому я сконфигурировал запуск контейнера следующим образом, чтобы сильно ограничить потребляемые ресурсы:
ipfs:
image: ipfs/kubo:latest
container_name: ipfs_container
volumes:
- /home/romka/ipfs:/data/ipfs
- /var/www/romka.eu/public:/data/ipfs/public:ro
<...>
restart: always
command: daemon --enable-gc --migrate=true --enable-pubsub-experiment
cpus: 0.3
mem_limit: 256m
memswap_limit: 256m
environment:
GOMAXPROCS: 1
Теперь ipfs-процесс ограничен по ресурсам и хостер доволен, но вот команда ipfs add
стала падать с ошибкой вида Error: unexpected EOF
. Я не стал копать глубже, но в свете того что ошибка появилась после добавления ограничений, похоже на то что это следствие того что ОС просто прибивает процесс ipfs из-за Out of Memory error (OOM). Дальше я расскажу как мне удалось исправить эту проблему.
После нескольких подходов к решению проблемы, среди которых были эксперименты с флагом --only-hash
, MFS и другие, максимально простым и надёжным решением оказалось просто останавливать ipfs демон на время добавления файлов. Однако есть один нюанс. С конфигурацией выше, а именно из-за строки command: daemon --enable-gc --migrate=true --enable-pubsub-experiment
, ipfs становится init-процессом (PID 1) внутри контейнера, поэтому при его остановке докер считает, что контейнер завершился, и из-за restart: always
тут же перезапускает его.
При этом с одной стороны мне важно чтобы контейнер автоматически рестартился если ipfs daemon падает, с другой стороны – мне нужна возможность иногда останавливать ipfs для того чтобы добавлять файлы. Я пробовал переписать конфиг так, чтобы использовался стандартный Докер-образ ipfs/kubo:latest
, но внутри контейнера запускался кастомный init-процесс, управляющий ipfs демоном. Однако в такой конфигурации мне не удалось сделать так, чтобы и была возможность временно остановить ipfs, и контейнер рестартился в случае падения по OOM.
В итоге пришлось собрать кастомный образ, в котором супервизор s6-supervise управляет ipfs демоном. Вот мой Dockerfile
:
FROM ipfs/kubo:latest AS kubo
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
s6 \
curl \
ca-certificates && \
rm -rf /var/lib/apt/lists/*
COPY --from=kubo /usr/local/bin/ipfs /usr/local/bin/ipfs
RUN mkdir -p /etc/s6/ipfs
COPY ./run-ipfs-daemon.sh /etc/s6/ipfs/run
RUN chmod +x /etc/s6/ipfs/run
ENTRYPOINT ["/usr/bin/s6-svscan", "/etc/s6"]
В этом образе выполняются следующие действия:
- берётся последний LTS-релиз Ubuntu,
- в него копируется последний доступный релиз
kubo
(это официальная реализация протокола ipfs), - устанавливается супервизор
s6
, - для этого супервизора написан скрипт
run-ipfs-daemon.sh
, запускающий ipfs daemon:
#!/bin/sh
exec ipfs daemon --enable-gc --migrate=true --enable-pubsub-experiment
Чтобы начать использовать этот образ, его нужно собрать. Для этого в директории где лежат указанные выше Dockerfile
и run-ipfs-daemon.sh
нужно выполнить следующую команду:
docker build -t ipfs-s6-supervise .
Здесь ipfs-s6-supervise
это имя нового образа, которое можно будет использовать для запуска контейнера.
Когда образ собран его нужно прописать в docker-compose.yaml
вместо ipfs/kubo:latest
. Помимо этого пришлось немного подправить пути куда монтируются директории внутри контейнера:
ipfs:
image: ipfs-s6-supervise
container_name: ipfs_container
volumes:
- /home/romka/ipfs:/root/.ipfs
- /var/www/romka.eu/public:/root/.ipfs/public:ro
<...>
restart: always
# Следующая строка больше не нужна, так как эта команда переехала в run-ipfs-daemon.sh
# command: daemon --enable-gc --migrate=true --enable-pubsub-experiment
cpus: 0.3
mem_limit: 256m
memswap_limit: 256m
environment:
GOMAXPROCS: 1
Теперь процедура публикации сайта в ipfs выглядит так:
docker exec ipfs_container s6-svc -d /etc/s6/ipfs
docker exec ipfs_container ipfs add -r --nocopy /root/.ipfs/public
docker exec ipfs_container s6-svc -u /etc/s6/ipfs
docker exec ipfs_container ipfs name publish /ipfs/<Root directory CID>/
С такой конфигурацией все около 10 Гб файлов сайта добавляются в ipfs меньше чем за пару минут.