Docker 工具类/通用函数封装实践

举报
yd_276803270 发表于 2026/10/20 21:56:45 2026/10/20
【摘要】 ## 一、为什么需要封装 Docker 工具类?在实际项目中,我们经常会遇到以下场景:- 需要批量启动/停止多个容器- 需要检查容器健康状态- 需要清理无用的镜像和容器- 需要获取容器日志或执行命令- 需要构建镜像并推送到仓库如果每次都写一遍完整的 Docker 命令,不仅代码冗余,而且容易出错。通过封装通用函数,我们可以:1. **提高代码复用性**:一次封装,多处使用2. **统一错误处...

## 一、为什么需要封装 Docker 工具类?

在实际项目中,我们经常会遇到以下场景:

- 需要批量启动/停止多个容器
- 需要检查容器健康状态
- 需要清理无用的镜像和容器
- 需要获取容器日志或执行命令
- 需要构建镜像并推送到仓库

如果每次都写一遍完整的 Docker 命令,不仅代码冗余,而且容易出错。通过封装通用函数,我们可以:

1. **提高代码复用性**:一次封装,多处使用
2. **统一错误处理**:集中处理异常情况
3. **简化调用方式**:提供简洁的 API 接口
4. **便于测试和维护**:逻辑集中,易于调试

## 二、技术选型

根据不同的开发语言,我们可以选择不同的 Docker SDK:

| 语言 | 推荐库 | 说明 |
|------|--------|------|
| Python | docker-py | 官方维护,功能完善 |
| Go | docker/docker/client | 官方 SDK,性能好 |
| Node.js | dockerode | 社区活跃,API 友好 |
| Java | docker-java | 功能全面,支持异步 |

本文以 **Python** 为例进行演示,因为 docker-py 使用简单且功能强大。

## 三、核心工具类实现

### 1. 基础连接管理

```python
import docker
from typing import Optional


class DockerClient:
    """Docker 客户端管理类"""
    
    _instance = None
    
    def __new__(cls, base_url: Optional[str] = None):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._init_client(base_url)
        return cls._instance
    
    def _init_client(self, base_url: Optional[str] = None):
        """初始化 Docker 客户端"""
        try:
            if base_url:
                self.client = docker.DockerClient(base_url=base_url)
            else:
                self.client = docker.from_env()
            self.client.ping()  # 验证连接
        except Exception as e:
            raise ConnectionError(f"无法连接 Docker: {e}")
    
    def get_client(self) -> docker.DockerClient:
        """获取客户端实例"""
        return self.client
```

### 2. 容器管理通用函数

```python
class ContainerManager:
    """容器管理工具类"""
    
    def __init__(self, client: docker.DockerClient):
        self.client = client
    
    def list_containers(self, all: bool = False, filters: dict = None) -> list:
        """列出容器
        
        Args:
            all: 是否显示所有容器(包括停止的)
            filters: 过滤条件,如 {'status': 'running'}
        
        Returns:
            容器列表
        """
        try:
            return self.client.containers.list(all=all, filters=filters)
        except docker.errors.APIError as e:
            raise RuntimeError(f"获取容器列表失败: {e}")
    
    def start_container(self, container_id: str) -> bool:
        """启动容器"""
        try:
            container = self.client.containers.get(container_id)
            container.start()
            return True
        except docker.errors.NotFound:
            raise ValueError(f"容器 {container_id} 不存在")
        except docker.errors.APIError as e:
            raise RuntimeError(f"启动容器失败: {e}")
    
    def stop_container(self, container_id: str, timeout: int = 10) -> bool:
        """停止容器"""
        try:
            container = self.client.containers.get(container_id)
            container.stop(timeout=timeout)
            return True
        except docker.errors.NotFound:
            raise ValueError(f"容器 {container_id} 不存在")
        except docker.errors.APIError as e:
            raise RuntimeError(f"停止容器失败: {e}")
    
    def restart_container(self, container_id: str, timeout: int = 10) -> bool:
        """重启容器"""
        try:
            container = self.client.containers.get(container_id)
            container.restart(timeout=timeout)
            return True
        except docker.errors.NotFound:
            raise ValueError(f"容器 {container_id} 不存在")
        except docker.errors.APIError as e:
            raise RuntimeError(f"重启容器失败: {e}")
    
    def remove_container(self, container_id: str, force: bool = False) -> bool:
        """删除容器"""
        try:
            container = self.client.containers.get(container_id)
            container.remove(force=force)
            return True
        except docker.errors.NotFound:
            raise ValueError(f"容器 {container_id} 不存在")
        except docker.errors.APIError as e:
            raise RuntimeError(f"删除容器失败: {e}")
```

### 3. 镜像管理通用函数

```python
class ImageManager:
    """镜像管理工具类"""
    
    def __init__(self, client: docker.DockerClient):
        self.client = client
    
    def pull_image(self, image_name: str, tag: str = "latest") -> dict:
        """拉取镜像"""
        try:
            image = self.client.images.pull(image_name, tag=tag)
            return {
                "id": image.id,
                "tags": image.tags,
                "size": image.attrs.get("Size", 0)
            }
        except docker.errors.ImageNotFound:
            raise ValueError(f"镜像 {image_name}:{tag} 不存在")
        except docker.errors.APIError as e:
            raise RuntimeError(f"拉取镜像失败: {e}")
    
    def build_image(self, path: str, tag: str, dockerfile: str = "Dockerfile") -> str:
        """构建镜像"""
        try:
            image, logs = self.client.images.build(
                path=path,
                tag=tag,
                dockerfile=dockerfile,
                rm=True
            )
            return image.id
        except docker.errors.BuildError as e:
            raise RuntimeError(f"构建镜像失败: {e}")
    
    def remove_image(self, image_id: str, force: bool = False) -> bool:
        """删除镜像"""
        try:
            self.client.images.remove(image_id, force=force)
            return True
        except docker.errors.ImageNotFound:
            raise ValueError(f"镜像 {image_id} 不存在")
        except docker.errors.APIError as e:
            raise RuntimeError(f"删除镜像失败: {e}")
    
    def list_images(self, filters: dict = None) -> list:
        """列出本地镜像"""
        try:
            return self.client.images.list(filters=filters)
        except docker.errors.APIError as e:
            raise RuntimeError(f"获取镜像列表失败: {e}")
```

### 4. 容器日志与执行命令

```python
class ContainerOps:
    """容器操作工具类"""
    
    def __init__(self, client: docker.DockerClient):
        self.client = client
    
    def get_logs(self, container_id: str, tail: int = 100, timestamps: bool = False) -> str:
        """获取容器日志"""
        try:
            container = self.client.containers.get(container_id)
            return container.logs(tail=tail, timestamps=timestamps).decode("utf-8")
        except docker.errors.NotFound:
            raise ValueError(f"容器 {container_id} 不存在")
        except docker.errors.APIError as e:
            raise RuntimeError(f"获取日志失败: {e}")
    
    def exec_command(self, container_id: str, command: str) -> tuple:
        """在容器中执行命令
        
        Returns:
            (exit_code, output)
        """
        try:
            container = self.client.containers.get(container_id)
            exec_result = container.exec_run(command)
            return exec_result.exit_code, exec_result.output.decode("utf-8")
        except docker.errors.NotFound:
            raise ValueError(f"容器 {container_id} 不存在")
        except docker.errors.APIError as e:
            raise RuntimeError(f"执行命令失败: {e}")
    
    def get_container_stats(self, container_id: str) -> dict:
        """获取容器资源使用情况"""
        try:
            container = self.client.containers.get(container_id)
            stats = container.stats(stream=False)
            return {
                "cpu_usage": stats["cpu_stats"]["cpu_usage"]["total_usage"],
                "memory_usage": stats["memory_stats"]["usage"],
                "memory_limit": stats["memory_stats"]["limit"],
                "network_rx": stats["networks"].get("eth0", {}).get("rx_bytes", 0),
                "network_tx": stats["networks"].get("eth0", {}).get("tx_bytes", 0)
            }
        except docker.errors.NotFound:
            raise ValueError(f"容器 {container_id} 不存在")
        except docker.errors.APIError as e:
            raise RuntimeError(f"获取容器状态失败: {e}")
```

### 5. 批量操作与清理工具

```python
class DockerUtils:
    """Docker 通用工具函数"""
    
    def __init__(self, client: docker.DockerClient):
        self.client = client
        self.container_mgr = ContainerManager(client)
        self.image_mgr = ImageManager(client)
        self.container_ops = ContainerOps(client)
    
    def cleanup_unused(self, include_volumes: bool = False) -> dict:
        """清理未使用的资源
        
        Returns:
            清理结果统计
        """
        result = {
            "containers_removed": 0,
            "images_removed": 0,
            "volumes_removed": 0
        }
        
        # 清理停止的容器
        stopped_containers = self.client.containers.list(
            all=True, 
            filters={"status": "exited"}
        )
        for container in stopped_containers:
            try:
                container.remove()
                result["containers_removed"] += 1
            except docker.errors.APIError:
                continue
        
        # 清理悬空镜像
        dangling_images = self.client.images.list(filters={"dangling": True})
        for image in dangling_images:
            try:
                self.client.images.remove(image.id)
                result["images_removed"] += 1
            except docker.errors.APIError:
                continue
        
        # 清理未使用的卷
        if include_volumes:
            volumes = self.client.volumes.list()
            for volume in volumes:
                if not volume.attrs.get("Containers"):
                    try:
                        volume.remove()
                        result["volumes_removed"] += 1
                    except docker.errors.APIError:
                        continue
        
        return result
    
    def get_system_info(self) -> dict:
        """获取 Docker 系统信息"""
        info = self.client.info()
        return {
            "containers": info["Containers"],
            "running_containers": info["ContainersRunning"],
            "images": info["Images"],
            "version": info["ServerVersion"],
            "os_type": info["OSType"],
            "architecture": info["Architecture"],
            "memory_total": info["MemTotal"],
            "cpu_count": info["NCPU"]
        }
    
    def wait_for_container_ready(self, container_id: str, timeout: int = 30) -> bool:
        """等待容器就绪"""
        import time
        
        start_time = time.time()
        while time.time() - start_time < timeout:
            try:
                container = self.client.containers.get(container_id)
                if container.status == "running":
                    # 检查健康状态
                    health = container.attrs.get("State", {}).get("Health", {})
                    if not health or health.get("Status") == "healthy":
                        return True
            except docker.errors.NotFound:
                pass
            time.sleep(1)
        
        return False
```

## 四、使用示例

```python
# 初始化客户端
docker_client = DockerClient()
client = docker_client.get_client()

# 创建工具实例
utils = DockerUtils(client)

# 查看系统信息
info = utils.get_system_info()
print(f"Docker 版本: {info['version']}")
print(f"运行中容器: {info['running_containers']}/{info['containers']}")

# 列出所有运行中的容器
running_containers = utils.container_mgr.list_containers(
    filters={"status": "running"}
)
for container in running_containers:
    print(f"容器: {container.name} - {container.status}")

# 构建并启动容器
image_id = utils.image_mgr.build_image(
    path="./myapp",
    tag="myapp:latest"
)
print(f"镜像构建成功: {image_id}")

# 启动容器
container = client.containers.run(
    "myapp:latest",
    detach=True,
    ports={"8080/tcp": 8080}
)
print(f"容器启动成功: {container.id}")

# 等待容器就绪
if utils.wait_for_container_ready(container.id, timeout=60):
    print("容器已就绪")
    
    # 获取日志
    logs = utils.container_ops.get_logs(container.id, tail=50)
    print(f"最近日志:\n{logs}")
    
    # 执行命令
    exit_code, output = utils.container_ops.exec_command(
        container.id, 
        "curl -s http://localhost:8080/health"
    )
    print(f"健康检查结果: {output}")

# 清理资源
result = utils.cleanup_unused(include_volumes=True)
print(f"清理完成: {result}")
```

## 五、最佳实践建议

### 1. 错误处理
- 统一使用自定义异常类型,便于上层捕获和处理
- 对 Docker API 错误进行包装,提供更友好的错误信息

### 2. 连接管理
- 使用单例模式管理客户端连接,避免重复创建
- 支持自定义 Docker 主机地址,方便连接远程 Docker

### 3. 性能优化
- 批量操作时使用 Docker API 的过滤功能,减少数据传输
- 获取日志时使用 `tail` 参数限制返回数量

### 4. 安全性
- 避免在代码中硬编码敏感信息
- 使用环境变量或配置文件管理 Docker 连接参数

### 5. 测试
- 使用 `docker-mock` 等库进行单元测试
- 在 CI/CD 中集成 Docker 集成测试

## 六、总结

通过封装 Docker 工具类,我们可以:

- **减少重复代码**:将常用的 Docker 操作抽象为通用函数
- **提高代码质量**:统一的错误处理和日志记录
- **便于维护**:修改 Docker 相关逻辑只需改动一处
- **提升开发效率**:提供简洁的 API 接口,快速集成

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。