Dockerコンテナー内でansible_specを実行したらエラー


Dockerコンテナー内でansible_specを実行したらエラー

はじめに

Docker使ってAnsible開発しつつテストコード書いてたら、
以下エラーが出てハマったので備忘録的に残しておきます。

$ bundle exec rake all
rake aborted!
NoMethodError: private method `select' called for nil:NilClass
/workspace/rakefile:6:in `<top (required)>'
/usr/local/rbenv/versions/2.6.1/bin/bundle:23:in `load'
/usr/local/rbenv/versions/2.6.1/bin/bundle:23:in `<main>'
(See full trace by running task with --trace)

環境

前提としてWindows 10 + Docker for Windowsを使い、コンテナー内でAnsibleを開発。
Ansibleの定義ファイルはWindowsディスク上において、Volume mountでコンテナーにマウント。

Ansibleのディレクトリ構成

$ tree
.
|____.ansiblespec
|____.rspec
|____Gemfile
|____Gemfile.lock
|____group_vars
| |____webserver.yml
|____hosts
|____Rakefile
|____README.md
|____roles
| |____configure_nginx
| | |____spec
| | | |____configure_nginx_spec.rb
| | |____tasks
| | | |____main.yml
| | |____templates
| | | |____nginx.conf.j2
| |____install_nginx
| | |____spec
| | | |____install_nginx_spec.rb
| | |____tasks
| | | |____main.yml
|____site.yml
|____spec
| |____spec_helper.rb

原因

インベントリーファイルである hosts に実行権限があったのでエラーとなっていた。

WindowsディレクトリをVolume mountすると、権限が 777 になり、
ansible_spec動的インベントリーと勘違いしてエラーになる。

$ # 今回のインベントリーファイルは静的インベントリーです。
$ ls -l hosts
-rwxrwxrwx 1 root root 17 Jun 22 10:37 hosts
$ cat hosts
[webserver]
127.0.0.1

対処

Ansibleのファイルを全体的にコンテナー内にコピーし、 hosts の権限を 644 にする。
全部コピーだと大変なのである程度はシンボリックにした。

mkdir -p /tmp/ansible
cp -p /workspace/hosts /tmp/ansible
chmod 644 /tmp/ansible//hosts
ln -s /workspace/site.yml /tmp/ansible/site.yml
ln -s /workspace/Gemfile /tmp/ansible/Gemfile
ln -s /workspace/Gemfile.lock /tmp/ansible/Gemfile.lock
ln -s /workspace/Rakefile /tmp/ansible/Rakefile
ln -s /workspace/group_vars /tmp/ansible/group_vars
ln -s /workspace/roles /tmp/ansible/roles
ln -s /workspace/spec /tmp/ansible/spec
ln -s /workspace/.ansiblespec /tmp/ansible/.ansiblespec
ln -s /workspace/.rspec /tmp/ansible/.rspec
cd /tmp/ansible

原因の詳細

スタックトレースを覗いてみる。

$ bundle exec rake all --trace 2>&1 | head
rake aborted!
NoMethodError: private method `select' called for nil:NilClass
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/ansible_spec-0.3.1/lib/ansible_spec/load_ansible.rb:133:in `get_dynamic_inventory'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/ansible_spec-0.3.1/lib/ansible_spec/load_ansible.rb:24:in `load_targets'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/ansible_spec-0.3.1/lib/ansible_spec/load_ansible.rb:425:in `get_properties'
/workspace/rakefile:6:in `<top (required)>'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/rake-13.0.1/lib/rake/rake_module.rb:29:in `load'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/rake-13.0.1/lib/rake/rake_module.rb:29:in `load_rakefile'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/rake-13.0.1/lib/rake/application.rb:703:in `raw_load_rakefile'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/rake-13.0.1/lib/rake/application.rb:104:in `block in load_rakefile'

上から順番に見ていったのですが、今回関係あったのはこの部分。

/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/ansible_spec-0.3.1/lib/ansible_spec/load_ansible.rb:24:in `load_targets'

該当のソースを覗いてみると23行目でinventoryファイルに実行権限があるかどうかチェックしている。

$ cat -n /usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/ansible_spec-0.3.1/lib/ansible_spec/load_ansible.rb | head -n 30
     1  # -*- coding: utf-8 -*-
     2  require 'hostlist_expression'
     3  require 'oj'
     4  require 'open3'
     5  require 'yaml'
     6  require 'inifile'
     7  require 'ansible_spec/vendor/hash'
     8  if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1')
     9    require 'ansible/vault'
    10  end
    11
    12  module AnsibleSpec
    13    # param: inventory file of Ansible
    14    # param: return_type 'groups' or 'groups_parent_child_relationships'
    15    # return: Hash {"group" => ["192.168.0.1","192.168.0.2"]}
    16    # return: Hash {"group" => [{"name" => "192.168.0.1","uri" => "192.168.0.1", "port" => 22},...]}
    17    # return: Hash {"pg" => ["server", "databases"]}
    18    def self.load_targets(file, return_type = 'groups')
    19      if not ['groups', 'groups_parent_child_relationships'].include?(return_type)
    20        raise ArgumentError, "Variable return_type must be value 'groups' or 'groups_parent_child_relationships'"
    21      end
    22
    23      if File.executable?(file)
    24        return get_dynamic_inventory(file)
    25      end
    26      f = File.open(file).read
    27      groups = Hash.new
    28      group = ''
    29      hosts = Hash.new
    30      hosts.default = Hash.new

これは、動的インベントリーを判定するために行っており、
今回のhostsファイルは静的インベントリーなので実行権限を外してあげればいい。

余談

今だったらWSL2上で開発すればいいのですが、仕事で使うWindows端末がまだWSL2に対応したバージョンでなかったのでこんなことしました。
調べてもあんまり出てこなかったので同じような人が助かれば幸いです。
rubyとか全然触ってないのでスキル持ってればハマることはなかったかも。。。