OpenStack Keystone详解

Keystone 是 OpenStack 的核心组件之一,作为身份认证服务(Identity Service),它为整个 OpenStack 生态系统提供统一的身份验证、授权和服务目录管理。

三大核心功能:

  • 身份认证(Authentication):验证用户身份(如用户名 / 密码、令牌等)
  • 授权(Authorization):确定用户是否有权限执行特定操作
  • 服务目录(Service Catalog):管理 OpenStack 各服务的访问端点(Endpoint)

1、核心概念

1.1 User(用户)

代表一个使用 OpenStack 服务的个体、系统或服务。它拥有登录凭证(密码、API Key 等)。

1.2 Project / Tenant(项目 / 租户)

Project 用于将 OpenStack 的资源(计算、存储和网络)进行分组和隔离。

根据 OpenStack 服务的对象不同,Project 可以是一个客户(公有云,也叫租户)、部门或者项目组(私有云)。

注意:

  1. 资源的所有权是属于 Project 的,而不是 User。
  2. 每个 User(包括 admin)必须挂在 Project 里才能访问该 Project 的资源。 一个User可以属于多个 Project。
  3. admin 相当于 root 用户,具有最高权限

资源隔离的单元,用户必须属于某个项目才能操作资源(Tenant 是旧称,现在多称为 Project)

1.3 Role(角色)

权限的集合。它定义了一组用户可以执行的操作权限(如 member, admin, network-admin)。角色本身没有权限,其权限由各个 OpenStack 服务(Nova, Neutron等)在策略文件(policy.json)中定义。

示例:

json 复制代码
{   //用户需具有 admin角色 //
    "context_is_admin": "role:admin",
    //有管理员权限或请求用户的project_id和资源的project_id匹配 //
    "admin_or_owner": "is_admin:True or project_id:%(project_id)s",
    //检查用户上下文是否具有管理员权限。//
    "admin_api": "is_admin:True",
    //用户需具有 cloud_admin 角色 //
    "cloud_admin": "role:cloud_admin",

    // 引用规则admin_or_owner//
    "os_compute_api:servers:start": "rule:admin_or_owner",

    // 允许任何用户查看服务器元数据(metadata)
    "os_compute_api:servers:show_server_metadata": ""
}

1.4 Token(令牌)

一个加密字符串,是访问资源的凭证。用户认证后,Keystone 会颁发一个 Token。在后续调用其他服务 API 时,必须出示此 Token 以验证身份和权限。Token 有有效期。

1.5 Service(服务)

代表一个 OpenStack 服务,如 nova, neutron, cinder 等。在 Keystone 中注册

1.6 Endpoint(端点)

服务的访问地址(URL),分为 public(公网)、internal(内网)、admin(管理)三种类型一个服务对外暴露的可访问地址(URL)。

1.7 Catalog(服务目录)

所有 Service 和其 Endpoint 的列表。它在用户认证成功后随 Token 一起返回,告诉用户哪里可以访问所需的服务。

1.8 Domain(域)

项目、用户和组的集合。用于实现更高级别的隔离和管理隔离。一个域可以被认为是一个更大的组织或部门,其下包含多个项目和用户。默认有一个 Default 域。

1.9 Group(组)

用户的集合。可以给一个组分配角色,那么组内的所有用户都会继承这个角色在项目中的权限,简化了用户权限管理。

2、工作原理

sequenceDiagram actor User participant H as Horizon/CLI participant K as Keystone participant S as Other Service<br>(e.g., Nova) User->>H: 1. 提供用户名/密码<br>和项目名称 H->>K: 2. 认证请求<br>(用户名、密码、项目) K->>K: 3. 验证凭证<br>计算权限<br>生成Token<br>组装服务目录 K-->>H: 4. 返回认证响应<br>(Token & Service Catalog) H->>S: 5. 请求创建虚拟机<br>(携带Token) S->>K: 6. 验证Token有效性 K-->>S: 7. 返回Token详情<br>(用户、项目、角色) S->>S: 8. 根据策略(policy.json)<br>校验权限 S-->>H: 9. 执行操作并返回结果 H-->>User: 10. 显示操作结果

  1. 用户认证 (Authentication): 用户(通过 Horizon 或 CLI)向 Keystone 提供用户名、密码和要访问的项目名称。
  2. 验证与令牌颁发: Keystone 验证用户身份及其在该项目中的成员资格。验证通过后,Keystone 会:
    • 生成一个 Token
    • 根据用户的角色计算其权限。
    • 组装该用户可以访问的服务目录 (Catalog)
  3. 服务访问: 用户使用收到的 Token 去调用其他 OpenStack 服务(例如 Nova 创建虚拟机)。该请求中必须包含 X-Auth-Token 头信息。
  4. 令牌验证 (Validation): Nova 接收到请求后,无法自己识别 Token,它会向 Keystone 发出请求,询问"这个 Token 是否有效?它对应哪个用户和项目?"
  5. 授权决策 (Authorization): Keystone 验证 Token 并返回Token的详情(用户、项目、角色等)。Nova 然后根据自己的policy.json文件,判断该用户在该项目下拥有的角色是否有权执行创建虚拟机操作。
  6. 执行操作: 如果授权通过,Nova 执行操作;否则返回权限错误。

3、Keystone源码解析

Keystone 的源码主要位于 /keystone/ 目录下。

3.1 主要目录结构

  • keystone/common/: 通用工具(如配置、策略执行、上下文)
  • keystone/identity/: 身份后端驱动(SQL, LDAP)
  • keystone/assignment/: 资源(项目/域)和角色分配后端驱动
  • keystone/auth/: 认证插件(密码、令牌等)
  • keystone/token/: 令牌提供者(Fernet, UUID, PKI, PKIZ)
  • keystone/catalog/: 服务目录后端驱动
  • keystone/api/: 主要的 WSGI 路由和控制器(RESTful API 入口)
  • keystone/server/: 启动和部署相关的 Flask 应用配置

3.2 核心流程源码分析

3.2.1 密码认证与令牌颁发流程
  1. 请求入口:WSGI 路由与控制器
    文件keystone/auth/routers.py
python 复制代码
class AuthRouter(wsgi.ComposableRouter):
    def add_routes(self, mapper):
        # 注册认证路由
        mapper.connect('/auth/tokens',
                       controller=self._controllers['auth'],
                       action='authenticate_for_token',
                       conditions={'method': ['POST']})

当收到 POST /v3/auth/tokens请求时,路由将请求分发给 AuthController.authenticate_for_token方法。

  1. 认证控制器处理

文件路径: keystone/auth/controllers.py

python 复制代码
class AuthController(controller.V3Controller):
    def authenticate_for_token(self, context, auth_payload):
        # 1. 验证请求格式
        self._validate_authentication_request(context, auth_payload)
        
        # 2. 提取认证方法
        auth_methods = auth_payload['auth']['identity']['methods']
        auth_data = auth_payload['auth']['identity'].get('password', {})
        
        # 3. 选择认证插件
        auth_plugin = self._get_auth_plugin(auth_methods[0])
        
        # 4. 执行认证
        auth_context = auth_plugin.authenticate(context, **auth_data)
        
        # 5. 颁发令牌
        token_data = self.token_provider_api.issue_token(
            user_id=auth_context['user_id'],
            method_names=auth_methods,
            project_id=auth_context.get('project_id'),
            domain_id=auth_context.get('domain_id')
        )
        
        # 6. 构建响应
        response = wsgi.render_response(
            body=token_data,
            status=(201, 'Created')
        )
        response.headers['X-Subject-Token'] = token_data['token']['id']
        return response
  1. 密码认证插件实现
    文件路径: keystone/auth/plugins/password.py
python 复制代码
class Password(controller.AuthMethodHandler):
    def authenticate(self, context, user_id=None, password=None, 
                     user_domain_id=None, username=None):
        # 1. 获取用户域ID(如果未提供)
        if not user_domain_id:
            user_domain_id = CONF.identity.default_domain_id
        
        # 2. 获取用户对象
        try:
            if user_id:
                user_ref = self.identity_api.get_user(context, user_id)
            else:
                user_ref = self.identity_api.get_user_by_name(
                    context, username, user_domain_id)
        except exception.UserNotFound:
            raise exception.Unauthorized('Invalid user')
        
        # 3. 验证用户状态
        if not user_ref.get('enabled', True):
            raise exception.Unauthorized('User disabled')
        
        # 4. 验证密码
        try:
            self.identity_api.authenticate(
                context,
                user_id=user_ref['id'],
                password=password
            )
        except AssertionError:
            raise exception.Unauthorized('Invalid password')
        
        # 5. 返回认证上下文
        return {
            'user_id': user_ref['id'],
            'user_domain_id': user_domain_id,
            'project_id': self._get_project_id(context, auth_context),
            'domain_id': self._get_domain_id(context, auth_context)
        }
  1. 身份驱动验证密码
    文件路径: keystone/identity/drivers/sql.py
python 复制代码
class Identity(identity.Driver):
    def authenticate(self, context, user_id, password):
        # 1. 获取用户凭证
        try:
            cred_ref = self.get_credential(context, user_id)
        except exception.CredentialNotFound:
            raise AssertionError("Invalid credentials")
        
        # 2. 验证密码哈希
        if not self._check_password(password, cred_ref['blob']):
            raise AssertionError("Invalid credentials")
        
        # 3. 验证凭证状态
        if not cred_ref.get('enabled', True):
            raise AssertionError("Credential disabled")
  1. 令牌提供者颁发令牌
    文件路径: keystone/token/provider.py
python 复制代码
class TokenProviderAPI(object):
    def issue_token(self, user_id, method_names, **kwargs):
        # 1. 验证用户和项目/域
        self._assert_valid_user(user_id)
        self._assert_valid_project_or_domain(kwargs)
        
        # 2. 生成令牌ID
        token_id = self.token_formatter.create_token(
            user_id=user_id,
            **kwargs
        )
        
        # 3. 构建令牌数据
        token_data = {
            'token': {
                'methods': method_names,
                'expires_at': self._get_expiration_time(),
                'user': self._get_user_data(user_id),
                'project': self._get_project_data(kwargs.get('project_id')),
                'domain': self._get_domain_data(kwargs.get('domain_id')),
                'catalog': self._get_service_catalog(user_id, **kwargs)
            }
        }
        
        # 4. 持久化令牌
        self.token_provider.persist_token(token_id, token_data)
        
        return token_data
  1. Fernet 令牌驱动实现
    文件路径: keystone/token/providers/fernet.py
python 复制代码
class TokenFormatter(object):
    def create_token(self, user_id, project_id=None, domain_id=None):
        # 1. 准备令牌载荷
        payload = {
            'exp': int(time.time()) + CONF.token.expiration,
            'iat': int(time.time()),
            'user': {'id': user_id},
            'aud': self._get_audience(),
            'iss': CONF.token.issuer
        }
        
        if project_id:
            payload['project'] = {'id': project_id}
        elif domain_id:
            payload['domain'] = {'id': domain_id}
        
        # 2. 序列化载荷
        serialized_payload = msgpack.packb(payload)
        
        # 3. 使用Fernet加密
        fernet = Fernet(CONF.fernet_tokens.key_repository)
        token = fernet.encrypt(serialized_payload)
        
        return token.decode('utf-8')
3.2.2 令牌验证流程

其他服务调用 GET /v3/auth/tokens

  1. 请求入口:路由与控制器
    文件路径: keystone/auth/routers.py
python 复制代码
class AuthRouter(wsgi.ComposableRouter):
    def add_routes(self, mapper):
        # 注册令牌验证路由
        mapper.connect('/auth/tokens',
                       controller=self._controllers['auth'],
                       action='validate_token',
                       conditions={'method': ['GET']})
  1. 令牌验证控制器
    文件路径: keystone/auth/controllers.py
python 复制代码
class AuthController(controller.V3Controller):
    def validate_token(self, context):
        # 1. 获取待验证的令牌ID
        token_id = self._get_subject_token_id(context)
        
        # 2. 验证请求者权限
        self.assert_authenticated()
        
        # 3. 调用令牌提供者API进行验证
        token_data = self.token_provider_api.validate_token(token_id)
        
        # 4. 构建响应
        return wsgi.render_response(body=token_data)
  1. 令牌提供者 API 实现
    文件路径: keystone/token/provider.py
python 复制代码
class TokenProviderAPI(object):
    def validate_token(self, token_id):
        # 1. 检查令牌格式
        if not self.token_formatter.validate_token(token_id):
            raise exception.ValidationError(_('Invalid token format'))
        
        # 2. 从缓存或持久化存储获取令牌数据
        token_data = self._get_token_data(token_id)
        
        # 3. 验证令牌状态
        self._validate_token_state(token_data)
        
        # 4. 验证令牌有效期
        self._validate_token_expiration(token_data)
        
        # 5. 验证用户状态
        self._assert_valid_user(token_data['token']['user']['id'])
        
        # 6. 验证项目/域状态
        if 'project' in token_data['token']:
            self._assert_valid_project(token_data['token']['project']['id'])
        elif 'domain' in token_data['token']:
            self._assert_valid_domain(token_data['token']['domain']['id'])
        
        # 7. 构建响应数据
        return self._format_token_response(token_data)
  1. 令牌数据获取逻辑
    文件路径: keystone/token/provider.py
python 复制代码
class TokenProviderAPI(object):
    def _get_token_data(self, token_id):
        # 1. 检查缓存
        token_data = self.token_cache.get_token(token_id)
        if token_data:
            return token_data
        
        # 2. 从持久化存储获取
        token_data = self.token_driver.get_token_data(token_id)
        if not token_data:
            raise exception.TokenNotFound(_('Token not found'))
        
        # 3. 验证令牌签名
        if not self.token_formatter.validate_token_signature(token_id):
            raise exception.Unauthorized(_('Invalid token signature'))
        
        # 4. 缓存结果
        self.token_cache.set_token(token_id, token_data)
        
        return token_data
  1. Fernet 令牌验证实现
    文件路径: keystone/token/providers/fernet.py
python 复制代码
class TokenFormatter(object):
    def validate_token(self, token_id):
        # 1. 基本格式检查
        if not token_id or len(token_id) < CONF.fernet_tokens.min_token_size:
            return False
        
        # 2. Fernet 令牌格式检查
        return token_id.count('-') == 3 and len(token_id.split('-')[0]) == 8
    
    def validate_token_signature(self, token_id):
        # 1. 尝试使用所有可用密钥解密
        for key in self._get_valid_keys():
            try:
                fernet = Fernet(key)
                payload = fernet.decrypt(token_id.encode('utf-8'))
                msgpack.unpackb(payload)  # 验证载荷格式
                return True
            except (cryptography.fernet.InvalidToken, msgpack.UnpackException):
                continue
        
        return False
    
    def _get_valid_keys(self):
        """获取所有有效的加密密钥"""
        keys = []
        for key_repo in CONF.fernet_tokens.key_repository:
            for key_file in os.listdir(key_repo):
                if key_file.endswith('.key'):
                    with open(os.path.join(key_repo, key_file), 'rb') as f:
                        keys.append(f.read().strip())
        return keys
  1. 令牌缓存实现
    文件路径: keystone/token/providers/common.py
python 复制代码
class TokenCache(object):
    def __init__(self):
        self._cache = {}
        self._lock = threading.Lock()
    
    def get_token(self, token_id):
        with self._lock:
            # 1. 检查内存缓存
            if token_id in self._cache:
                entry = self._cache[token_id]
                if time.time() < entry['exp']:
                    return entry['data']
                del self._cache[token_id]
            
            # 2. 检查外部缓存(如 Memcached)
            if CONF.token.caching and CONF.cache.enabled:
                cache_key = f"token_{token_id}"
                cached = cache.get(cache_key)
                if cached and time.time() < cached['exp']:
                    self._cache[token_id] = cached  # 填充内存缓存
                    return cached['data']
        
        return None
    
    def set_token(self, token_id, token_data):
        with self._lock:
            # 1. 设置内存缓存
            entry = {
                'data': token_data,
                'exp': time.time() + CONF.token.cache_time
            }
            self._cache[token_id] = entry
            
            # 2. 设置外部缓存
            if CONF.token.caching and CONF.cache.enabled:
                cache_key = f"token_{token_id}"
                cache.set(cache_key, entry, CONF.token.cache_time)
3.2.3 服务注册
  1. 服务注册 API 入口
    文件路径: keystone/service/routers.py
python 复制代码
class ServiceRouter(wsgi.ComposableRouter):
    def add_routes(self, mapper):
        # 服务注册路由
        mapper.connect('/services',
                       controller=self._controllers['service'],
                       action='create_service',
                       conditions={'method': ['POST']})
        
        # 端点注册路由
        mapper.connect('/endpoints',
                       controller=self._controllers['endpoint'],
                       action='create_endpoint',
                       conditions={'method': ['POST']})
  1. 服务控制器实现
    文件路径: keystone/service/controllers.py
python 复制代码
class ServiceController(controller.V3Controller):
    @controller.protected()
    def create_service(self, context, service):
        """创建服务实体"""
        # 1. 验证输入数据
        validation.lazy_validate(schema.service_create, service)
        service_ref = service['service']
        
        # 2. 生成服务ID
        service_id = service_ref.get('id') or uuid.uuid4().hex
        
        # 3. 提取服务属性
        service_type = service_ref['type']
        service_name = service_ref.get('name', service_type)
        description = service_ref.get('description', '')
        enabled = service_ref.get('enabled', True)
        
        # 4. 调用服务API创建服务
        service_dict = self.catalog_api.create_service(
            service_id,
            {'name': service_name, 'type': service_type,
             'description': description, 'enabled': enabled}
        )
        
        # 5. 返回创建的服务信息
        return {'service': service_dict}
  1. 端点控制器实现
    文件路径: keystone/service/controllers.py
python 复制代码
class EndpointController(controller.V3Controller):
    @controller.protected()
    def create_endpoint(self, context, endpoint):
        """创建服务端点"""
        # 1. 验证输入数据
        validation.lazy_validate(schema.endpoint_create, endpoint)
        endpoint_ref = endpoint['endpoint']
        
        # 2. 生成端点ID
        endpoint_id = endpoint_ref.get('id') or uuid.uuid4().hex
        
        # 3. 提取端点属性
        service_id = endpoint_ref['service_id']
        interface = endpoint_ref['interface']  # public, admin, internal
        url = endpoint_ref['url']
        region_id = endpoint_ref.get('region_id')
        enabled = endpoint_ref.get('enabled', True)
        
        # 4. 验证服务是否存在
        self.catalog_api.get_service(service_id)
        
        # 5. 调用端点API创建端点
        endpoint_dict = self.catalog_api.create_endpoint(
            endpoint_id,
            {'service_id': service_id, 'interface': interface,
             'url': url, 'region_id': region_id, 'enabled': enabled}
        )
        
        # 6. 返回创建的端点信息
        return {'endpoint': endpoint_dict}
  1. 目录服务 API 实现
    文件路径: keystone/catalog/core.py
python 复制代码
class CatalogManager(object):
    def __init__(self):
        # 加载后端驱动
        self.driver = self._get_driver()
        
    def _get_driver(self):
        # 根据配置选择驱动
        if CONF.catalog.driver == 'sql':
            from keystone.catalog.backends import sql
            return sql.Catalog()
        elif CONF.catalog.driver == 'templated':
            from keystone.catalog.backends import templated
            return templated.Catalog()
        else:
            raise exception.ConfigError('Invalid catalog driver')
    
    def create_service(self, service_id, service):
        """创建服务实体"""
        # 验证服务类型
        self._validate_service_type(service['type'])
        
        # 调用驱动创建服务
        return self.driver.create_service(service_id, service)
    
    def create_endpoint(self, endpoint_id, endpoint):
        """创建服务端点"""
        # 验证接口类型
        self._validate_interface(endpoint['interface'])
        
        # 调用驱动创建端点
        return self.driver.create_endpoint(endpoint_id, endpoint)
  1. SQL 后端驱动实现
    文件路径: keystone/catalog/backends/sql.py
python 复制代码
class Catalog(DriverBase):
    def create_service(self, service_id, service):
        """在SQL数据库中创建服务"""
        with sql.session_for_write() as session:
            # 检查服务类型是否唯一
            if session.query(models.Service).filter_by(
                type=service['type']).first():
                raise exception.Conflict('Service type already exists')
            
            # 创建服务对象
            service_ref = models.Service()
            service_ref.update({
                'id': service_id,
                'name': service['name'],
                'type': service['type'],
                'description': service.get('description'),
                'enabled': service.get('enabled', True)
            })
            
            # 添加到会话并提交
            session.add(service_ref)
            session.flush()
            
            return service_ref.to_dict()
    
    def create_endpoint(self, endpoint_id, endpoint):
        """在SQL数据库中创建端点"""
        with sql.session_for_write() as session:
            # 检查服务是否存在
            service_ref = session.query(models.Service).get(
                endpoint['service_id'])
            if not service_ref:
                raise exception.ServiceNotFound(
                    service_id=endpoint['service_id'])
            
            # 检查接口类型是否重复
            if session.query(models.Endpoint).filter_by(
                service_id=endpoint['service_id'],
                interface=endpoint['interface']).first():
                raise exception.Conflict(
                    'Endpoint interface already exists for this service')
            
            # 创建端点对象
            endpoint_ref = models.Endpoint()
            endpoint_ref.update({
                'id': endpoint_id,
                'service_id': endpoint['service_id'],
                'interface': endpoint['interface'],
                'url': endpoint['url'],
                'region_id': endpoint.get('region_id'),
                'enabled': endpoint.get('enabled', True)
            })
            
            # 添加到会话并提交
            session.add(endpoint_ref)
            session.flush()
            
            return endpoint_ref.to_dict()
  1. 模板化后端驱动实现
    文件路径: keystone/catalog/backends/templated.py
python 复制代码
class Catalog(DriverBase):
    def __init__(self):
        # 加载配置文件
        self.conf = self._load_config()
        
    def _load_config(self):
        """从配置文件加载服务目录模板"""
        with open(CONF.catalog.template_file, 'r') as f:
            return json.load(f)
    
    def create_service(self, service_id, service):
        """模板驱动不支持动态创建服务"""
        raise exception.NotImplemented(
            'Dynamic service creation not supported in templated driver')
    
    def create_endpoint(self, endpoint_id, endpoint):
        """模板驱动不支持动态创建端点"""
        raise exception.NotImplemented(
            'Dynamic endpoint creation not supported in templated driver')
    
    def get_catalog(self, user_id, project_id):
        """从模板生成服务目录"""
        # 替换模板中的变量
        catalog = copy.deepcopy(self.conf['catalog'])
        for service in catalog:
            for endpoint in service['endpoints']:
                endpoint['url'] = endpoint['url'].replace(
                    '$HOST', CONF.default_public_host)
        return catalog

4、常用命令

4.1 身份(Identity)管理

1. 身份(Identity)管理

  • 创建用户:

    bash 复制代码
    openstack user create --password-prompt --email user@example.com --domain Default myuser
    • --password-prompt 会交互式让你输入密码。
    • --domain 指定用户所在的域。
  • 列出用户:

    bash 复制代码
    openstack user list
  • 创建用户组 (用于LDAP集成或批量分配角色):

    bash 复制代码
    openstack group create --description "Developers Group" developers
  • 将用户加入用户组:

    bash 复制代码
    openstack group add user developers myuser

4.2 资源(Resource)与分配(Assignment)管理

  • 创建项目(Project):

    bash 复制代码
    openstack project create --description "Demo Project" --domain Default demoproject
  • 创建角色(Role):

    bash 复制代码
    openstack role create myrole
    • 通常内置 admin, _member_, reader 等角色。
  • 给用户在项目上授予角色 (核心操作):

    bash 复制代码
    openstack role add --project demoproject --user myuser myrole
    • 这条命令意味着 myuserdemoproject 中拥有 myrole 的权限。
  • 检查角色分配:

    bash 复制代码
    openstack role assignment list --user myuser --project demoproject --names

4.3 服务目录(Catalog)管理

  • 注册服务 (如注册Glance镜像服务):

    bash 复制代码
    openstack service create --name glance --description "OpenStack Image" image
  • 创建服务的端点(Endpoint):

    bash 复制代码
    openstack endpoint create --region RegionOne image public http://glance-host:9292
    openstack endpoint create --region RegionOne image internal http://glance-host:9292
    openstack endpoint create --region RegionOne image admin http://glance-host:9292
  • 列出所有服务和端点:

    bash 复制代码
    openstack service list
    openstack endpoint list

4.4 令牌(Token)操作

  • 作为特定用户获取令牌(调试神器) :

    bash 复制代码
    openstack --os-username myuser --os-password mypassword --os-project-name demoproject token issue
    • 这会返回一个详细的令牌信息,包括过期时间、授权范围等,用于调试权限问题。

4.5 域(Domain)管理

  • 创建域:

    bash 复制代码
    openstack domain create --description "My Company" mycompany
  • 在特定域中创建用户:

    bash 复制代码
    openstack user create --email john@mycompany.com --domain mycompany john
相关推荐
哈里谢顿6 天前
Nova parse_args 函数详解
openstack
哈里谢顿8 天前
OpenStack 中的 nova-conductor 与 ironic-conductor 及其分布式锁机制详解
openstack
哈里谢顿12 天前
OpenStack oslo-config 详解
openstack
感哥17 天前
OpenStack Cinder 创建卷
openstack
感哥17 天前
OpenStack Cinder 架构
openstack
感哥17 天前
OpenStack Nova Scheduler 计算节点选择机制
openstack
感哥20 天前
OpenStack Nova 创建虚拟机
openstack
感哥20 天前
OpenStack Glance(镜像)
openstack
安全菜鸟1 个月前
传统方式部署OpenStack具体教程
openstack