CIWindows

インフラCI実践ガイドを試してみる⑤【テスト(後半)】

CI

購入した書籍「インフラCI実践ガイド」を試した際のメモの続きです。

第6章のユニットテストの後半となります。コードの構文チェックの方法についてと具体的なユニットテスト、インテグレーションテストを行ってます。

6.2 コードレビューの自動化

6.2.2 構文をチェックする

構文チェックには「シンタックスチェック」と「チェックモード」の2種類を利用できる。ただし、「チェックモード」は依存関係が多いインフラCIでは導入しづらい

今回の演習でも組み込んでない。理由は後で。

シンタックスチェックの実行

実際に–syntax-checkをつけてシンタックスチェックの動作を確認します。

問題なくテストが通過しました。

[root@infraci ~]# cd ~/ketchup-vagrant-ansible/
[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i ./hosts/ketchup/test_inventory cleanup.yml
(略)
[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i ./hosts/ketchup/test_inventory site.yml --syntax-check

playbook: site.yml
[root@infraci ketchup-vagrant-ansible]# echo $?
0

次にテストがエラーになるようにPlaybookを変更します。

[root@infraci ketchup-vagrant-ansible]# cd  ~/ketchup-vagrant-ansible/
[root@infraci ketchup-vagrant-ansible]# cp site.yml site_syntax_check.yml
[root@infraci ketchup-vagrant-ansible]# vim site_syntax_check.yml

- import_playbook: ketchup_hoge.yml <-存在しない
  tags:
    - ketchup
- import_playbook: ketchup_nginx.yml
  tags:
    - ketchup_nginx

変更後にシンタックスチェックをするとエラーとなります。

[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i ./hosts/ketchup/test_inventory site_syntax_check.yml --syntax-check
ERROR! Unable to retrieve file contents
Could not find or access '/root/ketchup-vagrant-ansible/ketchup_hoge.yml'
[root@infraci ketchup-vagrant-ansible]# echo $?
1

チェックモードの実行

チェックモードを使った構文の確認をします。ただし、エラーになってしまいます。

原因としては、site.yml内の処理でNginxをインストールするのですが、事前にリポジトリを登録する必要があります。ただしチェックモードでは実際にリポジトリの登録処理は行われないためエラーとなります。

依存関係があるようなPlaybookの場合にはチェックモードを利用するは難しそうです。

[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i ./hosts/ketchup/test_inventory site.yml --check
(略)
TASK [packages_el : Install nginx packages] *****************************************************************************************************************************
failed: [192.168.33.15] (item=[u'nginx']) => {"changed": false, "item": ["nginx"], "msg": "No package matching 'nginx' found available, installed or updated", "rc": 126, "results": ["No package matching 'nginx' found available, installed or updated"]}
        to retry, use: --limit @/root/ketchup-vagrant-ansible/site.retry

PLAY RECAP **************************************************************************************************************************************************************
192.168.33.14              : ok=12   changed=6    unreachable=0    failed=0
192.168.33.15              : ok=6    changed=3    unreachable=0    failed=1

6.3 ユニットテストの実装

ユニットテストでは、自動化によって設定された値が正しく設定されているか、また実行した手順が環境に反映されているかを確認します。

Ansibleでは構築や変更を行うPlaybookで実行された各タスクの結果が意図した結果になっているかを確認します。

ここでいう確認とは、構築タスクで設定された状態に対して期待した状態を定義し、その2つを比較することです。

6.3.1 ユニットテスト設計

ユニットテストの対象としては以下の3つが考えられます。

書籍では2.ロール単位のユニットテストを採用しています。

1.タスク単位のユニットテスト

設定や操作を行うタスクの直後に、その設定や操作を検証するテストタスクを挿入する方法。1つのファイル内に記載できるので見通しはよいが、タスクが多くなると全体の記述量が多くなりメンテナンス性が下がる。小さい自動化向け。

2.ロール単位のユニットテスト

作業単位に分割されたロールで行われる設定タスクに対して、対応するテストを定義する方法。テスト対象が明確で比較的容易に管理可能だが、ロールが別のロールと連携していると行えない。

Playbook単位のユニットテスト

テスト対象の作業をすべて実行した後にテストする方法。ロール間の連携についてもテスト可能だが、大きな自動化だと1か所の変更の影響範囲の把握が難しくなる。

6.3.2 ロール単位のテスト実装

各ロールのtasksディレクトリにunit_test.ymlを配置します。

「main.yml」と「unit_test.yml」を1対1で対応させます。

実際にテストを実行するためのPlaybookを記載してからテストを実行してみます。

[root@infraci ~]# cd ~/ketchup-vagrant-ansible/
[root@infraci ketchup-vagrant-ansible]# vim c6-unit-test-repo-el.yml
- hosts: "ketchup"
  gather_facts: True
  tasks:
   - name: Exec repos_el role
     include_role:
       name: repos_el
       tasks_from: main
   - name: Unit Test fro repos_el role
     include_role:
       name: repos_el
       tasks_from: unit_test

[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i hosts/ketchup/test_inventory cleanup.yml
(略)

[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i hosts/ketchup/test_inventory c6-unit-test-repo-el.yml
(略)
TASK [Unit Test fro repos_el role] **************************************************************************************************************************************

TASK [repos_el : include task specific variables] ***********************************************************************************************************************
ok: [192.168.33.14] => (item=/root/ketchup-vagrant-ansible/roles/repos_el/vars/../vars/default.yml)

TASK [repos_el : Check status for repo files] ***************************************************************************************************************************
ok: [192.168.33.14] => (item=epel-7.repo)

TASK [repos_el : Gathaering registered repos] ***************************************************************************************************************************
ok: [192.168.33.14]

TASK [repos_el : Check epel is enabled] *********************************************************************************************************************************
ok: [192.168.33.14] => {
    "changed": false,
    "msg": "All assertions passed"
}

PLAY RECAP **************************************************************************************************************************************************************
192.168.33.14              : ok=8    changed=2    unreachable=0    failed=0

テストは成功しました。続いて他のロールもテストしてみます。

[root@infraci ketchup-vagrant-ansible]# cd ~/ketchup-vagrant-ansible/
[root@infraci ketchup-vagrant-ansible]# vim c6-unit-test-packages-el.yml
- hosts: "ketchup_nginx"
  gather_facts: True
  vars:
     packages_el_nginx_packages: True
  tasks:
   - name: Exec packages_el role
     include_role:
       name: packages_el
       tasks_from: main
   - name: Unit Test for packages_el role
     include_role:
       name: packages_el
       tasks_from: unit_test

[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i hosts/ketchup/test_inventory cleanup.yml
(略)

[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i hosts/ketchup/test_inventory c6-unit-test-packages-el.yml
(略)
TASK [packages_el : Install nginx packages] *****************************************************************************************************************************
failed: [192.168.33.15] (item=[u'nginx']) => {"changed": false, "item": ["nginx"], "msg": "No package matching 'nginx' found available, installed or updated", "rc": 126, "results": ["No package matching 'nginx' found available, installed or updated"]}
        to retry, use: --limit @/root/ketchup-vagrant-ansible/c6-unit-test-packages-el.retry

PLAY RECAP **************************************************************************************************************************************************************
192.168.33.15              : ok=3    changed=1    unreachable=0    failed=1

結果として失敗になります。エラーになった理由は先ほどの依存関係と同じでリポジトリがないためです。

そのような場合にはPlaybook単位でのテストを行う手法があります。

6.3.3 Playbook単位のテスト実装

ロール間の依存関係を解決してテストを実施する方法について

以下のように構築用のロールを最初に実行してからユニットテストを実行することで依存関連のエラーもなくなりテストが成功します。

[root@infraci ketchup-vagrant-ansible]# cd ~/ketchup-vagrant-ansible/
[root@infraci ketchup-vagrant-ansible]# vim c6-unit-test-playbook.yml
- hosts: "ketchup_nginx"
  gather_facts: True
  vars:
     packages_el_nginx_packages: True
  tasks:
    - include_role: name=repos_el tasks_from=main
    - include_role: name=packages_el tasks_from=main
    - include_role: name=nginx tasks_from=main

- hosts: "ketchup_nginx"
  gather_facts: True
  vars:
     packages_el_nginx_packages: True
  tasks:
    - include_role: name=repos_el tasks_from=unit_test
    - include_role: name=packages_el tasks_from=unit_test
    - include_role: name=nginx tasks_from=unit_test

[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i hosts/ketchup/test_inventory cleanup.yml
(略)

[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i hosts/ketchup/test_inventory c6-unit-test-playbook.yml
(略)
PLAY RECAP **************************************************************************************************************************************************************
192.168.33.15              : ok=33   changed=7    unreachable=0    failed=0

演習環境のサンプルは少しだけ記述が違うけれどやり方は同じ手法です。バックエンドとフロントエンドでの構築を分けているだけです。

6.3.4 Ansibleを使ったテストの記述方法

これまではunit_test.ymlを使ったものだったが、ここではAnsibleを使ったユニットテストの記述方法について紹介。

インフラのテストでは「Serverspec」や「Infrataster」というテストツールが有名だが、Ansibleだけでもテスト可能。むしろツールが統一されているメリットもある。

ポートの開放状態の確認

wait_forモジュールを利用してポートの状態をチェックできます。

  - name: Check Nginx port
    wait_for:
      host: localhost
      port: "{{ nginx_http_port }}"
      timeout: 120
      status: started

設定ファイルのパラメータの確認

wait_forモジュールはポートの開放だけでなく、ファイル内のパラメータの確認もでき、ファイル自体の有無も確認できます。

  - name: Check Nginx Parameters
    wait_for:
      path: /etc/nginx/nginx.conf
      search_regex: "{{ item }}"
      state: absent
      timeout: 5
    with_items: "{{ nginx_check_params }}"

ファイルの更新確認

ファイルの配置や更新はcopy,template,lineinfileなどのモジュールを利用します。

Ansibleは冪等性を持っているため、これを使ってファイルの更新状態を簡単に判別できます。

↓main.ymlのNginxの設定ファイルを配置している部分

  - name: Install nginx.conf for Nginx
    copy:
      src: nginx.conf
      dest: /etc/nginx/nginx.conf
      backup: yes

unit_test.ymlでチェックしている部分

  - name: Gathering nginx.conf status
    copy:
      src: nginx.conf
      dest: /etc/nginx/nginx.conf
    check_mode: true
    diff: true
    register: test_nginx_conf

  - name: Check Nginx configuration file
    assert:
      that:
        - "test_nginx_conf.changed|bool == 0"
      msg: "Nginx configuration is differ from templates. {{ test_nginx_conf.diff|default('') }}"

ユニットテスト側ではcheck_modeやdiffのオプションをtrueにした上でmain.ymlとおなじコピー操作を実施します。

先のmain.ymlでファイルのコピーは実行されているので、テストのときは「changed= false」になっていればテストは成功になります。

この値をregister: test_nginx_confに格納しておき、assertモジュールで判別しています。

コマンド実行結果確認

実際によく現場で行われる、サーバーやネットワーク機器上でコマンドを打った出力結果に対して確認するのもansibleで可能です。

コマンドで「getsebool httpd_can_network_connect」を実行して、その出力結果に対してassertモジュールで文字列onが含まれているか判別しています。

  - name: Getting selinux permission for Nginx
    command: getsebool httpd_can_network_connect
    register: test_getsebool
    ignore_errors: true
    changed_when: false

  - name: Check selinux permission for Nginx
    assert:
      that:
        - "'on' in test_getsebool.stdout|default('on')"
      msg: "SElinux permission is not set"

6.4 インテグレーションテストの実装

ユニットテストでは手順の確かさをテストしたが、インテグレーションテストでは手順が実行された結果に着目してテストを実施します。

サーバー間で連携が必要なインターフェース(AP-DB間)の設定が正しく行われているのか、ファイアウォールを経由してサーバー間で期待する通信ができるかなどをテストしてきます。

インフラではインテグレーションテストとシステムテストの線引きが難しい部分があります。なぜなら、インフラを構成する要素はアプリケーションに比べるとシステム構成上の相互依存が強いため、システム全体を使わないとテストできない項目が多いためです。

一般的な区分けとして「本番環境と同一かほぼ同等の環境でしか実施できないもの」をシステムテスト、「本番環境と多少差異があっても問題がないもの」をインテグレーションテストで実施します。

6.4.1 インテグレーションテストの設計

インテグレーションテストを実施するPlaybookはtestsディレクトリに配置します。

6.4.2 インテグレーションテストのタスク実装

正常動作の確認

例としてWebコンテンツを取得し、その中に期待する文字列やパラメータが入っていることを確認します。

演習のインテグレーションテスト(test/int_test_ketchup_nginx.yml)の中でフロントエンドのNginxから、バックエンドのKetchupへの接続、Ketchupが参照しているDBまでの設定が正しく行われているか確認します。

テストの中の一部を抜粋すると、確認用ページ(/ping)へのアクセスをフロントエンド経由で行い、正常であればpongというメッセージが返されるのでそれを確認しています。

    - name: Getting service readiness
      uri:
        url: "http://{{ ketchup_nginx_host }}/ping"
        return_content: yes
      register: ketchup_connection_result

    - name: Check service activation
      fail:
        msg: "application is not setup"
      when: "'pong' not in ketchup_connection_result.content"

エラー処理の確認

正常だけでなくエラーの確認も重要です。

テストの中の一部を抜粋すると/adminにアクセスしたときに301が返されることと、/errorという存在しないページにアクセスして404が返されることを確認してます。

    - name: Confirm redirect without the last slash
      uri:
        url: "http://{{ ketchup_nginx_host }}/admin"
        follow_redirects: none
        status_code: 301

    - name: Check Error Status
      uri:
        url: "http://{{ ketchup_nginx_host }}/error"
        return_content: yes
        status_code: 404

6.4.3 インテグレーションテストの実施

実際にインテグレーションテストを実施します。

実際に動作させる前に、規約の確認と構文の確認を実施します。

[root@infraci tests]# cd ~/ketchup-vagrant-ansible/tests/
[root@infraci tests]# ansible-lint --force-color -v int_test_ketchup_nginx.yml
Examining int_test_ketchup_nginx.yml of type playbook
[root@infraci tests]# ansible-playbook -i ../hosts/ketchup/test_inventory int_test_ketchup_nginx.yml --syntax-check

playbook: int_test_ketchup_nginx.yml
[root@infraci tests]#

チェックが終わったら、実際にPlaybookで環境構築をしてからインテグレーションテストを実行してみます。

[root@infraci ketchup-vagrant-ansible]# cd ~/ketchup-vagrant-ansible/
[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i hosts/ketchup/test_inventory cleanup.yml
(略)
[root@infraci ketchup-vagrant-ansible]# ansible-playbook -i hosts/ketchup/test_inventory  site.yml
(略)

[root@infraci ketchup-vagrant-ansible]# cd tests/
[root@infraci tests]# ansible-playbook -i ../hosts/ketchup/test_inventory int_test_ketchup_nginx.yml

PLAY [Integration test for ketchup app system] **************************************************************************************************************************

TASK [Confirm redirect without the last slash] **************************************************************************************************************************
ok: [127.0.0.1]

TASK [Check Error Status] ***********************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Getting service readiness] ****************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Check service activation] *****************************************************************************************************************************************
skipping: [127.0.0.1]

TASK [Login to kechup service] ******************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Debug login sessions] *********************************************************************************************************************************************
skipping: [127.0.0.1]

TASK [Check login Administration] ***************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Check Keyword] ****************************************************************************************************************************************************
ok: [127.0.0.1] => {
    "ketchup_login_result": {
        "changed": false,
        "connection": "close",
        "content_length": "0",
        "content_type": "text/plain; charset=utf-8",
        "cookies": {
            "session": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.UvF00CJyus5dsIfdKaBUVYyExjNG6kXwY5wMipWpGmPeaCEpuixz-GWC816mC_7QqbACqNtlGzHv2390ury2LQC0tr4_QMjiUieNBj0yAT8d1EKA2wwliaTNaQ-ZWsWMTqCiMuLQo9w1fx-90d9K_rfe2BVcviZh0pD0VoVdmaCoRjfe763b9zRR0QBQEO8tFEQRr-K69j6eLBNVNXMhPdry3EhTvUZANrGURSOU2FO35hjo6B9Bg-LiV5RSzWvjne4Lhn5MM_VhGuWNY6UZ5btgD0Bq9YJUrByIeklnk5k1dgtmVc4gQQCIET6QPNQUHRS-zsiaNgIlX9NAZXSQeg.vaKlCEHcl7_ribWe.ysF11E6Z80KjLC1g4yGOENhwvE_AGgE_NrhFiwj8f_zvZXTIYzIvDGLBjm3lkiH4-Npvy1865zsriBsuyPqaQyTu7q2LNqHkVQ9M4bnEX5jgbpeUpVY-kSRGpkdR9-216vbeTqQrjckNHND2v1ctsMfBAs5eg51MCZwDSGr-OhYUtALYY0ywbtAjOH_HwTv_mu6c94bdmCXNgOSyaMcC.aWzwT5g0wxnzUG9x55agYQ"
        },
        "date": "Fri, 26 Jun 2020 02:11:22 GMT",
        "failed": false,
        "msg": "OK (0 bytes)",
        "redirected": false,
        "server": "nginx/1.16.1",
        "set_cookie": "session=eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.UvF00CJyus5dsIfdKaBUVYyExjNG6kXwY5wMipWpGmPeaCEpuixz-GWC816mC_7QqbACqNtlGzHv2390ury2LQC0tr4_QMjiUieNBj0yAT8d1EKA2wwliaTNaQ-ZWsWMTqCiMuLQo9w1fx-90d9K_rfe2BVcviZh0pD0VoVdmaCoRjfe763b9zRR0QBQEO8tFEQRr-K69j6eLBNVNXMhPdry3EhTvUZANrGURSOU2FO35hjo6B9Bg-LiV5RSzWvjne4Lhn5MM_VhGuWNY6UZ5btgD0Bq9YJUrByIeklnk5k1dgtmVc4gQQCIET6QPNQUHRS-zsiaNgIlX9NAZXSQeg.vaKlCEHcl7_ribWe.ysF11E6Z80KjLC1g4yGOENhwvE_AGgE_NrhFiwj8f_zvZXTIYzIvDGLBjm3lkiH4-Npvy1865zsriBsuyPqaQyTu7q2LNqHkVQ9M4bnEX5jgbpeUpVY-kSRGpkdR9-216vbeTqQrjckNHND2v1ctsMfBAs5eg51MCZwDSGr-OhYUtALYY0ywbtAjOH_HwTv_mu6c94bdmCXNgOSyaMcC.aWzwT5g0wxnzUG9x55agYQ; Path=/; HttpOnly",
        "status": 200,
        "url": "http://192.168.33.15/api/v1/login"
    }
}

PLAY RECAP **************************************************************************************************************************************************************
127.0.0.1                  : ok=6    changed=0    unreachable=0    failed=0

成功しました。次はあえてバックエンドを構築しないでインテグレーションテストを実行してエラーになることを確認します。(割愛します)

6.6 演習環境のクリア

最後に演習環境をクリアしてます。

これで第6章は終わりになります。コードレビューでの規約の確認と構文の確認、ユニットテスト、インテグレーションテストと一通りの自動テストのやり方が学習できました。

今回は以上となります。次回はいよいよCIに突入です。

コメント