EC2のAmazon Linuxでknife solo cook
を実行しようとすると、rsyncで失敗してしまう現象が発生していました。
何故エラーが発生しているのか理解できなかったので解決まで時間がかかってしまいました。
実行環境
- クライアント側
- Mac OS X El Capitan(10.11.3)
- rsync 3.1.2
- Chef: 12.7.2
- サーバー側
- Amazon Linux AMI
現象について
EC2のAmazon Linuxのインスタンスを作成して、rootで直接ログイン可能な状態にした上で、作成したクックブックを実行しようとしました。
$ knife solo prepare ec2root $ knife solo cook ec2root
下記のようなエラーが出て止まってしまいます。
Uploading the kitchen... rsync: connection unexpectedly closed (0 bytes received so far) [sender] rsync error: unexplained error (code 255) at /BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-47/rsync/io.c(453) [sender=2.6.9] ERROR: RuntimeError: Failed to launch command ["rsync", "-rL", "--rsh=ssh root@ec2root -o ControlMaster=auto -o ControlPath=/Users/ch3cooh/.chef/knife-solo-sockets/%h -o ControlPersist=3600", "--delete-after", "-zt", "--exclude=revision-deploys", "--exclude=.git", "--exclude=.hg", "--exclude=.svn", "--exclude=.bzr", "/usr/local/lib/ruby/gems/2.2.0/gems/knife-solo-0.5.1/lib/knife-solo/resources/patch_cookbooks/", ":~/chef-solo/cookbooks-1"]
knife solo prepare
の方はきちんと動作していたのでSSH接続には問題がないと判断しました。表示されている通りrsyncがなんらかの理由でエラーを吐いていそうです。
ポートが閉じていないかSSH経由でrsyncが実行してみる
(0 bytes received so far)と表示されていることから、ポートが閉じていてデータが全く転送できていないのでは?とアタリをつけて、rsyncが失敗する理由を調べてみました。
ファイルが全く転送できていない状況を考えるとありえそうです。
ただ、EC2でインスタンスを作成すると22番ポートは最初から解放されていて、特に設定を変えていないので問題ないように思えます。rsyncがSSH経由で通るかなと思い、念のためテストしてみました。
↓のコマンドの結果は問題なくファイルを転送できました。
rsync -avz -e ssh ~/piyo/ ec2root:/home/ec2-user/
22番ポートは解放されていてSSH経由で接続できることも確認できました。これは原因ではなさそうです。
StrictHostKeyCheckingがyes
の場合もエラーを吐く
次にStrictHostKeyCheckingをyes
にしていた時にも発生するらしいです。
~/.ssh/config
は下記のように設定していましたのでこれでもなさそうです。
Host ec2root HostName ec2-xxxxxxxx.ap-northeast-1.compute.amazonaws.com User root UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile "/Users/user/.ssh/ec2.pem" IdentitiesOnly yes LogLevel FATAL Host *.amazonaws.com StrictHostKeyChecking no
念のためHost *.amazonaws.com
も追加してみましたが依然エラーが発生したままでした。
rsyncでホスト名の前にユーザー名を入れないとエラーが発生する
他にも下記のように、バージョンによってはホスト名の前にユーザー名を入れないとエラーが発生するようです。
ちょっと問題を解決できなくて困ってます。
rsyncを更新する
……んん?バージョン?と思いrsyncのバージョンを確認してみました。Mac OS Xのrsyncはv2.x系のようです。
$ rsync --version rsync version 2.6.9 protocol version 29
サーバーには接続できていないのであまり関係ありませんが、EC2上のAmazon Linuxのrsyncはv3.x系でした。
# rsync --version rsync version 3.0.6 protocol version 30
これが原因かな?と思い、rsyncをアップデートしました。
エラーが発生している状況は変わらずじまいでした。
結局 knife-soloのコードを直接読むことに……
にっちもさっちも行かないので遠回りになってしまいましたが、knife-soloがどういうコードになっているのか確認します。knife-soloはknife solo cook ec2root -VV
でデバッグ出力することができます。
/usr/local/lib/ruby/gems/2.2.0/gems/knife-solo-0.5.1/lib/knife-solo/tools.rb
をみるとこの処理で落ちているのがわかりました。
def system!(*command) raise "Failed to launch command #{command}" unless system(*command) end
system()関数は単に処理が成功したか失敗したかを判定するだけの関数なので実際にはcommand
が失敗しているコマンドということになります。puts関数で取り出すと以下の通りになりました。
rsync -rL --rsh=ssh root@ec2root -o ControlMaster=auto -o ControlPath=/Users/ch3cooh/.chef/knife-solo-sockets/%h -o ControlPersist=3600 --delete-after -zt --exclude=revision-deploys --exclude=.git --exclude=.hg --exclude=.svn --exclude=.bzr /usr/local/lib/ruby/gems/2.2.0/gems/knife-solo-0.5.1/lib/knife-solo/resources/patch_cookbooks/ :~/chef-solo/cookbooks-1
このまま実行すると下記のようなエラーが発生します。
ssh: connect to host port 22: Connection refused rsync: connection unexpectedly closed (0 bytes received so far) [sender] rsync error: error in rsync protocol data stream (code 12) at io.c(226) [sender=3.1.2]
なんとなく原因がわかってきました。rsync自体が悪いのではなくてknife-soloが実行するコマンドを生成するのですが余計な文字列が含まれるのでSSH接続に失敗して、結果的にファイル転送ができていないのではないかと仮説を立てて、パラメータを削っていきました。
以下のようにパラメータを指定すればエラーが発生しなくなるのがわかりました。
$ rsync -rL --rsh="ssh root@ec2root" --delete-after -zt --exclude=revision-deploys --exclude=.git --exclude=.hg --exclude=.svn --exclude=.bzr /usr/local/lib/ruby/gems/2.2.0/gems/knife-solo-0.5.1/lib/knife-solo/resources/patch_cookbooks/ :~/chef-solo/cookbooks-1
結論としては、-o ControlMaster=auto -o ControlPath=/Users/ch3cooh/.chef/knife-solo-sockets/%h -o ControlPersist=3600
の部分を削除すれば、rsyncで失敗せずに動くことがわかりました。
ソースコードを読んでいきます。実際にローカル(クライアント)側にあるクックブックをリモート(サーバー)側へrsyncで転送する処理がknife-solo-0.5.1/lib/chef/knife/solo_cook.rb
にありました。
def rsync(source_path, target_path, extra_opts = ['--delete-after', '-zt']) if config[:ssh_gateway] ssh_command = "ssh -TA #{config[:ssh_gateway]} ssh -T -o StrictHostKeyChecking=no #{ssh_args}" else ssh_command = "ssh #{ssh_args}" end cmd = ['rsync', '-rL', rsync_debug, rsync_permissions, %Q{--rsh=#{ssh_command}}] cmd += extra_opts cmd += rsync_excludes.map { |ignore| "--exclude=#{ignore}" } cmd += [ adjust_rsync_path_on_client(source_path), ':' + adjust_rsync_path_on_node(target_path) ] cmd = cmd.compact Chef::Log.debug cmd.inspect system!(*cmd) end
引き続き、ssh_args
の出所をgrepで追いかけていきます。knife-solo-0.5.1/ssh_command.rb
の処理がありました。
def ssh_args args = [] args << [user, host].compact.join('@') args << "-F #{config[:ssh_config]}" if config[:ssh_config] args << "-i #{config[:identity_file]}" if config[:identity_file] args << "-o ForwardAgent=yes" if config[:forward_agent] args << "-p #{config[:ssh_port]}" if config[:ssh_port] args << "-o UserKnownHostsFile=#{connection_options[:user_known_hosts_file]}" if config[:host_key_verify] == false args << "-o StrictHostKeyChecking=no" if config[:host_key_verify] == false args << "-o ControlMaster=auto -o ControlPath=#{ssh_control_path} -o ControlPersist=3600" unless config[:ssh_control_master] == "no" args.join(' ') end
config[:ssh_control_master]
がyesの場合にSSH接続のために文字列が追加されるようです。
解決編
Chefリポジトリ配下にある.chefディレクトリにknife.rbが格納されています。Chefリポジトリの設定ファイル的なものです。
/chef-test/.chef/knife.rb
を開いて、先ほどわかった設定を追加します。
cookbook_path ["cookbooks", "site-cookbooks"] node_path "nodes" role_path "roles" environment_path "environments" data_bag_path "data_bags" #encrypted_data_bag_secret "data_bag_key" knife[:berkshelf_path] = "cookbooks" Chef::Config[:ssl_verify_mode] = :verify_peer if defined? ::Chef knife[:ssh_control_master] = "no" # <- 追加する
設定ファイルを保存して、再度ターミナルでknife solo cook ec2root
を実行すると正しく動くようになります。