InSpecとKitchenを使用してAnsibleデプロイメントをテストする方法

提供:Dev Guides
移動先:案内検索

著者は、 Diversity in Tech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

InSpec は、規制上の懸念、推奨事項、または要件を記述およびテストするために使用される、オープンソースの監査および自動テストフレームワークです。 人間が読める形式で、プラットフォームに依存しないように設計されています。 開発者は、ローカルでInSpecを使用するか、SSH、WinRM、またはDockerを使用してテストを実行できるため、テスト対象のインフラストラクチャにパッケージをインストールする必要はありません。

InSpecを使用すると、サーバー上で直接テストを実行できますが、インフラストラクチャに問題を引き起こす可能性のある人為的エラーの可能性があります。 このシナリオを回避するために、開発者は Kitchen を使用して仮想マシンを作成し、テストが実行されているマシンに選択したOSをインストールできます。 Kitchenは、テストランナー、つまりテスト自動化ツールであり、1つ以上の分離されたプラットフォームでインフラストラクチャコードをテストできます。 また、多くのテストフレームワークをサポートし、Vagrant、AWS、DigitalOcean、Docker、LXCコンテナーなどのさまざまなプラットフォーム用のドライバープラグインアーキテクチャに柔軟に対応します。

このチュートリアルでは、DigitalOceanUbuntu18.04ドロップレットで実行されているAnsibleプレイブックのテストを作成します。 テストランナーとしてKitchenを使用し、テストを作成するためにInSpecを使用します。 このチュートリアルを終了するまでに、AnsiblePlaybookのデプロイをテストできるようになります。

前提条件

このガイドを開始する前に、以下に加えてDigitalOceanアカウントが必要です。

ステップ1—キッチンのセットアップと初期化

キッチンに同梱されている前提条件の一部としてChefDKをインストールしました。 このステップでは、DigitalOceanと通信するようにKitchenを設定します。

Kitchenを初期化する前に、プロジェクトディレクトリを作成して移動します。 このチュートリアルでは、これをansible_testing_dirと呼びます。

次のコマンドを実行して、ディレクトリを作成します。

mkdir ~/ansible_testing_dir

そしてそれに移動します:

cd ~/ansible_testing_dir

gemを使用して、kitchen-digitaloceanパッケージをローカルマシンにインストールします。 これにより、テストの実行時にDigitalOceanドライバーを使用するようにkitchenに指示できます。

gem install kitchen-digitalocean

プロジェクトディレクトリ内で、kitchen initコマンドを実行し、Kitchenを初期化するときに、プロビジョナーとしてansible_playbookを指定し、ドライバーとしてdigitaloceanを指定します。

kitchen init --provisioner=ansible_playbook --driver=digitalocean

次の出力が表示されます。

Outputcreate  kitchen.yml
create  chefignore
create  test/integration/default

これにより、プロジェクトディレクトリ内に次のものが作成されました。

  • test/integration/defaultは、テストファイルを保存するディレクトリです。
  • chefignore は、特定のファイルが Chef Infra Server にアップロードされないようにするために使用するファイルですが、このチュートリアルでは使用しません。
  • kitchen.ymlは、テスト構成(テストする対象とターゲットプラットフォーム)を説明するファイルです。

次に、CLIからドロップレットを作成するためのアクセス権を取得するために、DigitalOceanクレデンシャルを環境変数としてエクスポートする必要があります。 まず、次のコマンドを実行して、DigitalOceanアクセストークンから開始します。

export DIGITALOCEAN_ACCESS_TOKEN="YOUR_DIGITALOCEAN_ACCESS_TOKEN"

SSHキーID番号も取得する必要があります。 YOUR_DIGITALOCEAN_SSH_KEY_IDSは、シンボリック名ではなく、SSHキーの数値IDである必要があることに注意してください。 DigitalOcean APIを使用すると、次のコマンドでキーの数値IDを取得できます。

curl -X GET https://api.digitalocean.com/v2/account/keys -H "Authorization: Bearer $DIGITALOCEAN_ACCESS_TOKEN"

このコマンドから、SSHキーと関連するメタデータのリストが表示されます。 出力を読んで正しいキーを見つけ、出力内のID番号を特定します。

Output...
 {"id":your-ID-number,"fingerprint":"fingerprint","public_key":"ssh-rsa your-ssh-key","name":"your-ssh-key-name"
...

注:出力を読みやすくして数値IDを取得したい場合は、jqダウンロードページでOSに基づいたjqを見つけてダウンロードできます。 。 これで、次のようにjqにパイプされた前のコマンドを実行できます。

curl -X GET https://api.digitalocean.com/v2/account/keys -H "Authorization: Bearer $DIGITALOCEAN_ACCESS_TOKEN" | jq

SSHキー情報は次のようにフォーマットされています。

Output{
  "ssh_keys": [
    {
      "id": YOUR_SSH_KEY_ID,
      "fingerprint": "2f:d0:16:6b",
      "public_key": "ssh-rsa AAAAB3NzaC1yc2 [email protected]",
      "name": "sannikay"
    }
  ],
}

SSH数値IDを特定したら、次のコマンドを使用してそれらをエクスポートします。

export DIGITALOCEAN_SSH_KEY_IDS="YOUR_DIGITALOCEAN_SSH_KEY_ID"

kitchenを初期化し、DigitalOceanクレデンシャルの環境変数を設定しました。 次に、コマンドラインから直接DigitalOceanドロップレットでテストを作成して実行します。

ステップ2—AnsiblePlaybookを作成する

このステップでは、次のステップでkitchenによって作成されたドロップレットにNginxとNode.jsをセットアップするプレイブックとロールを作成します。 プレイブックで指定された条件が満たされていることを確認するために、プレイブックに対してテストが実行されます。

まず、NginxとNode.jsの両方の役割用にrolesディレクトリを作成します。

mkdir -p roles/{nginx,nodejs}/tasks

これにより、次のようなディレクトリ構造が作成されます。

roles
├── nginx
│   └── tasks
└── nodejs
    └── tasks

次に、お好みのエディタを使用して、roles/nginx/tasksディレクトリにmain.ymlファイルを作成します。

nano roles/nginx/tasks/main.yml

このファイルで、次のコンテンツを追加してNginxをセットアップおよび起動するタスクを作成します。

ロール/nginx/tasks/main.yml

---
- name: Update cache repositories and install Nginx
  apt:
    name: nginx
    update_cache: yes

- name: Change nginx directory permission
  file:
    path: /etc/nginx/nginx.conf
    mode: 0750

- name: start nginx
  service:
    name: nginx
    state: started

コンテンツを追加したら、ファイルを保存して終了します。

roles/nginx/tasks/main.ymlでは、Dropletのキャッシュリポジトリを更新するタスクを定義します。これは、サーバーでapt updateコマンドを手動で実行するのと同じです。 このタスクは、Nginx構成ファイルのアクセス許可も変更し、Nginxサービスを開始します。

また、roles/nodejs/tasksmain.ymlファイルを作成して、Node.jsをセットアップするタスクを定義します。

nano roles/nodejs/tasks/main.yml

このファイルに次のタスクを追加します。

ロール/nodejs/tasks/main.yml

---
- name: Update caches repository
  apt:
    update_cache: yes

- name: Add gpg key for NodeJS LTS
  apt_key:
    url: "https://deb.nodesource.com/gpgkey/nodesource.gpg.key"
    state: present

- name: Add the NodeJS LTS repo
  apt_repository:
    repo: "deb https://deb.nodesource.com/node_{{ NODEJS_VERSION }}.x {{ ansible_distribution_release }} main"
    state: present
    update_cache: yes

- name: Install Node.js
  apt:
    name: nodejs
    state: present

終了したら、ファイルを保存して終了します。

roles/nodejs/tasks/main.ymlでは、最初にドロップレットのキャッシュリポジトリを更新するタスクを定義します。 次に、次のタスクで、Node.jsaptリポジトリの信頼性を検証する手段として機能するNode.jsのGPGキーを追加します。 最後の2つのタスクは、Node.js aptリポジトリを追加し、Node.jsをインストールします。

次に、変数、ロールを実行する順序、スーパーユーザー特権設定などのAnsible構成を定義します。 これを行うには、Kitchenのエントリポイントとして機能するplaybook.ymlという名前のファイルを作成します。 テストを実行すると、Kitchenはplaybook.ymlファイルから開始し、実行するロール(roles/nginx/tasks/main.ymlおよびroles/nodejs/tasks/main.ymlファイル)を探します。

次のコマンドを実行して、playbook.ymlを作成します。

nano playbook.yml

次のコンテンツをファイルに追加します。

ansible_testing_dir / playbook.yml

---
 - hosts: all
   become: true
   remote_user: ubuntu
   vars:
    NODEJS_VERSION: 8

ファイルを保存して終了します。

プレイブックで指定された条件が満たされていることを確認するためにテストを実行するAnsibleプレイブックの役割を作成しました。

ステップ3—InSpecテストを作成する

このステップでは、Node.jsがDropletにインストールされているかどうかを確認するためのテストを作成します。 テストを作成する前に、InSpecテストの例の形式を見てみましょう。 多くのテストフレームワークと同様に、InSpecコードは自然言語に似ています。 InSpecには、調査対象と対象の予想される状態の2つの主要なコンポーネントがあります。

ブロックA

describe '<entity>' do
  it { <expectation> }
end

ブロックAでは、キーワードdoおよびendブロックを定義します。 describeキーワードは、一般にテストスイートと呼ばれ、テストケースが含まれています。 itキーワードは、テストケースを定義するために使用されます。

<entity>は、パッケージ名、サービス、ファイル、ネットワークポートなど、調べたい件名です。 <expectation>は、目的の結果または期待される状態を指定します。たとえば、Nginxをインストールするか、特定のバージョンにする必要があります。 InSpec DSLドキュメントをチェックして、InSpec言語の詳細を確認できます。

InSpecテストブロックの別の例:

ブロックB

control 'Can be anything unique' do  
  impact 0.7                         
  title 'A human-readable title'     
  desc  'An optional description'
  describe '<entity>' do             
    it { <expectation> }
  end
end

ブロックAとブロックBの違いは、controlブロックです。 controlブロックは、規制管理、推奨、または要件の手段として使用されます。 controlブロックには名前があります。 通常、一意のID、desctitleimpactなどのメタデータ、そして最後に関連するdescribeブロックをグループ化してチェックを実装します。

desctitle、およびimpactは、コントロールの重要性とその目的を簡潔かつ完全に説明するメタデータを定義します。 impactは、0.0から1.0の範囲の数値を定義します。ここで、0.0から<0.01は影響なし、[ X136X]から<0.4は低衝撃に分類され、0.4から<0.7は中程度の衝撃に分類され、0.7から<0.9は次のように分類されます。影響が大きく、0.9から1.0はクリティカルコントロールとして分類されます。

次に、テストを実装します。 ブロックAの構文を使用して、InSpecの package リソースを使用して、Node.jsがシステムにインストールされているかどうかをテストします。 テスト用に、test/integration/defaultディレクトリにsample.rbという名前のファイルを作成します。

sample.rbを作成します:

nano test/integration/default/sample.rb

ファイルに以下を追加します。

test / Integration / default / sample.rb

describe package('nodejs') do
  it { should be_installed }
end

ここでのテストでは、packageリソースを使用して、Node.jsがインストールされていることを確認しています。

終了したら、ファイルを保存して終了します。

このテストを実行するには、kitchen.ymlを編集して、前に作成したプレイブックを指定し、構成に追加する必要があります。

kitchen.ymlファイルを開きます。

nano ansible_testing_dir/kitchen.yml

kitchen.ymlの内容を次のように置き換えます。

ansible_testing_dir / kitchen.yml

---
driver:
  name: digitalocean

provisioner:
  name: ansible_playbook
  hosts: test-kitchen
  playbook: ./playbook.yml

verifier:
  name: inspec

platforms:
  - name: ubuntu-18
    driver_config:
      ssh_key: PATH_TO_YOUR_PRIVATE_SSH_KEY
      tags:
        - inspec-testing
      region: fra1
      size: 1gb
      private_networking: false
    verifier:
      inspec_tests:
        - test/integration/default
suites:
  - name: default

platformオプションには次のものがあります。

  • name:使用している画像。
  • driver_config:DigitalOceanドロップレット構成。 driver_configには次のオプションを指定しています。
    • ssh_keyYOUR_PRIVATE_SSH_KEYへのパス。 YOUR_PRIVATE_SSH_KEYは、sshキーの作成時に指定したディレクトリにあります。
    • tags:ドロップレットに関連付けられているタグ。
    • region:ドロップレットをホストするregion
    • size:ドロップレットに必要なメモリ。
  • verifier:これは、プロジェクトにInSpecテストが含まれていることを定義します。
    • inspec_testsの部分は、テストがプロジェクトtest/integration/defaultディレクトリの下に存在することを指定します。

nameおよびregionは略語を使用していることに注意してください。 使用できる略語については、test-kitchenのドキュメントを確認してください。

構成を追加したら、ファイルを保存して終了します。

kitchen testコマンドを実行してテストを実行します。 これにより、Node.jsがインストールされているかどうかが確認されます。現在、playbook.ymlファイルにNode.jsの役割がないため、意図的に失敗します。

kitchen test

次のような出力が表示されます。

Output: failing test results-----> Starting Kitchen (v1.24.0)
-----> Cleaning up any prior instances of <default-ubuntu-18>
-----> Destroying <default-ubuntu-18>...
       DigitalOcean instance <145268853> destroyed.
       Finished destroying <default-ubuntu-18> (0m2.63s).
-----> Testing <default-ubuntu-18>
-----> Creating <default-ubuntu-18>...
       DigitalOcean instance <145273424> created.
       Waiting for SSH service on 138.68.97.146:22, retrying in 3 seconds
       [SSH] Established
       (ssh ready)

       Finished creating <default-ubuntu-18> (0m51.74s).
-----> Converging <default-ubuntu-18>...
$$$$$$ Running legacy converge for 'Digitalocean' Driver
-----> Installing Chef Omnibus to install busser to run tests
       PLAY [all] *********************************************************************

       TASK [Gathering Facts] *********************************************************
       ok: [localhost]

       PLAY RECAP *********************************************************************
       localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

       Downloading files from <default-ubuntu-18>
       Finished converging <default-ubuntu-18> (0m55.05s).
-----> Setting up <default-ubuntu-18>...
$$$$$$ Running legacy setup for 'Digitalocean' Driver
       Finished setting up <default-ubuntu-18> (0m0.00s).
-----> Verifying <default-ubuntu-18>...
       Loaded tests from {:path=>". ansible_testing_dir.test.integration.default"}

Profile: tests from {:path=>"ansible_testing_dir/test/integration/default"} (tests from {:path=>"ansible_testing_dir.test.integration.default"})
Version: (not specified)
Target:  ssh://[email protected]:22

  System Package nodejs
     ×  should be installed 
     expected that System Package nodejs is installed

Test Summary: 0 successful, 1 failure, 0 skipped
>>>>>> ------Exception-------
>>>>>> Class: Kitchen::ActionFailed
>>>>>> Message: 1 actions failed.
>>>>>>     Verify failed on instance <default-ubuntu-18>.  Please see .kitchen/logs/default-ubuntu-18.log for more details
>>>>>> ----------------------
>>>>>> Please see .kitchen/logs/kitchen.log for more details
>>>>>> Also try running `kitchen diagnose --all` for configuration

  4.54s user 1.77s system 5% cpu 2:02.33 total

出力には、kitchenでプロビジョニングしたドロップレットにNode.jsがインストールされていないため、テストが失敗していることが示されます。 nodejsロールをplaybook.ymlファイルに追加してテストを修正し、テストを再実行します。

playbook.ymlファイルを編集して、nodejsロールを含めます。

nano playbook.yml

次の強調表示された行をファイルに追加します。

ansible_testing_dir / playbook.yml

---
 - hosts: all
   become: true
   remote_user: ubuntu
   vars:
    NODEJS_VERSION: 8

   roles:
    - nodejs

ファイルを保存して閉じます。

次に、kitchen testコマンドを使用してテストを再実行します。

kitchen test

次の出力が表示されます。

Output......
Target:  ssh://[email protected]:22

  System Package nodejs
     ✔  should be installed

Test Summary: 1 successful, 0 failures, 0 skipped
       Finished verifying <default-ubuntu-18> (0m4.89s).
-----> Destroying <default-ubuntu-18>...
       DigitalOcean instance <145512952> destroyed.
       Finished destroying <default-ubuntu-18> (0m2.23s).
       Finished testing <default-ubuntu-18> (2m49.78s).
-----> Kitchen is finished. (2m55.14s)
  4.86s user 1.77s system 3% cpu 2:56.58 total

nodejsロールを使用してNode.jsがインストールされているため、テストに合格します。

Test ActionでKitchenが行っていることの概要は次のとおりです。

  • ドロップレットが存在する場合は破棄します
  • ドロップレットを作成します
  • ドロップレットを収束します
  • InSpecでドロップレットを検証します
  • ドロップレットを破壊します

問題が発生した場合、KitchenはDropletの実行を中止します。 これは、Ansibleプレイブックが失敗した場合、InSpecが実行されず、Dropletが破棄されないことを意味します。 これにより、インスタンスの状態を検査して問題を修正する機会が得られます。 必要に応じて、最終的な破棄アクションの動作をオーバーライドできます。 kitchen help testコマンドを実行して、--destroyフラグのCLIヘルプを確認してください。

最初のテストを作成し、問題を修正する前に1つのインスタンスが失敗した状態で、プレイブックに対して実行しました。 次に、テストファイルを拡張します。

ステップ4—テストケースを追加する

このステップでは、テストファイルにさらにテストケースを追加して、NginxモジュールがDropletにインストールされており、構成ファイルに適切な権限があるかどうかを確認します。

sample.rbファイルを編集して、テストケースをさらに追加します。

nano test/integration/default/sample.rb

次のテストケースをファイルの最後に追加します。

test / Integration / default / sample.rb

. . .
control 'nginx-modules' do
  impact 1.0
  title 'NGINX modules'
  desc 'The required NGINX modules should be installed.'
  describe nginx do
    its('modules') { should include 'http_ssl' }
    its('modules') { should include 'stream_ssl' }
    its('modules') { should include 'mail_ssl' }
  end
end

control 'nginx-conf' do
  impact 1.0
  title 'NGINX configuration'
  desc 'The NGINX config file should owned by root, be writable only by owner, and not writeable or and readable by others.'
  describe file('/etc/nginx/nginx.conf') do
    it { should be_owned_by 'root' }
    it { should be_grouped_into 'root' }
    it { should_not be_readable.by('others') }
    it { should_not be_writable.by('others') }
    it { should_not be_executable.by('others') }
  end
end

これらのテストケースでは、ドロップレットのnginx-moduleshttp_sslstream_ssl、およびmail_sslが含まれていることを確認します。 /etc/nginx/nginx.confファイルのアクセス許可も確認しています。

itキーワードとitsキーワードの両方を使用してテストを定義しています。 キーワードitsは、リソースのプロパティにアクセスするためにのみ使用されます。 たとえば、modulesnginxのプロパティです。

テストケースを追加したら、ファイルを保存して終了します。

次に、kitchen testコマンドを実行して、もう一度テストします。

kitchen test

次の出力が表示されます。

Output...
Target:  ssh://[email protected]:22

  ↺  nginx-modules: NGINX modules
     ↺  The `nginx` binary not found in the path provided.
  ×  nginx-conf: NGINX configuration (2 failed)
     ×  File /etc/nginx/nginx.conf should be owned by "root"
     expected `File /etc/nginx/nginx.conf.owned_by?("root")` to return true, got false
     ×  File /etc/nginx/nginx.conf should be grouped into "root"
     expected `File /etc/nginx/nginx.conf.grouped_into?("root")` to return true, got false
     ✔  File /etc/nginx/nginx.conf should not be readable by others
     ✔  File /etc/nginx/nginx.conf should not be writable by others
     ✔  File /etc/nginx/nginx.conf should not be executable by others

  System Package nodejs
     ✔  should be installed
Profile Summary: 0 successful controls, 1 control failure, 1 control skipped
Test Summary: 4 successful, 2 failures, 1 skipped

一部のテストが失敗していることがわかります。 nginxロールをプレイブックファイルに追加し、テストを再実行することで、これらを修正します。 失敗したテストでは、現在サーバーに存在しないnginxモジュールとファイルのアクセス許可を確認しています。

playbook.ymlファイルを開きます。

nano ansible_testing_dir/playbook.yml

次の強調表示された行を役割に追加します。

ansible_testing_dir / playbook.yml

---
- hosts: all
  become: true
  remote_user: ubuntu
  vars:
  NODEJS_VERSION: 8

  roles:
  - nodejs
  - nginx

終了したら、ファイルを保存して閉じます。

次に、テストを再度実行します。

kitchen test

次の出力が表示されます。

Output...
Target:  ssh://[email protected]:22
✔  nginx-modules: NGINX version
     ✔  Nginx Environment modules should include "http_ssl"
     ✔  Nginx Environment modules should include "stream_ssl"
     ✔  Nginx Environment modules should include "mail_ssl"
  ✔  nginx-conf: NGINX configuration
     ✔  File /etc/nginx/nginx.conf should be owned by "root"
     ✔  File /etc/nginx/nginx.conf should be grouped into "root"
     ✔  File /etc/nginx/nginx.conf should not be readable by others
     ✔  File /etc/nginx/nginx.conf should not be writable by others
     ✔  File /etc/nginx/nginx.conf should not be executable by others

  System Package nodejs
     ✔  should be installed

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

nginxの役割をプレイブックに追加すると、すべてのテストに合格します。 出力は、http_sslstream_ssl、およびmail_sslモジュールがドロップレットにインストールされ、構成ファイルに適切なアクセス許可が設定されていることを示しています。

終了したら、またはドロップレットが不要になったら、kitchen destroyコマンドを実行してドロップレットを破棄し、テストの実行後にドロップレットを削除できます。

kitchen destroy

このコマンドに続いて、次のような出力が表示されます。

Output-----> Starting Kitchen (v1.24.0)
-----> Destroying <default-ubuntu-18>...
       Finished destroying <default-ubuntu-18> (0m0.00s).
-----> Kitchen is finished. (0m5.07s)
  3.79s user 1.50s system 82% cpu 6.432 total

プレイブックのテストを作成し、テストを実行し、失敗したテストを修正して、すべてのテストに合格していることを確認しました。 これで、仮想環境を作成し、Ansible Playbookのテストを作成し、Kitchenを使用して仮想環境でテストを実行する準備が整いました。

結論

これで、Ansibleデプロイメントをテストするための柔軟な基盤ができました。これにより、ライブサーバーで実行する前にプレイブックをテストできます。 テストをプロファイルにパッケージ化することもできます。 プロファイルを使用して、Githubまたは Chef Supermarket を介してテストを共有し、ライブサーバーで簡単に実行できます。

InSpecとKitchenのより包括的な詳細については、公式InSpecドキュメントおよび公式Kitchenドキュメントを参照してください。