Ansible Night in Tokyo 2018.04に参加してきた

RedHat社のセミナールームで開催されたAnsible Night in Tokyo 2018.04に参加してきました。

ansible-users.connpass.com

Ansibleユーザー会主催のイベントに参加するのは第1回もくもく会以来。

80名の参加枠に対して240名くらいの応募という人気っぷりで、抽選の結果、見事に外れた上、補欠順位も200番以降となってしまい、これは絶望的だなぁと一時は落胆したものの、ブログ枠が空いていたので滑り込んだ次第です。

そんな訳で、各講演を拝聴しての雑感を書き残しておきます。

Cisco with Ansible

シスコシステムズ加藤久慶さん、横石 雄大さん、畝高 孝雄さんの講演。お三方それぞれのテーマで発表。

NexusでAnsibleやってみた

まず、Ciscoについて。日本ではルーターやスイッチのイメージ強いけど、それらは一部のサービスで、ネットワークに関わる様々な製品を提供しているとのこと。

この発表ではCiscoのスイッチNexusシリーズ4台を使ったネットワークを、Ansibleで自動的に構築(スイッチ間でのP2Pセグメント設定、Loopbackインターフェースの作成、OSPFを有効にしての経路伝播等)するところをサクッとデモされていました。

NexusシリーズのOSであるNX-OS用のAnsibleモジュールは現在72種類あり、大概のことは自動化できるそうです。

なお、CiscoにはDevNetという開発者向けのサービスがあり、そちらに各種サービスのSandboxが提供されているので、実機の購入という高いハードルを越えずとも、気軽に検証することが可能とのこと(DevNetは様々なSSOに対応しており、GithubFacebook等のアカウントでログインできます)。

また、Learning Labsというコンテンツもあり、Cisco製品におけるAnsibleの活用方法や、Ansible自体の使い方等について学習することができる(Ansible関連以外のものも)ので、是非、有効に活用してもらいたいとのことでした。

Contiv + k8s + Ansible

Contivについての紹介がメインな発表。メモはとったものの、発表内容の要点はCiscoのブログにまとまっていたので、そちらの方が参考になります。

実務でコンテナ使えていないこともあって、正直、Contivについては全く知らなかったのだけれど、深掘りしたい内容でした。

Cisco ACIとAnsibleのステキな関係

www.slideshare.net

Ansibleの制御対象は増えるほど管理が大変。サーバーという点に対し、面であるネットワークの構成管理は勝手が違う。

という問題に対し、ACI(+APIC)の紹介をメインに、Ansibleとの連携でネットワーク構成管理の煩雑さがどう解消されるのかといった発表でした(多分)。ACI(+APIC)については自分のメモよりCiscoの製品紹介を見た方が参考になります。

ACIのAnsibleモジュールは、充実したドキュメントが公式に用意されているので、検証する際はそちらを参照すると良いとのこと。

なお、ACIにはacitoolkitというPython製のツール群があり、その内のACI Diagram generatorを利用すると、APICAPIアクセスしてACIのダイアグラムをグラフィカルに作成することができるのだとか。

Ansible2.5のアップデートとネットワーク自動化

RedHat社のAnsibleビジネスユニット プリンシパル・プロダクト・マネージャー、Sean Cavanaughさんの講演。

英語で通訳無しであった為、残念ながらあまり理解できませんでしたが、概ね以下のようなお話をされていたかと思います。

Ansibleを追いかける際の情報源を知れたのは収穫でした。

LT1:ANSIBLE ROLEの継続的自動UPDATE

chrojuさんの発表。

speakerdeck.com

Ansible Roleをソフトウェアパッケージに見立て、yarnやbundle、Gemfile.lock等を参考に工夫を凝らしてCIを回すというお話。

個人的にAnsibleはまだまだミニマムな使い方しかしていないけれど、ゆくゆくはぶつかりそうな問題なので参考になりました。

LT2:Junos モジュールのコネクションタイプの使い分け

akira6592さんの発表。

www.slideshare.net

英語の講演中に理解しきれなかった2.5から追加されたコネクションタイプについて、こちらのLTで補完されました。

LT3:Ansibleを使用したWindows管理

curry9999さんの発表。

speakerdeck.com

なかなか出来上がった状態での発表となったcurry9999さん。笑いあり介護ありで面白かったですw

AnsibleのWindowsモジュールは1.7からじわじわ増え、2.5では81種類となり、自動化できる範囲も広がっているので、積極的に活用して無駄な運用タスクを減らしていこうというお話。

curry9999さんはライフワーク(?)でAnsibleモジュールの歴史をまとめられているので、定期的にチェックさせてもらっています。

Ansible 2.6 対応 モジュールの歴史 フルアーマー版awsbloglink.wordpress.com

LT4:Ansible とStackStorm でつくるChatOps 環境

katsuhisaさんの発表。

speakerdeck.com

StackStormを活用したChatOpsの自動化について。

運用においてChatが有効活用できていない環境にいるので、ここまで自動化できてるのは個人的には未来感ありました。

IFTTTって知らなかった。トライしたい。

LT5:Windowsのcp932に苦闘している

Hidetoshi Hirokawaさんの発表。

www.slideshare.net

AnsibleでWindowsの自動化を進めていくとぶち当たる、文字コードという特有の問題に対するお話。

Windows文字コード問題は本当に苦労しますね…。

終わりに

知見の乏しいネットワーク領域に関する講演がメインであったので、その場では理解しきれないことも多かったのですが、日頃、サーバー用途でしかAnsibleを使っていない自分にとっては、全体的に新鮮な内容でありました。改めてAnsibleの適用範囲って広い。

なお、イベント終了後は恒例となっているAnsible飯に参加させて頂き、Ansibleに関する話題は当然として、バーベキューとか肉の妖精とかIoTプラレールとか、参加者の皆さんの興味深い趣味の話にも花が咲き、今回も有意義な時間を過ごさせてもらいました。

イベント主催者、スタッフの皆さん、RedHat社に感謝致します。有難うございました。

※ 会場が恵比寿だからか、イベント開始前にRedHat社の望月社長からヱビスビールが振る舞われました。ありがたやありがたや。

f:id:heroween:20180426184640j:plain

※ ブログ書くということでAnsibleノートも頂きました。ありがたやありがたや。

f:id:heroween:20180426185832j:plain

PackerでAMIを作ろうとしたら吐かれたsudoエラーについて

久々にPackerを使ってみて遭遇したエラーについてメモしておく。

環境

動作環境はMac Book AirOSX 10.8 Mountain Lion。Packerはv0.8.6を使用。
AMIにはAmazon Linux AMI 2015.09.1 (HVM), SSD Volume Type - ami-383c1956を選択。

Packerテンプレート

単純にAmazon LinuxのEC2インスタンスsudo yum update -yしたAMIを作るだけのものである。

{
  "variables": {
    "aws_access_key": "",
    "aws_secret_key": ""
  },

  "builders": [{
    "type": "amazon-ebs",
    "access_key": "{{user `aws_access_key`}}",
    "secret_key": "{{user `aws_secret_key`}}",
    "region": "ap-northeast-1",
    "source_ami": "ami-383c1956",
    "instance_type": "t2.micro",
    "ami_name": "wps-web-ami {{timestamp}}",
    "ssh_username": "ec2-user",
    "ssh_timeout": "5m",
    "tags": {
      "Name": "WordPress Server"
    }
  }],

  "provisioners": [{
    "type": "shell",
    "inline": [
      "sudo yum update -y"
    ]
  }]
}

エラーメッセージ

上記のテンプレートでビルドしたら以下の様なメッセージを吐いてコケた。

$ packer build ami.json 
amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name...
==> amazon-ebs: Inspecting the source AMI...
==> amazon-ebs: Creating temporary keypair: packer 5655d9e2-b5e7-8a86-9f64-33936ce5add0
==> amazon-ebs: Creating temporary security group for this instance...
==> amazon-ebs: Authorizing access to port 22 the temporary security group...
==> amazon-ebs: Launching a source AWS instance...
    amazon-ebs: Instance ID: i-ed34aa48
==> amazon-ebs: Waiting for instance (i-ed34aa48) to become ready...
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Connected to SSH!
==> amazon-ebs: Provisioning with shell script: /var/folders/_p/5nbnyk0n5x599chrtk9vswqc0000gn/T/packer-shell560434101
    amazon-ebs: sudo: sorry, you must have a tty to run sudo
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: No AMIs to cleanup
==> amazon-ebs: Deleting temporary security group...
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' errored: Script exited with non-zero exit status: 1

==> Some builds didn't complete successfully and had errors:
--> amazon-ebs: Script exited with non-zero exit status: 1

==> Builds finished but no artifacts were created.

原因と解決方法

エラーメッセージの赤く着色した部分が問題なのだが、読んだ通りで仮想端末を使わなければsudoできませんよと言われている。
一年くらい前にPackerで同じ様にAMIを作っていた時には、この設定内容で問題無かったような気がしたのだが、どういうことだろうと思い調べてみたところ、PackerのChangeLogにその答えが書いてあるのを見つけた。

core: SSH connection will no longer request a PTY by default. This can be enabled per builder.

Packerは2015年6月23日にv0.8.0へとアップデートしたのだが、その際、標準ではSSH接続にPTYを要求しなくなったそうだ。要するに、リモートマシンに仮想端末でログインせず、直接コマンドを投げているので、sudoersに外部からのコマンド実行を遮断されている訳である。
この問題を解決するのは簡単で、SSH接続にPTYを要求するよう設定してやれば良い。v0.8.2から追加されたCommunicatorのオプションであるssh_ptytrueを設定し、上記テンプレートのBuilder部分に追記してやると、SSH接続に仮想端末を使うようになるので、Provisionerのsudoコマンドが実行されるようになる。
なお、他の解決方法として、AMI Builderのuser_dataを利用し、sudoersの設定内容を書き換えてしまうということも可能ではあるのだが、当然、セキュリティ的に問題があるのでおすすめはしない。素直にssh_ptyを使った方が手間が無いし安全だろう。

ChefのパッケージをWeb API(Omnitruck API)で取得する

Chefのパッケージ取得方法について、ちょっとしたメモ。
Chef ClientやChef Serverのパッケージは、ダウンロードページから以外にも、Web APIを利用した取得方法が存在する。

Omnitruck API

Chef社はOmnitruck APIというWeb APIを公開しており、任意のURLをリクエストすることにより、リリースされているあらゆるバージョンのChef ClientやChef Serverのパッケージを取得することができる。
また、通常のダウンロードページには存在しない、プレリリース版やナイトリー版の取得にも対応していたりする。
Chef ServerのCommunity Cookbookなんかでは、OhaiとOmnitruck APIを利用して、環境に合わせた臨機応変なパッケージの取得を実現している。

Chef Clientの取得方法

以下のURLをリクエストすることで、パッケージを取得できる。

http://www.getchef.com/chef/download?p=$PLATFORM&pv=$PLATFORM_VERSION&m=$MACHINE_ARCH&v=latest&prerelease=false

また、上記URLのdawnloadをmetadataに変更すると、パッケージに付随したメタデータを取得することができる。

http://www.getchef.com/chef/metadata?p=$PLATFORM&pv=$PLATFORM_VERSION&m=$MACHINE_ARCH&v=latest&prerelease=false

メタデータは、パッケージのダウンロードURL、MD5とSHA256のチェックサムが記されたタブ区切りのテキストデータである。

url http://opscode-omnibus-packages.s3.amazonaws.com/el/6/x86_64/chef-11.16.0-1.el6.x86_64.rpm
md5 5c8cfdbab2684148e2bb859b736b6827
sha256 e4d0236cc495d080f8e7a01704e2ef554e12088687e4fe946ee8027b79871bbb 

クエリパラメーターについては、

  • pはプラットフォーム。debian、el(rhel系)、freebsdmac_os_x、solaris2、sles(SUSE Enterprise Linux Server)、suseubuntuwindowsといった値を指定する。
  • pvはプラットフォームバージョン。プラットフォームに応じて指定できる値は決められており、elであれば5や6、ubuntuであれば10.4や12.10等、mac_os_xであれば10.6や10.7等といった具合。
  • mはマシンアーキテクチャi386i686x86_64、amd64といった値を指定するが、これもプラットフォームに依存する。
  • vはChef Clientのバージョン。デフォルトはlatestで省略可能。特定のバージョンが欲しい場合は、Semantic Versioningに則った形式で、x.y.z(x=MAJOR、y=MINOR、z=PATCH)といった具合に指定する。
  • prereleaseはプレリリース版を取得するためのフラグ。デフォルトはfalseで省略可能。

といった感じ。
これらクエリパラメーターでサポートされている値のより詳細な情報は、公式のドキュメントの方を参照してほしい。

Chef Serverの取得方法

Chef ClientのダウンロードURL末尾に-serverを付与するとChef ServerのダウンロードURLとなる。

http://www.getchef.com/chef/download-server?p=$PLATFORM&pv=$PLATFORM_VERSION&m=$MACHINE_ARCH&v=latest&prerelease=false&nightlies=false

メタデータの取得URLも同様。

http://www.getchef.com/chef/metadata-server?p=$PLATFORM&pv=$PLATFORM_VERSION&m=$MACHINE_ARCH&v=latest&prerelease=false&nightlies=false

クエリパラメーターも基本的にChef Clientと同じ構成であるが、フラグが一つ追加されている。

  • nightliesはナイトリー版を取得するためのフラグ。デフォルトはfalseで省略可能。ナイトリー版はあくまで検証用として用意されているので、本番環境では決して使わないこと。

当然だが各クエリパラメーターで指定できる値はChef Clientとは異なるので、サポートされている値の詳細な情報は、公式のドキュメントの方を参照してほしい。
公式ドキュメントには、他にも取得例が記載されているので、自前でChef ClientやChef Serverのインストール自動化プログラムを作る際には参考になるだろう。

ドメインについて

最後に、Chef社の旧ドメインについても触れておく。
現在のChef社は、社名変更に伴ってgetchef.comドメインを使っているが、変更前のOpscode社時代ではopscode.comドメインが使われていた。
このドメインはまだ生きており、opscode.comにアクセスすると、getchef.comへとリダイレクトされることが分かる。
で、Omnitruck APIについてだが、こちらも以前のURLであるwww.opscode.comでリクエストしても、www.getchef.comでリクエストした場合と同様に、正常にパッケージを取得することができる。
ちなみに、冒頭で例に挙げたChef ServerのCommunity Cookbookなんかは、このエントリを書いた時点では、旧ドメインのままだったりする。
とはいえ、いずれはサポートの切れるかもしれないドメインではあるので、これからOmnitruck APIを利用する場合に、わざわざ旧ドメインAPIを使用するのは止めておいた方が良いだろう。

CentOS6.5でランダムSalt付きSHA-512のシャドウパスワードを生成する

CentOS6.5でランダムSalt付きSHA-512のシャドウパスワードを生成する方法を調べたのでメモしとく。

最初、OpenSSLでできるかなと思いついたが、openssl passwdMD5はいけるけど、SHAには対応しておらず駄目だった。

$ openssl passwd --help
Usage: passwd [options] [passwords]
where options are
-crypt             standard Unix password algorithm (default)
-1                 MD5-based password algorithm
-apr1              MD5-based password algorithm, Apache variant
-salt string       use provided salt
-in file           read passwords from file
-stdin             read passwords from stdin
-noverify          never verify when reading password from terminal
-quiet             no warnings
-table             format output as table
-reverse           switch table columns

他にその手のツールはないか調べたところ、要件にピッタリ合致するgrub-cryptコマンドというのを見つけた。

$ grub-crypt --help
Usage: grub-crypt [OPTION]...
Encrypt a password.

  -h, --help              Print this message and exit
  -v, --version           Print the version information and exit
  --md5                   Use MD5 to encrypt the password
  --sha-256               Use SHA-256 to encrypt the password
  --sha-512               Use SHA-512 to encrypt the password (default)

Report bugs to <bug-grub@gnu.org>.
EOF

このコマンドを使うと、以下のように対話式でランダムSalt付きのSHA-512シャドウパスワードが生成できる。

$ grub-crypt         ←デフォルトがSHA-512なのでオプション必要無し
Password:            ←パスワード入力(今回はsampleという文字列)
Retype password:     ←もう一度パスワード入力
$6$5TIs9aRT3V4IRwJz$guTjCJzy1k.t8Gy8Y0TQiY25S7y6ptVzxeS0/o1JJG52Xv32lVIGCqOh11QCSeTqSlR2CVI380kTHYEY6WWOq/

うんうん、ちゃんとシャドウパスワードの形式で出力されてるね。
とてもシンプルで良いコマンドです。

Cygwinをアンインストールする

Cygwinをアンインストールしようと思ったが、Cygwinにはアンインストールを自動で行う手段が無く、手作業で行う必要があったので、その手順をメモしておく。

アンインストール手順

手順は公式サイトのFAQに記載されている。

http://cygwin.com/faq/faq.html#faq.setup.uninstall-all

意訳すると、

  1. Cygwin内でサービスを起動している場合は以下の手順で全て削除しましょう。一般的にインストールされていそうなサービスはsshd、cron、cygserver、inetd、apachepostgresqlとかです。

    1. cygrunsrv -Lで登録しているサービスを確認します。cygrunsrvが無い場合はこの過程を飛ばしましょう。

    2. 起動しているサービスがあればcygrunsrv --stop service_nameで停止します。inetdを使ってスタンドアローンなサービスを起動している場合はcygrunsrv -Lで表示されませんが、cygrunsrv --stop inetdを実行すればそれらもまとめて停止します。

    3. 最後にcygrunsrv --remove service_nameで各サービスを削除しましょう。

  2. X11サーバーが起動していれば停止し、バックグラウンドで動いているCygwinプログラムも止めます。コマンドプロンプトを終了し、Cygwinプロセスが無い事を確認しましょう。もし、再インストールする時の為にマウントポイントを保存しておきたければ、mount -mでfstabに書き込んでおくと良いでしょう。

  3. cyglsa.dllをインストールしており、/usr/bin/cyglsa-configを実行していた場合、LSA authentication packageの使用を停止する必要があります。その為、レジストリの/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Lsa/Authentication Packagesの値を、元のmsv1_0という値に戻してWindowsを再起動しましょう。

  4. Cygwinのルートフォルダとサブフォルダを全て削除します。オブジェクトが使用されているといったエラーが出ましたら、全てのサービスが停止されて、全てのCygwinプログラムが終了しているかを確認しましょう。パーミッションエラーが出ましたら、エラーの原因となっているファイルやフォルダのパーミッションを変更しましょう。例えば、システムサービスによって使用されているファイルはSYSTEMアカウントが保持し、一般ユーザーからは書き込みができなくなります。

    手っ取り早く削除するには、自分に全てのファイルとフォルダの所有権限を付与しましょう。Windows Explorerを開き、Cygwinのルートフォルダで右クリックし、プロパティからセキュリティタブを選択します。もし、Windows XP Home Editionのシンプルファイル共有を使っているならば、これはセーフモードにしてからやる必要があります。それから、自分のアカウントを選択し、フルコントロールできるように変更すればOKです。まとめて削除できるようになるでしょう。

  5. デスクトップとスタートメニューのショートカット、setup-x86{_64}.exeがインストール時に使ったダウンロードフォルダを削除します。しかし、再インストールする場合であれば、setup-x86{_64}.exeは既にあるダウンロードフォルダをキャッシュとして再利用できるので、残しておくと再ダウンロードせずに済むでしょう。

  6. システムパスにCygwinを通していたら、同じ場所に再インストールする予定が無い限りは削除しましょう。CYGWIN環境変数も同様です。

  7. 最後に、徹底的にCygwinの痕跡を消したければ、レジストリのHKEY_LOCAL_MACHINEとHKEY_CURRENT_USER配下にある、Software/Cygwinを削除しましょう。しかしながら、ここまでの手順に従っていれば、既に削除すべきものは削除できています。通常、インストールフォルダは全てのレジストリに保持されています。

と、まあこんな感じか。

で、この通りにやって特に問題無くアンインストールできました。

MSYSのrxvtやCygwinでVagrantを実行した際に出力されるNIO4RのDLLエラーについて

Vagrantに内包されているMSYSのrxvtやCygwinで、Vagrantのコマンドを叩いた時に検出されたエラーについてメモしとく。
あらかじめ書いておくが、問題の回避はできたものの、根本的な原因の究明にまでは到っていない。

環境

VagrantWindows版のパッケージを使っていて、vagrant-berkshelfはコマンドプロンプトからインストールしている。

事象

コマンドプロンプトからVagrantのコマンドを実行すると問題無く動いていたのだが、Vagrantに内包されているMSYSのrxvtや、Cygwinから実行すると以下のエラーが出力されて止まってしまった。

$ vagrant status
Vagrant failed to initialize at a very early stage:

The plugins failed to load properly. The error message given is
shown below.

1114: ダイナミック リンク ライブラリ (DLL) 初期化ルーチンの実行に失敗しました。   - C:/Users/heroween/.vagrant.d/gems/gems/nio4r-1.0.0/lib/nio4r_ext.so

なんでかなーと原因を調べてみた。

原因を探る

まず、問題を起こしているNIO4Rについてだが、これはBerkshelfが依存しているGemであり、Vagrantプラグインであるvagrant-berkshelfをインストールすると一緒に入ってくる。
このNIO4Rはクロスプラットフォームな作りとなっており、実行環境のRubyの処理系に応じて実装が変わる。

  • MRI/YARV、Rubiniusの場合はlibevをベースとしてC言語で実装された拡張ライブラリが使われる。
  • JRubyの場合はJava NIOをベースとしたJavaで実装された拡張ライブラリが使われる。
  • 上記以外であればPure Ruby実装のライブラリが使われる。

と、READMEではこのように説明されている。
Pure Rubyだと処理速度が遅いので、CやJava実装の拡張ライブラリも使えるようになっているのだろう。

で、今回はVagrantに内包されている、WindowsRuby(MRI)で実行したら問題が起きた。
上記の説明の通りであれば、MRIなのでC実装の拡張ライブラリが使われて、その際にDLLエラーが発生した…ものと思われるのだが、腑に落ちない点がある。
MSYSのrxvtやCygwinで出るエラーが、コマンドプロンプトでは出ずに、普通に動いているという点だ。

よく分からないので、実装の変わる条件がどうなっているのか、インストールされたNIO4Rのコード(nio.rb)を確認してみた。

if ENV["NIO4R_PURE"] || (ENV["OS"] =~ /Windows/i && !defined?(JRUBY_VERSION))
  require 'nio/monitor'
  require 'nio/selector'
  NIO::ENGINE = 'select'
else
  require 'nio4r_ext'

  if defined?(JRUBY_VERSION)
    require 'java'
    require 'jruby'
    org.nio4r.Nio4r.new.load(JRuby.runtime, false)
    NIO::ENGINE = 'java'
  else
    NIO::ENGINE = 'libev'
  end
end

これを見ると、OSがWindowsだとJRubyで無い限りは、必ずPure RubyのNIO4Rが使われるようだ…。
コマンドプロンプトから実行した場合、エラーが出ずに正常動作するのは、C実装でなくPure RubyのNIO4Rが使われているからということになる。
それでは、MSYSのrxvtやCygwinで実行すると、何故、Pure RubyでなくC実装のNIO4Rが使われるのだろうか。
とりあえず、環境変数OSをコマンドプロンプトで確認してみると、値は以下のように表示された。

C:¥User¥heroween>echo %OS%
Windows_NT

同じようにrxvtとCygwinでも確認すると、

$ echo $OS
Windows_NT

やはり"Windows_NT"と表示される。
それでは、Vagrant実行時にどうなっているのかと、nio.rbに直接puts ENV["OS"]と書き込んで実行したところ、

# コマンドプロンプト
Windows_NT

# MSYSのrxvt
MINGW32_NT-6.1

# Cygwin
CYGWIN_NT-6.1

と、ターミナル毎に異なった値が出力された。
そこで、VagrantのShell実装コマンドを見てみると、以下のように環境変数OSが上書きされていることが分かった。

# Determine the OS that we're on, which is used in some later checks.
# It is very important we do this _before_ setting the PATH below
# because uname dependencies can conflict on some platforms.
OS=$(uname -s 2>/dev/null)

unameコマンドの値で環境変数OSから"Windows"が消えてしまったことで、C実装のNIO4Rが使われるようになっていた訳である。

ここまでで、コマンドプロンプトとMSYSのrxvtやCygwinで挙動が変わっていた原因は分かった。
それでは、DLLエラーを吐く根本的な原因はなんなのだろうか。

処理の実行に失敗しているnio4r_ext.soをobjdumpで確認してみると、

$ objdump -T nio4r_ext.so

nio4r_ext.so:     file format pei-i386

DYNAMIC SYMBOL TABLE:
no symbols


C:¥MinGW¥bin¥objdump.exe: nio4r_ext.so: not a dynamic object

と表示されたので、soファイル自体壊れている可能性もありそうなのだが、Windows環境とC言語に明るくない自分では、究明するまでにかなりハマりそうなので、ここで打ち切ることにした。

対応

対応として、環境変数NIO4R_PUREに真の値を設定しておけば、必ずPure RubyのNIO4Rを使うようになるので、この問題を回避することはできる。

nio.rbのコードを見ると分かるが、そもそも、OSがWindowsの場合はC実装のNIO4Rを使わないような条件になっているし、過去のIssueやPRを追ってみても、単純にサポートしてないだけのように思われる。
どうしてもWindowsでC実装のNIO4Rを使いたいという訳でも無いので、一先ずはここまでにしておく。

Windows7にRubyInstaller+Pikで複数バージョンのRuby環境を整える

Windows上で複数バージョンのRubyを使う必要に迫られたので、RubyInstallerとPikを使って実現してみた。

前置き

PikとはWindows上で複数バージョンのRubyを管理するためのツールである。

Pik - Github

このPikを使ってRubyのインストールからバージョンの切り替えまで一切を管理する…のが本来の使い方なのだろうが、Pikの開発とメンテナンスは数年前から停滞している模様で、最新版(0.3.0-pre)でもインストールできるRubyのバージョンが標準で1.9.2までだったり、各種コマンドの挙動が不安定だったりしたので、RubyのインストールはRubyInstallerで行い、Pikはあくまでもバージョン切り替えだけを目的として使うことにした。

Rubyのインストール

という訳で、まずはRubyInstallerでRubyをインストールする。

Downloads - RubyInstaller for Windows

上記のページから、このエントリー作成時点での最新バージョンであった2.0.0-p481(x86)のインストーラーをダウンロードし、ダウンロードが完了したら実行する。
ウィザードが起動し、インストーラーでの表示言語選択が出てくるので、日本語を選択してOKをクリックする。

f:id:heroween:20140630122036p:plain

ライセンスに同意して次へをクリックする。

f:id:heroween:20140630122509p:plain

インストールフォルダ(デフォルトはC:¥Ruby200-x64)と各オプションを設定し、インストールをクリックする。
今回の例では、RubyGUIアプリを作成するために必要なTd/Tkサポートが不要だったのでチェックを外した。

f:id:heroween:20140630122933p:plain

インストールが開始され、

f:id:heroween:20140630123058p:plain

インストールが完了。完了をクリックしてウィザードを閉じる。

f:id:heroween:20140630123208p:plain

コマンドプロンプトRubyのバージョンを確認してみる。

C:¥>ruby -v
ruby 2.0.0p481 (2014-05-08) [x64-mingw32]

Rubyのバージョンが表示され、無事インストールされていることが分かる。

DevKitのインストール

Rubyはインストールしたが、これだけではRubyGemsの拡張ライプラリ(C言語製の)をビルドすることができない。
この状態でjson等の拡張ライブラリをインストールしようとすると、以下のようなエラーメッセージが出力されることになるだろう。

Please update your PATH to include build tools or download the DevKit
from 'http://rubyinstaller.org/downloads' and follow the instructions
at 'http://github.com/oneclick/rubyinstaller/wiki/Development-Kit'

拡張ライブラリをビルドできるようにするためには、Rubyの本体に加えてDevKitもインストールする必要がある。
RubyInstallerのダウンロードページの下部にある、DEVELOPMENT KITからRuby2.0(x64)版をダウンロードして実行する。
ファイルは7zip形式のアーカイブなので、展開先を設定してExtractをクリック。

f:id:heroween:20140630125355p:plain

解凍が始まり、

f:id:heroween:20140630125404p:plain

ファイルの展開が完了するとウィザードは自動で閉じる。

インストールされたDevKitの中身を確認してみると、DevKitとはMinGWMSYSであることが分かる。
拡張ライブラリをビルドするのに最低限必要となるWindows移植版のGNUツール群であり、RubyGemsのコマンドを実行した時に、DevKitのツール群を利用するように設定してやることで、拡張ライブラリがビルドできるようになる。

で、その設定方法だが、インストールしたフォルダの直下にあるdk.rbを使って行う。
まず、コマンドプロンプトを開いて初期化コマンドであるdk.rb initを実行する。

C:¥devkit>ruby dk.rb init
[INFO] found RubyInstaller v2.0.0 at C:/Ruby200-x64

Initialization complete! Please review and modify the auto-generated
'config.yml' file to ensure it contains the root directories to all
of the installed Rubies you want enhanced by the DevKit.

すると、このマシンにインストールされているRubyが自動で検出され、DevKitのフォルダに以下の内容のconfig.ymlというファイルが生成される。

# This configuration file contains the absolute path locations of all
# installed Rubies to be enhanced to work with the DevKit. This config
# file is generated by the 'ruby dk.rb init' step and may be modified
# before running the 'ruby dk.rb install' step. To include any installed
# Rubies that were not automagically discovered, simply add a line below
# the triple hyphens with the absolute path to the Ruby root directory.
#
# Example:
#
# ---
# - C:/ruby19trunk
# - C:/ruby192dev
#
---
- C:/Ruby200-x64

注意すべき点として、複数バージョンのRubyがインストールされている場合は、config.ymlのコメントに例示されているように、複数のパスが検出されてファイルに記載される。
この状態で、次のDevKitの適用コマンドを実行すると、全てのRubyに対して適用されてしまうため、適用したくないバージョンが含まれている場合は、テキストエディタでconfig.ymlを開き、対象外のパスを削除しておく必要がある。
尚、既に他のDevKitが適用されているRubyには、その旨がメッセージとして出力されるだけで、上書きされるようなことは無い。
逆に言うと、一度適用してしまうと修正できなくなるということなのだが、その場合はdk.rb install -fとforceオプションを付与することで強制上書きすることができる。

ここで、config.ymlの内容に問題が無ければ、dk.rb installで対象となるRubyにDevKitを適用する。

C:¥devkit>ruby dk.rb install
[INFO] Updating convenience notice gem override for 'C:/Ruby200-x64'
[INFO] Installing 'C:/Ruby200-x64/lib/ruby/site_ruby/devkit.rb'

出力されたメッセージにもある通り、適用するとdevkit.rbというファイルが新規に生成される。devkit.rbはRubyGemsのフックスクリプトで、DevKitのバイナリパスと環境変数が定義されており、RubyGemsのコマンドが実行されるとこのスクリプトが読み込まれ、拡張ライブラリのビルドにDevKit内のgcc等が使われるようになる。

余談だが、1.9.3のDevKitではdevkit.rbに加え、operating_system.rbというファイルも生成される。operating_system.rbは元からRubyの本体にあるファイルなのだが、その内容が書き変わり、元のファイルは日時付きにリネームされて、バックアップファイルとして保持される。
尚、devkit.rbとoprating_system.rbの内容は概ね同じなのだが、operating_system.rbがRubyGemsのフックスクリプトで、devkit.rbはプログラム内でDevKitの設定をrequire 'devkit'して読み込む為のヘルパーライブラリという位置付けであった。2.0になってからはdevkit.rbに統合された模様である。
DevKitについてより詳しく知りたければGithubのWikiを参照すると良い。

これでDevKitのインストールは完了となり、Ruby2.0.0の実行環境が整った。
そうしたら、次は同様の手順で別のバージョンのRubyである1.9.3-p545をインストールしてみよう。
DevKitも1.9.3向けのものがあるので、そちらも合わせてインストールする。

DevKitのアンインストール

DevKitはRubyやPikと異なり、アンインストールするためのプログラムは用意されていないので、アンインストール方法についても触れておく。
DevKitをアンインストールをしたくなったら、インストールフォルダを丸ごと削除し、適用したRubyに生成されたdevkit.rbを削除する。
1.9.3の場合はこれに加えて、operating_system.rbをバックアップされている初期のファイルに差し替える必要がある。

Pikのインストール

さて、これで2つのバージョンのRubyがインストールできた。それでは、これらのRubyをPikで管理できるようにする。
PikはRubyGemsインストーラーを使ってインストールすることができるのだが、特定のバージョンのRubyに依存する形でインストールしたくなかったので、今回はインストーラーの方を使った。

pik - Download Packages

上記のページから最新版であるpik-0.3.0.pre.msiをダウンロードし、インストーラーを実行するとウィザードが起動するのでNextをクリックする。

f:id:heroween:20140701102104p:plain

ライセンスに同意し、

f:id:heroween:20140701102235p:plain

インストールフォルダ(デフォルトはC:¥pik)の設定とシステムパスへPikのバリナリパスを通すかをチェックし、

f:id:heroween:20140701102359p:plain

Installをクリック。

f:id:heroween:20140701102548p:plain

f:id:heroween:20140701102556p:plain

インストールが完了したらFinishをクリックでウィザードを閉じる。

f:id:heroween:20140701102627p:plain

コマンドプロンプトを開き以下のコマンドでバージョンを確認。

C:¥Users¥heroween>pik -v
pik 0.3.0.pre on Microsoft Windows [Version 6.1.7601]
by Gordon Thiesfeld (gthiesfeld@gmail.com)

creating C:¥Users¥heroween¥.pik

問題無くインストールできた。
コマンドの初回実行時には、ユーザーのホームフォルダ配下に.pikフォルダが生成される。
このフォルダは初め空の状態だが、Pikを使用することで設定ファイルが格納されることになる。

注意すべき点として、Pikをインストーラーでインストールした場合は、ユーザー環境変数HOME(もしくはPIK_HOME)に%USERPROFILE%を設定しておく必要がある。
このユーザー環境変数を設定しておかないと、Pikを使ってRubyのバージョンを切り替えることができないので、コマンドプロンプトなり、コントロールパネルのシステムから設定しておこう。

Pikで複数バージョンのRubyを使う

ユーザー環境変数の設定まで終えたら、Pikで複数バージョンのRubyを使ってみよう。
まずは、インストール済みのRubyをPikに登録していく。
pik listを実行してPikにRubyが未登録であることを確認。

C:¥Users¥heroween>pik list

pik addに引数としてRubyのバイナリパスを渡して、各バージョンを登録していく。

C:¥Users¥heroween>pik add C:¥Ruby200-x64¥bin
INFO: Adding:  [ruby-]2.0.0-p481
      Located at:  C:¥Ruby200-x64¥bin

C:¥Users¥heroween>pik add C:¥Ruby193¥bin
INFO: Adding:  [ruby-]1.9.3-p545
      Located at:  C:¥Ruby193¥bin

完了したらもう一度pik listを実行。

C:¥Users¥heroween>pik list
   ruby-1.9.3-p545
=> ruby-2.0.0-p481

C:¥Users¥heroween>ruby -v
ruby 2.0.0p481 (2014-05-08) [x64-mingw32]

Rubyが登録されていることが確認できる。
先頭に⇒が記されているRubyが現在有効になっているバージョンである。
ここで、.pikフォルダの中を確認してみると、以下のような内容のconfig.ymlというファイルが生成されている。

---
"[ruby-]1.9.3-p545":
  :path: !ruby/object:Pathname
    path: C:/Ruby193/bin
  :version: |
    ruby 1.9.3p545 (2014-02-24) [i386-mingw32]

"[ruby-]2.0.0-p481":
  :path: !ruby/object:Pathname
    path: C:/Ruby200-x64/bin
  :version: |
    ruby 2.0.0p481 (2014-05-08) [x64-mingw32]

--- {}

初回のpik addでこのconfig.ymlが生成され、登録したRubyの情報がここに定義されるという訳である。

次に、バージョンを切り替えてみよう。
登録されているRubyのバージョンを切り替えるにはpik useを使う。
pik useに渡す引数は、ruby-1.9.3-p545といったフルネームでも、1.9.3-p545といった省略形でもどちらでも良い。

C:¥Users¥heroween>pik use 1.9.3-p545

C:¥Users¥heroween>pik list
=> ruby-1.9.3-p545
   ruby-2.0.0-p481

C:¥Users¥heroween>ruby -v
ruby 1.9.3p545 (2014-02-24) [i386-mingw32]

これでバージョンが2.0.0-p481から1.9.3-p545へと切り替わった。

以上で、Windows上に複数バージョンのRuby環境が整い、Pikで切り替えながら使うことができるようになった。
さらに他のバージョンのRubyも使いたければ、これまでの手順通りにRubyをインストールしてPikに登録してやれば、同様に使い分けることができる。

Pikについて蛇足

最後にPikを使ってみての所感。
冒頭にも書いた通り、開発とメンテナンスが停滞してるためか、色々とコマンドの挙動が不安定だった。

例えば、

  • 登録したRubyのバージョンを削除するpik remove(rm)は、実行すると一見正常に動いているように見えるが実は機能していない。不要なRubyのバージョンを削除したければ、直接config.ymlをテキストエディタで編集した方が良い。
  • config.ymlを削除するpik implodeは、実行時にエラーを出力するけどconfig.ymlは削除されている。
  • Pikのバージョンアップをするpik update(up)は、0.3.0-preで実行すると0.2.8にダウングレードされ(安定版だからだろうが)、かつその時点で使っているRubyのgemにインストールされる。さらにpik list -rで表示される殆どのRubyのバージョンが消えてしまう。

等々。
他にも、pik install等のパッケージ周りのコマンドも、なんだかんだと前提条件が必要だったりで、単純に実行しただけではすんなりと動かないケースが多い。
興味があればGithubを参照しながらアレコレ試してみると良いが、きっと煩わしくなることが多いだろう。
インストール自体は楽だし、バージョンの切り替えだけに限定すれば特に問題は無いので、割り切って使うのが賢明かと思う。