うさぎ好きエンジニアの備忘録

うさぎたちに日々癒されているエンジニアが業務で直面したもの & 個人的な学習メモを残していきます。

Chef InSpecに入門してみる。

いつもはChefのaudit-modeを使ってインフラのテストをしていますが、Chef 15になってaudit-modeが廃止されました...

なのでChef InSpecに入門してこのやるせない気持ちをどうにかしようと思います。

概要

  • Chefのバージョンが15以上になるとaudit-modeが利用できなくなる。
  • いつもはaudit-modeでの動作検証をリリース時に実施しているため、InSpecで代用できないか試してみる。

InSpecとは?

インフラのテストなどを実施するための機能を提供しているOSSです。

www.inspec.io

Chef InSpec is a free and open-source framework for testing and auditing your applications and infrastructure. Chef InSpec works by comparing the actual state of your system with the desired state that you express in easy-to-read and easy-to-write Chef InSpec code. Chef InSpec detects violations and displays findings in the form of a report, but puts you in control of remediation.

簡単なテストを実装してみる

InSpecの実行環境整備

以下のリンクからダウンロードして、適宜インストールすればOKです。

downloads.chef.io

ちなみにgem installでも入ります。

$ sudo gem install inspec

インストールが正常に完了したら適当にコマンド叩いて、パスなどに問題がなく、正常に動くことを確認。

$ inspec -v
4.18.85

Skeltonの作成

以下のコマンドを実行するとinspecという名前のSkeletonをカレントディレクトリに作成できます。

$ inspec init profile inspec

 ─────────────────────────── InSpec Code Generator ───────────────────────────

Creating new profile at /Users/kazono/Workspace/chef/chef-repo/inspec
 • Creating file README.md
 • Creating directory controls
 • Creating file controls/example.rb
 • Creating file inspec.yml

ディレクトリ構成は↓のような感じ。

$ tree inspec
inspec
├── README.md
├── controls
│   └── example.rb
└── inspec.yml

1 directory, 3 files

テストの実行

サンプルとして生成されたテストを試しに実行してみます。テスト実行時はinspec exec <PATH/TO/TEST_FILE>をすればOKです。

$ inspec exec inspec/controls/example.rb

Profile: tests from inspec/controls/example.rb (tests from inspec.controls.example.rb)
Version: (not specified)
Target:  local://

  ✔  tmp-1.0: Create /tmp directory
     ✔  File /tmp is expected to be directory

  File /tmp
     ✔  is expected to be directory

Profile Summary: 1 successful control, 0 control failures, 0 controls skipped
Test Summary: 2 successful, 0 failures, 0 skipped

テスト用のスクリプトが複数あり、全てを同時に実行したい場合には、inspec exec .を実行すればOKです。

$ cd inspec
$ inspec exec .

Profile: InSpec Profile (inspec)
Version: 0.1.0
Target:  local://

  ✔  tmp-1.0: Create /tmp directory
     ✔  File /tmp is expected to be directory

  File /tmp
     ✔  is expected to be directory

Profile Summary: 1 successful control, 0 control failures, 0 controls skipped
Test Summary: 2 successful, 0 failures, 0 skipped

サーバを指定したテストの実施

指定したサーバに対してテストを実施したい場合は、-tオプションを指定すればOKです。

今回テスト用に作成したスクリプトは以下(単純にKafkaのポートLISTENを確認するだけのテスト)。

control 'kafka' do
  impact 1.0
  title 'Checks for Kafka Broker'

  describe port(9092) do
    it { should be_listening }
  end
end

これをkafka001.ponteru.co.jpに対してテストしてみるとこんな感じ。

$ inspec exec controls/kafka.rb -t ssh://$USER@kafka001.ponteru.co.jp

Profile: tests from controls/kafka.rb (tests from controls.kafka.rb)
Version: (not specified)
Target:  ssh://kazono@kafka001.ponteru.co.jp:22

  ✔  kafka: Checks for Kafka Broker
     ✔  Port 9092 is expected to be listening


Profile Summary: 1 successful control, 0 control failures, 0 controls skipped
Test Summary: 1 successful, 0 failures, 0 skipped

テストコードの書き方

Skeletonを作成したときに作成されるexample.rb自体が良い例なのでこれを読めば基本的には問題なく理解できるかと思われます。

# copyright: 2018, The Authors

title "sample section"

# you can also use plain tests
describe file("/tmp") do
  it { should be_directory }
end

# you add controls here
control "tmp-1.0" do                        # A unique ID for this control
  impact 0.7                                # The criticality, if this control fails.
  title "Create /tmp directory"             # A human-readable title
  desc "An optional description..."
  describe file("/tmp") do                  # The actual test
    it { should be_directory }
  end
end

titleでテストブロックやテスト自体にタイトルをつけることができ、impactでテストの実行に失敗したときの深刻度を定義できます。 軽く調べた感じ、impactについてはこんな感じで指定しておけば良さそうです。

  • impact が 0 ~ 0.4 ... low
  • impact が 04 ~ 0.7 ... mid
  • impact が 0.7 ~ 1 ... high

テスト自体は↓のようなブロックで記述します。この際、使用可能なリソースについては、以下のドキュメントにまとめられているので一読しておくと良いかも。

describe file("/tmp") do  
  it { should be_directory }  
end  

www.inspec.io

ちなみにdescribe のブロックを1ファイルに複数書くこともできるし、controlブロックの中に複数のdescribeブロックを作成してテストを実施することもできます。

commandリソースを使ってsudoを実行したい

以下のようにcommandリソース内でsudoを使うと、sudo: no tty present and no askpass program specifiedと怒られてしまいます。

そのため、commandリソース内でsudoを実行したい場合は以下のようにして--sudo-passwordオプションにsudoパスワードを渡しましょう。

$ read -s password
# sudoパスワードを入力

$ inspec exec controls/kafka.rb -t ssh://$USER@kafka001.ponteru.co.jp --sudo --sudo-password=${password}

テスト失敗時にリトライ処理をしたい

ローカルホストであれば、以下のようにテストを記述することで失敗時にリトライさせることができます。

control 'kafka' do
  impact 1.0
  title 'Checks for Kafka Broker'

  describe port(9092) do
    before do
      60.times do
        unless port(9092).listening?
          puts "Port 9092 isn't ready, retrying.."
          sleep 10
        end
      end
    end
    it { should be_listening }
  end
end

ただ、これをリモートホストに対して流すと、延々とPort 9092 isn't ready, retrying..を表示し続けてしまいました。

Port 9092 isn't ready, retrying..
Port 9092 isn't ready, retrying..
Port 9092 isn't ready, retrying..
Port 9092 isn't ready, retrying..
# 本当はここらへんでKafka Brokerを再起動しているので、LISTENになっているはず...
Port 9092 isn't ready, retrying..
Port 9092 isn't ready, retrying..
Port 9092 isn't ready, retrying..
Port 9092 isn't ready, retrying..
Port 9092 isn't ready, retrying..
Port 9092 isn't ready, retrying..
...

調べてみると、どうやらリモートホストの状態は最初の一回しか確認せず、以降はその状態を保持してしまうため、ポートが正常にLISTENになってもループを抜けられない模様。

リモートホストのテストで、リトライ処理を正常に行いたい場合は--no-backend-cachを使えば、リモートホストの状態をキャッシュしなくなるので意図した挙動になるようです。

$ inspec exec controls/kafka.rb -t ssh://$USER@kafka001.ponteru.co.jp --no-backend-cach