1. 从一次控制台卡顿说起:Horizon到底在OpenStack里扮演什么角色
如果你正在维护一套私有云环境,大概率遇到过这样的场景:计算节点跑得好好的,虚拟机也正常在线,但用户突然在群里喊“控制台打不开了”“创建实例的页面一直转圈”。这时候你登上控制节点一看,Nova、Neutron、Glance这些核心服务全绿,唯独Horizon的日志里在疯狂刷超时。这个场景几乎每个OpenStack运维都踩过,而它恰恰暴露了一个很多人忽略的事实——Horizon虽然是OpenStack的“门面”,但它本身并不承载任何云平台的核心业务逻辑,它只是一个基于Django的Web前端,把各个组件的REST API包装成人类能看懂的图形界面。
我接触Horizon大概是在五六年前,那时候刚从命令行转向图形化管理,第一感觉就是“终于不用记一堆openstack命令了”。但用得越深越发现,Horizon的水远比表面看到的深。它涉及Django框架、Apache/httpd集成、Keystone认证对接、策略引擎、多区域多项目管理、前端静态资源打包、插件化Dashboard扩展等一系列技术点。你如果只是照着文档装一遍,能跑起来,但一旦遇到性能瓶颈、权限异常、页面加载慢、插件冲突这些问题,没有对Horizon内部机制的深入理解,基本就是抓瞎。
这篇文章我打算把Horizon从架构设计到实操部署、从核心配置到常见故障排查,完整地梳理一遍。适合正在搭建或维护OpenStack私有云的运维工程师、需要做二次开发的平台开发者,以及准备学习OpenStack组件架构的技术人员。不管你是刚接触Horizon的新手,还是已经用过一段时间但总觉得“知其然不知其所以然”的老手,应该都能从下面的内容里找到对你有用的东西。
2. Horizon的架构设计与核心思路拆解
2.1 为什么OpenStack需要一个独立的Dashboard组件
OpenStack的设计哲学是“每个服务只做一件事”,Nova管计算、Neutron管网络、Cinder管块存储、Glance管镜像,它们各自暴露REST API,彼此通过消息队列和数据库协调。这种架构对自动化和API调用非常友好,但对普通用户来说门槛太高——你不可能要求每个需要创建虚拟机的人都去学curl命令和JSON格式。
Horizon的定位就是在这堆API之上加一层Web界面。它不直接操作数据库,也不直接跟消息队列打交道,而是通过各组件公开的REST API来完成所有操作。这个设计决策非常关键,它意味着Horizon本质上是一个“API消费者”,它的稳定性和性能在很大程度上取决于后端API的响应速度和Keystone的认证效率。
从代码结构上看,Horizon基于Django框架构建,采用了一种叫“Dashboard + Panel”的插件化架构。一个Dashboard代表一个大的功能模块(比如“计算”“网络”“存储”),一个Panel代表Dashboard下的一个具体页面(比如“实例列表”“镜像管理”)。这种设计让Horizon天然支持扩展——你可以通过编写自定义的Dashboard和Panel来添加新功能,而不需要修改核心代码。
2.2 Dashboard与Panel的插件化机制到底怎么运作
很多人第一次看到Horizon的目录结构会有点懵,openstack_dashboard/dashboards/下面一堆子目录,每个子目录里又有panel.py、views.py、urls.py、templates/这些文件。其实这套结构跟Django的app组织方式是一脉相承的,只是Horizon在上面加了一层自己的注册和发现机制。
具体来说,每个Dashboard目录下都有一个dashboard.py文件,里面定义了一个继承自Dashboard的类,通过Horizon.register()方法注册到系统中。每个Panel目录下有一个panel.py文件,定义了继承自Panel的类,通过dashboard.register()注册到所属的Dashboard中。Horizon启动时会自动扫描这些注册信息,动态构建导航菜单和URL路由。
这个机制的好处是显而易见的:你新增一个功能模块,只需要创建一个新的Dashboard或Panel目录,写好注册代码,Horizon就能自动识别并展示出来。不需要去改核心的URL配置文件,也不需要动导航菜单的模板。但这里有个坑——注册顺序和命名空间是有讲究的。如果你的自定义Panel的URL命名空间跟已有的冲突,或者注册时没有正确设置slug,轻则页面显示异常,重则整个Dashboard加载失败。
2.3 认证流程:从登录到API调用的完整链路
Horizon的认证流程是理解它运行机制的关键。当用户在浏览器中输入用户名密码点击登录时,Horizon并不会自己去验证这个密码,而是把凭证转发给Keystone,由Keystone完成认证并返回一个Token。Horizon拿到Token后,会把它存储在服务端的Session中(默认用数据库或缓存后端),后续所有对Nova、Neutron等组件的API调用都携带这个Token。
这里有一个容易混淆的点:Horizon的Session存储和Token缓存是两回事。Session存储的是用户登录状态和Token信息,而Token缓存(通常在Keystone侧配置)是为了减少Token验证的开销。如果Session存储出了问题,用户会频繁被踢出登录;如果Token缓存出了问题,API调用会变慢甚至超时。
另外,Horizon支持多种认证方式,包括传统的用户名密码、LDAP集成、SAML2、OpenID Connect等。在企业环境中,LDAP集成是最常见的需求之一。配置LDAP时需要注意,Horizon本身不直接连接LDAP,而是通过Keystone的LDAP驱动来完成认证。Horizon侧只需要在local_settings.py中配置好Keystone的认证端点即可。
3. 部署实操:从零搭建一个可用的Horizon环境
3.1 环境准备与依赖梳理
在开始部署Horizon之前,有几个前置条件必须满足。首先,Keystone必须已经部署并正常运行,因为Horizon的所有认证都依赖它。其次,至少要有Nova、Neutron、Glance这几个核心服务的API端点可用,否则登录进去之后大部分页面都会报错。最后,你需要一个Web服务器来托管Horizon,官方推荐Apache httpd配合mod_wsgi,也可以用Nginx加uWSGI,但Apache的方案更成熟、文档更全。
操作系统层面,主流的选择是CentOS/RHEL系列或Ubuntu LTS系列。我个人的经验是,如果你用的是较新的OpenStack版本(比如Wallaby、Xena、Yoga),Ubuntu 20.04/22.04的包管理会更省心一些,因为很多Python依赖的版本兼容性在Ubuntu上处理得更好。CentOS 8之后改成了Stream模式,包生态有些变化,需要额外注意。
依赖方面,Horizon本质上是一个Python Django应用,所以Python版本、Django版本、以及各种Python库的版本兼容性是部署成功的关键。不同OpenStack版本对Django版本有明确要求,比如Wallaby要求Django 2.2,Xena要求Django 3.2,Yoga要求Django 4.0。如果你手动pip安装,很容易装错版本导致启动报错。所以强烈建议通过操作系统的包管理器或者OpenStack的官方仓库来安装,让依赖关系自动解决。
3.2 安装与基础配置
以Ubuntu 22.04 + OpenStack Yoga为例,安装Horizon的步骤大致如下。首先添加OpenStack的官方仓库,然后通过apt安装openstack-dashboard包。安装完成后,核心配置文件位于/etc/openstack-dashboard/local_settings.py,这个文件是Horizon所有配置的入口。
# 添加OpenStack Yoga仓库(以Ubuntu为例) sudo add-apt-repository cloud-archive:yoga sudo apt update # 安装Horizon sudo apt install openstack-dashboard -y安装完成后,你需要编辑local_settings.py,至少配置以下几项:
# Keystone认证端点 OPENSTACK_KEYSTONE_URL = "http://controller:5000/v3" OPENSTACK_KEYSTONE_DEFAULT_ROLE = "member" # 允许访问的主机 ALLOWED_HOSTS = ['*'] # Session存储后端(推荐用memcached或redis) SESSION_ENGINE = 'django.contrib.sessions.backends.cache' CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'controller:11211', } } # 时区设置 TIME_ZONE = "Asia/Shanghai"这里有几个配置项值得展开说。OPENSTACK_KEYSTONE_URL必须指向Keystone的v3端点,如果你写成了v2或者地址不对,登录页面会直接报认证失败。ALLOWED_HOSTS在测试环境可以设成['*'],但生产环境一定要改成具体的域名或IP,否则会有安全风险。Session存储后端的选择直接影响登录体验,默认的数据库存储在高并发场景下会成为瓶颈,换成memcached或redis之后响应速度会有明显提升。
3.3 Apache集成与WSGI配置
Horizon通过mod_wsgi与Apache集成,安装包通常会自动在/etc/apache2/conf-available/或/etc/httpd/conf.d/下生成配置文件。以Ubuntu为例,配置文件是/etc/apache2/conf-available/openstack-dashboard.conf,内容大致如下:
WSGIScriptAlias /horizon /usr/share/openstack-dashboard/openstack_dashboard/wsgi.py WSGIDaemonProcess horizon user=horizon group=horizon processes=3 threads=10 WSGIProcessGroup horizon Alias /static /var/lib/openstack-dashboard/static <Directory /usr/share/openstack-dashboard/openstack_dashboard> Require all granted </Directory>这里的WSGIDaemonProcess参数需要根据你的实际负载来调整。processes=3表示启动3个worker进程,threads=10表示每个进程10个线程。如果你的用户数量比较多,可以适当增加进程数,但不要超过CPU核心数的两倍,否则上下文切换的开销会抵消并发带来的收益。
静态文件的处理也需要注意。Horizon的前端资源(CSS、JavaScript、图片)需要收集到/var/lib/openstack-dashboard/static目录下,通过python manage.py collectstatic命令完成。如果你更新了Horizon版本或者修改了前端代码,记得重新执行这个命令,否则浏览器可能加载到旧版本的静态文件。
3.4 数据库初始化与首次启动验证
Horizon使用数据库来存储Session、用户偏好设置等数据。在首次启动之前,需要执行数据库迁移:
sudo -u horizon python3 /usr/share/openstack-dashboard/manage.py migrate sudo -u horizon python3 /usr/share/openstack-dashboard/manage.py collectstatic --noinput迁移完成后,重启Apache服务,然后在浏览器中访问http://<your-host>/horizon,应该能看到登录页面。用Keystone中已有的管理员账号登录,如果一切正常,你会看到默认的Dashboard界面,包括“计算”“网络”“存储”等菜单。
如果登录后页面报500错误,第一时间去看Apache的错误日志(通常在/var/log/apache2/error.log)和Horizon的日志(通常在/var/log/horizon/horizon.log)。大部分启动阶段的问题都能从日志里找到线索,比如Python依赖缺失、数据库连接失败、Keystone端点不可达等。
4. 核心配置项深度解析与调优经验
4.1 local_settings.py里那些容易踩坑的参数
local_settings.py是Horizon配置的核心文件,里面有几十个可配置项,但真正影响日常使用和性能的其实就那么几个。我把它们整理成了一张表,方便你对照检查:
| 配置项 | 作用 | 推荐值 | 踩坑提示 |
|---|---|---|---|
OPENSTACK_KEYSTONE_URL | Keystone认证端点 | http://controller:5000/v3 | 必须是v3,写错直接登录失败 |
SESSION_ENGINE | Session存储后端 | 缓存后端(memcached/redis) | 默认数据库存储在高并发下会拖慢登录 |
CACHES | 缓存配置 | memcached集群地址 | 缓存不可用会导致Session丢失 |
ALLOWED_HOSTS | 允许访问的域名 | 具体域名或IP | 生产环境不要用['*'] |
TIME_ZONE | 时区 | Asia/Shanghai | 不设置会导致日志时间对不上 |
OPENSTACK_API_VERSIONS | API版本 | 按实际环境设置 | 版本不匹配会导致部分功能不可用 |
LAUNCH_INSTANCE_DEFAULTS | 创建实例默认值 | 按需配置 | 默认值不合理会增加用户操作步骤 |
IMAGE_CUSTOM_PROPERTY_TITLES | 镜像自定义属性 | 按需配置 | 不配置的话镜像属性显示不友好 |
其中SESSION_ENGINE和CACHES的配合是最容易出问题的。我见过好几次因为memcached服务挂了导致所有用户被踢出登录的情况。如果你的环境对可用性要求比较高,建议给memcached做集群或者用redis的持久化模式,至少保证缓存服务本身不会成为单点。
4.2 多区域与多项目管理配置
在企业私有云环境中,经常需要管理多个区域(Region)或者多个项目(Project)。Horizon对这两种场景都有支持,但配置方式不太一样。
多区域配置需要在local_settings.py中设置OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True,然后在Keystone中创建对应的Region和Endpoint。Horizon会自动从Keystone获取Region列表,并在登录页面提供区域选择下拉框。这里需要注意的是,不同Region的API端点必须在Keystone中正确注册,否则切换区域后页面会报错。
多项目管理相对简单一些,用户登录后可以在右上角的项目下拉框中切换。但如果你希望限制某些用户只能看到特定的项目,就需要配合Keystone的Policy来实现。Horizon本身不做权限控制,它只是把Keystone返回的项目列表展示出来。真正的权限判断是在API调用时由各组件自己完成的。
4.3 性能调优:让Horizon页面加载快起来
Horizon页面加载慢是很多运维头疼的问题。根据我的经验,慢的原因通常来自三个方面:API响应慢、前端资源加载慢、Session读写慢。
API响应慢是最常见的。Horizon的每个页面在渲染之前,可能需要调用多个后端API来获取数据。比如实例列表页面,除了获取实例列表本身,还要获取镜像名称、 flavor名称、网络信息等,这些都需要额外的API调用。如果某个组件的API响应时间超过1秒,整个页面的加载时间就会被拖长。解决办法是在Horizon侧开启API缓存,把一些不常变化的数据(比如flavor列表、镜像列表)缓存起来,减少重复调用。
前端资源加载慢通常是因为静态文件没有正确配置缓存策略。Apache可以通过mod_expires和mod_headers来设置静态文件的过期时间,让浏览器缓存CSS和JavaScript文件。另外,Horizon的前端资源在发布时会做压缩和合并,如果你是从源码部署的,记得执行collectstatic和compress命令。
Session读写慢的根源通常是存储后端性能不足。如果你用的是数据库存储Session,每次页面请求都要读写数据库,并发一高就扛不住了。换成memcached或redis之后,Session读写基本在毫秒级别,对页面加载速度的提升非常明显。
5. 二次开发与插件扩展实战
5.1 创建一个自定义Dashboard的完整流程
假设你需要为公司的私有云添加一个“成本管理”Dashboard,用来展示各个项目的资源使用情况和费用估算。这个需求在标准Horizon中是没有的,你需要自己开发一个插件。
第一步是创建目录结构。在openstack_dashboard/dashboards/下新建一个cost_management目录,里面至少要有__init__.py、dashboard.py、panel.py(如果只有一个Panel的话可以省略)、views.py、urls.py和templates/目录。
第二步是编写dashboard.py:
from django.utils.translation import gettext_lazy as _ import horizon class CostManagement(horizon.Dashboard): name = _("Cost Management") slug = "cost_management" panels = ('overview',) # 引用Panel的slug default_panel = 'overview' horizon.register(CostManagement)第三步是编写Panel和View。Panel负责注册到Dashboard,View负责处理请求和渲染模板。这里的关键是URL命名空间要和Panel的slug保持一致,否则反向解析会出错。
第四步是在enabled/目录下创建一个软链接或者直接修改_9000_cost_management.py文件,把自定义Dashboard加入到启用列表中。Horizon启动时会扫描enabled/目录下的所有文件,把里面注册的Dashboard加载进来。
5.2 前端模板与静态资源处理
Horizon的前端模板基于Django Template Language,同时大量使用了Bootstrap和AngularJS(较新版本已经迁移到其他前端框架)。如果你要自定义页面样式,可以通过继承基础模板来覆盖默认的CSS和JavaScript。
静态资源的处理有一个容易忽略的点:Horizon在发布时会自动压缩和合并静态文件,但如果你在开发过程中修改了CSS或JavaScript,需要手动执行collectstatic和compress才能看到效果。另外,Horizon的静态文件路径配置在local_settings.py的STATIC_ROOT和STATIC_URL中,确保这两个配置指向正确的目录。
5.3 策略引擎与权限控制
Horizon的权限控制依赖于Keystone的Policy机制。每个API调用都会携带Token,各组件根据Policy文件判断当前用户是否有权限执行该操作。Horizon侧可以通过policy.json文件来覆盖默认的权限规则,比如限制某些用户只能查看不能创建。
这里有一个常见的误区:很多人以为在Horizon的配置文件中设置了权限规则就能生效,实际上Horizon只是把Policy文件传递给后端组件,真正的权限判断是在Nova、Neutron等组件侧完成的。所以如果你修改了Horizon的Policy文件但没有同步修改后端组件的Policy文件,权限控制是不会生效的。
6. 常见故障排查与避坑指南
6.1 登录失败与认证异常排查
登录失败是Horizon最常见的问题之一。排查思路可以按照以下顺序进行:
首先检查Keystone服务是否正常。用openstack token issue命令测试一下,如果能正常获取Token,说明Keystone本身没问题。然后检查Horizon的local_settings.py中OPENSTACK_KEYSTONE_URL是否配置正确,特别注意端口号和版本号。
如果Keystone正常但Horizon登录报错,去看Horizon的日志。常见的错误包括:Unable to establish connection to Keystone(网络不通或端点错误)、Invalid credentials(用户名密码错误或Domain配置不对)、Token not found(Session存储异常)。
还有一个比较隐蔽的问题是多Domain环境下的登录。如果你的Keystone配置了多个Domain,Horizon登录页面需要用户填写Domain信息。如果OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT没有设置为True,用户就无法指定Domain,导致认证失败。
6.2 页面加载超时与API调用异常
页面加载超时通常表现为“转圈圈”然后报500错误。这时候需要区分是Horizon本身的问题还是后端API的问题。一个简单的判断方法是直接调用后端API,看响应时间是否正常。如果API响应正常但Horizon页面加载慢,问题就在Horizon侧;如果API本身就慢,那需要去排查对应组件的性能问题。
Horizon侧导致加载慢的常见原因包括:Session存储后端响应慢、API缓存未配置、WSGI进程数不足、静态文件未正确缓存。我遇到过一次因为memcached连接数打满导致所有页面加载超时的案例,后来把memcached的最大连接数调大,并增加了连接池配置,问题就解决了。
6.3 静态文件丢失与样式错乱
静态文件丢失的表现是页面能打开但样式全无,或者JavaScript报404错误。这个问题通常发生在Horizon版本升级或者服务器迁移之后。解决办法是重新执行collectstatic命令,并检查Apache的静态文件别名配置是否正确。
还有一个容易忽略的点是浏览器缓存。如果你更新了静态文件但浏览器加载的还是旧版本,可以尝试强制刷新(Ctrl+Shift+R)或者清除浏览器缓存。在开发环境中,可以在local_settings.py中设置DEBUG = True来禁用静态文件缓存,方便调试。
6.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 登录页面报500 | Keystone端点配置错误 | 检查local_settings.py | 修正OPENSTACK_KEYSTONE_URL |
| 登录后频繁被踢出 | Session存储不稳定 | 检查memcached/redis状态 | 修复缓存服务或更换后端 |
| 页面加载慢 | API响应慢或缓存未配置 | 直接调用API测试响应时间 | 开启API缓存,优化后端性能 |
| 静态文件404 | collectstatic未执行 | 检查STATIC_ROOT目录 | 重新执行collectstatic |
| 权限异常 | Policy文件不一致 | 对比Horizon和后端Policy | 同步修改Policy文件 |
| 多区域切换报错 | Region端点未注册 | 检查Keystone Endpoint列表 | 注册缺失的Endpoint |
7. 我在实际运维中积累的几个关键体会
Horizon这个组件,装起来不难,但要用好、用稳,需要你对它背后的Django框架、Keystone认证、API调用链路有比较深入的理解。我刚开始接触的时候,遇到问题就重启Apache,很多时候确实能临时解决,但根本原因没找到,过几天又复现。后来养成了看日志的习惯,Horizon的日志其实写得很详细,大部分问题都能从日志里找到线索。
另一个体会是,不要在生产环境直接改Horizon的代码。如果你需要定制功能,走插件化的路子,把自定义的Dashboard和Panel放在独立的目录里,通过enabled/目录来启用。这样升级Horizon版本的时候,你的自定义代码不会跟核心代码冲突,维护起来也方便得多。
还有就是Session存储后端的选择,真的不要用默认的数据库存储。我见过太多环境因为Session表锁竞争导致登录缓慢甚至超时。换成memcached或redis之后,不仅速度快了,而且Session管理也更灵活,比如可以设置自动过期、可以跨节点共享。
最后说一个容易被忽略的点:Horizon的时区配置。如果你不设置TIME_ZONE,Horizon默认用UTC时间,用户看到的操作日志时间会比本地时间差8小时,排查问题的时候很容易搞混。这个配置项虽小,但影响挺大,建议部署的时候就设好。