Serverspec から Goss に移行してサーバーのテストが 60 秒から 3 秒に!

TypetalkのSREの二橋です。今回は、Gossの紹介をしたいと思います。

TypetalkではサーバーのテストにServerspecを利用していました。機能的には申し分なかったのですが、サーバーの台数/環境/テストの項目が増えるにつれて、ツールの実行速度やメンテナンス性に改善が必要になりました。Gossに乗り換えることで問題が解消されました。

サーバーのテストとは?

サーバーのテストとは、サーバーの状態が意図したものになっているか検証するものです。例えば、以下のような項目を検証します。

  • 意図したパッケージのバージョンがインストールされているか?
  • 意図したサーバーと通信できるか?
  • 意図したポートが待ち受けているか?
  • 意図したユーザー/グループが存在するか?
  • 意図したプロセスが動作しているか?
  • 意図したファイルに指定した記述が存在するか?

Serverspecで困っていたこと

①テスト実行時間が長い

テスト対象のインスタンス数やテストの項目数の増加に伴い、テスト実行時間が長くなりストレスに感じてました。並行実行するなど工夫はしていたのですが、それでも満足のできる実行時間は出せていませんでした。

②テスト項目がぱっと見たときに分かりづらい

Serverspecでは、specファイルというものにテスト項目の定義をします。
制御文や変数などのプログラム要素が使えます。便利ではあるのですが、ぱっと見た時に冗長な部分がありテスト項目が分かりづらく感じていました。

③Rakefileのコーディングが面倒

Rakefileは実行するタスクの定義をするファイルで、投入されたコマンドと実行されるテスト対象の関連付けなどを行います。Typetalkでは、下記の要素を選択できるように、Rakefileをカスタマイズしていました。

  • ターゲットの環境(本番環境/ステージング環境)
  • 対象ホスト(指定した単体/全体)
  • 実行方法(単一/並列)

ですが、本来はテスト項目であるspecの作成の方に時間を割きたいところです。

Gossとは?

GossはインフラのテストのためのGo言語製のOSSです。機能的にServerspecの代わりになるものです。ライセンスはApache License 2.0となってます。

Gossの特長

①テストの実行が爆速

テストの実行速度がとても早いです。理由としては、Serverspecはリモートに対して実行するのですが、Gossはローカルに対して実行するという違いが大きいと思います。また、Go言語という点も要因だと思います。

②テスト項目の可読性が高い

テスト項目の設定ファイルがYAMLなので、とてもシンプルで見やすいです。ひと目で何のテストを実行するのかが分かります。また、誰が書いても同じようなコードになります。

③Serverspecユーザーに優しい

実行結果をServerspecと同じ形式で出力することができるので、Serverspecユーザーは親しみやすいと思います。また、様々な出力形式をサポートしています。

④テスト自動生成機能がある

対象のサーバーの現在の設定を読み取って、テストを自動生成することもできます。使い始める方はまずはこの自動生成機能を使ってみて、不要なテストを削ったり必要なテストを足したりすると良いと思います。

Gossの短所

ローカルでしか実行できない

Gossはローカルでしか実行することができません。一方、Serverspecはsshを使ってリモートのサーバーに対して実行することができます。

Gossを使ってみよう

インストール方法

GoがInstallされていれば、下記のコマンドだけで最新のGossをインストールすることができます。

curl -fsSL https://goss.rocks/install | sh

その他のインストール方法に関しては、公式のドキュメントを参照してください。

設定方法

Gossのドキュメントを参考にして、テスト項目をYAMLでファイルに定義します。例えば、下記のファイルはコメントに書いてあるようなテストが定義されています。

$ cat goss.yaml 
# portの22番がIPv4/IPv6を全IPlisteningしていること
port:
  tcp:22:
    listening: true
    ip:
    - 0.0.0.0
  tcp6:22:
    listening: true
    ip:
    - '::'

# serviceのsshdがenabledでrunningであること
service:
  sshd:
    enabled: true
    running: true

# user sshdがgroup sshdに所属しており、uid/gidが74で、ログインできないshellであること
user:
  sshd:
    exists: true
    uid: 74
    gid: 74
    groups:
    - sshd
    home: /var/empty/sshd
    shell: /sbin/nologin
    
# sshdというgroupのgidが74であること
group:
  sshd:
    exists: true
    gid: 74
    
# sshdのprocessがrunningであること
process:
  sshd:
    running: true

テスト項目の自動生成機能を使うと、テスト項目の作成が楽になります。例えば、sshdに関するテスト項目を自動生成する場合は下記のコマンドを実行します。

$ goss autoadd sshd

これにより自動生成されたファイルは、以下のようなものになります。これを不要な設定を削ったり、必要な設定を足したりすると良いと思います。

port:
  tcp:22:
    listening: true
    ip:
    - 0.0.0.0
  tcp6:22:
    listening: true
    ip:
    - '::'
service:
  sshd:
    enabled: true
    running: true
user:
  sshd:
    exists: true
    uid: 74
    gid: 74
    groups:
    - sshd
    home: /var/empty/sshd
    shell: /sbin/nologin
group:
  sshd:
    exists: true
    gid: 74
process:
  sshd:
    running: true

また、テスト項目数が増えてきてファイルの見通しが悪くなってきたら、ファイルを分割すると管理しやすくなります。下記のようにすることで、他の設定ファイルを読むことができるので、ファイルを分割することができます。

gossfile:
  /etc/goss/config/goss_file1.yml: {}
  /etc/goss/config/goss_file2.yml: {}
  /etc/goss/config/goss_file3.yml: {}

実行方法と実行結果

下記のコマンドでテストを実行することができます。これは全てのテストを成功した場合の例です。

$ /usr/local/bin/goss validate
..........................

Total Duration: 0.326s
Count: 26, Failed: 0, Skipped: 0

テストに失敗すると、下記のように失敗したテストの内容と、Failedのカウントが増えます。

$ /usr/local/bin/goss validate
........F........F........

Failures/Skipped:

Port: tcp:25: listening:
Expected
  : false
to equal
  : true
  
Command: /usr/local/bin/svstat /service/smtpd: stdout: patterns not found: [/: up/]

Total Duration: 0.172s
Count: 26, Failed: 2, Skipped: 0

Serverspecとの比較

設定ファイル

GossはYAMLでテスト項目を定義します。ひと目でテスト項目の内容が分かり、良いですね。

file:
  /vol/web/test.conf:
    exists: true
    
port:
  tcp6:7777:
    listening: true
    
command:
  convert --version:
    exit-status: 0
    stdout:
    - "/^Version: ImageMagick/"

Serverspecでは、下記のようなファイルになります。

describe file('/vol/web/test.conf') do
  it { should be_file }
end

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

describe command("convert --version") do
  its(:stdout) { should match /^Version: ImageMagick/ }
end

実行速度

ServerspecとGossの実行にかかる時間を簡単に比較しました。10サーバーあたりで合計300の試験を、シリアル実行/パラレル実行した場合のそれぞれの時間を比較しています。GossはFabric経由でリモートサーバーの試験を実行しています。

結果としては、シリアル実行では90秒から15秒に、パラレル実行では60秒から3秒に短縮できました。

ちょっと工夫したこと

Ansible/Fabricに組み込んでリモート上でGossの実行を実現

前述したように、Gossはローカルに対してしか実行できません。この問題を解決するために、AnsibleやFabricに組み込んで、リモートのサーバーのGossを実行をできるようにしました。リモートホストの管理はAnsibleやFabricに任せて、サーバーのテストはGossに任せるという形です。元々AnsibleやFabricを使っているのであれば、Gossを組み込むだけで良いので簡単に実現できます。

テスト項目のプログラミングにはAnsibleでJinjaテンプレートを使用

Gossの設定ファイルでプログラミングが必要な箇所は、AnsibleでJinjaテンプレートを使いました。これにより、環境やサーバーに応じたテスト項目の分岐がAnsibleで管理/制御できて、サーバー上にはシンプルなテスト項目のGossファイルが配備されるようにしました。Gossでもプログラミングはできるのですが、このようにすることで実際のテスト内容が見やすくなり、テスト項目の変更時に差分が分かります。

さいごに

Serverspecは素晴らしいソフトウェアでこれまで長い間助けられました。この場を借りて関係各位に感謝を述べさせて頂きます。

本記事が私と同じ悩みを抱えている方々のお役に立てれば幸いです。

 

開発メンバー募集中

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる