Делаем песочницу для запуска кода на любом языке
Всем привет! Меня зовут Макс. Понадобилось для своего проекта мне запилить платформу, в которой бы я мог запускать код, любой, на любом языке — безопасно и быстро, и чтобы было удобно добавлять новый код.
Давайте попробуем это сделать 🚀
Исследуем готовые решения
Кандидат1. Если посидеть на гитхабе, то можно найти judge0 — крутая штука, сервис написан на руби, встроено 60+ языков.
Изоляция у него сделана просто: в один Dockerfile напиханы бинарники для всех языков. Но что с изоляцией выполняющего кода?
Изоляция — важный момент для такой задачи, тк код запускается любой и нужно оградиться от злонамерений — использование ресурсов, памяти, CPU, времени рабоыт сервиса и сама система должна быть не тронутой
Изоляция сделана в judge0 через ограния linux: cgroup и namespaces через старое решение для конкурсов isolate. Всем вроде хороша, но наружу торчит много ограничений — обновляется последние пару лет слабо — ребята продают свое АПИ через разные площадки, запуск много-файлового кода немного проблемная, куча не нужного функционала — тянут БД, сложные обертки.
Например даже TS код корректно запустить получилось с пируэтами, тк tsconfig никакой не поставляется и банальные типы вроде []Foo или Array<Foo> не работают из коробки (даже в их веб IDE).
Кандидат 2. Я пишу на го и часто соприкасался с песочницей Google для Golang. Поизучал их код — сделано прикольно. Основной пинцип основан на том, что есть сервис, который принимает го-код, компилирует его и потом отдает внутреннему сервису по приватной сети для выполнения. Этот внутренний сервис поднимает на каждый запрос (заранее) контейнеры, готовый принять скомпилированные бинарные данные и выполнить их.
Изоляция (которая важна) для среды контейнеров являлась всегда слабым местом, тк приложение, которое запускает их, само в докере и ему надо в докере поднимать другие контейнеры, а значит нужен привилегированный доступ. Для решения этой проблемы Google разработал решение — gVisor. Суть в том, что этот gVisor подменяет рантайм докера и когда сервис, создающий контейнеры, создает новый докер — он обращается к докеру с указанием, что будет работать через эту прослойку. Этим и достигается изоляция.
Параллельное решение — создавать виртуалки. Но виртуалки медленные, но очень изолированные, а контейнеры супер-быстрые. Google пошел по решению проблемы с изоляцией — написал ранйтайм для Docker. Его использует, например, Digital Ocean для запуска сервисов в App Platform. Тогда как AWS для своих сервисов пошел по пути ускорения виртуалок и появился Firecracker и по сути решил эту же проблему, но через виртуальные машины.
Пишем свое решение
Итого judge0 не подошел из-за слабой поддержки, минусами расширения и акценте на продажу API, а Google Playground оказался простым для прототипа — его за основу решения и решил взять.
Летс гоу!!!
Так как у нас предполагается любой язык, то вариант Google с сервисом для компиляции кода на Go не подходи и все уезжает в sandbox. Схема вышла такой:
Сборка/компиляция происходят в том же sandbox, где и выполнение кода.
Немного подшаманив код наша задача сводится лишь к тому, чтобы в Sandbox загрузить готовые контейнеры, накрутить маппинг языка к запущенным контейнерам, поставить конфигурации и ву-а-ля — все готово.
Ко всему прочему добавили в API возможность грузить несколько файлов в таком виде
И получается все очень даже удобно и красиво.
Так выглядит доставка конфигураций
Изоляция
Опять злополучная изоляция. Так как мы используем gVisor от Google, то мы перекрыли в нашей песочнице все что можем, запустив песочницу без сети и с ограничением памяти. Но так как, в отличие от Google Golang, у нас первый сервис (Playground) с доступом к сети не может собрать проект, а код собирать как-то нужно (pip у Python, или npm у JS/TS или composer у PHP) через сеть, то нужно что-то придумать.
На помощь нам пришло решение squid — в одной Docker-сети с изолированной от всего песочницей поднимается еще один контейнер, который контролирует исходящие сетевые запросы. Конфигурация его выглядит примерно так — белый список разрешенных ресурсов, остальное запрещено:
Тут список разрешенных ресурсов с зависимостями для разных языков
Все!
Результат
Спустя двух с половиной месяцев написания не самого сложного Go-кода мы получили готовое решение. Сейчас проект еще немного в разработке, но уже можно запускать. Готовы даже инструкции Terraform для деплоя в Digital Ocean.
Буду рад любой помощи, вопросам и предложениям 🤗🤗🤗