Glance 深度解析:OpenStack 镜像服务原理与实践

Glance 深度解析:OpenStack 镜像服务原理与实践

定位与职责

Glance 是 OpenStack 的镜像服务,负责虚拟机镜像的存储、检索和分发:

  • 上传、下载、删除虚拟机镜像
  • 管理镜像元数据(格式、架构、最小内存等)
  • 支持多种存储后端(本地文件系统、Swift、Ceph、S3)
  • 镜像共享(跨 Project 共享)
  • 镜像签名验证(安全性)

架构总览

1
2
3
4
5
6
7
8
9
10
glance-api(唯一进程)

├── Registry Layer(元数据管理)→ MySQL

└── Store Layer(镜像数据存储)
├── filesystem store → /var/lib/glance/images/
├── rbd store → Ceph RBD Pool
├── swift store → Swift 对象存储
├── s3 store → AWS S3 / MinIO
└── http store → 外部 HTTP URL

从 Ocata 版本起,glance-registry 进程被废弃,功能合并到 glance-api。


镜像格式

磁盘格式(disk_format)

格式 说明 适用场景
qcow2 QEMU Copy-On-Write v2,支持快照、压缩、加密 KVM 虚拟化首选
raw 裸格式,无额外开销,性能最好 配合 Ceph 使用
vmdk VMware 格式 VMware 迁移
vhd Hyper-V 格式 Windows 虚拟化
iso 光盘镜像 系统安装盘
aki/ari/ami Amazon 镜像格式 AWS 兼容

容器格式(container_format)

格式 说明
bare 无容器,直接是磁盘镜像(最常用)
ovf Open Virtualization Format
ova OVF 的打包格式

镜像上传流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# glance/api/v2/images.py

class ImagesController:
def upload(self, req, image_id, data, size):
# 1. 验证 Token 和权限
# 2. 将数据流写入 Store
self.gateway.upload_image_data(
req.context, image, data, backend=backend
)

# glance/location.py
def store_add_to_backend(image_id, data, size, store, ...):
# 调用具体 Store 的 add 方法
(uri, size, checksum, loc) = store.add(image_id, data, size)
# 更新数据库中的 location 信息

上传到 Ceph RBD

1
2
3
4
5
6
7
8
9
10
11
12
13
# glance_store/_drivers/rbd.py

class Store(driver.Store):
def add(self, image_id, image_file, image_size, ...):
# 创建 RBD image
with self.get_connection() as conn:
rbd_image = rbd.Image(conn.ioctx, image_id)
# 分块写入
bytes_written = 0
for chunk in image_file:
rbd_image.write(chunk, bytes_written)
bytes_written += len(chunk)
return (self._get_uri(image_id), bytes_written, checksum, {})

镜像属性(Properties)

镜像属性是 Glance 的重要特性,Nova 调度和创建 VM 时会读取这些属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 上传镜像时设置属性
openstack image create ubuntu-22.04 \
--file ubuntu-22.04-server-cloudimg-amd64.img \
--disk-format qcow2 \
--container-format bare \
--property hw_disk_bus=virtio \
--property hw_vif_model=virtio \
--property hw_scsi_model=virtio-scsi \
--property os_type=linux \
--property os_distro=ubuntu \
--property os_version=22.04 \
--property hw_qemu_guest_agent=yes \
--min-disk 20 \
--min-ram 1024

Nova 读取镜像属性的关键场景

属性 作用
hw_disk_bus 磁盘总线类型(virtio/ide/scsi)
hw_vif_model 网卡型号(virtio/e1000/rtl8139)
hw_numa_nodes NUMA 节点数,影响调度
hw_cpu_policy CPU 绑定策略(dedicated/shared)
hw_machine_type QEMU 机器类型(pc/q35)
img_config_drive 是否需要 config drive
os_require_quiesce 快照前是否需要 quiesce(需要 QEMU Guest Agent)

镜像缓存机制

Nova compute 节点会缓存 Glance 镜像,避免每次创建 VM 都下载:

1
2
3
4
/var/lib/nova/instances/_base/
├── <image-id> ← 原始镜像缓存
├── <image-id>_10G ← resize 后的缓存
└── <image-id>.sha256 ← 校验文件
1
2
3
4
5
6
7
8
9
10
11
# nova/virt/libvirt/imagebackend.py

class Qcow2(Image):
def create_image(self, fetch_func, filename, size, ...):
# 检查缓存是否存在
if not os.path.exists(self.path):
# 从 Glance 下载到缓存目录
fetch_func(target=filename)
# 基于缓存创建 COW 子镜像(瞬间完成)
utils.execute('qemu-img', 'create', '-f', 'qcow2',
'-b', filename, self.path)

这就是为什么同一镜像创建第二个 VM 比第一个快很多——第一次需要下载,后续直接 COW。


镜像共享与可见性

1
2
3
4
5
6
7
8
9
10
11
12
# 镜像可见性级别
# public - 所有 Project 可见
# private - 只有创建者可见(默认)
# shared - 指定 Project 可见
# community - 所有 Project 可见,但不在默认列表

# 共享镜像给其他 Project
openstack image add project <image-id> <project-id>
openstack image set --shared <image-id>

# 接受共享镜像
openstack image set --accept <image-id>

镜像签名验证

从 Mitaka 版本起,Glance 支持镜像签名,防止镜像被篡改:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 生成密钥对
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

# 对镜像签名
openssl dgst -sha256 -sign private.pem ubuntu.img | base64 > signature.b64

# 上传带签名的镜像
openstack image create ubuntu-signed \
--property img_signature=$(cat signature.b64) \
--property img_signature_hash_method=SHA-256 \
--property img_signature_key_type=RSA-PSS \
--property img_signature_certificate_uuid=<cert-uuid>

多存储后端(Glance Store)

1
2
3
4
5
6
7
8
9
10
# glance-api.conf
[glance_store]
stores = rbd,http
default_store = rbd

[glance_store]
rbd_store_pool = images
rbd_store_user = glance
rbd_store_ceph_conf = /etc/ceph/ceph.conf
rbd_store_chunk_size = 8

源码关键路径

1
2
3
4
5
6
7
8
9
10
11
glance/
├── api/
│ └── v2/
│ ├── images.py # 镜像 CRUD API
│ └── image_data.py # 镜像数据上传/下载
├── common/
│ └── store_utils.py # Store 工具函数
├── db/ # 数据库模型
│ └── sqlalchemy/
│ └── models.py # Image, ImageProperty, ImageMember
└── location.py # Store 位置管理

生产运维要点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看镜像存储位置
openstack image show <image-id> | grep locations

# 镜像格式转换(qcow2 → raw,配合 Ceph 性能更好)
qemu-img convert -f qcow2 -O raw ubuntu.qcow2 ubuntu.raw

# 检查镜像完整性
qemu-img check ubuntu.qcow2

# 压缩 qcow2 镜像
qemu-img convert -c -f qcow2 -O qcow2 ubuntu.qcow2 ubuntu-compressed.qcow2

# 清理 Nova 镜像缓存(释放磁盘空间)
nova-manage image clean_compute_nodes