Всем привет! Я Влад, сооснователь MapMagic — каталога готовых маршрутов и планировщика для пеших походов и велопутешествий.
В первой статье я рассказал, как мы создали собственные топографические карты: откуда берём данные, как генерируем векторные тайлы и контуры изолиний. В этой статье разберем вопросы: как наладить процесс регулярного обновления карт и отдачи пользователям в векторном и растровом форматах.
Архитектура обновления карт
Общая схема процесса
Полный цикл обновления карт выглядит так:
Lambda Trigger → Preemptible VM → planet.osm.pbf → Tilemaker → PMTiles → S3
Разберём каждый компонент:
Lambda Trigger — Yandex Cloud Function, которая запускает процесс по расписанию
Preemptible VM — временная виртуальная машина генерации тайлов
planet.osm.pbf — источник свежих данных OpenStreetMap
Tilemaker — генератор векторных тайлов (о нём подробно в первой статье)
PMTiles — итоговый файл с тайлами в оптимизированном формате
S3 — объектное хранилище Yandex Cloud
Почему Preemptible VM
Preemptible VM (прерываемые виртуальные машины) — это ключевой элемент нашей стратегии экономии. В Yandex Cloud они стоят существенно дешевле обычных инстансов.
Трейдофф в том, что такая ВМ может быть принудительно остановлена в любой момент и не может работать дольше 24 часов. Но для нашей задачи это не проблема — если процесс прервался, просто запускаем заново.
Ещё один важный момент — авто-удаление дисков. Когда ВМ удаляется, все подключенные диски с флагом auto_delete тоже удаляются.
Пример конфигурации из python скрипта создания ВМ:
# Создаём политику планирования для прерываемой ВМ scheduling_policy = SchedulingPolicy( preemptible=config['preemptible'] # True по умолчанию ) # Ресурсы resources_spec=ResourcesSpec( memory=config['vm_memory_gb'] * 1024 * 1024 * 1024, cores=config['vm_cores'], core_fraction=config['vm_core_fraction'] ) # Загрузочный диск с авто-удалением boot_disk_spec=AttachedDiskSpec( auto_delete=config['auto_delete_disks'], disk_spec=AttachedDiskSpec.DiskSpec( type_id='network-ssd', size=config['boot_disk_size_gb'] * 1024 * 1024 * 1024, image_id=config['image_id'] ) )
Параметры реальной VM, используемой для генерации тайлов для всего мира:
24 vCPU, 240 GB RAM
50 GB загрузочный SSD
1023 GB кэш-диск (network-ssd-nonreplicated) — для хранения входных и выходных данных
HTTP API для управления генерацией
На ВМ крутится простой HTTP-сервер, который принимает команды:
Endpoint | Метод | Описание |
|---|---|---|
/status | GET | Статус генерации (ready/running/done/error) |
/run | POST | Запуск генерации с URL на PBF-файл |
/kill | POST | Принудительная остановка |
/log | GET | Логи процесса |
/health | GET | Health check |
Пример использования:
# Запуск генерации curl -X POST http://vm-ip:8000/run \ -H "Content-Type: application/json" \ -d '{"url": "https://planet.maps.mail.ru/pbf/planet-latest.osm.pbf"}' # Проверка статуса curl http://vm-ip:8000/status # Получение логов curl http://vm-ip:8000/log
Оркестрация через Lambda функции
За автоматизацию всего процесса отвечают две Yandex Cloud Functions.
vm_creator.py — создание ВМ
Первая функция создаёт виртуальную машину с нужными параметрами. Она вызывается по расписанию (раз в неделю) или вручную.
Ключевой элемент — cloud-init конфигурация, которая выполняется при первом запуске ВМ:
metadata={ 'user-data': f"""#cloud-config users: - name: {config['vm_user']} sudo: ['ALL=(ALL) NOPASSWD:ALL'] ssh-authorized-keys: - {config['ssh_public_key']} runcmd: # Монтируем кэш-диск - mkfs.ext4 -F /dev/vdb - mount /dev/vdb /mnt/cache - echo "/dev/vdb /mnt/cache ext4 defaults,nofail 0 2" >> /etc/fstab # Клонируем репозиторий - cd /mnt/cache - sudo -u {config['vm_user']} git clone {config['git_repo']} # Запускаем сервер - cd $REPO_DIR - sudo -u {config['vm_user']} nohup python3 server.py > /mnt/cache/server.log 2>&1 & """ }
Что происходит при старте:
Создаётся пользователь с SSH-ключом
Форматируется и монтируется кэш-диск в
/mnt/cacheКлонируется Git-репозиторий с конфигурацией Tilemaker
Запускается HTTP-сервер на порту 8000
Вся настройка занимает около 2-3 минут — после этого ВМ готова принимать команды.
vm_trigger.py — мониторинг и удаление
Вторая функция вызывается каждые нескольку минут и выполняет управление:
Находит ВМ по имени
Проверяет её статус через HTTP API
Запускает генерацию, если ВМ готова
Удаляет ВМ при выполнении условий
Логика принятия решения об удалении:
def should_delete_vm(vm, status_data, config, uptime_minutes, vm_name): """Определяет, нужно ли удалять ВМ""" uptime_hours = uptime_minutes / 60 current_status = status_data.get('status') # Условие 1: статус "done" — генерация завершена if current_status == config['target_status']: return True, "target_status_reached" # Условие 2: ошибка выполнения скрипта if current_status and current_status.startswith('error'): return True, "script_error" # Условие 3: прошло 20 минут, но сервер не ответил if (uptime_minutes >= config['max_startup_minutes'] and current_status in ['unknown', 'no_ip', 'connection_failed']): return True, "startup_timeout" return False, "no_reason"
Итого, три условия для удаления:
Статус “done” — нормальное завершение генерации
Ошибка выполнения скрипта — returncode ≠ 0
Сервер не ответил за 20 минут — что-то пошло не так при старте
Telegram уведомления
Все важные события дублируются в Telegram-чат:
def create_telegram_message(vm_name, vm_id, reason, uptime_minutes, vm_status, ...):
if action == 'triggered':
emoji = " 