墨佳美 发表于 2025-9-23 19:08:49

OpenStack Glance(镜像)

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 查看镜像列表

openstack image list2.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=virtio2.5 删除镜像

openstack image delete <image-id>2.6 检查 Glance 服务状态

openstack catalog list
# 或者查看服务端点
openstack endpoint list --service image3、配置与优化

3.1 核心配置文件

Glance 的主要配置文件是 /etc/glance/glance-api.conf,关键配置项包括:

connection = mysql+pymysql://glance:GLANCE_DBPASS@controller/glance


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


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 等计算服务提供可靠的镜像支持。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

阎怀慕 发表于 前天 00:41

过来提前占个楼
页: [1]
查看完整版本: OpenStack Glance(镜像)