酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

EC2 Amazon Linuxで`knife solo cook`を実行しようとするとrsyncで失敗する

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の場合もエラーを吐く

次にStrictHostKeyCheckingyesにしていた時にも発生するらしいです。

~/.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を実行すると正しく動くようになります。

関連記事