1

Как управлять Docker из PHP: от exec до Docker SDK

Раздел: DevOps -> Автоматизация

При автоматизации процессов с помощью PHP часто возникает необходимость взаимодействовать с Docker: запускать контейнеры, проверять их статус, управлять образами. Выполнение команд Docker из PHP открывает широкие возможности для создания инструментов развертывания, CI/CD и администрирования. В этой статье рассмотрены различные способы интеграции Docker в PHP-код, от простых вызовов shell до использования специализированных библиотек и прямого обращения к Docker API.

Основные подходы к выполнению команд Docker из PHP

Как наиболее эффективно выполнять команды Docker из PHP, обеспечив безопасность и удобство?

Наиболее эффективным решением является использование официальной библиотеки docker-php (Docker PHP SDK). Она предоставляет высокоуровневый интерфейс для управления Docker через его REST API, избавляя от необходимости вручную формировать команды и обрабатывать вывод.

Установка через Composer:

composer require docker-php/docker-php

Php exec docker (выполнение команд docker из php)

Пример запуска контейнера с образом nginx:latest:

use Docker\DockerClient;
use Docker\Context\ContextBuilder;

$client = DockerClient::createUnixSocketClient();
$containerConfig = [
    'Image' => 'nginx:latest',
    'Cmd' => ['nginx', '-g', 'daemon off;'],
    'ExposedPorts' => ['80/tcp' => []]
];
$container = $client->containers()->create($containerConfig);
$container->start();
echo "Контейнер создан с ID: " . $container->getId();

Php генерация кода (генерация кода php)

Пояснение шагов:

  1. Создание клиента через Unix-сокет Docker (/var/run/docker.sock).
  2. Формирование конфигурации контейнера (образ, команда, порты).
  3. Создание контейнера через API.
  4. Запуск контейнера.

Типичные ошибки и решения:

  • Permission denied при подключении к сокету: пользователь, под которым работает PHP, должен быть в группе docker. Решение - добавить пользователя в группу.
  • Class not found: не установлен Composer или не выполнен composer install.
  • Image not found: образ необходимо предварительно скачать через $client->images()->pull().

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

Как запустить команду docker с помощью exec() в PHP?

Функция exec() выполняет внешнюю команду и возвращает последнюю строку вывода. Пример получения списка контейнеров:

$output = [];
$returnCode = 0;
exec('docker ps -a', $output, $returnCode);
if ($returnCode === 0) {
    echo implode("\n", $output);
} else {
    echo "Ошибка выполнения";
}

Пояснение:

  • $output - массив строк вывода.
  • $returnCode - код возврата (0 успех).

Проблемы:

  • Инъекция shell-команд: если часть команды формируется из пользовательского ввода, необходимо экранировать аргументы с escapeshellarg().
  • Отсутствие вывода ошибок: exec() не перехватывает stderr. Для этого используйте 2>&1 или функцию proc_open().
  • Длительные команды: блокировка скрипта до завершения команды. Для асинхронной работы лучше использовать proc_open().

Этот вариант подходит для простых сценариев, где важна скорость реализации и не требуется сложное управление.

Как получить полный вывод команды docker в виде строки?

Функция shell_exec() возвращает весь вывод в виде строки. Пример проверки версии Docker:

$version = shell_exec('docker --version');
echo $version;

Отличие от exec() - возвращается строка, а не массив. Удобно для коротких команд. Те же проблемы безопасности и отсутствия stderr.

Как выполнить команду docker с полным контролем над потоками ввода/вывода?

Функция proc_open() позволяет управлять stdin, stdout и stderr. Пример запуска контейнера в интерактивном режиме:

$descriptors = [
    0 => ['pipe', 'r'], // stdin
    1 => ['pipe', 'w'], // stdout
    2 => ['pipe', 'w']  // stderr
];
$process = proc_open('docker run -i alpine echo "Hello"', $descriptors, $pipes);
if (is_resource($process)) {
    fclose($pipes[0]);
    $stdout = stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[2]);
    $returnCode = proc_close($process);
    echo "Вывод: $stdout";
    echo "Ошибки: $stderr";
}

Пояснение: создаются три потока. В примере stdin закрывается сразу, так как не нужен. Полученные выводы позволяют обрабатывать ошибки отдельно.

Типичные ошибки:

  • Утечка ресурсов: если не закрыть все pipes и не вызвать proc_close(), процесс зависнет.
  • Блокировка при чтении: если команда не завершилась, stream_get_contents() будет ждать. Нужно настроить неблокирующий режим или использовать proc_get_status().

Как безопасно выполнять команды docker в Symfony-приложении?

Компонент Symfony Process предоставляет удобную оболочку для управления внешними процессами. Установка:

composer require symfony/process

Пример запуска docker images:

use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

$process = new Process(['docker', 'images']);
$process->run();

if (!$process->isSuccessful()) {
    throw new ProcessFailedException($process);
}
echo $process->getOutput();

Преимущества: автоматическое экранирование аргументов, поддержка таймаутов, возможность работы с переменными окружения. Рекомендуется для проектов на Symfony и любых PHP-приложений, где важна безопасность.

Как управлять Docker через его REST API из PHP без использования shell?

Можно напрямую обращаться к Docker API через Unix-сокет с помощью cURL. Пример создания контейнера:

$ch = curl_init('unix:///var/run/docker.sock:/containers/create');
$payload = json_encode(['Image' => 'alpine', 'Cmd' => ['echo', 'hello']]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_UNIX_SOCKET_PATH, '/var/run/docker.sock');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

Пояснение: cURL подключается к сокету, отправляет POST-запрос с JSON. Это низкоуровневый способ, требующий знания API Docker.

Проблемы:

  • Сложность: необходимо самостоятельно обрабатывать все endpoint'ы.
  • Обработка ошибок: нужно разбирать JSON-ответы, смотреть статус-коды.
  • Версионирование: API Docker может меняться.

Этот метод полезен, когда библиотека docker-php недоступна или требуется нестандартное взаимодействие.

Расширенные примеры выполнения команд Docker из PHP

Пример 1: Управление контейнерами через Docker PHP SDK

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

Пример
use Docker\DockerClient;
use Docker\API\Exception\ContainerCreateConflictException;

$client = DockerClient::createUnixSocketClient();

// 1. Скачиваем образ, если его нет
$images = $client->images();
try {
    $images->pull('busybox', 'latest');
} catch (\Exception $e) {
    echo "Образ уже есть или не удалось скачать: " . $e->getMessage() . "\n";
}

// 2. Создаем контейнер
$config = [
    'Image' => 'busybox',
    'Cmd' => ['/bin/sh', '-c', 'for i in 1 2 3; do echo "Line $i"; sleep 1; done']
];
$container = $client->containers()->create($config);
$containerId = $container->getId();
echo "Создан контейнер $containerId\n";

// 3. Запускаем
$container->start();
echo "Контейнер запущен\n";

// 4. Ждем завершения
$container->wait();

// 5. Получаем логи
$logs = $container->logs()->getContents();
echo "Логи:\n" . $logs . "\n";

// 6. Удаляем контейнер
$container->remove(['force' => true]);
Создан контейнер abc123def
Контейнер запущен
Логи:
Line 1
Line 2
Line 3

Пример 2: Запуск docker-compose через exec с обработкой ошибок

Предположим, есть файл docker-compose.yml. Запускаем сборку и получаем вывод.

Пример
$projectDir = '/var/www/app';
$command = "docker-compose -f {$projectDir}/docker-compose.yml up -d 2>&1";
$output = [];
$retCode = 0;
exec($command, $output, $retCode);

if ($retCode !== 0) {
    echo "Ошибка выполнения docker-compose:\n" . implode("\n", $output);
} else {
    echo "Сервисы запущены:\n" . implode("\n", $output);
}
Сервисы запущены:
Container nginx  Started
Container php-fpm  Started

Важно: использование 2>&1 для перенаправления stderr в stdout, иначе ошибки не отобразятся.

Пример 3: Асинхронный запуск нескольких контейнеров через proc_open

Запускаем два контейнера параллельно и собираем их выводы.

Пример
$commands = [
    'docker run alpine echo "First"',
    'docker run alpine echo "Second"'
];
$processes = [];
foreach ($commands as $index => $cmd) {
    $descriptors = [
        0 => ['pipe', 'r'],
        1 => ['pipe', 'w'],
        2 => ['pipe', 'w']
    ];
    $process = proc_open($cmd, $descriptors, $pipes);
    $processes[] = ['process' => $process, 'pipes' => $pipes, 'index' => $index];
}

// Закрываем stdin для всех процессов
foreach ($processes as &$p) {
    fclose($p['pipes'][0]);
    // Неблокирующее чтение
    stream_set_blocking($p['pipes'][1], false);
    stream_set_blocking($p['pipes'][2], false);
}

// Собираем вывод
$outputs = [];
$errors = [];
foreach ($processes as &$p) {
    $out = stream_get_contents($p['pipes'][1]);
    $err = stream_get_contents($p['pipes'][2]);
    fclose($p['pipes'][1]);
    fclose($p['pipes'][2]);
    proc_close($p['process']);
    $outputs[] = $out;
    $errors[] = $err;
}

foreach ($outputs as $i => $o) {
    echo "Вывод процесса $i: $o\n";
}
foreach ($errors as $i => $e) {
    if ($e) echo "Ошибка процесса $i: $e\n";
}
Вывод процесса 0: First

Вывод процесса 1: Second

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

Пример 4: Получение информации о контейнерах через REST API Docker с помощью cURL

Запрос списка всех контейнеров (включая остановленные) и форматированный вывод.

Пример
$socketPath = '/var/run/docker.sock';
$url = 'http://localhost/containers/json?all=true';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_UNIX_SOCKET_PATH, $socketPath);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

if ($response === false) {
    echo "cURL error: " . curl_error($ch) . "\n";
} else {
    $containers = json_decode($response, true);
    foreach ($containers as $ct) {
        echo "ID: " . substr($ct['Id'], 0, 12) . "\n";
        echo "Image: " . $ct['Image'] . "\n";
        echo "State: " . $ct['State'] . "\n";
        echo "Names: " . implode(', ', $ct['Names']) . "\n";
        echo str_repeat('-', 30) . "\n";
    }
}
ID: abc123def456
Image: nginx:latest
State: running
Names: /webserver
------------------------------
ID: 789012345678
Image: redis:alpine
State: exited
Names: /cache
------------------------------

Этот подход даёт максимум гибкости, но требует знания Docker REST API и обработки потоковых ответов.

выполнение команд Docker из PHP - comments

En
Php exec docker (php)