Apache Libcloud Docker Compose 部署指南

用 Docker Compose 编排 python:3.10-slim 镜像运行 Apache Libcloud 3.9.1 演示工程(Compute Dummy 驱动 + Storage 本地驱动),覆盖编排文件、启动验证、数据持久化、等价 docker run 与常用运维命令。

Apache LibcloudApache Libcloud2026/10/12

Apache Libcloud Docker Compose 部署指南

目标:Apache Libcloud 是 Python 库,不提供官方镜像、也不是常驻服务,因此没有「拉起一个 Libcloud 服务」的 Compose 场景。本文给出一份运行演示工程的 Compose 编排——用 python:3.10-slim 基础镜像挂载演示工程目录,启动时安装 apache-libcloud==3.9.1 及依赖并运行 Compute(Dummy 驱动)+ Storage(本地驱动)完整演练,采集真实回显。已在 Ubuntu 22.04、Docker Compose v5.5.1 环境下实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS,Kernel 6.8.0-138-generic
  • Docker Compose:v5.5.1(docker compose version 实测)
  • 基础镜像:python:3.10-slim(内含 Python 3.10)
  • 目标库版本:apache-libcloud 3.9.1 + fasteners 0.20(存储本地驱动依赖)
  • 演示工程目录:/opt/data/ops/libcloud-compose(含 compose.yml 与 app/)
  • 持久化卷:libcloud-compose_storage-data(对象存储根目录)、libcloud-compose_pip-cache(pip 缓存)

二、镜像选型说明

用途镜像说明
运行演示工程python:3.10-slim官方 Python 3.10 精简镜像,满足 Libcloud 3.9.1 的 Requires-Python >=3.10,体积小、启动快
运行演示工程(备选)python:3.12-slim更新解释器版本,Libcloud 兼容;如驱动生态对旧版本有要求可回退 3.10

💡 说明:Apache Libcloud 官方不发布任何 Docker 镜像——它是被上层 Python 应用 import 的库。上表镜像仅充当「运行 Libcloud 演示程序」的载体;生产中应把 Libcloud 作为依赖写进业务镜像的 requirements.txt。

三、前置条件

  • 服务器已安装 Docker 与 Docker Compose 插件。
  • 可访问 PyPI 镜像源(本文用清华镜像 https://pypi.tuna.tsinghua.edu.cn/simple,在启动命令中显式指定)。
  • 已创建演示工程目录 /opt/data/ops/libcloud-compose,包含 compose.yml 与 app/main.py、app/requirements.txt(见第四节)。

四、部署步骤(演示工程 Compose)

4.1 创建演示工程文件

工程目录结构:

mkdir -p /opt/data/ops/libcloud-compose/app

依赖清单 /opt/data/ops/libcloud-compose/app/requirements.txt:

apache-libcloud==3.9.1
fasteners==0.20

演示程序 /opt/data/ops/libcloud-compose/app/main.py(Compute 用内置 Dummy 驱动离线演练,Storage 用本地文件驱动,无需真实云账号):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Libcloud 3.9.1 演示工程:Compute(Dummy 驱动) + Storage(本地驱动) 全流程演练。"""
import os
import shutil

from libcloud.compute.providers import get_driver as get_compute_driver
from libcloud.compute.types import Provider as ComputeProvider
from libcloud.storage.providers import get_driver as get_storage_driver
from libcloud.storage.types import Provider as StorageProvider

import libcloud

STORAGE_ROOT = os.environ.get("LIBCLOUD_STORAGE_ROOT", "/opt/libcloud-demo/storage-data")


def main():
    print("=" * 60)
    print("libcloud version:", libcloud.__version__)
    print("=" * 60)

    # ---------- Compute: Dummy 驱动 ----------
    cls = get_compute_driver(ComputeProvider.DUMMY)
    driver = cls("admin")

    images = driver.list_images()
    sizes = driver.list_sizes()
    print("[compute] images:", [(i.id, i.name) for i in images])
    print("[compute] sizes :", [(s.id, s.name, s.ram, s.disk) for s in sizes])

    node = driver.create_node(name="demo-node", image=images[0], size=sizes[0])
    print("[compute] created:", node.id, node.name, node.state, node.public_ips)

    nodes = driver.list_nodes()
    print("[compute] list_nodes:", [(n.id, n.name, str(n.state)) for n in nodes])

    print("[compute] reboot_node ->", driver.reboot_node(node))
    print("[compute] destroy_node ->", driver.destroy_node(node))
    print("[compute] remaining:", [(n.id, n.name) for n in driver.list_nodes()])

    # ---------- Storage: 本地驱动 ----------
    # 清空存储根目录内容而非目录本身(目录可能是挂载点,直接删除会报错)
    if os.path.exists(STORAGE_ROOT):
        for entry in os.listdir(STORAGE_ROOT):
            p = os.path.join(STORAGE_ROOT, entry)
            shutil.rmtree(p) if os.path.isdir(p) else os.remove(p)
    os.makedirs(STORAGE_ROOT, exist_ok=True)

    payload = b"libcloud demo object payload\n"
    with open("/tmp/demo-payload.txt", "wb") as f:
        f.write(payload)

    scls = get_storage_driver(StorageProvider.LOCAL)
    sdriver = scls(STORAGE_ROOT)

    container = sdriver.create_container(container_name="demo-bucket")
    obj = sdriver.upload_object("/tmp/demo-payload.txt", container, object_name="payload.txt")
    print("[storage] uploaded:", obj.name, "size:", obj.size)

    download_path = "/tmp/demo-payload-downloaded.txt"
    sdriver.download_object(obj, download_path, overwrite_existing=True)
    with open(download_path, "rb") as f:
        data = f.read()
    assert data == payload, "downloaded content mismatch"
    print("[storage] downloaded content matches, bytes:", len(data))

    print("[storage] objects:", [(o.name, o.size) for o in container.list_objects()])
    print("[storage] containers:", [c.name for c in sdriver.list_containers()])

    print("=" * 60)
    print("ALL CHECKS PASSED")


if __name__ == "__main__":
    main()

4.2 编写 Compose 编排文件

创建 /opt/data/ops/libcloud-compose/compose.yml:

services:
  libcloud-demo:
    image: python:3.10-slim
    container_name: libcloud-demo
    working_dir: /app
    command: ["sh", "-c", "pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt && python main.py"]
    volumes:
      - ./app:/app
      - storage-data:/opt/libcloud-demo/storage-data
      - pip-cache:/root/.cache/pip
    environment:
      - LIBCLOUD_STORAGE_ROOT=/opt/libcloud-demo/storage-data
      - PIP_CACHE_DIR=/root/.cache/pip

volumes:
  storage-data:
  pip-cache:

💡 说明:./app 挂载演示源码;storage-data 命名卷承接 Storage 本地驱动的对象根目录,实现数据落盘;pip-cache 缓存 wheel 包,重复启动免重复下载。

4.3 启动并运行演示工程

cd /opt/data/ops/libcloud-compose
docker compose up

真实回显(关键行):

 Volume libcloud-compose_storage-data Creating
 Volume libcloud-compose_pip-cache Creating
 Network libcloud-compose_default Creating
 Volume libcloud-compose_storage-data Created
 Volume libcloud-compose_pip-cache Created
 Network libcloud-compose_default Created
Attaching to libcloud-demo
 Container libcloud-demo Starting
 Container libcloud-demo Started
libcloud-demo  | Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
libcloud-demo  | Collecting apache-libcloud==3.9.1
libcloud-demo  |   Downloading https://pypi.tuna.tsinghua.edu.cn/packages/ed/fd/6dbe8405dfbd6980513af65901548e3ee894a2879f3be1627830f023e38e/apache_libcloud-3.9.1-py3-none-any.whl (3.8 MB)
libcloud-demo  | Collecting fasteners==0.20
libcloud-demo  |   Downloading https://pypi.tuna.tsinghua.edu.cn/packages/51/ac/e5d886f892666d2d1e5cb8c1a41146e1d79ae8896477b1153a21711d3b44/fasteners-0.20-py3-none-any.whl (18 kB)
libcloud-demo  | Successfully installed apache-libcloud-3.9.1 certifi-2026.7.22 charset_normalizer-3.5.2 fasteners-0.20 idna-3.20 requests-2.34.2 urllib3-2.8.0
libcloud-demo  | ============================================================
libcloud-demo  | libcloud version: 3.9.1
libcloud-demo  | ============================================================
libcloud-demo  | [compute] images: [('1', 'Ubuntu 9.10'), ('2', 'Ubuntu 9.04'), ('3', 'Slackware 4')]
libcloud-demo  | [compute] sizes : [('1', 'Small', 128, 4), ('2', 'Medium', 512, 16), ('3', 'Big', 4096, 32), ('4', 'XXL Big', 8192, 128)]
libcloud-demo  | [compute] created: 3 dummy-3 running ['127.0.0.3']
libcloud-demo  | [compute] list_nodes: [('1', 'dummy-1', 'running'), ('2', 'dummy-2', 'running'), ('3', 'dummy-3', 'running')]
libcloud-demo  | [compute] reboot_node -> True
libcloud-demo  | [compute] destroy_node -> True
libcloud-demo  | [compute] remaining: [('1', 'dummy-1'), ('2', 'dummy-2')]
libcloud-demo  | [storage] uploaded: payload.txt size: 29
libcloud-demo  | [storage] downloaded content matches, bytes: 29
libcloud-demo  | [storage] objects: [('payload.txt', 29)]
libcloud-demo  | [storage] containers: ['demo-bucket']
libcloud-demo  | ============================================================
libcloud-demo  | ALL CHECKS PASSED
libcloud-demo exited with code 0

退出码 0 且末行输出 ALL CHECKS PASSED 即演练成功。

4.4 查看运行状态

一次性运行的服务正常退出即成功:

docker compose ps -a

真实回显:

NAME            IMAGE              COMMAND                   SERVICE          CREATED          STATUS                              PORTS
libcloud-demo   python:3.10-slim   "sh -c 'pip install …"   libcloud-demo    11 seconds ago   Exited (0) Less than a second ago

查看完整运行日志:

docker compose logs libcloud-demo

真实回显(尾部关键行):

libcloud-demo  | [storage] uploaded: payload.txt size: 29
libcloud-demo  | [storage] downloaded content matches, bytes: 29
libcloud-demo  | [storage] objects: [('payload.txt', 29)]
libcloud-demo  | [storage] containers: ['demo-bucket']
libcloud-demo  | ============================================================
libcloud-demo  | ALL CHECKS PASSED

4.5 在编排环境内执行自定义命令

需要单独跑一段验证代码时用 docker compose run(注意必须重新安装依赖,见 FAQ 11.2):

docker compose run --rm libcloud-demo sh -c "pip install -q -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt && python -c 'import libcloud; print(\"compose run libcloud:\", libcloud.__version__)'"

真实回显(关键行):

compose run libcloud: 3.9.1

五、数据持久化说明

卷挂载点用途持久化内容
storage-data/opt/libcloud-demo/storage-dataStorage 本地驱动对象根目录演示程序上传的对象文件(按 bucket 建子目录)
pip-cache/root/.cache/pippip 下载缓存wheel 包缓存,重复启动加速

查看卷:

docker volume ls | grep libcloud

真实回显:

local     libcloud-compose_pip-cache
local     libcloud-compose_storage-data

💡 说明:docker compose down 不会删除命名卷,数据可跨启停保留;docker compose down -v 会连同卷一起删除,属于破坏性操作。

六、常用环境变量表

环境变量示例值作用
LIBCLOUD_STORAGE_ROOT/opt/libcloud-demo/storage-data演示程序 Storage 本地驱动根目录(由 main.py 读取)
PIP_CACHE_DIR/root/.cache/pippip 缓存目录,配合 pip-cache 卷加速重复安装
LIBCLOUD_DEBUG/tmp/libcloud-debug.log开启 Libcloud HTTP 请求调试日志并落盘
LIBCLOUD_DEBUG_PRETTY_PRINT_RESPONSEtrue调试日志以更可读格式输出响应体
LIBCLOUD_RETRY_FAILED_HTTP_REQUESTStrue全局启用失败请求重试(对瞬断/限流生效)
SSL_CERT_FILE/path/to/ca-bundle.pem覆盖 CA 证书包路径(libcloud.security 读取)

七、常用配置参数说明(command)

compose.yml 的 command 字段决定该服务一次性执行的动作,按需替换:

command: ["sh", "-c", "pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt && python main.py"]
  • pip install -i <镜像源> -r requirements.txt:按清单安装依赖,镜像源显式指定避免默认源慢。
  • python main.py:执行演示程序;替换为 python your_script.py 即可运行自己的脚本。
  • 需要交互式探索时改为 ["python"] 并配合 docker compose run --rm libcloud-demo 进入 REPL(同样需先安装依赖)。

八、配置文件持久化(挂载)

演示工程的全部文件经 ./app:/app 绑定挂载进运行环境,宿主机直接编辑、运行环境即时生效:

  • /opt/data/ops/libcloud-compose/app/main.py:演示程序
  • /opt/data/ops/libcloud-compose/app/requirements.txt:依赖清单

如需额外配置(如 CA 证书包),把宿主机文件挂入并配环境变量即可:

    volumes:
      - ./app:/app
      - ./certs/corporate-ca.pem:/etc/ssl/libcloud-ca.pem:ro
    environment:
      - SSL_CERT_FILE=/etc/ssl/libcloud-ca.pem

九、等价 docker run 运行方式

不用 Compose 时,等价的单条 docker run 命令如下(与 4.3 的编排完全等价,实测退出码 0):

docker run --rm --name libcloud-demo-run \
  -v /opt/data/ops/libcloud-compose/app:/app \
  -w /app \
  -v libcloud-compose_storage-data:/opt/libcloud-demo/storage-data \
  -e LIBCLOUD_STORAGE_ROOT=/opt/libcloud-demo/storage-data \
  python:3.10-slim \
  sh -c "pip install -q -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt && python main.py"

真实回显(尾部关键行):

[storage] objects: [('payload.txt', 29)]
[storage] containers: ['demo-bucket']
============================================================
ALL CHECKS PASSED

十、常用运维命令

# 启动并前台运行演示工程(完整输出)
docker compose up

# 后台启动
docker compose up -d

# 查看服务状态(含已退出)
docker compose ps -a

# 查看日志
docker compose logs libcloud-demo
docker compose logs -f libcloud-demo

# 临时执行一条自定义命令(用完即删)
docker compose run --rm libcloud-demo sh -c "pip install -q -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt && python -V"

# 停止并删除运行环境(保留数据卷)
docker compose down

# 停止并删除运行环境与数据卷(破坏性)
docker compose down -v

docker compose down 真实回显(关键行):

 Container libcloud-demo Stopping
 Container libcloud-demo Stopped
 Container libcloud-demo Removing
 Container libcloud-demo Removed
 Network libcloud-compose_default Removing
 Network libcloud-compose_default Removed

十一、常见问题(FAQ)

Q1:运行时报 OSError: [Errno 16] Device or resource busy?

真实报错:

OSError: [Errno 16] Device or resource busy: '/opt/libcloud-demo/storage-data'

定位:程序里对 Storage 根目录执行了 shutil.rmtree(STORAGE_ROOT),而该目录被挂载为数据卷,挂载点本身不能删除。解决:清空目录内容而不是删除目录本身(本文 4.1 的 main.py 已按此方式处理):

for entry in os.listdir(STORAGE_ROOT):
    p = os.path.join(STORAGE_ROOT, entry)
    shutil.rmtree(p) if os.path.isdir(p) else os.remove(p)

Q2:docker compose run 报 ModuleNotFoundError: No module named 'libcloud'?

真实报错:

ModuleNotFoundError: No module named 'libcloud'

定位:docker compose run libcloud-demo python -c 这类覆盖了默认 command 的调用,新的运行环境里没有执行过 pip install。解决:在 run 的命令里显式补一条安装(见 4.5),或保留默认 command 重新 docker compose up。

Q3:pip 安装慢或超时?

启动命令里的 -i https://pypi.tuna.tsinghua.edu.cn/simple 指定了国内镜像源;同时启用 pip-cache 卷,首次之后的启动走本地缓存。

Q4:如何接入真实云厂商做验证?

把 main.py 中的 Provider.DUMMY 换成目标厂商(如 Provider.EC2、Provider.ALIYUN_ECS),凭据通过 environment 注入驱动构造参数(key/secret),切勿写死进镜像或编排文件。

Q5:如何开启 Libcloud 自身的请求调试日志?

在 environment 增加 LIBCLOUD_DEBUG=/tmp/libcloud-debug.log(目录需可写),即可把每次 HTTP 请求/响应以 curl 命令形式记录下来。

十二、参考链接