Оптимизация контейнерной рабочей нагрузки oneAPI для развертывания

Что такое контейнер?

В своем последнем блоге я уделил некоторое время разговору о том, почему для меня важно быть эффективным разработчиком. Распространение контейнерных технологий может существенно помочь нам в достижении этой цели.

Однако сначала давайте быстро рассмотрим, что такое контейнер для тех, кто не знает. Docker — самое распространенное решение для контейнеризации, поэтому я возьму определение контейнера с их сайта:

Контейнер — это стандартная единица программного обеспечения, которая упаковывает код и все его зависимости, чтобы приложение быстро и надежно запускалось из одной вычислительной среды в другую. Образ контейнера Docker — это легкий, автономный исполняемый пакет программного обеспечения, который включает в себя все необходимое для запуска приложения: код, среду выполнения, системные инструменты, системные библиотеки и настройки.

Контейнеры могут работать в системах Windows или Linux, их относительно легко настроить и развернуть, ими легко делиться или распространять среди других (через такие сервисы, как Docker Hub).

Конечно, есть и некоторые ограничения на использование контейнеров. Вы должны настроить среду выполнения контейнера в каждой системе, где вы хотите запускать свои контейнеры, контейнеры могут быть довольно большими, что может быть проблемой, если вам приходится часто загружать и скачивать их, и они не обеспечивают жесткого разделения ресурсы в базовой системе, как виртуальная машина (ВМ). Несмотря на эти ограничения, во многих случаях контейнеры — отличный способ, поэтому давайте поговорим о том, как они помогают нам как разработчикам.

Что это значит для разработчика?

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

Еще одна интересная вещь — вы можете создавать и запускать несколько сред разработки и тестирования (также называемых несколькими версиями Linux) в одной системе. Самое главное, вам обычно не нужно беспокоиться о том, чтобы испортить вашу хост-систему, поскольку контейнер изолирует большинство ресурсов.

Для разработчиков, чьи клиенты используют контейнеры, отправка приложения в контейнере позволяет работать в более контролируемой среде. Это может помочь уменьшить количество ошибок, уменьшить площадь поверхности тестирования и сократить время выхода на рынок (TTM), что поможет как вам, так и вашим клиентам.

Существуют и другие варианты решения некоторых проблем «множественной среды разработки», такие как решения на основе виртуальных машин (ВМ). Вы можете создать базовый экземпляр подсистемы Windows для Linux (WSL), настроить в нем среду разработки и развернуть экземпляр несколько раз. Вы также можете использовать корпоративные решения, такие как VMware Virtual Desktop Infrasture (VDI), которые еще более надежны. Однако эти решения доступны не всем на всех платформах, поэтому для целей этого блога я сосредоточусь на использовании контейнеров для разработки и развертывания программного обеспечения с использованием oneAPI.

Использование контейнеров разработки oneAPI

Чтобы использовать контейнеры разработки oneAPI для обеспечения вышеупомянутой стабильной среды разработки, нам сначала нужно настроить Docker в нашей системе разработки. В общем, вы можете просто зайти на сайт Docker и следовать их инструкциям по установке. Возможно, вы помните из моего предыдущего блога, что я использую среду WSL для своей разработки, так что это было немного сложнее.

Я собирался сделать простую статью об этом, но, к счастью, многие люди до меня сделали это. Я обнаружил, что история ferarias самая простая для понимания, если вы хотите следовать этому подходу.

После настройки Docker вы можете получить последние и лучшие контейнеры разработки oneAPI из Docker Hub.

Существуют контейнеры разработки oneAPI для разработки кода oneAPI в CentOS8, Ubuntu 18.04 или Ubuntu 20.04. Для этого примера я собираюсь собрать и упаковать простой код SYCL, поэтому я возьму образ OneAPI Base Toolkit, Ubuntu 20.04. Используя командную строку Docker, я просто запускаю:

> docker pull intel/oneapi-basekit:devel-ubuntu20.04

Это загружает контейнер в мою локальную систему. Чтобы попасть в среду разработки, вы можете запустить контейнер с помощью следующей команды:

> docker run -ti --name=ubuntu-dev-20.04 intel/oneapi-basekit:devel-ubuntu20.04

Флаг -ti говорит Docker предоставить мне интерактивный терминал в контейнере, как только он будет запущен и запущен. Флаг name дает нашему запущенному контейнеру имя ubuntu-dev-20.04.

Создание кода

Чтобы упростить задачу, я буду использовать образцы oneAPI. Я пошел к образцам oneAPI Github:



… и клонировал репозиторий в моем контейнере для разработки. Я буду использовать образец Nbody, расположенный в папке DirectProgramming->DPC++->N-BodyMethods->Nbody.

Преимущество использования контейнера разработки в том, что мне НЕ нужно запускать

> source /opt/intel/oneapi/setvars.sh

Как и в моем последнем примере, потому что контейнер разработки oneAPI уже был запущен с этой командой. Я просто могу перейти в папку Nbody и следовать инструкциям по сборке и запуску примера Nbody:

> mkdir build
> cd build
> cmake ..
> make
> make run

Вот результат моей системы Intel i9 Alder Lake Alienware R13:

Создание производственного контейнера

Теперь, когда мы знаем, что наш код собирается в контейнере разработки, давайте посмотрим, как мы можем отправить его в контейнере клиенту для запуска в его системе.

Docker создаст новый контейнер, используя формат Dockerfile. Для тех из вас, кто не знаком с тем, как использовать Dockerfile, ознакомьтесь с руководством Docker Начало работы.

Я собираюсь использовать возможность многоэтапной сборки Docker, чтобы указать процессу сборки Docker собрать мой код в одном контейнере, а затем скопировать его в другой контейнер. Это позволяет мне иметь весь рабочий процесс сборки и упаковки в одном месте, в то же время позволяя мне отправлять контейнер без всех инструментов разработки. Вот мой рабочий контейнер Dockerfile:

Вы увидите, что для производственного контейнера я использую контейнер intel/oneapi-runtime:latest в качестве основы, потому что он предоставляет все среды выполнения, необходимые для запуска любой рабочей нагрузки на основе oneAPI.

Следующий шаг — взять этот Dockerfile, который я назвал Dockerfile.runtime, и запустить команду сборки docker для создания моего нового контейнера.

> docker build . -f Dockerfile.runtime -t tonymintel/nbody:runtime

Эта команда сообщает Docker о сборке с использованием текущего пути, моего файла Dockerfile.runtime и пометки образа как tonym/nbody:runtime.

Быстрый запуск контейнера показывает, что наша сборка была успешно завершена, и код иссякает, как мы и хотели.

Теперь мне просто нужно упаковать мой контейнер и отправить его моему клиенту. Я могу отправить его в Docker Hub или использовать команду docker save, чтобы сохранить образ в моей локальной системе и отправить файл.

В моем случае я просто отправляю это в Docker Hub:

> docker push tonymintel/nbody:runtime

Это отправляет образ в мою учетную запись tonymintel Docker Hub и сохраняет его в репозиторий nbody со средой выполнения тега. Теперь любой, у кого есть доступ к репозиторию tonymintel/nbody Docker Hub, может получить и запустить код с помощью обычных команд Docker.

Уменьшение моего производственного контейнера в 4 раза

Как для меня важно быть эффективным разработчиком, так и для пользователей наших приложений важно иметь эффективное решение. Как говорит мой коллега Джеймс Рейндерс, мы должны быть уверены, что приносим им «радость из коробки».

На моем снимке экрана видно, что контейнер, который я загрузил, имеет размер 1,32 ГБ. Это довольно большой контейнер Docker, который может загрузить мой клиент.

Мой производственный контейнер основан на контейнере intel/oneapi-runtime, который предоставляет все среды выполнения для всех наборов инструментов, создающих код oneAPI. Это не обязательно плохо. oneAPI предоставляет множество библиотек, которые я, возможно, захочу интегрировать со своим кодом, поэтому иметь пакет времени выполнения, который, как я знаю, будет запускать мой код, — это здорово. Однако, поскольку я разработчик и знаю, какие библиотеки я использовал, давайте посмотрим, сможем ли мы оптимизировать наш производственный контейнер, чтобы сделать его меньше.

После некоторой проверки я создал новый Dockerfile.prod, который выглядит так:

Здесь много текста, поэтому позвольте мне попытаться объяснить, что происходит:

  1. Строки 1–9 и 32–34 одинаковы в обоих файлах Dockerfile. Они собирают наш бинарник и запускают его при старте контейнера.
  2. Строки 11–30 в основном представляют собой шаблонный код, который настраивает репозитории Intel apt, а затем устанавливает некоторые пакеты Intel через APT. Эти строки взяты из oneapi-runtime Dockerfile, который можно найти здесь:


Ключевой строкой в ​​моем Dockerfile.prod является строка 28. Если вы посмотрите на исходный intel/oneapi-runtime Dockerfile (щелкните здесь и щелкните дайджест SHA и найдите самый большой слой изображения), он выполняет apt-get для всех возможных пакетов времени выполнения. . В моем случае я знаю, что использовал только DPC++, поэтому мой Dockerfile.prod включает только этот пакет intel-oneapi-runtime-dpcpp-cpp.

Теперь я просто создаю свой новый Dockerfile, проверяю, работает ли он как раньше, и отправляю его в облако.

> docker build . -f Dockerfile.prod -t tonymintel/nbody:production
> docker run -it tonymintel/nbody:production
> docker push tonymintel/nbody:production

Теперь я вижу увеличение размера загрузки для моего клиента примерно в 4 раза… надеюсь, немного больше радости из коробки.

Вы спросите, могу ли я сделать еще лучше? Чтобы избежать неопределенности, ответ положительный, но для этого нужно зайти и извлечь отдельные файлы, чтобы пропустить различные неиспользуемые компоненты среды выполнения. Это немного хлопотно, но возможно. Если вам интересно, в итоге размер пакета составил около 250 МБ, что примерно в 5 раз больше.

Я считаю это большой победой, потому что это действительно может повлиять на опыт ваших клиентов. Очевидно, что если вашему клиенту необходимо загрузить контейнер через стандартное подключение к Интернету, это большая победа. В крупномасштабной среде, такой как центр обработки данных или облачная среда, это может быть еще более ценным, поскольку развертывание одного и того же модуля на десятках или сотнях узлов является очень распространенным явлением, а использование сети, насыщение и задержка всегда вызывают беспокойство. Возможно, самое главное, если ваш клиент платит за сеть или хранилище для развертывания своих контейнеров, он будет вам благодарен (или, по крайней мере, не будет жаловаться) на то, что контейнер обходится ему дороже. :)

Заключение

Контейнеры — это полезная технология, которая помогает нам, разработчикам, создавать и тестировать наш код. В зависимости от нашего клиента (то есть, будет ли он использовать контейнер), это также может действительно помочь нам предоставить более надежный код нашему клиенту.

Безусловно, изучение всех технологий требует определенной работы, но во многих случаях оно того стоит. Я надеюсь, что это помогло вам узнать немного о том, как использовать контейнеры и как оптимизировать эти контейнеры для вашего клиента.

До скорого!

Want to Connect?
If you want to see what random tech news I’m reading, you can follow me on Twitter.
Tony is a Software Architect and Technical Evangelist at Intel. He has worked on several software developer tools and most recently led the software engineering team that built the data center platform which enabled Habana’s scalable MLPerf solution.
Intel, the Intel logo, and other Intel marks are trademarks of Intel Corporation or its subsidiaries.