月別アーカイブ: 2018年7月

proxy dhcpを使ってネットワークブートする方法.

概要

ネットワークブートは一般にdhcpの機能として提供されます.dhcpのサーバの動作は以下の手順を踏みます.
1. 対象のマシンを起動させるときに,ネットワークブートを選択します.
2. ネットワークブートを選択すると,dhcpサーバをブロードキャストアドレスに問い合わせて探します.
3. dhcpサーバは対象のマシンにipアドレスを割り当てると同時に,ネットワークブートする際に必要なイメージの場所を通知します(このとき使用されるのはtftpサーバのアドレスです.).
4. 対象のマシンは通知されたipアドレスを用いてネットワークに接続し,ブートイメージを指定されたtftpサーバから落として起動します.
5. これ以降は,使用するブートイメージによって動作が変わります.

さて,上述したとおり,ネットワークブートではdhcpサーバの設定を変更することが必須となるわけですが,ネットワーク管理者でない限り,dhcpサーバの設定をいじれることは稀です.ではもうどうしようもないのかというとそんなことはなくて,proxy dhcpという方法があります.これは,proxy dhcpサーバという,ネットワークブートの機能だけをもたせたサーバをたてることで,ネットワークブートを行います.このサーバはipアドレスなどの割当は本物のdhcpサーバにお願いし,ブートに関する処理だけをproxy dhcp側で分離して行うものです.

今回の例では,以下のようなディレクトリ構成を想定しています.
– /srv/tftpディレクトリをtftpでの公開ディレクトリとする
– /srv/tftp/pxelinux.0という名前でpxelinuxのブートローダのイメージを配置しておく.
– /srv/tftp/debianディレクトリにカーネルといろいろを格納しておく.
– /srv/tftp/pxelinux.cfgディレクトリにpxelinuxの設定を置く.

やり方

proxy dhcpとして使える有名なソフトはdnsmasqというものです.これは標準レポジトリでインストールすることができます.dnsmasq自体は,汎用のdhcpサーバとして使える上に,tftpサーバの役割も持っています.ここでは,dnsmasqのproxy dhcpとtftpサーバの機能のみを利用することにします.そのために以下の設定を/etc/dnsmasq.confに加えます.

port=0はproxy dhcpとして使用することを意味しています.次のenable-tftpはtftpサーバを有効にする設定です.tftp-rootはtftpサーバのルートディレクトリを示します.ここでは/srv/tftp以下をtftpサーバで公開することになります.dhcp-rangeはproxy dhcpの適用範囲となるホストをネットワークアドレスで指定します.最後の,pxe-serviceですが,最初のx86PCは絶対に必要なおまじないだと考えましょう.次の”pxelinux”は単にラベルなので好きに指定して良いです.最後のpxelinuxはブートイメージを表します.この名前の最後には自動的に’.0’が末尾に追加されることになるので,実際に指定されるファイルの絶対アドレスは/srv/tftp/pxelinux.0となります.ここではネットワークブートにpxelinuxを使用するので,ブートするイメージはpxelinux.0です(このファイルはネット上から適当にダウンロードした後に,事前に/srv/tftpディレクトリを作ってコピーしておきます).

さて,この設定を行ったらdnsmasqを再起動します.次に必要なのはpxelinuxの設定です.この設定ファイルは/srv/tftp/pxelinux.cfgディレクトリにまとめます(存在しなければ作ってください).このディレクトリの位置は(おそらく)pxelinux.0と同じ階層である必要があるのかなと思います.このディレクトリ内のファイル名にはMACアドレスかipアドレス,そしてdefaultが使えます.例えばネットワークブートするマシンのMACアドレスがac-1f-6b-0a-XX-XXの場合には,pxelinux.cfgの中に,”01-ac-1f-6b-0a-XX-XX”というファイルを作ると,pxelinux.0はこのファイルを読み出すように働きます.もし,pxelinux.cfgの中に該当するMACアドレスのファイルがなければ,次にipアドレスで合致するファイルを探し,それもなければ”default”という名前のファイルを読み出します.

設定ファイルには例えば以下のようなことを書きます.

まずdefault debianですが,これは”debian”という名前のファイル,もしくはlabelを最初に読み出すことを指示します.例えばブート時にメニューを表示したいときには,menu.c32をpxelinux.0と同じディレクトリに持ってきておいて,default menu.c32とすれば,メニューを表示させることができます(現在は,menu.c32を使用する際に,libutil.c32もコピーしておく必要があります).ここでは,defaultの次の行にある,LABEL debian以下の設定を標準で呼び出すことを表しています.次の,kernel debian/vmlinuzはカーネルとしてdebian/vmlinuzを読み出せという指示です.このアドレスは,tftpのルートから見たディレクトリを指します.ですから,今回の場合は,/srv/tftp/debian/vmlinuzのことです.次のappendの行は様々な設定を行うためのものです.ここの部分の各オプションは次の意味を持ちます.

  • root : ブートしたあとのルートとなるデバイスを指定します.ディスクレスサーバの場合は/dev/nfsです.
  • initrd : initramfsのイメージを指定します.
  • net.ifnames : ネットワークカードの名前を認識した順でeth0, eth1, .., ethnという形で/dev/以下に割り当てます.ネットワークカードのデバイス名がわかっている場合はこのオプションは必要ありません.
  • nfsroot : nfsでルートシステムをマウントする際に指定します.ここではipアドレス172.20.24.21の/srv/nfsrootというディレクトリがマウントされます.
  • ip : ipアドレスの割り当て方を指定します.dhcpで割り当てる場合はip=dhcpとします.静的な場合は,ip=<ipアドレス>:<ネットワークブートの接続先サーバのipアドレス>:<デフォルトゲートウェイ>:<サブネットマスク>:<ホスト名>:<使用するネットワークデバイス>という形で指定します.後ろの方を省略する場合は,指定したいところまで記述してそれ以降を単に書かなければ良いです.例えばip=172.20.24.10:172.20.24.21だけでもOKです.
  • init : おそらく一番最初に起動するプロセスのことではないでしょうか.昔はinitというプロセスが一番最初に呼び出されて動いてましたが,今はsystemdを使うのが一般的です.
  • rw : ディスクへの読み書きを許可します.異常が出る可能性も充分あるので,roにしたほうが良いかもしれません.
  • quiet : 忘れました.

こんな感じの設定を各設定ファイルに書きます.もしすべてのマシンで同じ設定でブートするのであれば,pxelinux.cfgにはdefaultファイルのみを作ればいいです.さて,上記の設定を見れば分かる通り,カーネルとinitramfsのイメージだけは必ず用意する必要があります.iniramfsのイメージの作り方は,まずinitramfs.confを作ります.例を示します.

特に特筆すべき事はありませんが,一つだけ.DEVICEオプションには使用するネットワークデバイスを指定できます.さて,この設定ファイルを作ったら,

を実行します.ここで,-dオプションはinitramfs.confの場所を指定します.また-oオプションは出力ファイル名のことです.これを実行すると大量にエラーが出ますが,あまり気にする必要はありません.最後に,vmlinuzを実行中のリナックスからとってきます.ホストのカーネルイメージは/boot以下にあります.例えば,/boot/vmlinuz-4.9.0-amd64みたいな感じです.これをvmlinuzにして/srv/tftp/debian/以下にコピーします.

さてここまでをまとめるとディレクトリ構造は以下の通りとなります.

最後に,必要なものがあります.それはldlinux.c32というファイルです.これは/usr/lib/syslinux/modules/bios/ldlinux.c32というところにあります.これをpxelinux.0があるディレクトリにコピーします(sudo apt install syslinuxを事前にしておくこと).

以上により必要な処理は終わりです.ネットワークブートをして動作を確認します.

内容についてどう思いますか?
  • いいね (3)
  • だめ (0)

べき乗則の話について

最近,「歴史は「べき乗則」で動く」という本を読んだ.amazonのURI

この本では,世の中の多くの減少における確率分布がべき乗則に従っており,その背景にはフラクタル構造のような再帰的な枠組みが隠されているのだということを終始語っている.これはべき乗則がスケールに対する普遍性を持っており,その背後にフラクタルのようないくら拡大縮小しても構造が変わらないものが存在していると仮定するとうまく説明可能であることに由来している.

本書では,地震の規模と発生頻度,株価の変動,その他生物の絶滅などに至る多くの事柄がべき乗則に従っていることを述べている.本書で取り扱われる議論をそのまま適用すれば,これらの現象を支配する要因にはフラクタル構造が隠れており,それゆえ大地震が発生した理由や,株価が大暴落することには特別な意味があるわけではないことになる.したがって,なぜ人がこれらの現象が起きるのか説明(予測)できないのかといえば,そもそも予測すること自体が不可能な事柄であるからという結論になるのである.

この話は一見,非常に的を得た説明のように感じられる.しかし,本書の最後にある訳者のコメントには,このべき乗則による説明は不十分であるという見解が述べられている.理由としては簡単で,たとえ現象の発生確率がべき乗則ではなく,正規分布に従っている場合でも,平均から大きくハズレた現象が発生することを予想することは困難であるからである.これも確かに正しいように感じられる.

さて,ではどちらの意見のほうが正しいのだろうか.この話を考える際には,おそらく次の2つのことに注意しなければならない.それはある分布からサンプリングすると必ずその分布にそったランダムな値が得られてるということ.またそのことと,予想可能であるかどうかは特に関連がないことである.すなわち,ある現象の発生確率がある分布に従っているとするとき,その情報だけでは,ある瞬間にその現象が起きる可能性は確率的にしか評価できない.したがって,その事実だけではどれほどの大きさで対象となる現象が発生するのか予測することは困難となる.しかし,この予測可能性は,分布のサンプリングだけに着目するのではなく,分布の形に着目すれば,その可否がわかるというのが本書の主題だろう.例えば,正規分布に従っている現象を対象とすれば,正規分布はスケールに対する普遍性を持たないのだから,平均に近い部分に対応する現象と,平均から外れた部分に対応する現象とでは,その発生原因は大きく異なるだろうという予想がたつ.しかし,べき乗則のような分布に従っていた場合には,スケール普遍性により,正規分布に当たるような特定の発生原因(すなわち予兆)を見つけることが困難なので,極端な事象(大地震など)を予想することができないというのが,本書の著者が言いたいことだったのではないだろうか.

しかし,このような説明もあまりに感覚に訴えかけすぎていて,その妥当性がそこまで納得できるものになっていない.結局の所どういうふうに考えればよいのだろうか(誰か教えてください).

内容についてどう思いますか?
  • いいね (1)
  • だめ (0)

配列の中で2番めに大きい要素

最近ブログを書いてないって言われたので,何かを書こうとおもったけれど,何を書くのが全く思いつかないので,今日,聞かれた質問について書こうと思う.

聞かれたのは,与えられた配列の中から2番めに大きい要素を知る方法である.1番大きい要素の取得の仕方はよくある話だが2番めはあまり聞いたことがない気がする.どうやって解くのが最も効率が良いだろうか.ちなみにプログラム演習の課題のようなので,あまり高度なライブラリを使用するのは避けなければいけないという制約がついている.

最も簡単な解法は,一番最大の要素をまず見つけ出し,次にその要素を取り除いた配列から最大の要素を取り出す方法だ.これは愚直だがわかりやすく書きやすい.ただし,最大値を求めるループを2回使う必要があり,いくらか冗長な気がする.次に,思いつくのはソートして2番めの要素を取り出す方法である.スクリプト言語を使っているのであればこれが一番ありがちなのではないだろうか.ただし,一から全て実装する場合は,これは少し面倒だ(ライブラリがまったくないそんな状態は普通ないのだが).

さてここではよりフォーマルな形で定式化することで,この問題を効率よく解く方法を考えたい.まず与えられた配列を\(a_n, \dots, a_1\)という数列であるとしよう.\(a_k, \dots, a_1\)の中で一番大きい要素を返す関数を\(f(k)\)とする.このとき明らかに
$$f(k) = \begin{cases}
-\infty & (k=0) \\
f(k-1) & (f(k-1) \geq a_{k}) \\
a_{k} & (otherwise)
\end{cases}$$
が成り立つ.次に,\(a_k, \dots, a_1\)の中で2番めに大きい要素を返す関数を\(g(k)\)としよう.定義から明らかに\(g(k) \leq f(k)\)である.\(g(k)\)の計算は\(f(k)\)と非常に近いはずである.しかし,2番めであるという制約が,問題を難しくしている.まず,\(g(k)\)と\(g(k-1)\),そして\(a_k\)の関係を考えよう.まず,\(a_k\leq g(k-1)\)のときは明らかに\(g(k)=g(k-1)\)である.次に,\(a_k> g(k-1)\)の場合は,\(g(k)=a_k\)としたいところだが,\(f(k)\)との兼ね合いがあるため,それだけでは条件として不十分だ.実際には,\(f(k-1) < a_k\)のときは,\(g(k)\ne a_k\)としなければ,2番めに大きいという条件を満たせない.逆に言えば,これだけで必要条件を満たすことができる.したがって,まとめれば
$$g(k) = \begin{cases}
-\infty & (k=0) \\
g(k-1) & (g(k-1) \geq a_{k}) \\
a_{k} & (f(k-1) \geq a_{k}) \\
g(k-1) & (otherwise)
\end{cases}$$
である.思ったより簡単だった.

内容についてどう思いますか?
  • いいね (2)
  • だめ (1)