Files
DevOpsLab/app/core/docker_client.py
Сергей Антропов d4b0d6f848 Исправление синтаксической ошибки в molecule_executor.py и обновление k8s preset'ов
- Исправлена незакрытая скобка в _build_test_command (строка 745)
- Добавлена поддержка k8s preset'ов: выполнение create_k8s_cluster.py перед create.yml
- Обновлены образы в k8s preset'ах: заменен недоступный ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy на inecs/ansible-lab:ubuntu22-latest
- Обновлены preset'ы в базе данных через SQL
- Обновлены файлы: k8s-single.yml, k8s-multi.yml, k8s-istio-full.yml
2026-02-16 00:31:09 +03:00

212 lines
8.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Docker клиент для управления контейнерами
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import docker
from docker import APIClient
import os
from typing import List, Dict, Optional
from app.core.config import settings
import logging
logger = logging.getLogger(__name__)
class DockerClient:
"""Клиент для работы с Docker API"""
def __init__(self):
"""Инициализация Docker клиента (ленивая инициализация)"""
self._client = None
@property
def client(self):
"""Ленивая инициализация Docker клиента"""
if self._client is None:
try:
# Временно удаляем DOCKER_HOST из окружения, если он установлен
# Это необходимо, так как Docker SDK может неправильно парсить его
original_docker_host = os.environ.pop("DOCKER_HOST", None)
try:
# Пробуем docker.from_env() без DOCKER_HOST
logger.info("Trying docker.from_env() without DOCKER_HOST")
self._client = docker.from_env()
self._client.ping()
logger.info("Docker client initialized successfully with docker.from_env()")
except Exception as e1:
logger.warning(f"docker.from_env() failed: {e1}")
# Если from_env не работает, пробуем прямой base_url
try:
# Используем прямой путь к Docker socket
base_url = "unix:///var/run/docker.sock"
logger.info(f"Trying direct base_url: {base_url}")
self._client = docker.DockerClient(base_url=base_url)
self._client.ping()
logger.info("Docker client initialized successfully with direct base_url")
except Exception as e2:
logger.error(f"Direct base_url also failed: {e2}")
# Последняя попытка - используем APIClient
try:
logger.info("Trying APIClient as last resort")
api_client = APIClient(base_url="unix:///var/run/docker.sock")
api_client.version()
# Если APIClient работает, создаем DockerClient
self._client = docker.DockerClient(base_url="unix:///var/run/docker.sock")
self._client.ping()
logger.info("Docker client initialized successfully with APIClient")
except Exception as e3:
logger.error(f"All methods failed. Last error: {e3}")
raise
finally:
# Восстанавливаем DOCKER_HOST, если он был установлен
if original_docker_host:
os.environ["DOCKER_HOST"] = original_docker_host
except Exception as e:
logger.error(f"Failed to initialize Docker client: {e}")
logger.error(f"DOCKER_HOST env: {os.getenv('DOCKER_HOST', 'not set')}")
logger.error(f"Settings DOCKER_HOST: {settings.DOCKER_HOST}")
import traceback
logger.error(traceback.format_exc())
raise
return self._client
def list_images(self) -> List[Dict]:
"""Список всех Docker образов"""
try:
images = self.client.images.list()
return [
{
"id": img.id,
"tags": img.tags,
"created": img.attrs.get("Created"),
"size": img.attrs.get("Size", 0)
}
for img in images
]
except Exception as e:
logger.error(f"Error listing images: {e}")
return []
def list_containers(self, all: bool = False) -> List[Dict]:
"""Список контейнеров"""
try:
containers = self.client.containers.list(all=all)
return [
{
"id": container.id,
"name": container.name,
"status": container.status,
"image": container.image.tags[0] if container.image.tags else container.image.id,
"ports": container.ports,
"created": container.attrs.get("Created")
}
for container in containers
]
except Exception as e:
logger.error(f"Error listing containers: {e}")
return []
def get_container_ip(self, container_name: str) -> Optional[str]:
"""Получение IP адреса контейнера"""
try:
container = self.client.containers.get(container_name)
network_settings = container.attrs.get("NetworkSettings", {})
networks = network_settings.get("Networks", {})
# Ищем IP в первой доступной сети
for network_name, network_info in networks.items():
ip = network_info.get("IPAddress")
if ip:
return ip
return None
except Exception as e:
logger.error(f"Error getting container IP for {container_name}: {e}")
return None
def create_container(
self,
image: str,
name: str,
command: Optional[str] = None,
environment: Optional[Dict] = None,
volumes: Optional[Dict] = None,
network: Optional[str] = None,
privileged: bool = False
) -> Dict:
"""Создание контейнера"""
try:
container = self.client.containers.run(
image=image,
name=name,
command=command,
environment=environment or {},
volumes=volumes or {},
network=network,
privileged=privileged,
detach=True,
remove=False
)
return {
"success": True,
"id": container.id,
"name": container.name,
"status": container.status
}
except Exception as e:
logger.error(f"Error creating container: {e}")
return {
"success": False,
"error": str(e)
}
def stop_container(self, container_name: str) -> bool:
"""Остановка контейнера"""
try:
container = self.client.containers.get(container_name)
container.stop()
return True
except Exception as e:
logger.error(f"Error stopping container {container_name}: {e}")
return False
def remove_container(self, container_name: str, force: bool = False) -> bool:
"""Удаление контейнера"""
try:
container = self.client.containers.get(container_name)
container.remove(force=force)
return True
except Exception as e:
logger.error(f"Error removing container {container_name}: {e}")
return False
def get_container_logs(self, container_name: str, tail: int = 100) -> str:
"""Получение логов контейнера"""
try:
container = self.client.containers.get(container_name)
logs = container.logs(tail=tail, timestamps=True)
return logs.decode('utf-8', errors='replace')
except Exception as e:
logger.error(f"Error getting logs for {container_name}: {e}")
return ""
def exec_command(self, container_name: str, command: str) -> Dict:
"""Выполнение команды в контейнере"""
try:
container = self.client.containers.get(container_name)
result = container.exec_run(command)
return {
"success": result.exit_code == 0,
"output": result.output.decode('utf-8', errors='replace'),
"exit_code": result.exit_code
}
except Exception as e:
logger.error(f"Error executing command in {container_name}: {e}")
return {
"success": False,
"error": str(e)
}