Ironic 中 Clean Step 延迟执行的原因分析
当使用 OpenStack client 执行 clean step 后,确实不会立即执行,而是需要等待一段时间。这是由 Ironic 的架构设计和工作机制决定的,主要原因如下:
1. 异步架构设计
1.1 API 与 Conductor 分离
python
# ironic/api/controllers/v1/nodes.py
@expose.expose(None, types.uuid, body=types.jsontype,
status_code=http_client.ACCEPTED)
def set_provision_state(self, node_ident, target, configdrive=None):
"""设置节点的供应状态"""
# API 只是接收请求并更新节点状态
node = api_utils.get_rpc_node(node_ident)
# 通过 RPC 发送到 conductor
topic = pecan.request.rpcapi.get_topic_for(node)
pecan.request.rpcapi.do_node_clean(
pecan.request.context, node.uuid, clean_steps, topic)
# 立即返回 202 Accepted,而不等待执行完成
return None
关键点:
- API 服务接收请求后立即返回 202 状态
- 实际执行由 Conductor 服务异步处理
- 这种设计避免了 API 请求超时
1.2 RPC 消息队列机制
python
# ironic/conductor/rpcapi.py
def do_node_clean(self, context, node_id, clean_steps, topic=None):
"""通过 RPC 发送清理任务"""
# 消息放入队列,等待 conductor 处理
cctxt = self._prepare_call(topic=topic)
return cctxt.cast(context, 'do_node_clean',
node_id=node_id,
clean_steps=clean_steps)
2. Conductor 的处理延迟
2.1 节点锁定机制
python
# ironic/conductor/manager.py
def do_node_clean(self, context, node_id, clean_steps):
"""执行节点清理"""
# 等待获取节点锁(可能需要等待)
with task_manager.acquire(context, node_id, shared=False) as task:
node = task.node
# 验证节点状态
if node.provision_state != states.MANAGEABLE:
raise exception.InvalidStateRequested(...)
# 开始清理流程
self._do_node_clean(task, clean_steps)
延迟原因:
- 需要获取节点独占锁
- 如果节点正在被其他操作使用,需要等待
- 多个 conductor 可能竞争同一节点
2.2 状态转换验证
python
def _do_node_clean(self, task, clean_steps):
"""实际执行清理"""
# 状态转换:MANAGEABLE -> CLEANING
task.process_event('clean', target_state=states.CLEANING)
# 准备清理环境(可能需要时间)
task.driver.deploy.prepare_cleaning(task)
# 启动 ramdisk(需要等待节点启动)
self._spawn_worker(self._do_node_clean_steps, task, clean_steps)
3. 节点启动和 Agent 连接延迟
3.1 Ramdisk 启动过程
python
# ironic/drivers/modules/pxe.py
def prepare_ramdisk(self, task, ramdisk_params):
"""准备 ramdisk 启动"""
# 1. 生成 PXE 配置文件
pxe_utils.create_pxe_config(task, ramdisk_params)
# 2. 设置启动设备为 PXE
task.driver.management.set_boot_device(task, 'pxe')
# 3. 重启节点
manager_utils.node_power_action(task, states.REBOOT)
# 节点需要时间来:
# - 关机
# - 启动
# - PXE 启动
# - 下载 ramdisk
# - 启动 agent
3.2 Agent 心跳等待
python
# ironic/conductor/manager.py
def _spawn_worker(self, func, *args, **kwargs):
"""在后台线程中执行任务"""
# 创建后台任务
self._spawn(func, *args, **kwargs)
def _do_node_clean_steps(self, task, clean_steps):
"""执行清理步骤"""
# 等待 agent 上线并发送心跳
# 这可能需要几分钟时间
self._wait_for_agent_heartbeat(task)
# 开始执行清理步骤
for step in clean_steps:
self._execute_clean_step(task, step)
4. 实际时间线分析
4.1 典型的 Clean Step 执行时间线
bash
# T+0s: 用户执行命令
openstack baremetal node clean --clean-steps '[...]' <node-uuid>
# T+0.1s: API 返回 202 Accepted
# 节点状态: MANAGEABLE -> CLEANING
# T+0.5s: Conductor 获取节点锁,开始准备清理
# 节点状态: CLEANING
# T+1-30s: 准备 ramdisk,设置 PXE,重启节点
# 等待时间取决于:
# - 节点关机时间
# - BIOS 启动时间
# - 网络速度(下载 ramdisk)
# T+30s-2min: Agent 启动并发送第一次心跳
# 节点状态: CLEANING (等待 agent)
# T+2min+: 开始执行实际的清理步骤
# 节点状态: CLEANING (执行中)
4.2 查看实际进度
bash
# 查看节点当前状态
openstack baremetal node show <node-uuid> -c provision_state -c last_error
# 查看详细的 provision_state 信息
openstack baremetal node show <node-uuid> -c provision_updated_at -c target_provision_state
# 查看 conductor 日志
sudo journalctl -u ironic-conductor -f
# 查看 API 日志
sudo journalctl -u ironic-api -f
5. 配置优化建议
5.1 减少启动等待时间
ini
# /etc/ironic/ironic.conf
[conductor]
# 减少心跳超时时间
heartbeat_timeout = 60
# 增加并发处理数量
workers_pool_size = 100
[deploy]
# 启用快速启动
fast_track = true
# 减少电源操作等待时间
power_state_change_timeout = 30
5.2 使用本地 ramdisk 缓存
ini
[pxe]
# 启用 HTTP 服务
enable_pxe_http = true
http_root = /tftpboot
http_url = http://ironic-conductor:8088
# 预缓存 ramdisk 镜像
kernel = file:///var/lib/ironic/images/deploy.kernel
ramdisk = file:///var/lib/ironic/images/deploy.ramdisk
6. 监控和调试
6.1 实时监控节点状态
python
#!/usr/bin/env python3
# monitor_clean_progress.py
import time
import subprocess
import json
def monitor_node_cleaning(node_uuid):
"""监控节点清理进度"""
while True:
# 获取节点状态
result = subprocess.run([
'openstack', 'baremetal', 'node', 'show',
node_uuid, '-f', 'json'
], capture_output=True, text=True)
node_data = json.loads(result.stdout)
provision_state = node_data['provision_state']
print(f"[{time.strftime('%H:%M:%S')}] "
f"State: {provision_state}")
if provision_state in ['available', 'clean failed']:
break
time.sleep(10)
if __name__ == '__main__':
import sys
monitor_node_cleaning(sys.argv[1])
6.2 查看详细日志
bash
# 过滤特定节点的日志
sudo journalctl -u ironic-conductor | grep "node-uuid-here"
# 查看 agent 相关日志
sudo journalctl -u ironic-conductor | grep -i "agent\|heartbeat"
# 查看清理步骤执行日志
sudo journalctl -u ironic-conductor | grep -i "clean.*step"
7. 常见延迟问题和解决方案
7.1 节点启动慢
原因:
- BIOS 启动时间长
- 网络下载 ramdisk 慢
- 硬件自检时间长
解决方案:
ini
# 优化 BIOS 设置
# - 禁用不必要的自检
# - 设置快速启动
# - 优化启动顺序
# 使用本地镜像服务器
[glance]
glance_api_servers = http://local-glance:9292
# 启用镜像缓存
[agent]
image_download_source = local
7.2 心跳超时
原因:
- Agent 启动失败
- 网络连接问题
- 防火墙阻塞
解决方案:
bash
# 检查网络连接
ping <node-ip>
# 检查端口访问
telnet <ironic-api-ip> 6385
# 查看 DHCP 分配
sudo journalctl -u isc-dhcp-server
总结
Ironic Clean Step 延迟执行的主要原因:
- 架构设计:异步处理模式,API 立即返回
- 资源竞争:节点锁定和 conductor 调度
- 物理限制:节点重启和 ramdisk 启动时间
- 网络因素:镜像下载和心跳建立
这种设计虽然增加了等待时间,但提供了更好的可扩展性 、稳定性 和并发处理能力。通过合理的配置优化和监控,可以有效减少延迟时间。