Как запустить Data Services за 30 минут "из коробки"

Интро

Если вы разрабатываете какой-то серьёзный проект, связанный с блокчейном Waves, то вы уже наверняка поставили свою ноду. Собственная нода позволяет обойти лимиты официального пула и сильно развязывает руки в плане возможностей взаимодействия с блокчейном.

Тем не менее, сильно нагружать ноду опасно и чревато отказами в работе. Чтобы сильно не нагружать ноду, а также чтобы дать доступ к большему количеству информации, ребята из Waves начали разработку Data Services. Официальные дата-сервисы находятся по этой ссылке, но пользоваться ими также довольно проблематично из-за лимитов, так как это “общие” сервисы, а значит, как только вы наиграетесь с ними и поймёте, что они вам действительно нужны, то встанет вопрос о запуске собственных дата-сервисов. Эта статья о том, как можно быстро запустить дата-сервисы Waves.

Исходные данные

Операционная система: Ubuntu 18.04 LTS
Количество ядер: 8
Количество RAM: 32GB
Количество места на диске: 640GB

Да, при текущем размере блокчейна Waves порядка 30GB, дата-сервисы занимают ещё около 500GB. Команда обещает ужать их примерно в 2 раза, но, в любом случае, возьмём с запасом.

Также, для ускорения, будем всё делать из под root. Да, несекьюрно, но кому надо - займётесь безопасностью дополнительно. Также сразу скажу, что гайд пока очень быстрый, подробности будут потом. По итогу всех действий Мы должны получить полностью рабочую систему, устойчивую к крашам и перезагрузкам инстанса.

Предполагаем, что у вас нет ноды, но если вдруг есть, то можно будет поиграться с конфиг файлами, собственно, поэтому тут их много, хотя можно было бы сделать всего 1 конфиг и запустить всё в 5 команд.

Шаг 1. Логин

Входим в систему

ssh root@your_ip

Шаг 2. Подготовка системы

Обновляем и перезагружаем систему:

apt update && apt upgrade -y && reboot

Шаг 3. Установка и настройка кубера

Всё будем поднимать в Kubernetes. Быстро, надёжно, удобно. Ставим Microk8s:

snap install microk8s --classic

Подставим алиас, чтобы меньше возиться с командной строкой:

snap alias microk8s.kubectl kubectl

Стартуем:

microk8s.start

Супер, кубер запущен, теперь наладим связь с внешним миром, чтобы можно было простукивать внейшний ip:

microk8s.enable dns

Всё готово :slight_smile: начинаем запускать поды.

Шаг 4. Создаём директории и нейм спейс

Конфиги настроены так, чтобы несмотря на запуск в контейнерах, стейт ноды и база PostgreSQL не удалялась при каждом рестарте контейнеров. Поэтому создадим 2 директории.

Для стейта ноды и базы PostgreSQL.:

mkdir /mnt/node && mkdir /mnt/DataServices && mkdir /mnt/DataServices/postgres

ВАЖНО: у вас должны быть директории /mnt/node/ и /mnt/DataServices/postgres/, иначе фейл.

Проверяем, что мы никуда не убежали из папки /root

pwd

Должны увидеть /root

Добавляем конфиг нейм-спейса:

nano namespace.yaml

кладём туда:

---
apiVersion: v1
kind: Namespace
metadata:
  name: data-services

кладём в кубер:

kubectl apply -f namespace.yaml

Шаг 5. Запускаем под с нодой

Создаём файл шаблона конфига ноды:

nano node-config.yaml

Кладём туда следующее:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: node-config
  namespace: data-services
data:
  template.conf: |
    waves {
      max-cache-size = 10

      network {
        known-peers = [
          "13.228.86.201:6868"
          "13.229.0.149:6868"
          "18.195.170.147:6868"
          "34.253.153.4:6868"
          "35.156.19.4:6868"
          "52.50.69.247:6868"
          "52.52.46.76:6868"
          "52.57.147.71:6868"
          "52.214.55.18:6868"
          "54.176.190.226:6868"
        ]
        bind-address = "0.0.0.0"
        port = 6868
      }
      wallet {
        password = "data-service"
      }

      blockchain.type = MAINNET
      matcher {
        enable = false
      }

      rest-api {
        enable = yes
        bind-address = "0.0.0.0"
        port = 6869
        api-key-hash = "…"
      }

      synchronization {
        synchronization-timeout = 120s
      }

      directory = "/waves/data"

      enable-blockchain-updates = no

      features {
        supported = [2] # NG
      }
    }

    kamon {
      enable = false
    }

Добавляем конфиг в кубер:

kubectl -n data-services apply -f node-config.yaml

Создаём деплой-конфиг для ноды:

nano node-deployment.yaml

Кладём туда это:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: node
  namespace: data-services
spec:
  minReadySeconds: 60
  revisionHistoryLimit: 2
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: node
  replicas: 1
  template:
    metadata:
      labels:
        app: node
    spec:
      containers:
        - name: node
          image: wavesplatform/node:latest
          imagePullPolicy: Always
          env:
            - name: WAVES_NETWORK
              value: 'MAINNET'
            - name: WAVES_HEAP_SIZE
              value: '3g'
            - name: WAVES_LOG_LEVEL
              value: 'INFO'
            - name: WAVES__REST_API__ENABLE
              value: 'yes'
            - name: WAVES_CONFIG_FILE
              value: '/opt/waves/template.conf'
          volumeMounts:
            - mountPath: '/waves'
              name: node-data
            - name: config-volume
              mountPath: /opt/waves/template.conf
              subPath: template.conf
          resources:
            requests:
              memory: '8000Mi'
              cpu: '3000m'
            limits:
              memory: '12000Mi'
              cpu: '6000m'
      volumes:
        - name: node-data
          hostPath:
            path: /mnt/node
            type: Directory
        - name: config-volume
          configMap:
            name: node-config

Отдаём в кубер:

kubectl -n data-services apply -f node-deployment.yaml

Теперь пробросим наружу REST API ноды. Создаём конфиг:

nano node-service.yaml

кладём туда

---
apiVersion: v1
kind: Service
metadata:
  name: node-service
  namespace: data-services
  labels:
    app: node
spec:
  type: NodePort
  ports:
    - port: 6869
      nodePort: 30036
      targetPort: 6869
      protocol: TCP
  selector:
    app: node

и отдаём в кубер:

kubectl -n data-services apply -f node-service.yaml

Шикарно. Теперь рест апи ноды можно потрогать тут: http://your_ip:30036

Шаг 6. Запускаем под с PosgreSQL

Создаём конфиг:

nano postgresql.yaml

кладём туда

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-init
  namespace: data-services
data:
  init.sh: |
    #!/bin/bash
    set -e
    wget -qO- https://raw.githubusercontent.com/wavesplatform/blockchain-postgres-sync/master/schema.sql | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB"

---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: data-services
  labels:
    app: postgres
spec:
  ports:
    - port: 5432
      protocol: TCP
  selector:
    app: postgres

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: data-services
spec:
  minReadySeconds: 60
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: postgres
  replicas: 1
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:alpine
          imagePullPolicy: Always
          env:
            - name: POSTGRES_PASSWORD
              value: 'postgres'
            - name: POSTGRES_USER
              value: 'postgres'
            - name: POSTGRES_DB
              value: 'mainnet'
          volumeMounts:
            - name: config-volume
              mountPath: /docker-entrypoint-initdb.d/init.sh
              subPath: init.sh
            - mountPath: /var/lib/postgresql/data
              name: postgredb
          resources:
            requests:
              memory: '3000Mi'
              cpu: '2000m'
            limits:
              memory: '16000Mi'
              cpu: '3000m'
      volumes:
        - name: postgredb
          hostPath:
            path: /mnt/DataServices/postgres
            type: Directory
        - name: config-volume
          configMap:
            name: postgres-init

и добавляем в кубер:

kubectl -n data-services apply -f postgresql.yaml

Шикарно, под с PostgreSQL запущен, размечен и готов принимать индексы :slight_smile:

Шаг 7. Настраиваем краулер

Сейчас нам нужно поднять под с краулером, который будет парсить стейт ноды и распихивать его в дата-сервисы. Для этого создаём конфиг:

nano crawler.yaml

и кладём туда это:

---
apiVersion: apps/v1
kind: Deployment
metadata:
 name: crawler
 namespace: data-services
spec:
 minReadySeconds: 60
 strategy:
   type: Recreate
 selector:
   matchLabels:
     app: crawler
 replicas: 1
 template:
   metadata:
     labels:
       app: crawler
   spec:
     containers:
       - name: crawler
         image: wavesplatform/blockchain-postgres-sync:develop
         imagePullPolicy: Always
         env:
           - name: NODE_ADDRESS
             value: node-service.data-services:6869
           - name: PGHOST
             value: postgres.data-services
           - name: PGDATABASE
             value: 'mainnet'
           - name: PGUSER
             value: 'postgres'
           - name: PGPASSWORD
             value: 'postgres'
           - name: BLOCKS_PER_REQUEST
             value: '10'
           - name: BLOCKS_PER_UPDATE
             value: '5'
           - name: UPDATE_THROTTLE_INTERVAL
             value: '500'
           - name: ON_CONFLICT
             value: 'nothing'
         resources:
           requests:
             memory: '2000Mi'
             cpu: '1000m'
           limits:
             memory: '4000Mi'
             cpu: '2000m'

Запускаем под:

kubectl -n data-services apply -f crawler.yaml

Шикарно! Осталось совсем немного.

Шаг 8. Запускаем дата-сервисы и пробрасываем их наружу

Создаём конфиг:

nano data-service.yaml

Кладём туда:

---
apiVersion: v1
kind: Service
metadata:
  name: ds-service
  namespace: data-services
  labels:
    app: ds
spec:
  type: NodePort
  ports:
    - port: 3000
      protocol: TCP
      targetPort: 3000
      nodePort: 30037
  selector:
    app: ds

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ds
  namespace: data-services
spec:
  minReadySeconds: 60
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 0
  selector:
    matchLabels:
      app: ds
  replicas: 1
  template:
    metadata:
      labels:
        app: ds
    spec:
      containers:
        - name: ds
          image: wavesplatform/data-service
          imagePullPolicy: Always
          env:
            - name: PORT
              value: '3000'
            - name: PGHOST
              value: postgres.data-services
            - name: PGPORT
              value: '5432'
            - name: PGDATABASE
              value: 'mainnet'
            - name: PGUSER
              value: 'postgres'
            - name: PGPASSWORD
              value: 'postgres'
            - name: PGPOOLSIZE
              value: '20'
            - name: LOG_LEVEL
              value: 'info'
            - name: DEFAULT_MATCHER
              value: '3PJaDyprvekvPXPuAtxrapacuDJopgJRaU3'
          resources:
            requests:
              memory: '2000Mi'
              cpu: '1000m'
            limits:
              memory: '4000Mi'
              cpu: '2000m'

и добавляем под:

kubectl -n data-services apply -f data-service.yaml

Дата-сервисы запущены и доступны по адресу http://your_ip:30037

Шаг 9. Добавляем в сервисы свечки

Создаём конфиг:

nano ds-candle.yaml

и кладём туда

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ds-candles
  namespace: data-services
spec:
  minReadySeconds: 60
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: ds-candles
  replicas: 1
  template:
    metadata:
      labels:
        app: ds-candles
    spec:
      containers:
        - name: ds-candles
          image: wavesplatform/data-service-candles
          imagePullPolicy: Always
          env:
            - name: PGHOST
              value: postgres.data-services
            - name: PGPORT
              value: '5432'
            - name: PGDATABASE
              value: 'mainnet'
            - name: PGUSER
              value: 'postgres'
            - name: PGPASSWORD
              value: 'postgres'
            - name: PGPOOLSIZE
              value: '20'
            - name: LOG_LEVEL
              value: 'info'
            - name: CANDLES_UPDATE_INTERVAL
              value: '2500'
            - name: CANDLES_UPDATE_TIMEOUT
              value: '20000'
            - name: RECALCULATE_ALL_CANDLES_ON_START
              value: 'true'
          resources:
            requests:
              memory: '1000Mi'
              cpu: '500m'
            limits:
              memory: '2000Mi'
              cpu: '1000m'

Добавляем под:

kubectl -n data-services apply -f ds-candle.yaml

Шаг 10. Добавляем в сервисы инфу о парах

Создаём конфиг:

nano ds-pairs.yaml

и кладём туда

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ds-pairs
  namespace: data-services
spec:
  minReadySeconds: 60
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: ds-pairs
  replicas: 1
  template:
    metadata:
      labels:
        app: ds-pairs
    spec:
      containers:
        - name: ds-pairs
          image: wavesplatform/data-service-pairs
          imagePullPolicy: Always
          env:
            - name: PGHOST
              value: postgres.data-services
            - name: PGPORT
              value: '5432'
            - name: PGDATABASE
              value: 'mainnet'
            - name: PGUSER
              value: 'postgres'
            - name: PGPASSWORD
              value: 'postgres'
            - name: PGPOOLSIZE
              value: '20'
            - name: LOG_LEVEL
              value: 'info'
            - name: PAIRS_UPDATE_INTERVAL
              value: '2500'
            - name: PAIRS_UPDATE_TIMEOUT
              value: '20000'
          resources:
            requests:
              memory: '1000Mi'
              cpu: '500m'
            limits:
              memory: '2000Mi'
              cpu: '1000m'

Добавляем под:

kubectl -n data-services apply -f ds-pairs.yaml

Всё готово! Дата-сервисы запущены и готовы к бою.

Факапы

Как обычно, без факапов никуда. Вот несколько команд, которые сильно помогут в отладке.

Чтобы убедиться, что всё огонь, вводим команду:

kubectl -n data-services get all

Должны увидеть что-то вроде

Чтобы залезть в под и убедиться, что всё хорошо, нужно ввести команду:

kubectl -n data-services logs -f pod/*podname*

podname можно взять тут:

Например, зайдя в под ноды, вы должны увидеть что-то вроде:

Видим, что нода синкается, а краулер успешно забирает блоки.

Ещё может быть так, что под не стартует. Тогда нам надо посмотреть почему. Для этого вводим команду:

kubectl -n data-services describe pod/*podname*

Где взять podname, надеюсь, помните.

Пролог

Гайд будет пополняться, например планирую засунуть сюда ingress настройки.

Текущая версия гайда: v0.1

По любым вопросам пишите в эту ветку. Бyдем разбираться что и как.

2 Симпатий

Расписано всё подробней некуда, но как ни пытался в 30 минут не уложился, однако всё заработало, спасибо за помощь!