Тестирование производительности Docker клиентов для Mac
Недавно я опубликовал статью OrbStack: Почему я забыл про Docker Desktop, которая вызвала оживленную дискуссию в комментариях. Основные вопросы возникли вокруг производительности различных Docker-подобных решений. Мои аргументы, основанные в первую очередь на личном опыте использования, оказались недостаточно убедительными.
Чтобы получить объективную картину и предоставить сообществу реальные данные, я решил разработать комплексный benchmark для сравнения различных решений. В процессе разработки тестов, комментаторы предложили несколько интересных идей, которые помогли расширить список тестируемых движков. В итоге в тестировании приняли участие:
Docker Desktop
Podman Desktop
Rancher Desktop
OrbStack
Colima
Конфигурация хоста:
OS: MacOS 15.0.1
Железо: MacBook Pro 16
Процессор: M1 Max (10 ядер (8 производительности и 2 эффективности))
RAM: 64 GB
Будем измерять:
Время запуска docker (условно нажали на приложение, запускается и становится доступным для запуска контейнеров)
Время сборки тяжелых образов (Heavy build) — 2 разных образа
CPU и Memory
Энергопотребление (чуть ниже описал как считаю, что и для чего это)
Все тестирование, которое будет описано ниже, производилось практически в одно и то же время, с одинаковым набором запущенных программ.
> Если вы найдете в скриптах/логике ошибку, свяжитесь со мной любым удобным способом. Я доработаю и обновлю статью, если будет возможность, либо только скрипты, если редактирование не будет доступно.
Подготовка
Вначале я эту часть написал максимально подробно, но когда текст перевалил за 30 тысяч символов, понял, что читать никто не будет, даже если весь код будет под спойлером.
Поэтому если хотите изучить скрипты, найти ошибки, предложить улучшения/доработки — заходите на Github. Кроме того, это гарантия того, что скрипты будут актуальными, так как они однозначно будут улучшаться.
Прежде чем перейдем к самому тестированию, хотелось бы рассказать о нескольких моментах.
Во-первых, я постарался написать скрипты таким образом, чтобы можно было как «Запустить все», так и, например, протестировать только runtime для Docker Desktop. У каждого скрипта есть команда --help, благодаря которой можно узнать набор атрибутов, все максимально гибко.
Во-вторых, хотелось бы пояснить расчеты по «Энергопотреблению» и для чего это нужно. Мне хотелось вычислить, сколько энергии тратит приложение, вдохновился бенчмарком OrbStack. Там написано следующее:
> After waiting for CPU usage to settle, we measured the long-term average power usage over 10 minutes by sampling the process group’s estimated energy usage (in nJ) from the macOS kernel before and after the test, and converting it to power usage (in mW) using the elapsed time.
Если перевести упрощенно — дождались, пока нагрузка CPU будет стабильной, измерили среднее энергопотребление за период, выбрав расчетное энергопотребление группы процессов и преобразовали его в энергопотребление. Просто мерить, сколько осталось батареи после запуска, я посчитал совсем не точной метрикой.
Самый близкий аналог этого — колонка энерговоздействия в Mac OS
Мониторинг системы
У меня были написаны все скрипты, потому что в целом легко засечь время запуска, посчитать CPU/RAM, замерить время сборки, а вот с энергопотреблением появилось так много сложностей, что вместо расчетных 4-х часов на это занятие, потратил несколько дней. После огромного количества итераций и гугления нашел статью.
И с командой:
top -stats pid,command,power -o power -l 0 | grep 'Docker Desktop'
У меня даже стали появляться результаты:
21930 Docker Desktop 0.4
43853 Docker Desktop H 0.0
21955 Docker Desktop H 0.0
21953 Docker Desktop H 0.0
43853 Docker Desktop H 1.1
21930 Docker Desktop 0.3
21953 Docker Desktop H 0.1
Но эти значения были сильно нереалистичны, потому что просто с запущенными базами, полдня использования компьютера убивают батарею полностью, а тут просто какие-то микродоли. И было непонятно — мне их суммировать или выводить среднее (равное нулю, естественно). Я еще помучился и решил вернуться к парсингу команды:
powermetrics -i 1000 --poweravg 1 | grep 'Average cumulatively decayed power score' -A 20
Переписал скрипт с bash на python, чтобы проще было все парсить. Но, честно говоря, до сих пор не уверен, что верно считаю эти данные. Первый блин комом, но хотя бы работает, получил такие цифры:
Начальная мощность: 1035.0mW
Конечная мощность: 1295.0mW
Средняя мощность: 1307.7mW
Потом добавил обработку, чтобы вычислить, сколько именно наш процесс потребляет — общую нагрузку CPU, из нее CPU наших процессов и на основе этого вычисляю долю от общей загрузки. В мониторинге параллельно смотрел, что доля CPU была от 16 до 20%, поэтому после доработки результат вышел вроде реалистичный:
Начальная мощность: 7.2mW
Конечная мощность: 706.5mW
Средняя мощность: 149.1mW
Потом счастливый запустил общий скрипт на тестирования, но рано обрадовался. Несмотря на то что я каждый скрипт в отдельности протестировал, когда запустил общий посыпалось куча ошибок, пришлось долго и упорно дорабатывать. Но в итоге все получилось.
Затем счастливый запустил общий скрипт на тестирование, но рано обрадовался. Несмотря на то, что я каждый скрипт в отдельности протестировал, когда запустил общий — посыпалась куча ошибок, пришлось дорабатывать. Но в итоге все получилось.
Dockerfiles
Сборку хотелось бы провести на действительно тяжелых образах, но без фанатизма — 10–15 минут на сборку нормально, а вот по часу точно не хотелось бы ждать, особенно в рамках отладки скрипта.
Поэтому в первую очередь я сделал простой сервис для отладки bash скрипта.
# test-builds/simple/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN echo '{"name":"test","version":"1.0.0","dependencies":{"express":"^4.18.2"}}' > package.json
RUN npm install
RUN mkdir src
COPY . .
RUN npm install -g typescript && \
echo '{"compilerOptions":{"target":"es6","outDir":"dist"}}' > tsconfig.json && \
mkdir -p src && \
echo 'const greeting: string = "Hello World"; console.log(greeting);' > src/main.ts && \
tsc
CMD ["node", "dist/main.js"]
Придумать или скачать тяжелый образ оказалось сложнее. OrbStack в своих benchmarks тестирует на PostHog и Open edX, но у меня нормально их собрать вообще не получилось. Я решил сделать свои. Второй сервис — Java Spring приложение с множеством зависимостей:
# test-builds/java/Dockerfile
# Multi-stage build for Spring Boot application
FROM maven:3.8.4-openjdk-17 AS builder
WORKDIR /app
RUN mkdir -p src/main/java/com/example/demo
# Copy configuration files
COPY pom.xml .
COPY DemoApplication.java src/main/java/com/example/demo/
# Download dependencies and build
RUN mvn dependency:go-offline
RUN mvn package -DskipTests
# Final stage
FROM openjdk:17-slim
COPY --from=builder /app/target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Нужно еще два файла создать, первый pom.xml
:
4.0.0
org.springframework.boot
spring-boot-starter-parent
3.1.0
com.example
demo
0.0.1-SNAPSHOT
demo
Demo Spring Boot Application
17
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-security
org.postgresql
postgresql
runtime
org.springframework.boot
spring-boot-maven-plugin
второй файл DemoApplication.java
:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Третий сервис — Python ML приложение с TensorFlow:
# test-builds/ml/Dockerfile
FROM python:3.9 as builder
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
curl \
software-properties-common \
git \
&& rm -rf /var/lib/apt/lists/*
# Create requirements.txt
RUN echo 'tensorflow==2.13.0' > requirements.txt && \
echo 'torch==2.0.1' >> requirements.txt && \
echo 'transformers==4.31.0' >> requirements.txt && \
echo 'scipy==1.11.2' >> requirements.txt && \
echo 'scikit-learn==1.3.0' >> requirements.txt && \
echo 'pandas==2.0.3' >> requirements.txt && \
echo 'numpy==1.24.3' >> requirements.txt && \
echo 'matplotlib==3.7.2' >> requirements.txt && \
echo 'seaborn==0.12.2' >> requirements.txt
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Create sample ML application
COPY . .
RUN echo 'import tensorflow as tf' > app.py && \
echo 'import torch' >> app.py && \
echo 'import transformers' >> app.py && \
echo 'from sklearn.ensemble import RandomForestClassifier' >> app.py && \
echo 'import numpy as np' >> app.py && \
echo 'import pandas as pd' >> app.py && \
echo 'print("TensorFlow version:", tf.__version__)' >> app.py && \
echo 'print("PyTorch version:", torch.__version__)' >> app.py && \
echo 'print("Transformers version:", transformers.__version__)' >> app.py
FROM python:3.9-slim
WORKDIR /app
# Copy only necessary files from builder
COPY --from=builder /app/app.py .
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
CMD ["python", "app.py"]
Теперь ничего не мешает запустить тестирование.
Тестирование
Для запуска тестирования достаточно запустить скрипт:
./engine-benchmark.sh -v all #Запуск тестирования всех движков
# Или запустить только нужный
./engine-benchmark.sh -v podman-desktop
Результаты складываются по каждому тесту, и общий результирующий сохраняется в result
. Например docker-desktop_idle_resources.json
:
{
"engine": "docker-desktop",
"timestamp": "2024-10-28T21:49:35.419319Z",
"repeat_count": 3,
"results": {
"average": 10.817972977956137,
"min": 3.1761507987976074,
"max": 25.995931148529053,
"all_times": [
25.995931148529053,
3.281836986541748,
3.1761507987976074
]
}
}
Во время работы приложения требуется периодически вводить пароль — для установки и удаления, а также для запуска последнего теста, так как powermetrics
может работать только с правами sudo. Также нужно периодически реагировать на окна, так как время от времени требуется разрешение на запуск, например, для Podman Desktop. Полностью автоматизировать процесс, чтобы запустить на два часа без вмешательства, не удалось, но и так в целом неплохо, улучшить можно позже.
Результаты
Чтобы удобно работать с результатами, написал простой скрипт. Что бы посмотреть — достаточно зайти в ./graphics
и открыть index.html
Для того чтобы быть уверенным, что один движок не повлияет на другой — сначала устанавливается первый движок, прогоняется весь набор тестов, удаляется и только потом происходит переход к следующему.
Все результаты тестов и логи загружены на github, вы можете зайти в папку graphics и запустить index.html.
Время запуска
Проверяется 4 раза. При этом на первом запуске везде есть нюансы. Например, у Docker после проверки надо запустить вручную первый раз, непонятно почему — он запускает его, но приложение не стартует. Остальные приложения тоже в первый раз открываются долго. Поэтому решил считать среднее между тремя последними запусками.
Также при запуске я выполняю три команды, чтобы проверить, что все работает корректно:
if docker info >/dev/null 2>&1 && \
docker ps >/dev/null 2>&1 && \
docker run --rm hello-world >/dev/null 2>&1; then
return 0
fi
Вначале хотел удалить строку с запуском контейнера, потом понял, что раз я не считаю первый запуск, эффекта никакого нет, поэтому оставил.
Еще отдельная история с Rancher — пофиксить так и не смог. Когда случается сбой, первый запуск надо делать вручную, не знаю почему, все последующие скрипт корректно запускает и останавливает.
[2024-10-29 22:40:45] Тестирование времени запуска rancher-desktop...
Тестирование rancher-desktop...
Попытка 1 из 4
Запуск rancher-desktop...
При этом он позволяет подряд запустить только два раза, на третий каждый раз выдает 0 (перезапускал несколько раз, пофиксить так и не смог). Но, к счастью, у него в интерфейсе есть информация о том, сколько стартует виртуальная машина — это, как правило, 30–40 секунд. Поэтому тут решил не добавлять, в любом случае цифры реалистичные.
Тестирование запуска. Кобинировано
То же самое, но в другом отображении:
Тестирование запуска. Разделено
Тут из интересного — при тестировании запуск Podman занял 0 секунд. После первого запуска действительно столько. Первый запуск был 5 секунд (тоже самый низкий из всех), 3 последующих — за 0. Я отдельно попробовал, запустил тест для него, получил в целом аналогичные результаты:
Тестирование podman-desktop...
Попытка 1 из 4
Запуск podman-desktop...
Первый запуск пропущен: 5.0 секунд
Останавливаем podman-desktop...
Попытка 2 из 4
Запуск podman-desktop...
Время запуска: 0 секунд
Останавливаем podman-desktop...
Попытка 3 из 4
Запуск podman-desktop...
Время запуска: 1.0 секунд
Останавливаем podman-desktop...
Попытка 4 из 4
Запуск podman-desktop...
Время запуска: 1.0 секунд
Останавливаем podman-desktop...
Результаты сохранены в results/startup/podman-desktop_startup.json
Тестирование завершено. Результаты в директории results/startup
Сборка
Тут из проблемного — это Colima. Она ругалась на docker-compose, отсутствие buildx (без него может работать, но ругается, что deprecated, поэтому добавил сборку через него) и docker. То есть помимо Colima нужно устанавливать и эти инструменты. Buildx решил не ставить, потому что потребовалось бы много дополнительной логики для тестирования, да и понятно, что через него будет быстрее. После всех доработок она начала просто отказываться работать, непонятно почему, но проснулся утром и все заработало. В общем, очень странная история, но потом все стало нормально функционировать. А затем опять начала ругаться на docker compose, пришлось добавить условия для Colima — запуск через docker-compose
, а не docker compose
. Это помогло.
Результат:
Результаты сборки
Производительность
Вначале я хотел запустить тесты на 10 минут, но по два теста общая работа скрипта занимает примерно 2 часа. После большого количества запусков во время отладки понял, что, скорее всего, огромной разницы не будет, порядок сохранится. Поэтому здесь результаты минутных запусков, но возможно в дальнейшем я перетестирую с большей продолжительностью.
СPU
RAM
Энергопотребление
Приятно удивил Docker Desktop по энергопотреблению. Либо я тест неправильно сделал, либо они сильно продвинулись с прошлого года, когда был сделан тест от OrbStack.
Итоги
Техническая сторона доставила мне много проблем, почти все очень деликатное, постоянно какие-то нюансы, хотя казалось бы, везде, кроме Podman, обратная совместимость по командам полная. Но я однозначно получил удовольствие от процесса еще и параллельно познакомился со всеми продуктами очень плотно.
В итоге реализация есть, она работает, но иногда надо помогать — вводить пароль, открывать приложение при первом запуске. Глобально можно позже допилить и сделать более удобным и стабильным (надеюсь, что сообщество поможет). Кроме того, можно расширить набор тестов, например, добавить I/O.
Самое важное, что инструмент дает данные, и на мой взгляд, достаточно реалистичные, и данный Benchmark позволит сделать выводы для себя. Я для себя решил остаться на OrbStack, но параллельно буду присматриваться к Podman Desktop. Что выберете вы — решать исключительно вам.
P.S. Когда вы будете воспроизводить тесты, учитывайте разность окружений. Я ожидаю, что цифры будут другими, но порядок останется одинаковым.
P.P. S. Для тех, кто пролистал, ссылка на Github