InSpecとKitchenを使用してAnsibleデプロイメントをテストする方法
著者は、 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アカウントが必要です。
- マシンへの
Ruby
のローカルインストール。 シリーズのディストリビューションのチュートリアルに従ってRubyをインストールできます:Rubyのローカルプログラミング環境をインストールおよびセットアップする方法。 - Chef開発キット(ChefDK)がマシンにインストールされています。
- SSHキーの設定方法の手順1と2に従って、マシンにSSHキーを設定します。 SSH公開鍵をDigitalOceanアカウントにアップロードするには、SSH鍵をDigitalOceanアカウントに追加する方法チュートリアルに従ってください。
- 読み取りおよび書き込みDigitalOceanパーソナルアクセストークン。 トークンを安全な場所に記録してください。 このチュートリアルの後半で使用します。 これにより、このチュートリアルでテストを実行する場所である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/tasks
にmain.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、desc
、title
、impact
などのメタデータ、そして最後に関連するdescribe
ブロックをグループ化してチェックを実装します。
desc
、title
、および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_key
:YOUR_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-modules
にhttp_ssl
、stream_ssl
、およびmail_ssl
が含まれていることを確認します。 /etc/nginx/nginx.conf
ファイルのアクセス許可も確認しています。
it
キーワードとits
キーワードの両方を使用してテストを定義しています。 キーワードits
は、リソースのプロパティにアクセスするためにのみ使用されます。 たとえば、modules
はnginx
のプロパティです。
テストケースを追加したら、ファイルを保存して終了します。
次に、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_ssl
、stream_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ドキュメントを参照してください。