tk_ch’s blog

インフラエンジニアのブログ

Serverspecの実行環境をDockerコンテナにする(Serverspec2.42.2、RockyLinux8、SSHパスワード認証)

Serverspecを使う際に、以下のような環境面での悩みが出てくるケースがある。

  • 複数の構成管理サーバにServerspecをインストールするのが手間。
  • Serverspecの実行環境にはRubyが必要だが、Serverspecのために特定バージョンのRubyをインストールして環境が汚れるのが嫌。
  • 複数のバージョンのServerspecを同じサーバで実行させたい場合、共存させるのが面倒(というか共存できない?)。

→これらは、Serverspecの実行環境をDockerコンテナにすれば解決できる。
 という訳で、Serverspec実行用のDockerコンテナを用意してみる。

環境

  • DockerホストのOS:RockyLinux8.6
  • 管理対象サーバ(Serverspecの実行先)のOS:RockyLinux8.6
  • Docker:20.10.18
  • Serverspec:2.42.2

実施内容

使用するServerspecのバージョンを決める

ServerspecのGitリポジトリReleasesを見ると、2.42.2が現在(2023年2月)時点の最新版の模様。
また、Ruby3.1以上が必要と記載されている。
Serverspecのコンテナイメージをビルドする前に、実際に使用するコンテナイメージにRuby3.1以上とServerspec2.42.2がインストールできるか試してみる。

RockyLinux8.6のコンテナを起動。

# docker run --rm -it rockylinux:8.6 /bin/bash

Rubyをインストールしてみる。

インストールできるRubyのバージョンを確認する。
デフォルトでRuby 2.5がインストールされ、最新のバージョンとしては3.1を選択できることが分かる。
# dnf module list ruby
Last metadata expiration check: 0:01:00 ago on Fri Feb 17 04:15:04 2023.
Rocky Linux 8 - AppStream
Name          Stream           Profiles           Summary
ruby          2.5 [d]          common [d]         An interpreter of object-oriented scripting language
ruby          2.6              common [d]         An interpreter of object-oriented scripting language
ruby          2.7              common [d]         An interpreter of object-oriented scripting language
ruby          3.0              common [d]         An interpreter of object-oriented scripting language
ruby          3.1              common [d]         An interpreter of object-oriented scripting language

Ruby3.1を有効化する。
# dnf module -y enable ruby:3.1
# dnf module list ruby
Last metadata expiration check: 0:09:18 ago on Fri Feb 17 04:15:04 2023.
Rocky Linux 8 - AppStream
Name          Stream           Profiles           Summary
ruby          2.5 [d]          common [d]         An interpreter of object-oriented scripting language
ruby          2.6              common [d]         An interpreter of object-oriented scripting language
ruby          2.7              common [d]         An interpreter of object-oriented scripting language
ruby          3.0              common [d]         An interpreter of object-oriented scripting language
ruby          3.1 [e]          common [d]         An interpreter of object-oriented scripting language

Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled

Ruby3.1をインストールする
# dnf install -y ruby

バージョン確認
# ruby --version
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]

Serverspec2.42.2をインストールしてみる。

インストールできるServerspecのバージョンを確認する。最新バージョンが2.42.2であることが確認できる。
# gem list serverspec -rea

*** REMOTE GEMS ***

serverspec (2.42.2, 2.42.1, 2.42.0, 2.41.8, 2.41.7, 2.41.6, 2.41.5, 2.41.4, 2.41.3, 2.41.2, 2.41.1, 2.41.0, 2.40.0, 2.39.2, 2.39.1, 2.39.0, 2.38.1, 2.38.0, 2.37.2, 2.37.1, 2.37.0, 2.36.1, 2.36.0, 2.35.0, 2.34.0, 2.33.0, 2.32.0, 2.31.1, 2.31.0, 2.30.1, 2.30.0, 2.29.2, 2.29.1, 2.29.0, 2.28.0, 2.27.0, 2.26.0, 2.25.0, 2.24.3, 2.24.2, 2.24.1, 2.24.0, 2.23.1, 2.23.0, 2.22.0, 2.21.1, 2.21.0, 2.20.0, 2.19.0, 2.18.0, 2.17.1, 2.17.0, 2.16.0, 2.15.0, 2.14.1, 2.14.0, 2.13.0, 2.12.0, 2.11.0, 2.10.2, 2.10.1, 2.10.0, 2.9.1, 2.9.0, 2.8.2, 2.8.1, 2.8.0, 2.7.2, 2.7.1, 2.7.0, 2.6.0, 2.5.0, 2.4.0, 2.3.1, 2.3.0, 2.2.0, 2.1.0, 2.0.1, 2.0.0, 1.16.0, 1.15.0, 1.14.0, 1.13.0, 1.12.0, 1.11.0, 1.10.0, 1.9.1, 1.9.0, 1.8.0, 1.7.1, 1.7.0, 1.6.0, 1.5.0, 1.4.2, 1.4.1, 1.4.0, 1.3.0, 1.2.0, 1.1.0, 1.0.0, 0.16.0, 0.15.5, 0.15.4, 0.15.3, 0.15.2, 0.15.1, 0.15.0, 0.14.4, 0.14.3, 0.14.2, 0.14.1, 0.14.0, 0.13.7, 0.13.6, 0.13.5, 0.13.4, 0.13.3, 0.13.2, 0.13.1, 0.13.0, 0.12.0, 0.11.5, 0.11.4, 0.11.3, 0.11.2, 0.11.1, 0.11.0, 0.10.13, 0.10.12, 0.10.11, 0.10.10, 0.10.9, 0.10.8, 0.10.7, 0.10.6, 0.10.5, 0.10.4, 0.10.3, 0.10.2, 0.10.1, 0.10.0, 0.9.8, 0.9.7, 0.9.6, 0.9.5, 0.9.4, 0.9.3, 0.9.2, 0.9.1, 0.9.0, 0.8.1, 0.8.0, 0.7.13, 0.7.12, 0.7.11, 0.7.10, 0.7.9, 0.7.8, 0.7.7, 0.7.6, 0.7.5, 0.7.4, 0.7.3, 0.7.2, 0.7.1, 0.7.0, 0.6.30, 0.6.29, 0.6.28, 0.6.27, 0.6.26, 0.6.25, 0.6.24, 0.6.23, 0.6.22, 0.6.21, 0.6.20, 0.6.19, 0.6.18, 0.6.17, 0.6.16, 0.6.15, 0.6.13, 0.6.12, 0.6.11, 0.6.10, 0.6.9, 0.6.8, 0.6.7, 0.6.6, 0.6.5, 0.6.4, 0.6.3, 0.6.2, 0.6.1, 0.6.0, 0.5.8, 0.5.7, 0.5.6, 0.5.5, 0.5.4, 0.5.3, 0.5.2, 0.5.1, 0.5.0, 0.4.14, 0.4.13, 0.4.12, 0.4.11, 0.4.10, 0.4.9, 0.4.8, 0.4.7, 0.4.6, 0.4.5, 0.4.4, 0.4.3, 0.4.2, 0.4.1, 0.4.0, 0.3.2, 0.3.1, 0.3.0, 0.2.28, 0.2.27, 0.2.26, 0.2.25, 0.2.24, 0.2.23, 0.2.22, 0.2.21, 0.2.20, 0.2.19, 0.2.18, 0.2.17, 0.2.16, 0.2.15, 0.2.14, 0.2.13, 0.2.12, 0.2.11, 0.2.10, 0.2.9, 0.2.8, 0.2.7, 0.2.6, 0.2.5, 0.2.4, 0.2.3, 0.2.2, 0.2.1, 0.1.7, 0.1.6, 0.1.5, 0.1.4, 0.1.3, 0.1.2, 0.1.1, 0.1.0, 0.0.19, 0.0.18, 0.0.17, 0.0.16, 0.0.15, 0.0.14, 0.0.13, 0.0.12, 0.0.11, 0.0.10, 0.0.9, 0.0.8, 0.0.7, 0.0.6, 0.0.5, 0.0.4, 0.0.3, 0.0.2, 0.0.1)

Serverspec2.42.2をインストール
# gem install serverspec -v 2.42.2

バージョン確認
# gem list serverspec

*** LOCAL GEMS ***

serverspec (2.42.2)

Serverspec実行用のDockerイメージをビルドする

Dockerfileを作成する。 Dockerfile_serverspec

# ベースイメージ
FROM rockylinux:8.6

# 変数定義
ARG SERVERSPEC_VERSION

# 必要なパッケージのインストール
RUN dnf module -y enable ruby:3.1 && \
    dnf -y install ruby \
    openssh-clients && \
    dnf clean all && \
    rm -rf /var/cache/dnf/*

# Serverspecのインストール
RUN gem install serverspec -v $SERVERSPEC_VERSION
RUN gem install rake
RUN gem install highline

# ロケールを日本語に設定
RUN dnf -y install glibc-locale-source glibc-langpack-en && \
    dnf clean all && \
    rm -rf /var/cache/dnf/*
RUN localedef -f UTF-8 -i ja_JP ja_JP.utf8
RUN echo 'LANG="ja_JP.UTF-8"' >  /etc/locale.conf

# タイムゾーンをJSTに設定
RUN echo 'ZONE="Asia/Tokyo"' > /etc/sysconfig/clock
RUN rm -f /etc/localtime
RUN ln -fs /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

# コンテナログイン時のカレントディレクトリを設定
WORKDIR /work/serverspec

→Dockerfileの解説

  • ベースイメージがRockyLinux8.6なのは使い慣れているからで、特に意味は無い。他のOSで作っても良い。
  • Serverspecのインストール方法は、Serverspec公式ドキュメントを参照。
  • openssh-clientsは、Serverspecから管理対象サーバへ接続するために必要なのでインストールした。
  • インストールするServerspecのバージョンを制御できるよう、ARGにて変数SERVERSPEC_VERSIONを定義し、Serverspecインストール時に指定している。 変数に設定する値は、後ほど「docker build」実行時に--build-argオプションで指定する。
    詳細はDockerの公式ドキュメントのここを参照。
  • dnf実行時の書き方については、RedHatの「How to build tiny container images」を参考にした。
  • ロケールタイムゾーンの設定は必須ではないが、自前のコンテナイメージ作成時はいつもやっているので入れている。

Serverspecバージョンを2.42.2と指定し、コンテナイメージをビルドする。

インストールしたいServerspecのバージョンを環境変数に格納
# export SERVERSPEC_VERSION=2.42.2

Dockerイメージをビルド
# docker build --no-cache=true -f Dockerfile_serverspec -t serverspec:$SERVERSPEC_VERSION --build-arg SERVERSPEC_VERSION=$SERVERSPEC_VERSION .

ビルドされたイメージを確認
# docker images | grep serverspec | grep $SERVERSPEC_VERSION
serverspec            2.42.2    ca0aa9b20af6   3 seconds ago        297MB

ビルドされたイメージを起動し、想定通りのServerspecバージョンがインストールされたことを確認
# docker run --rm -it serverspec:$SERVERSPEC_VERSION gem list serverspec

*** LOCAL GEMS ***

serverspec (2.42.2)

ビルドしたイメージを使ってServerspecを実行する

先ほどビルドしたコンテナイメージを指定し、Serverspecを実行してみる。
まず、必要なファイルをserverspec-initコマンドで生成する。
注意点として、コンテナ内のファイルはコンテナ終了時に削除されてしまうため、Serverspec用のディレクトリはDockerホストをマウントする。
今回は、DockerfileでWORKDIRとして指定したコンテナ内のディレクトリ(/work/serverspec)に、Dockerホストのserverspec用ファイル置き場として作成した/root/serverspecをマウントする。

使用するServerspecのバージョンを指定
# export SERVERSPEC_VERSION=2.42.2

serverspec-initを実行して、Serverspecで使うファイルを生成する。
# docker run --rm -it -v /root/.ssh/:/root/.ssh/ -v /root/serverspec:/work/serverspec serverspec:$SERVERSPEC_VERSION serverspec-init

Select OS type:

  1) UN*X
  2) Windows

Select number: 1

Select a backend type:

  1) SSH
  2) Exec (local)

Select number: 1

Vagrant instance y/n: n
Input target host name: 192.168.10.123
 + spec/
 + spec/192.168.10.123/
 + spec/192.168.10.123/sample_spec.rb
/usr/local/share/gems/gems/serverspec-2.42.2/lib/serverspec/setup.rb:155: warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.
/usr/local/share/gems/gems/serverspec-2.42.2/lib/serverspec/setup.rb:155: warning: Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.
 + spec/spec_helper.rb
 + Rakefile

作成されたファイル、ディレクトリをコンテナ内から確認する
# docker run --rm -it -v /root/.ssh/:/root/.ssh/ -v /root/serverspec:/work/serverspec serverspec:$SERVERSPEC_VERSION ls -l
total 4
-rw-r--r-- 1 root root 685  2月 18 22:39 Rakefile
drwxr-xr-x 3 root root  48  2月 18 22:39 spec

Dockerホスト側からも参照できる。ディレクトリ構成は以下のようになっている。
# tree -a --charset=c /root/serverspec
/root/serverspec
|-- .rspec
|-- Rakefile
`-- spec
    |-- 192.168.10.123
    |   `-- sample_spec.rb
    `-- spec_helper.rb

2 directories, 4 files

→DockerホストのServerspec用ディレクトリにコンテナ内で作成したServerspec用ファイルが残っていることが確認できる。

ちなみに、作成された.rspecRakefile、spec_helper.rbの中身は以下の通り。 .rspec

--color
--format documentation

Rakefile

require 'rake'
require 'rspec/core/rake_task'

task :spec    => 'spec:all'
task :default => :spec

namespace :spec do
  targets = []
  Dir.glob('./spec/*').each do |dir|
    next unless File.directory?(dir)
    target = File.basename(dir)
    target = "_#{target}" if target == "default"
    targets << target
  end

  task :all     => targets
  task :default => :all

  targets.each do |target|
    original_target = target == "_default" ? target[1..-1] : target
    desc "Run serverspec tests to #{original_target}"
    RSpec::Core::RakeTask.new(target.to_sym) do |t|
      ENV['TARGET_HOST'] = original_target
      t.pattern = "spec/#{original_target}/*_spec.rb"
    end
  end
end

spec_helper.rb

require 'serverspec'
require 'net/ssh'

set :backend, :ssh

if ENV['ASK_SUDO_PASSWORD']
  begin
    require 'highline/import'
  rescue LoadError
    fail "highline is not available. Try installing it."
  end
  set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
else
  set :sudo_password, ENV['SUDO_PASSWORD']
end

host = ENV['TARGET_HOST']

options = Net::SSH::Config.for(host)

options[:user] ||= Etc.getlogin

set :host,        options[:host_name] || host
set :ssh_options, options

# Disable sudo
# set :disable_sudo, true


# Set environment variables
# set :env, :LANG => 'C', :LC_MESSAGES => 'C'

# Set PATH
# set :path, '/sbin:/usr/local/sbin:$PATH'

Serverspecでテストを実行する前に、テスト内容をパスできるものに変更する。
sshdサービスが動いていることを確認する内容にした。

sample_spec.rb

require 'spec_helper'

describe service('sshd'), :if => os[:family] == 'redhat' do
  it { should be_running }
end

serverspecを実行する。
今回対象としているサーバはSSHにパスワード認証が必要なので、途中でパスワードを入力する。

# export SERVERSPEC_VERSION=2.42.2
# docker run --rm -it -v /root/.ssh/:/root/.ssh/ -v /root/serverspec:/work/serverspec serverspec:$SERVERSPEC_VERSION  rake spec
/usr/bin/ruby -I/usr/local/share/gems/gems/rspec-support-3.12.0/lib:/usr/local/share/gems/gems/rspec-core-3.12.1/lib /usr/local/share/gems/gems/rspec-core-3.12.1/exe/rspec --pattern spec/192.168.10.123/\*_spec.rb
/usr/local/share/gems/gems/specinfra-2.85.0/lib/specinfra/backend/ssh.rb:82:in `create_ssh': Passing nil, or [nil] to Net::SSH.start is deprecated for keys: user
root@192.168.10.123's password:

Service "sshd"
  is expected to be running

Finished in 0.06606 seconds (files took 5.44 seconds to load)
1 example, 0 failures

→Serverspecが実行され、テストをパスした。

SSHの認証パスワードを自動入力するよう設定する

SSHする際にパスワード認証が必要なサーバに対しServerspecを実行する際の設定は、以下に記載がある。

→spec_helper.rbを修正して、環境変数「LOGIN_PASSWORD」にSSHパスワードを格納してServerspecを実行すればパスワードの手動入力が不要になる模様。

上記ドキュメント通りに、spec_helper.rbを修正する。 spec_helper.rb

require 'serverspec'
require 'net/ssh'
require 'highline/import' ##追加

set :backend, :ssh

if ENV['ASK_SUDO_PASSWORD']
  begin
    require 'highline/import'
  rescue LoadError
    fail "highline is not available. Try installing it."
  end
  set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
else
  set :sudo_password, ENV['SUDO_PASSWORD']
end

host = ENV['TARGET_HOST']

options = Net::SSH::Config.for(host)

options[:user] ||= Etc.getlogin

 ##ここから
if ENV['ASK_LOGIN_PASSWORD']
  options[:password] = ask("\nEnter login password: ") { |q| q.echo = false }
else
  options[:password] = ENV['LOGIN_PASSWORD']
end
 ##ここまで追加

set :host,        options[:host_name] || host
set :ssh_options, options

# Disable sudo
# set :disable_sudo, true


# Set environment variables
# set :env, :LANG => 'C', :LC_MESSAGES => 'C'

# Set PATH
# set :path, '/sbin:/usr/local/sbin:$PATH'

試してみる。

# export SERVERSPEC_VERSION=2.42.2
# export LOGIN_PASSWORD=SSHの認証パスワード

# docker run --rm -it -v /root/.ssh/:/root/.ssh/ -v /root/serverspec:/work/serverspec serverspec:$SERVERSPEC_VERSION rake spec LOGIN_PASSWORD=$LOGIN_PASSWORD
/usr/bin/ruby -I/usr/local/share/gems/gems/rspec-support-3.12.0/lib:/usr/local/share/gems/gems/rspec-core-3.12.1/lib /usr/local/share/gems/gems/rspec-core-3.12.1/exe/rspec --pattern spec/192.168.10.123/\*_spec.rb
/usr/local/share/gems/gems/specinfra-2.85.0/lib/specinfra/backend/ssh.rb:82:in `create_ssh': Passing nil, or [nil] to Net::SSH.start is deprecated for keys: user

Service "sshd"
  is expected to be running

Finished in 0.06473 seconds (files took 2.39 seconds to load)
1 example, 0 failures

SSHの認証パスワードの入力を求められずにServerspecを実行できた。

※ちなみに、以下のようにパスワードをspec_helper.rbに書いても同様の動作になる。
環境変数に設定するのが面倒ならこの方法もありだが、パスワードをファイルに平文で記載するのはセキュリティ的には良くないので注意すること。 spec_helper.rb

require 'serverspec'
require 'net/ssh'
require 'highline/import'

LOGIN_PASSWORD="SSHの認証パスワード"  ## 変更点

set :backend, :ssh

if ENV['ASK_SUDO_PASSWORD']
  begin
    require 'highline/import'
  rescue LoadError
    fail "highline is not available. Try installing it."
  end
  set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
else
  set :sudo_password, ENV['SUDO_PASSWORD']
end

host = ENV['TARGET_HOST']

options = Net::SSH::Config.for(host)

options[:user] ||= Etc.getlogin

if ENV['ASK_LOGIN_PASSWORD']
  options[:password] = ask("\nEnter login password: ") { |q| q.echo = false }
else
  options[:password] = LOGIN_PASSWORD ## 変更点
end

set :host,        options[:host_name] || host
set :ssh_options, options

# Disable sudo
# set :disable_sudo, true


# Set environment variables
# set :env, :LANG => 'C', :LC_MESSAGES => 'C'

# Set PATH
# set :path, '/sbin:/usr/local/sbin:$PATH'

aliasの設定

Serverspec実行時に毎回「docker run ~」と入力するのは手間なので、エイリアスを使って簡単に実行できるようにする。
Dockerホストの「~/.bashrc」に以下の2行を追記する。

export SERVERSPEC_VERSION=2.42.2
alias rake='docker run --rm -it -v /root/.ssh/:/root/.ssh/ -v /root/serverspec:/work/serverspec serverspec:$SERVERSPEC_VERSION rake'

試してみる。

変更を反映
# source ~/.bashrc

エイリアスを指定してServerspecを実行する。
# export LOGIN_PASSWORD=SSHの認証パスワード
# rake spec LOGIN_PASSWORD=$LOGIN_PASSWORD
/usr/bin/ruby -I/usr/local/share/gems/gems/rspec-support-3.12.0/lib:/usr/local/share/gems/gems/rspec-core-3.12.1/lib /usr/local/share/gems/gems/rspec-core-3.12.1/exe/rspec --pattern spec/192.168.10.123/\*_spec.rb
/usr/local/share/gems/gems/specinfra-2.85.0/lib/specinfra/backend/ssh.rb:82:in `create_ssh': Passing nil, or [nil] to Net::SSH.start is deprecated for keys: user
root@192.168.10.123's password:

Service "sshd"
  is expected to be running

Finished in 0.06606 seconds (files took 5.44 seconds to load)
1 example, 0 failures

→「rake spec」と実行するだけでDockerコンテナを使ってServerspecを実行できるようになった。
別バージョンのServerspecも実行したい場合は、該当バージョンのServerspecがインストールされたDockerイメージを用意し、環境変数「SERVERSPEC_VERSION」の値を変えることで実行するバージョンを制御できる。

まとめ

  • Serverspecの実行環境をDockerコンテナにすることができた。Dockerさえインストールされていれば、簡単にServerspecの実行環境を整備できる。

参考文献