めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

NTTdocomo-openstackのOSインストール方式

(参考)
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とセットでイメージを用意しないとダメですね。カーネルアップデートなどがちょっと面倒になりそうです。

というわけで、今回は、インストール方式を見ました。この後は、スケジューラの実装あたりが面白そうですね。