Ansible拡張まとめ

22704 ワード

Ansibleの概要
AnsibleはPythonが開発したメンテナンスツールで、作業にはAnsibleに触れる必要があり、Ansibleに統合されることが多いため、Ansibleに対する理解が増えています.
Ansibleとは何なのでしょうか?私の理解では、サーバにログインして、いくつかの操作を完了するためにコマンドを実行する必要がありました.Ansibleは私たちの代わりにそれらのコマンドを実行します.また,Ansibleにより複数の機器を制御し,機器上でタスクの編成と実行を行うことができ,Ansibleではplaybookと呼ぶ.
では、Ansibleはどうやってやったのでしょうか.簡単に言えば、Ansibleは私たちが実行するコマンドをスクリプトを生成し、sftpを通じてコマンドを実行するサーバにスクリプトをアップロードし、sshプロトコルを通じてスクリプトを実行し、実行結果を返します.
では、Ansibleは具体的にどうやってやったのでしょうか.次に、モジュールとプラグインから、Ansibleがモジュールの実行をどのように完了するかを見てみましょう.
PS:以下の分析はすべてAnsibleに対していくつかの具体的な使用経験があった後に、ソースコードを読むことを通じて更に得た実行結論なので、本文を見る時、Ansibleに対して一定の理解がある基礎の上で創立することを望んで、少なくともAnsibleのいくつかの概念に対して解があって、例えばinventory、module、playbooksなど
Ansibleモジュール
モジュールはAnsibleが実行する最小単位であり、Pythonで作成してもよいし、Shellで作成してもよいし、他の言語で作成してもよい.モジュールには、特定の操作手順と実際の使用に必要なパラメータが定義されています.
実行されるスクリプトは、モジュールに基づいて実行可能なスクリプトを生成します.
では、Ansibleはどのようにしてこのスクリプトをサーバにアップロードし、結果を取得しますか?
Ansibleカード
接続プラグイン
プラグインを接続し、指定したsshパラメータに基づいて指定したサーバを接続し、実際にコマンドを実行するインタフェースを切断します.
shellプラグイン
コマンドプラグインは、shタイプに応じてconnectionで実行するコマンドを生成します.
strategyプラグイン
ポリシープラグインを実行します.デフォルトはリニアプラグインです.タスクの次のタスクが下に実行され、このプラグインはタスクを実行器に捨てて実行します.
Actionプラグイン
アクションプラグインは、実質的にタスクモジュールのすべての動作であり、ansibleのモジュールに特に作成されたactionプラグインがない場合、デフォルトではnormalまたはasync(この2つはモジュールがasyncかどうかによって選択される)であり、normalおよびasyncで定義されているのがモジュールの実行ステップである.たとえば、ローカルでテンポラリファイルを作成したり、テンポラリファイルをアップロードしたり、スクリプトを実行したり、スクリプトを削除したりします.すべてのモジュールに特別なステップを追加したい場合は、actionプラグインを追加することで拡張できます.
Ansible実行モジュールフロー
  • ansibleコマンドは実質的にansible/cli/adhocを通過する.pyを実行し、パラメータ情報を収集します.
  • Play情報を設定し、TaskQueueManagerでrun,
  • を行います.
  • TaskQueueManagerにはInventory(ノードウェアハウス)、variable_manager(収集変数)、options(コマンドラインで指定したパラメータ)、stdout_callback(コールバック関数)
  • task_queue_manager.pyでrunを見つける
  • 初期化時にキュー
  • が設定.
  • はoptions,,variable_に基づいてmanager,passwordsなどの情報はPlayContext情報(playbooks/playcontext.py)
  • に設定されている.
  • プラグイン情報callback_を設定するloader(コールバック)、strategy_loader(実行ポリシー)、module_loader(タスクモジュール)
  • strategyを通過loader(strategyプラグイン)のrun(デフォルトのstrategyタイプはlinear、線形実行)は、すべてのタスクを順番に実行します(1つのモジュールを実行し、複数のタスクを実行する可能性があります)
  • strategy_loaderプラグインrunの後、actionタイプが判断されます.metaタイプの場合は個別に実行され(具体的なansibleモジュールでない場合)、他のモジュールの場合はキュー_にロードされます.queue_task
  • はキュー内でWorkerProcessを呼び出して処理し、workerprocessの実際のrunの後、TaskExecutorを使用して
  • を実行する.
  • TaskExecutorでconnectionプラグインが設定され、taskのタイプ(モジュール.またはincludeなど)に基づいてactionプラグインが取得されます.対応するモジュールです.モジュールにカスタム実行がある場合はカスタムactionが実行され、ない場合はnormalまたはasyncが使用されます.これはタスクのasync属性かどうかに基づいて
  • が決定されます.
  • は、Actionプラグインにおいて実行の順序と、例えば一時ディレクトリの生成、一時スクリプトの生成などの具体的な動作を定義しているので、統合モードで追加の処理を統合する場合には、Actionのメソッド
  • を書き換えることができる.
  • Connectionプラグインによりアクションを実行する各操作ステップ
  • .

    拡張Ansibleインスタンス
    実行ノードPython環境拡張
    実際のニーズでは、拡張されたいくつかのAnsibleモジュールでは、3つのライブラリを使用する必要がありますが、各ノードにこれらのライブラリをインストールするのは管理しにくい場合があります.ansible実行モジュールの本質はノードのpython環境で生成されたスクリプトを実行することであるので,ノード上のPython環境を指定し,ローカルエリアネットワーク内のpython環境をnfsとして共有することを提案する.Actionプラグインを拡張することで、ノードにnfsをマウントし、実行が終了した後にノードのnfsをアンインストールします.具体的な実施手順は以下の通りである.
    拡張コード:
    ActionBaseのexecuteを書き換えるmoduleメソッド
    # execute_module
    
    from __future__ import (absolute_import, division, print_function)
    __metaclass__ = type
    
    import json
    import pipes
    
    from ansible.compat.six import text_type, iteritems
    
    from ansible import constants as C
    from ansible.errors import AnsibleError
    from ansible.release import __version__
    
    try:
        from __main__ import display
    except ImportError:
        from ansible.utils.display import Display
        display = Display()
    
    
    class MagicStackBase(object):
    
        def _mount_nfs(self, ansible_nfs_src, ansible_nfs_dest):
            cmd = ['mount',ansible_nfs_src, ansible_nfs_dest]
            cmd = [pipes.quote(c) for c in cmd]
            cmd = ' '.join(cmd)
            result = self._low_level_execute_command(cmd=cmd, sudoable=True)
            return result
    
        def _umount_nfs(self, ansible_nfs_dest):
            cmd = ['umount', ansible_nfs_dest]
            cmd = [pipes.quote(c) for c in cmd]
            cmd = ' '.join(cmd)
            result = self._low_level_execute_command(cmd=cmd, sudoable=True)
            return result
    
        def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, delete_remote_tmp=True):
            '''
            Transfer and run a module along with its arguments.
            '''
    
            # display.v(task_vars)
    
            if task_vars is None:
                task_vars = dict()
    
            # if a module name was not specified for this execution, use
            # the action from the task
            if module_name is None:
                module_name = self._task.action
            if module_args is None:
                module_args = self._task.args
    
            # set check mode in the module arguments, if required
            if self._play_context.check_mode:
                if not self._supports_check_mode:
                    raise AnsibleError("check mode is not supported for this operation")
                module_args['_ansible_check_mode'] = True
            else:
                module_args['_ansible_check_mode'] = False
    
            # Get the connection user for permission checks
            remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user
    
            # set no log in the module arguments, if required
            module_args['_ansible_no_log'] = self._play_context.no_log or C.DEFAULT_NO_TARGET_SYSLOG
    
            # set debug in the module arguments, if required
            module_args['_ansible_debug'] = C.DEFAULT_DEBUG
    
            # let module know we are in diff mode
            module_args['_ansible_diff'] = self._play_context.diff
    
            # let module know our verbosity
            module_args['_ansible_verbosity'] = display.verbosity
    
            # give the module information about the ansible version
            module_args['_ansible_version'] = __version__
    
            # set the syslog facility to be used in the module
            module_args['_ansible_syslog_facility'] = task_vars.get('ansible_syslog_facility', C.DEFAULT_SYSLOG_FACILITY)
    
            # let module know about filesystems that selinux treats specially
            module_args['_ansible_selinux_special_fs'] = C.DEFAULT_SELINUX_SPECIAL_FS
    
            (module_style, shebang, module_data) = self._configure_module(module_name=module_name, module_args=module_args, task_vars=task_vars)
            if not shebang:
                raise AnsibleError("module (%s) is missing interpreter line" % module_name)
    
            # get nfs info for mount python packages
            ansible_nfs_src = task_vars.get("ansible_nfs_src", None)
            ansible_nfs_dest = task_vars.get("ansible_nfs_dest", None)
    
            # a remote tmp path may be necessary and not already created
            remote_module_path = None
            args_file_path = None
            if not tmp and self._late_needs_tmp_path(tmp, module_style):
                tmp = self._make_tmp_path(remote_user)
    
            if tmp:
                remote_module_filename = self._connection._shell.get_remote_filename(module_name)
                remote_module_path = self._connection._shell.join_path(tmp, remote_module_filename)
                if module_style in ['old', 'non_native_want_json']:
                    # we'll also need a temp file to hold our module arguments
                    args_file_path = self._connection._shell.join_path(tmp, 'args')
    
            if remote_module_path or module_style != 'new':
                display.debug("transferring module to remote")
                self._transfer_data(remote_module_path, module_data)
                if module_style == 'old':
                    # we need to dump the module args to a k=v string in a file on
                    # the remote system, which can be read and parsed by the module
                    args_data = ""
                    for k,v in iteritems(module_args):
                        args_data += '%s=%s ' % (k, pipes.quote(text_type(v)))
                    self._transfer_data(args_file_path, args_data)
                elif module_style == 'non_native_want_json':
                    self._transfer_data(args_file_path, json.dumps(module_args))
                display.debug("done transferring module to remote")
    
            environment_string = self._compute_environment_string()
    
            remote_files = None
    
            if args_file_path:
                remote_files = tmp, remote_module_path, args_file_path
            elif remote_module_path:
                remote_files = tmp, remote_module_path
    
            # Fix permissions of the tmp path and tmp files.  This should be
            # called after all files have been transferred.
            if remote_files:
                self._fixup_perms2(remote_files, remote_user)
    
    
            # mount nfs
            if ansible_nfs_src and ansible_nfs_dest:
                result = self._mount_nfs(ansible_nfs_src, ansible_nfs_dest)
                if result['rc'] != 0:
                    raise AnsibleError("mount nfs failed!!! {0}".format(result['stderr']))
    
            cmd = ""
            in_data = None
    
            if self._connection.has_pipelining and self._play_context.pipelining and not C.DEFAULT_KEEP_REMOTE_FILES and module_style == 'new':
                in_data = module_data
            else:
                if remote_module_path:
                    cmd = remote_module_path
    
            rm_tmp = None
            if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp:
                if not self._play_context.become or self._play_context.become_user == 'root':
                    # not sudoing or sudoing to root, so can cleanup files in the same step
                    rm_tmp = tmp
    
            cmd = self._connection._shell.build_module_command(environment_string, shebang, cmd, arg_path=args_file_path, rm_tmp=rm_tmp)
            cmd = cmd.strip()
            sudoable = True
            if module_name == "accelerate":
                # always run the accelerate module as the user
                # specified in the play, not the sudo_user
                sudoable = False
    
    
            res = self._low_level_execute_command(cmd, sudoable=sudoable, in_data=in_data)
    
            # umount nfs
            if ansible_nfs_src and ansible_nfs_dest:
                result = self._umount_nfs(ansible_nfs_dest)
                if result['rc'] != 0:
                    raise AnsibleError("umount nfs failed!!! {0}".format(result['stderr']))
    
            if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp:
                if self._play_context.become and self._play_context.become_user != 'root':
                    # not sudoing to root, so maybe can't delete files as that other user
                    # have to clean up temp files as original user in a second step
                    tmp_rm_cmd = self._connection._shell.remove(tmp, recurse=True)
                    tmp_rm_res = self._low_level_execute_command(tmp_rm_cmd, sudoable=False)
                    tmp_rm_data = self._parse_returned_data(tmp_rm_res)
                    if tmp_rm_data.get('rc', 0) != 0:
                        display.warning('Error deleting remote temporary files (rc: {0}, stderr: {1})'.format(tmp_rm_res.get('rc'), tmp_rm_res.get('stderr', 'No error string available.')))
    
            # parse the main result
            data = self._parse_returned_data(res)
    
            # pre-split stdout into lines, if stdout is in the data and there
            # isn't already a stdout_lines value there
            if 'stdout' in data and 'stdout_lines' not in data:
                data['stdout_lines'] = data.get('stdout', u'').splitlines()
    
            display.debug("done with _execute_module (%s, %s)" % (module_name, module_args))
            return data
    

    normalに統合pyとasync.pyでは、この2つのプラグインをansibleに挿入することを覚えておいてください.cfgでの構成
    from __future__ import (absolute_import, division, print_function)
    __metaclass__ = type
    
    from ansible.plugins.action import ActionBase
    from ansible.utils.vars import merge_hash
    
    from common.ansible_plugins import MagicStackBase
    
    
    class ActionModule(MagicStackBase, ActionBase):
    
        def run(self, tmp=None, task_vars=None):
            if task_vars is None:
                task_vars = dict()
    
            results = super(ActionModule, self).run(tmp, task_vars)
            # remove as modules might hide due to nolog
            del results['invocation']['module_args']
            results = merge_hash(results, self._execute_module(tmp=tmp, task_vars=task_vars))
            # Remove special fields from the result, which can only be set
            # internally by the executor engine. We do this only here in
            # the 'normal' action, as other action plugins may set this.
            #
            # We don't want modules to determine that running the module fires
            # notify handlers.  That's for the playbook to decide.
            for field in ('_ansible_notify',):
                if field in results:
                    results.pop(field)
    
            return results
  • ansibleを配置する.cfgは、拡張されたプラグインをansibleに必要なactionプラグイン
  • として指定する.
  • プラグインの書き換え方法、ポイントはexecute_module
  • 実行コマンドではPython環境を指定する必要があり、nfsマウントおよびアンインストールのパラメータ
  • に必要なパラメータを追加する.
    ansible 51 -m mysql_db -a "state=dump name=all target=/tmp/test.sql" -i hosts -u root -v -e "ansible_nfs_src=172.16.30.170:/web/proxy_env/lib64/python2.7/site-packages ansible_nfs_dest=/root/.pyenv/versions/2.7.10/lib/python2.7/site-packages ansible_python_interpreter=/root/.pyenv/versions/2.7.10/bin/python"