Инжиниринг платформы
Как DeepCure масштабирует открытие лекарств в облаке
Обзор облачного автоматизированного решения для поиска лекарств, созданного с помощью Kubernetes.
Компьютерный дизайн лекарств (CADD), также известный как скрининг in silico, стал эффективным методом, позволяющим сократить время и деньги, необходимые для открытия новых лекарств. Как следует из названия, этот процесс включает в себя использование вычислительных ресурсов, а более широкое внедрение передовых моделей машинного обучения (ML), работающих с постоянно растущими наборами данных, значительно увеличило требуемую мощность. Традиционно организации, использующие методы CADD, полагались на предоставление этой вычислительной инфраструктуры самостоятельно, что требует больших затрат времени, денег и человеческих ресурсов. Когда у вас есть инфраструктура, вам по-прежнему нужен способ операционализации всех рабочих нагрузок и экспериментов, которые необходимо выполнить. При неправильном подходе это может привести к недоиспользованию ресурсов и потере времени.
В DeepCure мы создаем конвейер для поиска лекарств с помощью ИИ. С самого начала мы не хотим быть связанными ограничениями локально поддерживаемого оборудования и построили нашу платформу так, чтобы она была облачной. С небольшой командой инженеров мы можем поддерживать и быстро масштабировать наши операции для выполнения следующих задач:
- Извлечение, преобразование и загрузка (ETL) конвейеров для преобразования данных в желаемый формат для индексации поиска и последующего анализа
- Обучение машинному обучению (ML) для создания и обновления моделей
- Задания логического вывода ML для улучшения наборов данных с предсказанными свойствами
- Молекулярный докинг и моделирование молекулярной динамики
В оставшейся части этой статьи представлен обзор того, как мы создали нашу платформу, и рассматриваются следующие темы:
- Управление нашей инфраструктурой в облаке
- Использование контейнеров для предоставления неизменяемых программных сред
- Оркестрация рабочих нагрузок с помощью Apache Spark
- Автоматизация сложных рабочих процессов поиска лекарств
- Масштабирование платформы для предоставления доступа к вычислительным ресурсам по мере необходимости
- Обеспечение наблюдения за платформой, чтобы упростить устранение проблем и оптимизацию использования.
- Разработка элегантного интерфейса для разработчиков, который позволяет пользователям легко взаимодействовать с платформой.
- Будущая работа, которую мы запланировали для платформы
Инфраструктура
Мы размещаем всю нашу инфраструктуру в облаке с помощью Amazon Web Services (AWS). Ландшафт резко изменился, когда дело доходит до поставщиков облачных услуг, и это гораздо более конкурентное пространство, чем раньше, с большим выбором. Поскольку многие из команды DeepCure знакомы с AWS по предыдущим местам работы, для нас имеет смысл использовать его сейчас, чтобы сократить время обучения и повысить производительность как можно быстрее. Ниже я упомяну некоторые варианты технологий, которые можно переносить, чтобы не привязываться к одному поставщику облачных услуг. Но по большей части мы пытаемся использовать все интеграции AWS, чтобы свести к минимуму время, затрачиваемое на настройку. Это позволяет нам тратить большую часть времени на решение сложных проблем в области разработки лекарств.
У нас есть несколько учетных записей AWS. Один используется в качестве среды разработки. Это первое место, где обновления развертываются в наших конвейерах непрерывного развертывания. Наша производственная среда размещена в отдельной учетной записи, почти идентичной по настройке среде разработки. После выполнения автоматизированных и ручных тестов в среде разработки изменения могут быть развернуты в рабочей среде.
Мы используем сочетание AWS Cloud Development Kit (CDK) и Terraform для предоставления ресурсов в наших учетных записях AWS. Для новых проектов мы сначала используем CDK. Python — это предпочтительный язык для большинства вещей, которые мы делаем в DeepCure. Возможность также использовать Python для выделения ресурсов в AWS — отличное преимущество. Это на один язык/синтаксис меньше, который человек должен выучить, чтобы выполнить свою работу.
Контейнеры
Для многих операций, выполняемых на нашей платформе, требуется много программного обеспечения машинного обучения, такого как PyTorch и пакетов вычислительной химии. Чтобы обеспечить согласованную среду выполнения, мы упаковываем все это в образы Docker. Эти образы представляют собой встроенные конвейеры CI/CD, которые запускаются в GitLab, помечаются версией и хранятся в реестре образов контейнеров. Затем рабочие нагрузки могут ссылаться на образ и тег версии для получения воспроизводимых результатов. Управление версиями образов контейнеров позволяет нам тестировать обновления пакетов, не влияя на рабочие нагрузки, выполняемые в рабочей среде. Наши конвейеры CI/CD также предназначены для развертывания версий образов, когда разработчик открывает запрос на слияние (MR). Это позволяет выполнять автоматические и ручные тесты с использованием вновь созданного образа. Это помогает свести к минимуму регрессии, которые могут возникнуть при развертывании обновлений. Разработчики также могут создавать образ и запускать эти контейнеры локально на своих ноутбуках, что помогает в процессе разработки.
Оркестровка
В DeepCure мы используем Kubernetes для организации планирования и управления всеми контейнерами, которые обрабатывают данные в необходимом нам масштабе. Работать с Kubernetes может быть сложно, но мы считаем, что это стоящая инвестиция. Это облачно-независимая технология, которая открывает дверь для развертывания на любых крупных поставщиках облачных услуг, если мы этого захотим. Существует большая экосистема инструментов и фреймворков, которые расширяют возможности Kubernetes и делают его привлекательным решением для управления рабочими нагрузками ваших контейнеров. Для выполнения некоторых наших рабочих нагрузок требуются тысячи контейнеров, работающих одновременно. Kubernetes позволяет нам легко масштабировать наши ресурсы, чтобы удовлетворить эти требования.
AWS предоставляет свой Elastic Kubernetes Service (EKS), который значительно упрощает использование Kubernetes. Мы используем AWS CDK для подготовки наших кластеров Kubernetes и ресурсов, работающих в EKS. До этого мы использовали модуль Terraform EKS и обнаружили, что CDK лучше подходит для этой задачи.
Помимо создания кластеров Kubernetes, диаграммы Helm также можно развернуть с помощью CDK. Ниже приведен фрагмент, показывающий, как развернуть диаграмму в существующем кластере.
eks_cluster.add_helm_chart( "aws-node-termination-handler", chart="aws-node-termination-handler", version="0.15.0", release="aws-node-termination-handler", repository="https://aws.github.io/eks-charts/", namespace="kube-system", values={ "nodeSelector": {"karpenter.sh/capacity-type": "spot"}, }, )
Со всем, что Kubernetes предоставляет для запуска контейнерных рабочих нагрузок, нам нужно было что-то большее, чтобы определять и выполнять наши рабочие нагрузки. Мы используем Apache Spark, чтобы предоставить механизм выполнения поверх Kubernetes. С PySpark мы можем использовать Python и Pandas, с которыми у большей части нашей команды есть опыт. Spark поставляется с собственным планировщиком Kubernetes, который позволяет запускать приложения Spark в Kubernetes.
Со всем вышеперечисленным можно начать работу с приложениями Spark в Kubernetes. Однако мы используем проект с открытым исходным кодом, чтобы сделать этот процесс еще проще. Оператор Kubernetes для Apache Spark создает настраиваемые ресурсы Kubernetes, которые позволяют указывать, запускать и просматривать статус приложений Spark. Ниже приведен пример того, как выглядит определение приложения Spark:
apiVersion: "sparkoperator.k8s.io/v1beta2" kind: SparkApplication metadata: name: spark-pi namespace: default spec: type: Python pythonVersion: "3" mode: cluster image: "gcr.io/spark-operator/spark-py:v3.1.1" imagePullPolicy: Always mainApplicationFile: local:///opt/spark/examples/src/main/python/pi.py sparkVersion: "3.1.1" restartPolicy: type: Never driver: cores: 1 coreLimit: "1200m" memory: "512m" labels: version: 3.1.1 serviceAccount: spark executor: cores: 1 instances: 1 memory: "512m" labels: version: 3.1.1
Наличие настраиваемых ресурсов приложения Spark значительно упрощает создание и управление. Их статус можно получить так же, как и любой другой ресурс Kubernetes:
kubectl describe sparkapplications <name> -n <namespace>
Трубопроводы
Со временем у нас возникла необходимость сшивать вместе некоторые из этих приложений Spark, чтобы запускать серию из них в определенном порядке. Сейчас существует множество движков рабочего процесса, из которых можно выбирать. Мы решили использовать фреймворк, родной для Kubernetes. Многие механизмы рабочих процессов достаточно гибки, чтобы работать со многими различными средами выполнения. Однако с этой гибкостью возникает дополнительная сложность. Argo Workflows — это контейнерный движок рабочего процесса с открытым исходным кодом для Kubernetes. Он похож на проект Spark Operator тем, что определяет настраиваемые ресурсы Kubernetes, которые могут задавать и запускать рабочие процессы. Рабочий процесс с шагами в виде приложений Spark может выглядеть так:
--- apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: name: test-workflow namespace: platform spec: entrypoint: spark-app-steps serviceAccountName: spark-operator ttlStrategy: secondsAfterCompletion: 300 secondsAfterSuccess: 300 secondsAfterFailure: 300 templates: - name: 'spark1' resource: action: create successCondition: status.applicationState.state in (COMPLETED) failureCondition: status.applicationState.state in (FAILED, SUBMISSION_FAILED, UNKNOWN) manifest: | apiVersion: "sparkoperator.k8s.io/v1beta2" kind: SparkApplication metadata: name: spark-pi namespace: default spec: type: Python pythonVersion: "3" mode: cluster image: "gcr.io/spark-operator/spark-py:v3.1.1" imagePullPolicy: Always mainApplicationFile: local:///opt/spark/examples/src/main/python/pi.py sparkVersion: "3.1.1" driver: cores: 1 coreLimit: "1200m" memory: "512m" executor: cores: 1 instances: 1 memory: "512m" - name: spark-app-steps steps: - - name: 'First Step' template: 'spark1' - - name: 'Second Step' template: 'spark1'
Рабочие процессы Argo могут создавать ресурсы Kubernetes из манифеста, а затем мы можем указать настраиваемые условия успеха и отказа, которые будут проверяться на соответствие статусу ресурса приложения Spark. Argo Workflows поставляется с веб-интерфейсом, который позволяет просматривать и отслеживать состояние запущенных рабочих процессов. Вы также можете увидеть статус рабочего процесса с помощью следующего:
kubectl describe workflows <name> -n <namespace>
Это решение позволяет построить ориентированный ациклический граф (DAG) заданий. Мы создали собственное веб-приложение React, в котором можно визуализировать и отслеживать эти рабочие процессы. Ниже приведен пример того, как это выглядит.
Достижение масштаба
Для эффективного выполнения одного из наших рабочих процессов могут потребоваться тысячи процессоров и графических процессоров. Количество запущенных рабочих процессов может варьироваться от нуля до нескольких в течение обычного рабочего дня в DeepCure. Чтобы легко и быстро масштабировать вычислительные узлы, необходимые в кластере Kubernetes, мы используем Karpenter, проект с открытым исходным кодом от AWS. С помощью Karpenter вы можете определить настраиваемые ресурсы, которые действуют как поставщики вычислительных ресурсов для ожидающих выполнения рабочих нагрузок в кластере. Поставщики позволяют указать несколько вещей, в том числе:
- Использование спотовых инстансов или инстансов по запросу
- Типы экземпляров. Вы можете позволить Карпентеру выбрать наиболее подходящий для запрашиваемых ресурсов или предоставить собственный список типов экземпляров, которые следует использовать. Некоторые из наших рабочих нагрузок лучше выполняются на разных типах инстансов; нам нужна гибкость, чтобы указать это.
- Дополнительные сведения, например, какой AMI, группу безопасности и роль IAM должны использовать новые узлы кластера.
Когда есть ожидающие контейнеры, Карпентер предоставит соответствующие узлы для добавления в кластер на основе определения поставщиков. Для этого он напрямую взаимодействует с API AWS EC2. Другие инструменты масштабирования, такие как Kubernetes Cluster Autoscaler, требуют групп автоматического масштабирования, которые инструмент обновляет для добавления или удаления емкости. Группы автоматического масштабирования имеют самоопределяемые периоды ожидания и охлаждения, которые должны истечь, прежде чем будут добавлены дополнительные узлы. Это может увеличить время при быстром масштабировании вычислительных ресурсов для больших рабочих нагрузок. С помощью Karpenter мы можем эффективно масштабировать наши кластеры вверх и вниз, поскольку он выделяет сами узлы кластера. Он также использует самые актуальные цены на спотовые вычисления, чтобы определить наиболее экономичные типы инстансов для выделения ожидающих рабочих нагрузок.
Наблюдаемость
Все журналы приложений, работающих в кластере Kubernetes, попадают в AWS CloudWatch. Разворачиваем на кластере Fluent Bit, который собирает логи и публикует их в CloudWatch. Также развернут Kubecost, который собирает много информации об использовании затрат, включая показатели производительности. Имея данные о производительности в Kubecost, мы можем запросить их, чтобы получить данные об использовании ресурсов для приложения Spark. Мы визуализируем это в нашем веб-приложении React.
Мы можем получить журналы драйверов Spark из CloudWatch и отобразить их в веб-приложении React. Это позволяет пользователю платформы легко увидеть, как ведет себя его приложение и есть ли какие-либо ошибки.
Все завершенные приложения Spark сохраняют свои журналы в корзину S3. Чтобы проанализировать производительность приложений Spark, мы развертываем сервер истории Spark, который отображает пользовательский интерфейс Spark для завершенных приложений с использованием файлов журнала. С помощью Spark History Server можно выполнить детализацию, чтобы точно увидеть, что делает приложение, и определить, можно ли выполнить какие-либо оптимизации.
Опыт разработчиков
Очень важно максимально упростить для разработчиков создание прототипов и запуск рабочих нагрузок на нашей платформе. Для этого мы создали сервис платформы вокруг этой инфраструктуры. Платформа включает микросервисы, которые предоставляют REST API, позволяя пользователям и клиентам создавать и отслеживать рабочие нагрузки. На приведенной ниже диаграмме показана базовая архитектура этого сервиса, работающего в нашей учетной записи AWS. Существует служба плоскости управления, которая предоставляет доступ к API и обрабатывает запросы от внешних клиентов. Затем он взаимодействует с зарегистрированными кластерами Kubernetes для планирования и мониторинга рабочих нагрузок. Плоскость управления может планировать рабочие нагрузки для нескольких кластеров. У нас есть инфраструктура даже для поддержки некоторых из этих кластеров, существующих в других облаках, если мы когда-нибудь решим развернуть гибридное облако. Рабочие нагрузки выполняются в кластерах Kubernetes и взаимодействуют с данными, которые хранятся в озере данных, которое находится в AWS S3.
У нас есть внутренняя библиотека Python, которая предоставляет некоторые вспомогательные утилиты, упрощающие запуск скрипта рабочей нагрузки локально или по расписанию в кластере Kubernetes. Режим локального запуска удобен тем, что позволяет пользователям создавать прототипы и проверять, что все работает в небольших масштабах. Чтобы запланировать рабочую нагрузку на кластер Kubernetes, можно запустить скрипт из командной строки и указать необходимые вычислительные ресурсы с помощью параметров командной строки.
python pi.py --cs_executor_cores=8 \ --cs_instance_memory_gb=24 \ --cs_executor_instances=100 \ --cs_driver_cores=4 \ --cs_driver_memory_gb=16
Выполнение вышеуказанного приведет к отправке POST-запроса к REST API. Затем плоскость управления планирует выполнение рабочей нагрузки в кластере. Сценарий рабочей нагрузки загружается в место на S3, что позволяет выполнять его в кластере Kubernetes.
Пока рабочая нагрузка выполняется, ее можно отслеживать из веб-приложения React. Мы можем отображать статус заданий приложения Spark, получая информацию от работающего драйвера с помощью Spark REST API. В этом представлении видны журналы вместе с текущим использованием ресурсов и любыми ошибками работающих исполнителей.
Будущая работа
Все, что описано в этой статье, представляет собой мощное решение для запуска рабочих нагрузок в DeepCure. С этой властью приходит большая ответственность за то, чтобы мы тратили на эту часть нашего бизнеса лишь небольшие деньги. Предположения могут быть ошибочными, а для некоторых рабочих нагрузок может потребоваться много ресурсов и денег. Мы хотим улучшить наблюдаемость стоимости наших ресурсов Kubernetes. Мы используем теги распределения затрат и можем видеть на высоком уровне, какая часть нашего счета AWS тратится на наши рабочие нагрузки. AWS позволяет определять бюджеты с помощью тегов распределения затрат. У нас есть оповещения, публикуемые в канале Slack, когда эти пороговые значения дневного бюджета превышены. Kubecost может предоставить дополнительную информацию и позволить нам углубиться, чтобы точно увидеть, во сколько нам обходится конкретная рабочая нагрузка.
Мы работаем над этой платформой для поиска лекарств уже более двух лет в DeepCure. Мы получили большую выгоду от возможности использовать многие проекты с открытым исходным кодом. Мы сделали несколько вкладов в некоторые из этих проектов и надеемся увеличить их в будущем.
Подпишитесь на Блог DeepCure, чтобы узнать больше историй и ресурсов, и ознакомьтесь с DeepCure на LinkedIn, чтобы узнать последние новости о компании.