Glance 是 OpenStack 的镜像服务(Image Service),它负责虚拟机镜像的发现、注册、检索和交付。它提供了一个 RESTful API,允许用户查询虚拟机镜像元数据并检索实际镜像。
1、概述
1.1 核心概念
- 镜像(Image):虚拟机的模板,包含操作系统和必要的软件
- 镜像元数据(Metadata):描述镜像的信息,如名称、大小、格式、操作系统类型等
- 镜像格式:支持多种格式,如 QCOW2、RAW、VMDK、VHD 等
- 存储后端:存储镜像数据的位置,可以是本地文件系统、对象存储等
1.2 架构
- Glance API:接收用户请求,提供 RESTful API 接口
- Glance Registry:管理镜像元数据(Queens 版本开始已被整合到 API 服务中)
- Glance Store:处理镜像数据的存储和检索,支持多种后端存储
- 数据库:存储镜像元数据信息
- +----------------+ +----------------+ +----------------+
- | 客户端 | | Nova 服务 | | 管理员 |
- +-------+--------+ +-------+--------+ +-------+--------+
- | | |
- +--------------------+--------------------+
- |
- v
- +---------------------------------------------------------+
- | Glance API |
- +---------------------------------------------------------+
- | |
- v v
- +----------------+ +---------------------------+
- | 数据库 | | Glance Store |
- | (元数据存储) | | (镜像数据存储) |
- +----------------+ +---------------------------+
- |
- +-----------------------+-----------------------+
- | | |
- +-------v-------+ +-------v-------+ +-------v-------+
- | 本地文件系统 | | 对象存储 | | 块存储/分布式|
- | (file) | | (Swift/S3) | | 文件系统 |
- +---------------+ +---------------+ +---------------+
复制代码 2、操作示例
2.1 创建镜像
- openstack image create "ubuntu-22.04" \
- --file ./ubuntu-22.04.qcow2 \
- --disk-format qcow2 \
- --container-format bare \
- --public \
- --property hw_qemu_guest_agent=yes \
- --property os_distro=ubuntu
复制代码
- --file: 最重要参数,指定镜像文件路径并直接上传。
- --disk-format: 必须指定。
- --container-format: 通常指定为 bare。
- --public: 设置为公共镜像。
- --property: 设置自定义属性,Nova/Cinder 等组件会利用这些属性。
2.2 查看镜像列表
2.3 查看镜像详情
- openstack image show <image-id-or-name>
复制代码 2.4 更新镜像属性
- openstack image set <image-id> \
- --min-disk 20 \
- --min-ram 1024 \
- --property hw_vif_model=virtio
复制代码 2.5 删除镜像
- openstack image delete <image-id>
复制代码 2.6 检查 Glance 服务状态
- openstack catalog list
- # 或者查看服务端点
- openstack endpoint list --service image
复制代码 3、配置与优化
3.1 核心配置文件
Glance 的主要配置文件是 /etc/glance/glance-api.conf,关键配置项包括:- [database]
- connection = mysql+pymysql://glance:GLANCE_DBPASS@controller/glance
- [keystone_authtoken]
- www_authenticate_uri = http://controller:5000
- auth_url = http://controller:5000
- memcached_servers = controller:11211
- auth_type = password
- project_domain_name = Default
- user_domain_name = Default
- project_name = service
- username = glance
- password = GLANCE_PASS
- [glance_store]
- stores = file,http
- default_store = file
- filesystem_store_datadir = /var/lib/glance/images/
复制代码 3.2 性能优化建议
- 使用分布式存储(如 Ceph)作为后端,提高可用性和性能
- 配置镜像缓存,减少重复下载
- 启用镜像压缩,节省存储空间和网络带宽
- 对大镜像采用分块上传方式
4、源码
4.1 重要源码目录
- glance/api/: RESTful API 的实现,是请求的入口。
- glance/common/: 通用工具和中间件(如 Keystone 认证、策略检查)。
- glance/db/: 数据库模型和抽象层(SQLAlchemy)。
- glance/domain/: 核心领域模型(如 Image, Task)。
- glance/async/: 异步任务(如复制镜像、迁移存储后端)的实现。
- glance/store/: 存储后端抽象层 的实现。这是 Glance 最核心的模块之一,每个支持的存储类型(file, swift, rbd, s3)在此都有对应的 driver.py。
- glance/cmd/: 服务启动脚本(如 api.py)。
4.2 上传流程
sequenceDiagram participant Client as 客户端 (openstack CLI) participant GlanceAPI participant GlanceRegistry participant DB as 数据库 participant Store as 存储后端 Client->>GlanceAPI: POST /v2/images (创建镜像元数据) GlanceAPI->>DB: 创建镜像记录 (queued状态) DB-->>GlanceAPI: 返回镜像ID GlanceAPI-->>Client: 返回镜像ID和位置 Client->>GlanceAPI: PUT /v2/images/{image_id}/file (上传镜像数据) GlanceAPI->>DB: 更新状态为 saving GlanceAPI->>Store: 分块上传镜像数据 Store-->>GlanceAPI: 上传完成确认 GlanceAPI->>DB: 更新状态为 active DB-->>GlanceAPI: 确认更新 GlanceAPI-->>Client: 返回成功响应
- 客户端命令入口 (python-openstackclient)
文件路径: openstackclient/image/v2/image.py
- class CreateImage(command.ShowOne):
- def get_parser(self, prog_name):
- parser = super(CreateImage, self).get_parser(prog_name)
- parser.add_argument("name", metavar="<image-name>", help="New image name")
- parser.add_argument("--file", metavar="<file>", help="Upload image from local file")
- parser.add_argument("--disk-format", default="qcow2", help="Disk format (default: qcow2)")
- parser.add_argument("--container-format", default="bare", help="Container format")
- return parser
- def take_action(self, parsed_args):
- image_client = self.app.client_manager.image
-
- # 创建镜像元数据
- image = image_client.create_image(
- name=parsed_args.name,
- disk_format=parsed_args.disk_format,
- container_format=parsed_args.container_format
- )
-
- # 如果有文件则上传
- if parsed_args.file:
- with open(parsed_args.file, 'rb') as image_data:
- image_client.upload_image(image.id, image_data)
-
- return self.dict2columns(image)
复制代码
- Glance API - 创建镜像元数据
文件路径: glance/api/v2/images.py
- class ImagesController(object):
- @utils.mutating
- def create(self, req, **kwargs):
- # 验证输入数据
- image_schema = schemas.image_create
- data = kwargs.pop('body', {})
- self.validate(data, image_schema)
-
- # 准备镜像属性
- image_data = data['image']
- image_properties = {
- 'name': image_data.get('name'),
- 'disk_format': image_data.get('disk_format'),
- 'container_format': image_data.get('container_format'),
- 'status': 'queued', # 初始状态
- 'min_disk': image_data.get('min_disk', 0),
- 'min_ram': image_data.get('min_ram', 0),
- 'protected': image_data.get('protected', False),
- 'visibility': image_data.get('visibility', 'private')
- }
-
- # 创建镜像记录
- image_repo = self.gateway.get_repo(req.context)
- image = image_repo.create(image_properties)
-
- # 构建响应
- return web.Response(
- status=201,
- content_type='application/json',
- body=jsonutils.dumps({'image': image}),
- charset='utf-8'
- )
复制代码
- Glance Registry - 数据库操作
文件路径: glance/db/sqlalchemy/api.py
- def image_create(context, values):
- session = get_session()
- with session.begin():
- # 创建Image对象
- image_ref = models.Image()
- image_ref.update(values)
- image_ref.save(session=session)
-
- # 处理额外属性
- if 'properties' in values:
- for key, value in values['properties'].items():
- prop_ref = models.ImageProperty()
- prop_ref.image_id = image_ref.id
- prop_ref.name = key
- prop_ref.value = value
- prop_ref.save(session=session)
-
- return _image_get(context, image_ref.id, session=session)
复制代码
- Glance API - 上传镜像数据
文件路径: glance/api/v2/image_data.py
- class ImageDataController(object):
- @utils.mutating
- def upload(self, req, image_id, data, content_length):
- # 获取镜像记录
- image_repo = self.gateway.get_repo(req.context)
- image = image_repo.get(image_id)
-
- # 检查状态是否允许上传
- if image.status not in ['queued', 'saving']:
- raise exception.InvalidImageStatus(image_id=image_id)
-
- # 更新状态为saving
- image_repo.update(image_id, {'status': 'saving'})
-
- try:
- # 获取存储后端
- store_api = self.gateway.get_store_api(req.context)
-
- # 分块上传数据
- chunk_size = CONF.glance_store.upload_chunk_size
- bytes_written = 0
- checksum = hashlib.md5()
-
- while True:
- chunk = data.read(chunk_size)
- if not chunk:
- break
-
- # 写入存储
- store_api.add_chunk(image_id, chunk, bytes_written)
-
- # 更新进度
- bytes_written += len(chunk)
- checksum.update(chunk)
-
- # 更新数据库进度
- if bytes_written % (chunk_size * 10) == 0:
- image_repo.update(image_id, {
- 'size': bytes_written,
- 'checksum': checksum.hexdigest()
- })
-
- # 完成上传
- location, size, checksum_value, metadata = store_api.finalize(image_id)
-
- # 更新镜像记录
- image_repo.update(image_id, {
- 'status': 'active',
- 'size': size,
- 'checksum': checksum_value,
- 'locations': [{'url': location, 'metadata': metadata}]
- })
-
- return web.Response(status=204) # No Content
-
- except Exception as e:
- # 出错时标记为killed状态
- image_repo.update(image_id, {'status': 'killed'})
- raise
复制代码
- 存储后端实现 - 文件系统存储
文件路径: glance_store/_drivers/filesystem.py
- class Store(driver.Store):
- def add_chunk(self, image_id, chunk_data, offset):
- # 确保目录存在
- store_dir = self._option_get('filesystem_store_datadir')
- if not os.path.exists(store_dir):
- os.makedirs(store_dir)
-
- # 打开文件并写入
- image_path = os.path.join(store_dir, image_id)
- with open(image_path, 'ab') as f:
- f.seek(offset)
- f.write(chunk_data)
-
- def finalize(self, image_id):
- # 获取文件信息
- store_dir = self._option_get('filesystem_store_datadir')
- image_path = os.path.join(store_dir, image_id)
-
- # 计算文件大小和校验和
- size = os.path.getsize(image_path)
- with open(image_path, 'rb') as f:
- checksum = hashlib.md5(f.read()).hexdigest()
-
- # 返回存储位置信息
- return (
- f"file://{image_path}",
- size,
- checksum,
- {'fs_path': image_path}
- )
复制代码
- 存储后端实现 - Swift 对象存储
文件路径: glance_store/_drivers/swift.py
- class Store(driver.Store):
- def __init__(self):
- # 创建Swift连接
- self.conn = swiftclient.Connection(
- authurl=CONF.swift.auth_address,
- user=CONF.swift.user,
- key=CONF.swift.key,
- auth_version='2.0'
- )
-
- def add_chunk(self, image_id, chunk_data, offset):
- # Swift使用分段上传
- if not hasattr(self, 'segments'):
- self.segments = []
- self.container = f"glance_{image_id}"
- self.conn.put_container(self.container)
-
- # 创建分段
- segment_name = f"{image_id}_seg{len(self.segments)}"
- self.conn.put_object(
- self.container,
- segment_name,
- chunk_data
- )
- self.segments.append(segment_name)
-
- def finalize(self, image_id):
- # 创建manifest文件
- manifest = {
- 'container': self.container,
- 'object': image_id,
- 'segments': self.segments
- }
-
- # 上传manifest
- self.conn.put_object(
- CONF.swift.container,
- image_id,
- json.dumps(manifest),
- headers={'X-Object-Manifest': f"{self.container}/{image_id}_seg"}
- )
-
- # 获取对象信息
- obj_info = self.conn.head_object(CONF.swift.container, image_id)
-
- return (
- f"swift://{CONF.swift.container}/{image_id}",
- int(obj_info['content-length']),
- obj_info['etag'],
- {'swift_container': CONF.swift.container}
- )
复制代码 5、总结
Glance 作为 OpenStack 的镜像服务,为整个云平台提供了统一的镜像管理解决方案。它支持多种镜像格式和存储后端,具有良好的灵活性和扩展性。通过 Glance,用户可以方便地管理虚拟机镜像,为 Nova 等计算服务提供可靠的镜像支持。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |