【Ansible】Ansibleロール単体テストツールMoleculeを触ってみた
はじめに
AnsibleロールをテストするツールMoleculeを試してみました。
参考:公式ドキュメント
ついでにJenkins+Gitと連携させて、
インフラCIのパイプラインに乗っかる感じで使ってみました。
前提
- Jenkinsマスター・Jenkinsスレーブ・GitLabはDockerコンテナ
- PlaybookのテストはCentos8のDockerコンテナで実行させる
- JenkinsスレーブにDockerをインストール
- ホストOSのdocker daemonを共有させているので、Dockerコンテナが立ち上がるのは、ホストOS上(Not Docker in Docker)
- テストコードもPlaybookで記述
- PlaybookはGitLabの
sample_playbook
リポジトリに格納
環境構成
構成図
バージョン情報
項目 | 値 |
---|---|
Ansible | 2.9.0 |
Molecule | 3.0.5 |
ansible-lint | 4.2.0 |
Docker | 18.09.8-ce |
Jenkins | 2.176.1 |
Jenkins Remoting | 3.29 |
Git | 2.22.4 |
シナリオ
- Ansibleロールを作成
- Ansibleロールを単体テストするPlaybookを作成
- GitにPushする
- Jenkinsジョブ実行
- moleculeテスト結果出力
3→4とか自動化したいとこですが、
そこまではやってません。
Ansibleロール作成
下記のコマンドでmolecule設定ファイルを含んだロールが生成されます。
moecule init role <ロール名>
今回は下記のコマンドを実行しました。
molecule init role sample
sample
ロールが生成されます。
※Moleculeを動かしてみることが目的なので、とってもイージーなPlaybookです。
test
と記載されたファイルを/tmp/test.txt
に配置するだけのPlaybookです。
- ./roles/sample/tasks/main.yml
--- - name: copy test file copy: src: test.txt dest: /tmp/test.txt
- ./roles/sample/files/test.txt
test
【参考】既存のロールにmolecule設定ファイルを追加する場合
ロールのディレクトリ配下に移動して、下記のコマンドを実行します。
molecule init scenario -r <ロール名>
ちなみに1階層上(roles直下)実行すると、以下のようなエラーが出ます。
ロールのディレクトリ直下で実行しましょう。
ERROR: The role '<ロール名>' not found. Please choose the proper role name.
Ansibleロールを単体テストするPlaybookを作成
molecule.ymlの編集
単体テストコードを作成する前に、moleculeの動作設定を確認しておきましょう。
- ./roles/sample/molecule/default/molecule.yml
--- dependency: name: galaxy driver: name: docker lint: ansible-lint platforms: - name: instance image: docker.io/pycontribs/centos:8 pre_build_image: true provisioner: name: ansible verifier: name: ansible
今回デフォルトから変更した点は、以下の点です。
ansible-lintによる静的解析を実行したいので下記の2行を追記しました。
lint: ansible-lint
lintの定義はコマンドさながらに定義できるので、
例えばansible-lintで無視させたいルールがあるとしたら、
下記のように定義すればOKです。
lint: ansible-lint -x 201
※201のルールを無視させています(参考)
デフォルトから変えていませんが、
前提条件で謳っていた下記の項目はmolecule.ymlの定義に依存しています。
- PlaybookのテストはCentos8のDockerコンテナで実行させる
driver: name: docker
platforms: - name: instance image: docker.io/pycontribs/centos:8 pre_build_image: true
- テストコードもPlaybookで記述
verifier: name: ansible
ほかにもansibleオプションを定義できたり、
テストをtestinfraというpython製のツールで実行できたり、
いろいろ設定できるようです。
詳細は公式ドキュメントをご確認ください。
テストコードの作成
moleculeコマンドで生成されるverify.ymlにテスト処理を定義します。
- ./roles/sample/molecule/default/verify.yml
--- - name: Verify hosts: all tasks: - name: Verify with wait_for module wait_for: path: /tmp/test.txt search_regex: "{{ item }}" timeout: 5 with_items: - "test" - name: Verify with command module Execution command: cat /tmp/test.txt register: command_result ignore_errors: true changed_when: false - name: Verify with command module Check Result assert: that: - "'test' in command_result.stdout"
テスト対象のPlaybookが、
test
と記載されたファイルを/tmp/test.txt
に配置するだけのPlaybook
なので、
配置したtest.txtにtest
という文字列が含まれているか
を確認するPlaybookを作成しました。
下記の2種類の確認方法(やっていることはほぼ同じ)を仕込んでみました。
- wait_forモジュールを使用した確認
- Verify with wait_for module
- commandモジュールを使用した確認
- Verify with command module Execution
- Verify with command module Check Result
※冪等性の試験とかもいつか試したいと思います。
GitにPushする
作成した./roles/sampleをGitリポジトリsample_playbook
にpushします。
Jenkinsジョブ実行
Jenkinsには以下の設定がしてある前提です。
- 認証情報に
git-credential
を登録している - Jenkinsスレーブに
slave-node
というラベルを付与している
Jenkinsの新規ジョブ作成
から下記の設定のジョブを作成します。
- ジョブ名:Execute_Molecule
- 種類:パイプライン
ビルドのパラメータ化にチェックを入れ、下記のパラメータを設定します。
パラメータ名 | 型 | デフォルト値 |
---|---|---|
GIT_REPOSITORY_PATH | 文字列 | http://127.0.0.1/gitlab/sample_playbook.git |
ROLE_PATH | テキスト | sample |
pipeline{ agent{ node{ label 'slave-node' } } stages{ stage('Gitクローン'){ steps{ deleteDir() git( url: GIT_REPOSITORY_PATH, credentialsId: 'git_credential', branch: 'master' ) } } stage('molecule実行'){ steps{ script{ def roles = ROLE_LIST.split("\n") sh "mkdir logs" roles.each{ role -> echo """Start Testing ${role}""" dir("${WORKSPACE}/roles/${role}"){ def result = sh ( script: "/usr/local/bin/molecule test", returnStdout: true ) echo "${result}" writeFile( file: "${WORkSPACE}/logs/${role}_molecule.log", text: "${result}" ) } } } } } stage('Archive artifacts'){ steps{ archiveArtifacts "logs/*" } } } }
上記のジョブを作成し、
下記のパラメータを設定して実行してみてください。
パラメータ | 値 |
---|---|
GIT_REPOSITORY_PATH | Gitのリポジトリパス |
ROLE_LIST | Moleculeを実行したいロール名(改行区切りで複数指定可) |
moleculeテスト結果出力
ジョブに定義したパイプラインを見れば気づくと思いますが、
moleculeの実行結果を<ロール名>_molecule.log
に出力し、
Jenkinsジョブの成果物として保存されます。
こんなログが出てくるかと思います。
--> Test matrix └── default ├── dependency ├── lint ├── cleanup ├── destroy ├── syntax ├── create ├── prepare ├── converge ├── idempotence ├── side_effect ├── verify ├── cleanup └── destroy --> Scenario: 'default' --> Action: 'dependency' Skipping, missing the requirements file. Skipping, missing the requirements file. --> Scenario: 'default' --> Action: 'lint' --> Executing: ansible-lint --> Scenario: 'default' --> Action: 'cleanup' Skipping, cleanup playbook not configured. --> Scenario: 'default' --> Action: 'destroy' --> Sanity checks: 'docker' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=instance) TASK [Wait for instance(s) deletion to complete] ******************************* ok: [localhost] => (item=None) ok: [localhost] TASK [Delete docker network(s)] ************************************************ PLAY RECAP ********************************************************************* localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 --> Scenario: 'default' --> Action: 'syntax' playbook: /home/jenkins/agent/workspace/Execute_Molecule/ansible_playbooks/sample/molecule/default/converge.yml --> Scenario: 'default' --> Action: 'create' PLAY [Create] ****************************************************************** TASK [Log into a Docker registry] ********************************************** skipping: [localhost] => (item=None) TASK [Check presence of custom Dockerfiles] ************************************ ok: [localhost] => (item=None) ok: [localhost] TASK [Create Dockerfiles from image names] ************************************* skipping: [localhost] => (item=None) TASK [Discover local Docker images] ******************************************** ok: [localhost] => (item=None) ok: [localhost] TASK [Build an Ansible compatible image (new)] ********************************* skipping: [localhost] => (item=molecule_local/docker.io/pycontribs/centos:8) TASK [Create docker network(s)] ************************************************ TASK [Determine the CMD directives] ******************************************** ok: [localhost] => (item=None) ok: [localhost] TASK [Create molecule instance(s)] ********************************************* changed: [localhost] => (item=instance) TASK [Wait for instance(s) creation to complete] ******************************* FAILED - RETRYING: Wait for instance(s) creation to complete (300 retries left). changed: [localhost] => (item=None) changed: [localhost] PLAY RECAP ********************************************************************* localhost : ok=5 changed=2 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0 --> Scenario: 'default' --> Action: 'prepare' Skipping, prepare playbook not configured. --> Scenario: 'default' --> Action: 'converge' PLAY [Converge] **************************************************************** TASK [Gathering Facts] ********************************************************* ok: [instance] TASK [Include sample] ********************************************************** TASK [sample : copy test file] **************************************************** changed: [instance] PLAY RECAP ********************************************************************* instance : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 --> Scenario: 'default' --> Action: 'idempotence' Idempotence completed successfully. --> Scenario: 'default' --> Action: 'side_effect' Skipping, side effect playbook not configured. --> Scenario: 'default' --> Action: 'verify' --> Running Ansible Verifier PLAY [Verify] ****************************************************************** TASK [Gathering Facts] ********************************************************* ok: [instance] TASK [Verify with wait_for module] ********************************************* ok: [instance] => (item=test) TASK [Verify with command module Execution] ************************************ ok: [instance] TASK [Verify with command module Check Result] ********************************* ok: [instance] => { "changed": false, "msg": "All assertions passed" } PLAY RECAP ********************************************************************* instance : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Verifier completed successfully. --> Scenario: 'default' --> Action: 'cleanup' Skipping, cleanup playbook not configured. --> Scenario: 'default' --> Action: 'destroy' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=instance) TASK [Wait for instance(s) deletion to complete] ******************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Delete docker network(s)] ************************************************ PLAY RECAP ********************************************************************* localhost : ok=2 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 --> Pruning extra files from scenario ephemeral directory
上記、クリアしている例ですが、
何かしらテストがNGになると、
molecule test
コマンドのリターンコードが0
以外になるので、
ジョブが異常終了します。
おわりに
PlaybookもCIしていくって考え、大事だとおもいます。
業務で触っているということもあり、Jenkinsに乗せてみましたが、
Github actionやGitLab runnerを使用したインフラCIもいつか試したいと思います。