Horizon 深度解析:OpenStack Web 控制台架构与定制开发

Horizon 深度解析:OpenStack Web 控制台架构与定制开发

定位与职责

Horizon 是 OpenStack 的 Web 管理控制台,基于 Django 框架构建:

  • 提供图形化界面管理所有 OpenStack 资源
  • 通过 OpenStack Python SDK 调用各组件 API
  • 支持插件化扩展(自定义 Panel/Dashboard)
  • 多租户视图(普通用户 vs 管理员视图)

架构总览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
浏览器
│ HTTP

Apache/Nginx(静态资源)
│ WSGI

Django Application(horizon)
├── openstack_dashboard/ ← 主应用
│ ├── dashboards/
│ │ ├── project/ ← 项目视图(普通用户)
│ │ ├── admin/ ← 管理员视图
│ │ └── identity/ ← 身份管理视图
│ └── api/ ← 封装 OpenStack API 调用

└── horizon/ ← 框架核心(Panel/Dashboard 基类)
├── base.py ← Dashboard/Panel 基类
├── tables.py ← 数据表格框架
├── forms.py ← 表单框架
└── workflows.py ← 多步骤向导框架

Panel 插件体系

Horizon 的核心设计是插件化,每个功能模块是一个 Panel:

1
2
3
4
5
6
7
8
9
10
11
12
13
# horizon/base.py

class Dashboard(Registry, HorizonComponent):
"""顶级导航项(如 Project、Admin、Identity)"""
name = "Project"
slug = "project"
panels = ('compute', 'network', 'storage', ...)

class Panel(HorizonComponent):
"""二级导航项(如 Instances、Networks)"""
name = "Instances"
slug = "instances"
urls = 'openstack_dashboard.dashboards.project.instances.urls'

注册自定义 Panel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# myapp/dashboard.py
from django.utils.translation import gettext_lazy as _
import horizon

class MyCustomPanel(horizon.Panel):
name = _("My Custom Panel")
slug = "my_custom"

class MyDashboard(horizon.Dashboard):
name = _("My Dashboard")
slug = "my_dashboard"
panels = ('my_custom',)
default_panel = 'my_custom'

# 注册
horizon.register(MyDashboard)

数据表格框架

Horizon 的 DataTable 是最常用的 UI 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# openstack_dashboard/dashboards/project/instances/tables.py

from horizon import tables

class TerminateInstance(tables.BatchAction):
name = "terminate"
action_type = "danger"

def action(self, request, obj_id):
api.nova.server_delete(request, obj_id)

class InstancesTable(tables.DataTable):
name = tables.Column("name", verbose_name=_("Instance Name"),
link="horizon:project:instances:detail")
status = tables.Column("status", verbose_name=_("Status"),
status=True,
status_choices=STATUS_CHOICES)
ip = tables.Column("ip", verbose_name=_("IP Address"))
flavor = tables.Column("flavor_name", verbose_name=_("Flavor"))

class Meta:
name = "instances"
verbose_name = _("Instances")
table_actions = (LaunchLink, TerminateInstance, InstancesFilterAction)
row_actions = (StartInstance, StopInstance, RebootInstance,
TerminateInstance, ConsoleLink)

API 封装层

Horizon 通过 openstack_dashboard/api/ 封装所有 OpenStack API 调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# openstack_dashboard/api/nova.py

def server_list(request, search_opts=None, all_tenants=False):
"""获取虚拟机列表"""
c = novaclient(request)
if all_tenants:
search_opts['all_tenants'] = True
return c.servers.list(True, search_opts)

def server_create(request, name, image, flavor, key_name, ...):
"""创建虚拟机"""
return novaclient(request).servers.create(
name, image, flavor,
key_name=key_name,
security_groups=security_groups,
nics=nics,
...
)

def novaclient(request):
"""获取 Nova 客户端(带 Token)"""
return nova_client.Client(
NOVA_API_VERSION,
session=keystone.get_session(request),
endpoint_override=base.url_for(request, 'compute'),
)

多步骤向导(Workflow)

创建虚拟机这类复杂操作使用 Workflow 框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# openstack_dashboard/dashboards/project/instances/workflows/create_instance.py

class SetInstanceDetails(workflows.Step):
action_class = SetInstanceDetailsAction
contributes = ("source_type", "source_id", "flavor_id", "count", "name")

class SetNetwork(workflows.Step):
action_class = SetNetworkAction
contributes = ("network_id",)

class SetSecurityGroups(workflows.Step):
action_class = SetSecurityGroupsAction
contributes = ("security_group_ids",)

class LaunchInstance(workflows.Workflow):
slug = "launch_instance"
name = _("Launch Instance")
steps = (SetInstanceDetails, SetNetwork, SetSecurityGroups,
SetKeypair, SetAdvanced)

def handle(self, request, context):
# 最终调用 Nova API 创建虚拟机
api.nova.server_create(
request,
context['name'],
context['source_id'],
context['flavor_id'],
...
)

定制开发实践

1. 添加自定义列

1
2
3
4
5
6
7
# 在现有表格中添加自定义列
class InstancesTable(tables.DataTable):
# 添加自定义字段
custom_tag = tables.Column(
lambda obj: obj.metadata.get('custom_tag', '-'),
verbose_name=_("Custom Tag")
)

2. 自定义 Action

1
2
3
4
5
6
7
8
9
10
11
class MigrateInstance(tables.LinkAction):
name = "migrate"
verbose_name = _("Live Migrate")
url = "horizon:admin:instances:live_migrate"
classes = ("ajax-modal",)
icon = "exchange"

def allowed(self, request, instance):
# 只有 admin 且实例运行中才显示
return (request.user.is_superuser and
instance.status == "ACTIVE")

3. 自定义 Panel 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# mypanel/panel.py
import horizon

class MyPanel(horizon.Panel):
name = "GPU 管理"
slug = "gpu_management"
permissions = ('openstack.roles.admin',)

# mypanel/views.py
from horizon import tables
from . import tables as gpu_tables

class IndexView(tables.DataTableView):
table_class = gpu_tables.GPUTable
template_name = 'admin/gpu_management/index.html'
page_title = "GPU 资源管理"

def get_data(self):
# 调用自定义 API 获取 GPU 数据
return api.get_gpu_list(self.request)

性能优化

Horizon 默认性能较差,生产环境需要优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# local_settings.py

# 1. 启用 Django 缓存(Redis)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': 'memcached:11211',
}
}

# 2. 压缩静态资源
COMPRESS_ENABLED = True
COMPRESS_OFFLINE = True

# 3. 减少 API 调用(按需加载)
OPENSTACK_NEUTRON_NETWORK = {
'enable_router': True,
'enable_quotas': True,
'enable_ipv6': False, # 不需要就关掉
}

# 4. 会话存储用 Redis
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

生产部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# nginx 配置
server {
listen 80;
server_name horizon.example.com;

location /static {
alias /var/lib/openstack-dashboard/static;
expires 30d;
}

location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
1
2
3
4
5
6
7
8
9
10
11
# 收集静态文件
python manage.py collectstatic --noinput

# 压缩静态文件
python manage.py compress --force

# 使用 gunicorn 运行
gunicorn openstack_dashboard.wsgi:application \
--workers 4 \
--bind 127.0.0.1:8080 \
--timeout 120