44
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Systemi(株式会社システムアイ)Advent Calendar 2024

Day 10

Ansibleでカスタムモジュールを作成してみる

Last updated at Posted at 2024-11-29

はじめに

Ansibleでは数多くの標準モジュールが用意されており、ほとんどの場合、これらを活用するだけで必要な処理を実現できます。
しかし、標準モジュールでは対応しきれないケースが発生することもあり、そのようなときに役立つのが「カスタムモジュール」となります。
カスタムモジュールを作成することで、自身で必要な処理を定義することができ、標準モジュールでは実現が難しかったことも行えるようになります。
今回は1からカスタムモジュールを作成してみようと思います。

Ansibleとは

Ansibleとは、RedHat社が提供するオープンソースのIT自動化エンジンです。
AnsibleはPlaybookと呼ばれるYAMLファイルを使用して、タスクの自動化を行います。
Playbookでローカルシステムやリモートシステムの望ましい状態を宣言し、Ansibleが宣言した状態を維持することを保証します。

モジュールとは

Ansibleのモジュールは、特定のタスクを実行するための再利用可能な部品のようなものとなります。
モジュールは、AnsibleのPlaybook内で使われ、ファイルの管理、パッケージのインストール、サービスの起動・停止などを自動化するために利用されます。

Ansibleでは標準のモジュールが多数用意されており、多くの操作が標準モジュールで行えます。
例)
・パッチ適用
・パッケージのインストール/アンインストール
・ディレクトリやファイルの作成/削除

カスタムモジュールとは

カスタムモジュールはユーザーが独自に開発したモジュールです。
既存のモジュールで対応できない処理がある場合、自身でモジュールを作成して使用することができます。
言語はRubyやPython、bashなど JSON を返すことができる任意の言語を使用することが可能です。
引数の処理や応答の書き込みなどを行うライブラリを使用することができるため、おすすめの言語はPythonとなります。

カスタムモジュールの作成

簡単にAnsibleやモジュールの説明を行ったところで、本題となるカスタムモジュールの作成に移ります。
今回は指定したファイルから特定の文字列を検索する「grepモジュール」を作成します。
文字列検索は shell モジュールから grep コマンドを実行することでも可能ですが、grep 専用のモジュールは存在しないため、理解度を深めるため作成してみようと思います。

作成手順

モジュールファイルの作成

モジュールファイルは以下のフォルダのいずれかに配置します

  1. すべてのPlaybookとロールで使用する場合
    • 環境変数 ANSIBLE_LIBRARY で設定された path
    • config ファイル DEFAULT_MODULE_PATH(library) で指定された path

  2. 特定のPlaybookやロールで使用する場合
    • プロジェクトディレクトリの「library」ディレクトリ配下
    • ロール内の「library」ディレクトリ配下



今回はプロジェクトディレクトリの「library」ディレクトリ配下にモジュールファイルを用意します。
なお、モジュールファイルにはPythonを使用します。

$ mkdir -v test_project #プロジェクトディレクトリ作成
$ mkdir -v ./test_project/library # libraryディレクトリ作成
$ touch ./test_project/library/grep.py #モジュールファイル作成

モジュールファイルの編集

はじめにPython2とPython3の互換性を持たせるため、行頭に以下の記述をします。

#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

Ansibleモジュールを作成するための基本的なクラスをインポートします。
このクラスを使ってモジュールの引数や結果を管理します。

from ansible.module_utils.basic import AnsibleModule

続いてrun_module関数にメインの処理を記載していきます

  • module_argsでモジュールが受け取るパラメータの仕様を定義します
    データ型やデフォルト値の指定などが可能です
  • resultには実行結果として返す値を定義します
  • module.params['key']でPlaybookにて指定したパラメータの値を取得できます
def run_module():
    module_args = dict(
        src = dict(type='str', required=True),
        check_list = dict(type='list', required=True),
        encoding = dict(type='str', required=False, default='utf-8')
    )

    result = dict(
        changed=False,
        lines=[],
        count=0
    )

    module = AnsibleModule(
        argument_spec = module_args
    )


    src = module.params['src']
    check_list = module.params['check_list']
    encoding = module.params['encoding']
    get_lines = []

    try:
        # ファイルの内容を1行ずつ分割して、リスト形式で変数に格納
        with open(src, encoding=encoding) as f:
            lines = f.read().splitlines()
    except (UnicodeDecodeError, IOError) as e:
        module.fail_json(msg="Failed to read file: {}".format(e))

    # ファイルの各行に検索対象の文字列が含まれているか確認
    for line in lines:
        for check_chara in check_list:
            if check_chara in line:
                get_lines.append(line)
                break

    result['lines'] = get_lines
    result['count'] = len(get_lines)
    module.exit_json(**result)

最後にrun_module関数を呼び出す処理を追加します

def main():
    run_module()

if __name__ == '__main__':
    main()

最終的なコードは以下の通りです
※使用例やDOCUMENTATIONも簡単に追加しました

grep.py
#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = r'''
---
module: grep
short_description: This grep a string from a file
description: Grep a string from a file. Duplicate values are removed.
version_added: "0.1.0"

options:
    src:
        description: File path to grep
        required: true
        type: str
    check_list:
        description: String to be searched
        required: true
        type: list
    encoding:
        description: Encoding of the file (default is UTF-8)
        required: false
        type: str
        default: 'utf-8'

author:
    - ooki
'''

EXAMPLES = '''
- name: Check the alert log for errors
  grep:
    src: /path/to/alert_oracle.log
    check_list:
      - ORA-
    encoding: 'shift-jis'
  register: result
'''

from ansible.module_utils.basic import AnsibleModule


def run_module():
    module_args = dict(
        src = dict(type='str', required=True),
        check_list = dict(type='list', required=True),
        encoding = dict(type='str', required=False, default='utf-8')
    )

    result = dict(
        changed=False,
        lines=[],
        count=0
    )

    module = AnsibleModule(
        argument_spec = module_args
    )


    src = module.params['src']
    check_list = module.params['check_list']
    encoding = module.params['encoding']
    get_lines = []

    try:
        with open(src, encoding=encoding) as f:
            lines = f.read().splitlines()
    except (UnicodeDecodeError, IOError) as e:
        module.fail_json(msg="Failed to read file: {}".format(e))

    for line in lines:
        for check_chara in check_list:
            if check_chara in line:
                get_lines.append(line)
                break

    result['lines'] = get_lines
    result['count'] = len(get_lines)
    module.exit_json(**result)

def main():
    run_module()

if __name__ == '__main__':
    main()

動作確認

grepモジュールをPlaybookで使用して、動作確認を行います
以下の形式でgrepモジュールのパラメータを指定します

パラメータ データ型 必須 説明
src string Yes 文字列を検索するファイルのPath
check_list list Yes 検索対象の文字列のリスト
encoding string No ファイルの文字コード

Playbookは下記を使用します

playbook.yml
---
- name: Check custom module
  hosts: localhost
  tasks:
    - name: Exec grep module
      grep:
        src: "/home/systemi/work/ansible/test_project/test.log"
        check_list:
          - "ORA-"
        encoding: "utf-8"
      register: grep_result

    - name: Check result
      ansible.builtin.debug:
        var: grep_result

srcに指定している test.log の内容は下記の通りです
「ORA-00257:」の行が取得できれば、期待通りの結果になります

test.log
Mon Aug  5 12:34:56 2024
Thread 1 advanced to log sequence 1234 (LGWR switch)
  Current log# 3 seq# 1234 mem# 0: /u01/app/oracle/oradata/ORCL/redo03.log
Mon Aug  5 12:35:00 2024
Errors in file /u01/app/oracle/diag/rdbms/orcl/ORCL/trace/alert_orcl.log:
ORA-00257: archiver error. Connect internal only, until freed.
Mon Aug  5 12:40:00 2024
Instance shutdown (abort)

実行結果
image.png
「ORA-00257:」の行が表示され、期待通りの結果となりました。



続いて、srcのファイルの文字コードをshift-jisへ変更して確認してみます。
使用するPlaybookと検索対象のログファイルは以下の通りです。

playbook.yml
---
- name: Check custom module
  hosts: localhost
  tasks:
    - name: Exec grep module
      grep:
        src: "/home/systemi/work/ansible/test_project/test.log"
        check_list:
          - "重大"
          - "警告"
        encoding: "shift-jis"
      register: grep_result

    - name: Check result
      ansible.builtin.debug:
        var: grep_result
test.log
2024/12/1 10:00:00 情報: システムが正常に起動しました
2024/12/1 10:05:00 警告: ディスクの空き容量が少なくなっています
2024/12/1 10:10:00 重大: ファイルが見つかりません
2024/12/1 10:15:00 情報: ユーザー(test_user)が作成されました
2024/12/1 10:20:00 重大: データベース接続に失敗しました

ログファイルから「重大」か「警告」を含む行が取得できれば期待通りの結果になります。

実行結果
image.png
「重大」、「警告」を含む行のみ表示され、期待通りの結果となりました。

さいごに

今回はPythonを使用してAnsibleのカスタムモジュールを作成しました。
まだまだ学習中ですので、少しずつできる幅を増やしていきたいと思います。

参考文献

Ansible documentation

44
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?