深挖Openstack Nova - Scheduler調度策略
一. Scheduler的作用就是在創建實例(instance)時,為實例選擇出合適的主機(host)。這個過程分兩步:過濾(Fliter)和計算權值(Weight)
1. 過濾:
過濾掉不符合我們的要求,或鏡像要求(比如物理節點不支持64bit,物理節點不支持Vmware EXi等)的主機,留下符合過濾算法的主機集合。
2. 計算權值
通過指定的權值計算算法,計算在某物理節點上申請這個虛機所必須的消耗cost。物理節點越不適合這個虛機,消耗cost就越大,權值Weight就越大,調度算法會選擇權值最小的主機。
二. 過濾策略
Filter算法在nova-scheduler中是通過oslo.config.cfg模塊從nova.conf配置文件中動態獲取的,應用了Python的反射機制,在運行時刻決定初始化所選擇的filter算法。
OpenStack支持多種過濾策略,均在/nova/scheduler/filters包下:
1. CoreFilter:根據CPU數過濾主機
2. RamFilter:根據指定的RAM值選擇資源足夠的主機
3. AvailabilityZoneFilter:返回創建虛擬機參數指定的集群內的主機
4. JsonFilter:根據JSON串指定的規則選擇主機
三. 目錄結構
1. /nova/scheduler/filter_scheduler.py:繼承於類Scheduler,實現基於主機過濾器選取主機節點方式的調度器
2. /nova/scheduler/host_manager.py: 描述了跟調度器操作相關的主機的實現,其中,HostState類描述了從主機獲取相關數據和狀態的一些實現,HostManager類描述了跟調度器操作相關的一些主機管理實現
3. /nova/weights.py:實現了跟計算權值相關的方法
四. 分析調度_schedule方法
該方法對應在/nova/scheduler/filter_scheduler.py中
-
# 調度方法,返回一系列滿足要求的主機(host)
-
def _schedule(self, context, request_spec, filter_properties)
1. 信息初始化
-
# 返回帶有admin標志設置的context的版本
-
elevated = context.elevated()
-
# 獲取實例信息
-
instance_properties = request_spec[ 'instance_properties']
2. 更新過濾器屬性信息
-
filter_properties.update({ 'context': context,
-
'request_spec': request_spec,
-
'config_options': config_options,
-
'instance_type': instance_type})
3. 過濾不可用的host
-
# 過濾掉不可用的主機節點
-
hosts = self._get_all_host_states(elevated)
深入_get_all_host_states方法,對應的是/nova/scheduler/host_manager.py。
(1)獲取可用的計算節點
-
# 獲取可用計算節點的資源使用情況
-
# 獲取所有compute_node(計算節點)
-
compute_nodes = objects.ComputeNodeList.get_all(context)
(2)設置基本信息
-
# 獲取主機host
-
host = compute.host
-
# 獲取hypervisor_hostname作為節點名
-
node = compute.hypervisor_hostname
-
state_key = (host, node)
-
# 從host_state_map獲取並更新host狀態
-
host_state = self.host_state_map.get(state_key)
-
if host_state:
-
host_state.update_from_compute_node(compute)
-
else:
-
host_state = self.host_state_cls(host, node, compute=compute)
-
self.host_state_map[state_key] = host_state
(3)更新host狀態
-
# 每次請求到來都要更新host狀態
-
host_state.aggregates = [self.aggs_by_id[agg_id] for agg_id in
-
self.host_aggregates_map[
-
host_state.host]]
-
host_state.update_service(dict(service))
-
self._add_instance_info(context, compute, host_state)
-
seen_nodes.add(state_key)
(4)刪除不活躍的計算節點
-
# 從host_state_map中刪除不活躍的計算節點
-
dead_nodes = set(self.host_state_map.keys()) - seen_nodes
-
for state_key in dead_nodes:
-
host, node = state_key
-
LOG.info(_LI( "Removing dead compute node %(host)s:%(node)s "
-
"from scheduler"), {'host':host, 'node': node})
-
del self.host_state_map[state_key]
4.循環遍歷實例,獲取符合過濾要求的host
-
for num in range(num_instances):
-
# 基於具體要求過濾本地主機
-
hosts = self.host_manager.get_filtered_hosts(hosts,
-
filter_properties, index=num)
-
# 一個符合要求的host都沒有
-
if not hosts:
-
break
深入get_filtered_hosts方法,對應的是/nova/scheduler/host_manager.py。
(1)定義所要使用的過濾器
-
# 如果沒有設置過濾器,則使用默認的過濾器
-
if filter_class_names is None:
-
filters = self.default_filters
-
else:
-
# 獲取過濾器方法
-
filters = self._choose_host_filters(filter_class_names)
(2)然后處理三種類型的host
1》忽略的host
ignore_hosts = filter_properties.get('ignore_hosts', [])
-
# 除去忽略的host
-
def _strip_ignore_hosts(host_map, hosts_to_ignore):
2》強制使用的host
force_hosts = filter_properties.get('force_hosts', [])
-
# 匹配強制使用的host
-
def _match_forced_hosts(host_map, hosts_to_force):
3》強制使用的nodes
force_nodes = filter_properties.get('force_nodes', [])
-
# 匹配強制使用的nodes
-
def _match_forced_nodes(host_map, nodes_to_force):
-
# 執行過濾操作,返回滿足所有過濾條件的host對象
-
return self.filter_handler.get_filtered_objects(filters,
-
hosts, filter_properties, index)
5. 對主機進行稱重
-
# 獲取並返回一個WeightedObjects的主機排序列表(最高分排在第一)
-
weighted_hosts = self.host_manager.get_weighted_hosts(hosts,
-
filter_properties)
深入get_weighted_hosts方法,最終對應的是/nova/weights.py。
(1)用相乘累加的方式計算host主機的權重
-
# 根據多方面參數來判定權值,比如主機剩余內存、剩余磁盤空間、vcpu的使用情況
-
# 每個參數乘於一個weight,累加得到host主機的權值
-
for i, weight in enumerate(weights):
-
obj = weighted_objs[i]
-
obj.weight += weigher.weight_multiplier() * weight
(2)將獲取權值的host主機排序后返回
-
# 對WeighedObjects列表進行排序返回
-
return sorted(weighed_objs, key=lambda x: x.weight, reverse=True)
開發者也可以實現自己的權值計算函數,對於OpenStack采用的方法來說,主機擁有的剩余內存越多,權值越小,被選擇在其上創建虛擬機的可能性就越大。
6. 設置調度使用的主機數目
-
# scheduler_host_subset_size:定義了新的實例將會被調度到一個主機上
-
# 這個主機是隨機從最好的(分數最高的)N個主機組成的子集中選擇出來
-
scheduler_host_subset_size = CONF.scheduler_host_subset_size
-
if scheduler_host_subset_size > len(weighed_hosts):
-
scheduler_host_subset_size = len(weighed_hosts)
-
if scheduler_host_subset_size < 1:
-
scheduler_host_subset_size = 1
7. 獲取隨機選擇出來的主機
-
# 從分數最高的若干主機組成的子集中,隨機選擇一個主機
-
# 新的實例將會調度到這個主機上
-
chosen_host = random.choice(
-
weighed_hosts[ 0:scheduler_host_subset_size])
-
LOG.debug( "Selected host: %(host)s", {'host': chosen_host})
-
# 把選好的主機增加到selected_hosts列表中
-
selected_hosts.append(chosen_host)
8. 為下一次實例選擇主機做好准備
-
# 此次選擇了一個主機后,在下一個實例選擇主機前,更新主機資源信息
-
chosen_host.obj.consume_from_instance(instance_properties)
-
if update_group_hosts is True:
-
if isinstance(filter_properties['group_hosts'], list):
-
filter_properties['group_hosts'] = set(
-
filter_properties['group_hosts'])
-
filter_properties['group_hosts'].add(chosen_host.obj.host)
9. 返回所有實例選擇的主機列表
-
# 循環為每一個實例獲取合適的主機后,返回選擇的主機列表
-
return selected_hosts
-