(参考)
General Bare Metal provisioning frame work(Youtube)
General Bare Metal provisioning frame work
儀式
# mkdir dodai-compute # cd dodai-compute # git init # git pull https://github.com/NTTdocomo-openstack/nova.git
物理サーバをVMとみなして管理するハイパーバイザっぽいもの
こちらはEssexベースなどで、若干、Refactoringされているようですが、基本的な流れは同じはず。。。。
「ハイパーバイザの違いを吸収する何か」が「driver」と改名されて、ベースクラスが提供されています。
nova/virt/driver.py
32 driver_opts = [ 33 cfg.StrOpt('compute_driver', 34 help='Driver to use for controlling virtualization. Options ' 35 'include: libvirt.LibvirtDriver, xenapi.XenAPIDriver, ' 36 'fake.FakeDriver, baremetal.BareMetalDriver, ' 37 'vmwareapi.VMWareESXDriver'), 38 ] 39 40 CONF = cfg.CONF 41 CONF.register_opts(driver_opts) 42 LOG = logging.getLogger(__name__) 43 ... 72 class ComputeDriver(object): 73 """Base class for compute drivers. 74
こちらが、BareMetal用のdriverの実装です。
nova/virt/baremetal/driver.py
107 class BareMetalDriver(driver.ComputeDriver): 108 """BareMetal hypervisor driver.""" 109 110 capabilities = { 111 "has_imagecache": True, 112 } 113 114 def __init__(self, virtapi, read_only=False): 115 super(BareMetalDriver, self).__init__(virtapi) 116 117 self.baremetal_nodes = importutils.import_object( 118 CONF.baremetal_driver) 119 self._vif_driver = importutils.import_object( 120 CONF.baremetal_vif_driver) 121 self._firewall_driver = firewall.load_driver( 122 default=DEFAULT_FIREWALL_DRIVER) 123 self._volume_driver = importutils.import_object( 124 CONF.baremetal_volume_driver, virtapi) 125 self._image_cache_manager = imagecache.ImageCacheManager()
ベアメタルをインストールするところ
で、興味のある「spawn」まで一気に飛びます。
nova/virt/baremetal/driver.py
171 def spawn(self, context, instance, image_meta, injected_files, 172 admin_password, network_info=None, block_device_info=None): 173 nodename = instance.get('node') 174 if not nodename: 175 raise exception.NovaException(_("Baremetal node id not supplied" 176 " to driver")) 177 node = bmdb.bm_node_get(context, nodename) 178 if node['instance_uuid']: 179 raise exception.NovaException(_("Baremetal node %s already" 180 " in use") % nodename) 181 182 # TODO(deva): split this huge try: block into manageable parts 183 try: 184 _update_baremetal_state(context, node, instance, 185 baremetal_states.BUILDING) 186 187 var = self.baremetal_nodes.define_vars(instance, network_info, 188 block_device_info) 189 190 self._plug_vifs(instance, network_info, context=context) 191 192 self._firewall_driver.setup_basic_filtering(instance, network_info) 193 self._firewall_driver.prepare_instance_filter(instance, 194 network_info) 195 # v--- インストールするイメージを用意。(元イメージにkey injectionなどを先にしてしまっているっぽい。) 196 self.baremetal_nodes.create_image(var, context, image_meta, node, 197 instance, 198 injected_files=injected_files, 199 admin_password=admin_password) # v--- PXE Bootの仕込みをしているっぽい。 200 self.baremetal_nodes.activate_bootloader(var, context, node, 201 instance, image_meta) 202 pm = get_power_manager(node) 203 state = pm.activate_node() # <- ここでサーバの電源を入れているはず。 204 205 _update_baremetal_state(context, node, instance, state) 206 207 self.baremetal_nodes.activate_node(var, context, node, instance) 208 self._firewall_driver.apply_instance_filter(instance, network_info) 209 210 block_device_mapping = driver.block_device_info_get_mapping( 211 block_device_info) 212 for vol in block_device_mapping: 213 connection_info = vol['connection_info'] 214 mountpoint = vol['mount_device'] 215 self.attach_volume(connection_info, instance['name'], 216 mountpoint) 217 218 pm.start_console() 219 except Exception, e: 220 # TODO(deva): add tooling that can revert a failed spawn 221 _update_baremetal_state(context, node, instance, 222 baremetal_states.ERROR) 223 raise e
まずは、PXE Bootの中身をみてみましょう。
nova/virt/baremetal/pxe.py
460 def activate_bootloader(self, var, context, node, instance, image_meta): 461 tftp_root = var['tftp_root'] 462 image_path = var['image_path'] 463 464 tftp_paths = self._put_tftp_images(context, instance, image_meta, 465 tftp_root) 466 LOG.debug(_("tftp_paths for instance %(uuid)s: %(tftp_paths)s"), 467 {'uuid': instance['uuid'], 'tftp_paths': tftp_paths}) 468 469 pxe_config_dir = os.path.join(tftp_root, 'pxelinux.cfg') 470 pxe_config_path = os.path.join(pxe_config_dir, 471 self._pxe_cfg_name(node)) 472 473 deployment = self._create_deployment(context, instance, image_path, 474 pxe_config_path) 475 476 pxe_ip = None 477 if CONF.baremetal_pxe_vlan_per_host: 478 pxe_ip_id = bmdb.bm_pxe_ip_associate(context, node['id']) 479 pxe_ip = bmdb.bm_pxe_ip_get(context, pxe_ip_id) 480 481 deployment_iscsi_iqn = "iqn-%s" % instance['uuid'] 482 iscsi_portal = None 483 if CONF.baremetal_pxe_append_iscsi_portal: 484 if pxe_ip: 485 iscsi_portal = pxe_ip['server_address'] # v--- ここで pxeのconfigファイルを作っている 486 pxeconf = _build_pxe_config(deployment['id'], 487 deployment['key'], 488 deployment_iscsi_iqn, 489 deployment_aki_path=tftp_paths[0], 490 deployment_ari_path=tftp_paths[1], 491 aki_path=tftp_paths[2], 492 ari_path=tftp_paths[3], 493 iscsi_portal=iscsi_portal) 494 fileutils.ensure_tree(pxe_config_dir) 495 libvirt_utils.write_to_file(pxe_config_path, pxeconf) 496 497 if CONF.baremetal_pxe_vlan_per_host: 498 _start_per_host_pxe_server(tftp_root, 499 node['prov_vlan_id'], 500 pxe_ip['server_address'], 501 pxe_ip['address'])
pxeのconfigファイルの中身は・・・・
nova/virt/baremetal/pxe.py
132 def _build_pxe_config(deployment_id, deployment_key, deployment_iscsi_iqn, 133 deployment_aki_path, deployment_ari_path, 134 aki_path, ari_path, 135 iscsi_portal): 136 # nova-baremetal-deploy-helper will change 'default deploy' 137 # to 'default boot' after the image deployment is complete 138 pxeconf = "default deploy\n" 139 pxeconf += "\n" 140 141 pxeconf += "label deploy\n" 142 pxeconf += "kernel %s\n" % deployment_aki_path 143 pxeconf += "append" 144 pxeconf += " initrd=%s" % deployment_ari_path 145 pxeconf += " selinux=0" 146 pxeconf += " disk=cciss/c0d0,sda,hda,vda" 147 pxeconf += " iscsi_target_iqn=%s" % deployment_iscsi_iqn 148 pxeconf += " deployment_id=%s" % deployment_id 149 pxeconf += " deployment_key=%s" % deployment_key 150 if CONF.baremetal_pxe_append_params: 151 pxeconf += " %s" % CONF.baremetal_pxe_append_params 152 pxeconf += "\n" 153 pxeconf += "ipappend 3\n" 154 pxeconf += "\n" 155 156 pxeconf += "label boot\n" 157 pxeconf += "kernel %s\n" % aki_path 158 pxeconf += "append" 159 pxeconf += " initrd=%s" % ari_path 160 # nova-baremetal-deploy-helper will set ${ROOT} to proper UUID 161 pxeconf += " root=${ROOT} ro" 162 if iscsi_portal: 163 pxeconf += ' bm_iscsi_portal=%s' % iscsi_portal 164 if CONF.baremetal_pxe_append_params: 165 pxeconf += " %s" % CONF.baremetal_pxe_append_params 166 pxeconf += "\n" 167 pxeconf += "\n" 168 return pxeconf
動的に作っているので見難いですが、要するに、下記のようなconfigができるっぽい。
default deploy label deploy kernel <deployment_aki_path> append initrd=<deployment_ari_path> selinux=0 disk=cciss/c0d0,sda,hda,vda \ iscsi_target_iqn=<deployment_iscsi_iqn> \ deployment_id=<deployment_id> deployment_key=<deployment_key> <CONF.baremetal_pxe_append_params> ipappend 3 label boot kernel <aki_path> append initrd=<ari_path> root=${ROOT} ro <CONF.baremetal_pxe_append_params>
最初に、「deploy」ラベルでインストールして、それが終わると「boot」ラベルに切り替えて起動するみたいです。つまり、インストール後もPXE Bootを継続するっぽいです。grub-installを省略しているようですね。
で、deployラベルで使うinitrdの中身が気になるところですが、実は、別ツールで先に作ってしまうようでした。というわけで再度、儀式を・・・・。
儀式
# mkdir dodai-compute-initrd # cd dodai-compute-initrd # git init # git pull https://github.com/NTTdocomo-openstack/baremetal-initrd-builder.git
Deploy用initrdの中身
/docomo-nova-initrd/scripts/init
132 target_disk=`find_disk "$DISK"` 133 echo "start iSCSI target on $target_disk" 134 start_iscsi_target "$ISCSI_TARGET_IQN" "$target_disk" ALL # インストール対象ディスクをiSCSIで公開 135 if [ $? -ne 0 ]; then 136 echo "Could not find disk to use." 137 echo "Starting troubleshooting shell." 138 bash 139 fi 140 141 echo "request boot server to deploy image" 142 d="i=$DEPLOYMENT_ID&k=$DEPLOYMENT_KEY&a=$BOOT_IP_ADDRESS&n=$ISCSI_TARGET_IQN" 143 wget --post-data "$d" "http://$BOOT_SERVER:10000" # 「インストールサーバ」にインストールをお願い 144 145 echo "waiting for notice of complete" 146 nc -l -p 10000 147 148 echo "stop iSCSI target on $target_disk" 149 150 stop_iscsi_target 151 152 echo "rebooting" 153 reboot -f
コメントに記載の通り、インストール対象ディスクをtgtdでiSCSIターゲットとして公開して、外部の「インストールサーバ」に書き込みをお願いするという面白い手法をとっています。これって、Novaと関係なく、一般的な「Baremetal Install as a Server」として使えそうですよね。
「インストールサーバ」の中身は・・・
これのようです。
bin/nova-baremetal-deploy-helper
182 def work_on_disk(dev, root_mb, swap_mb, image_path): 183 """Creates partitions and write an image to the root partition.""" 184 root_part = "%s-part1" % dev 185 swap_part = "%s-part2" % dev 186 187 if not is_block_device(dev): 188 LOG.warn("parent device '%s' not found", dev) 189 return 190 make_partitions(dev, root_mb, swap_mb) 191 if not is_block_device(root_part): 192 LOG.warn("root device '%s' not found", root_part) 193 return 194 if not is_block_device(swap_part): 195 LOG.warn("swap device '%s' not found", swap_part) 196 return 197 dd(image_path, root_part) 198 mkswap(swap_part) 199 root_uuid = block_uuid(root_part) 200 return root_uuid ... 314 if __name__ == '__main__': 315 config.parse_args(sys.argv) 316 logging.setup("nova") 317 app = BareMetalDeploy() 318 srv = simple_server.make_server('', 10000, app) 319 srv.serve_forever()
こちらは、コピー元のイメージと同じサイズのパーティションを切って、漢らしくddで叩きこんでいるようです。やはり、特にgrubの書き込みはおこなっていないようです。
この方式だと、必ずAKI/ARIとセットでイメージを用意しないとダメですね。カーネルアップデートなどがちょっと面倒になりそうです。
というわけで、今回は、インストール方式を見ました。この後は、スケジューラの実装あたりが面白そうですね。