我正在开发一个 Symfony 应用程序来备份和恢复数据库。我的应用程序在 Symfony 服务器上运行良好,但在尝试将它与我的 dockerized 设置一起使用时遇到问题。
背景:
我有两个 PostgreSQL 数据库:主数据库名为“potter”,备份数据库名为“backup”。我可以成功创建这些数据库的转储并将 SQL 文件存储在我的 Apache-PHP 容器中,位置为
/var/www/var/dump/
。
当我尝试将主数据库恢复到备份时,只有当我在项目终端中执行以下命令时,它才能完美运行:
docker exec -it safebase-backup-1 psql -U user -d backup -f /var/www/var/dump/potter_dump_02-10-2024_14-05-08.sql
但是,当我在另一个上下文中(例如,从我的应用程序)运行相同的命令时,我收到以下错误:
命令“'docker''exec''-i''safebase-backup-1''psql''-U''用户''-d''备份''-f''/var/www/var/转储/potter_dump_02-10-2024_14-05-08.sql'”失败。工作目录:/var/www/public 错误:proc_open(): posix_spawn() 失败:没有这样的文件或目录。
问题:
为什么该命令在终端中有效,但从我的应用程序执行时失败?
docker exec -it safebase-backup-1 psql -U user -d backup -f /var/www/var/dump/potter_dump_02-10-2024_14-05-08.sql
如何解决“proc_open(): posix_spawn() failed”错误?
任何帮助或建议将不胜感激!
谢谢!
我的代码:
恢复服务:
<?php
namespace App\Service;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
class RestoreService
{
public function restoreDatabase(string $fileName, string $databaseName): bool
{
$targetContainer = $this->getTargetContainer($databaseName);
$restoreCommand = [
'docker', 'exec', '-i', $targetContainer, 'psql', '-U', 'user', '-d', $databaseName, '-f', "/var/www/var/dump/$fileName"
];
$this->executeProcess($restoreCommand);
return true;
}
private function getTargetContainer(string $databaseName): string
{
switch ($databaseName) {
case 'potter':
return "safebase-database-1";
case 'backup':
return "safebase-backup-1";
default:
throw new \InvalidArgumentException("Database unknown : $databaseName");
}
}
private function executeProcess(array $command): void
{
$process = new Process($command);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
}
}
我的控制器:
#[Route('/backlog/restore/{id}', name: 'app_backup_restore')]
public function restoreForm(BackupLog $backupLog, ManagerRegistry $doctrine, Request $request): Response
{
$filePath = $backupLog->getFilePath();
if (!file_exists($filePath)) {
$this->addFlash('error', 'Le fichier de sauvegarde n\'existe pas.');
return $this->redirectToRoute('app_backups');
}
$databases = ['potter', 'backup'];
$form = $this->createForm(RestoreDatabaseType::class, null, ['databases' => $databases]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$databaseName = $data['database'];
try {
// Passer uniquement le nom du fichier à la méthode de restauration
$this->restoreService->restoreDatabase(basename($filePath), $databaseName);
$this->addFlash('success', 'La base de données a été restaurée avec succès.');
} catch (\Exception $e) {
$this->addFlash('error', 'Erreur lors de la restauration : ' . $e->getMessage());
}
return $this->redirectToRoute('app_backups');
}
return $this->render('backlog/restore.html.twig', [
'form' => $form->createView(),
'backupLog' => $backupLog,
]);
}
请勿将
docker exec
用于常规数据库操作。 这是不必要的,并且为调用者提供了对整个系统不受限制的根级别访问权限。
如果您只是尝试运行
psql
命令来恢复数据,那么我会完全删除子进程调用。 转储文件包含一系列重建数据库的 SQL 命令,您可以像运行任何其他 SQL 命令一样执行这些命令。 使用您的应用程序框架创建普通的数据库连接,读取转储文件的内容,并通过数据库连接执行它们。
如果你确实需要使用
psql
,那么我会直接在容器中运行它,而不是通过docker exec
。 您需要确保它已安装在您的 Dockerfile 中
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql-client
然后当您调用它时,通过
psql -h
(作为主机名)传递数据库容器名称,而不带 docker exec
。
$restoreCommand = [
'psql', '-h', $targetContainer, '-U', 'user', '-d', $databaseName, '-f', "/var/www/var/dump/$fileName"
];
在这两种情况下,请注意转储文件需要位于应用程序容器中,而不是数据库容器中;
docker exec
路径要求它位于数据库容器中,而不是应用程序容器中。
此外,在这两种情况下,应用程序和数据库都需要位于同一 Docker 网络上。 如果您在同一个 Compose 文件中启动这两个文件,则会自动发生(我倾向于建议删除所有手动
networks:
设置);如果您使用手动 docker run
命令,您需要确保您已经 docker network create
网络是 docker run --net
两个容器都位于同一网络上。
正如我在第一段中提到的,如果您可以运行任何
docker
命令,那么您几乎可以轻松 root 整个主机系统。 容器通常没有可用的 Docker 套接字来避免这种可能性。 容器镜像通常也没有比运行特定应用程序所需的更多的软件或工具。
您的
posix_spawn() failed: No such file or directory
错误基本上可以翻译为“容器没有 docker
二进制文件”。 我会尽力避免使用这个。 上述任一路径(使用直接数据库连接或 psql
的本地副本)都可以在任何容器或非容器环境中工作。 如果您严重依赖 docker
CLI 工具,那么它无法在非容器环境、非特权用户或 Kubernetes 等非 Docker 容器环境中工作。