使用 Ansible 在 RHEL 上部署和管理文件

AnsibleAnsibleBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

引言

在本实验中,你将学习使用 Ansible 在 Red Hat Enterprise Linux (RHEL) 系统上部署和管理文件的基本技能。你将亲手实践一些最常用且强大的用于文件操作的 Ansible 模块,从基本的文件部署到更高级的内容操作和状态管理。

你将首先使用 ansible.builtin.copy 模块来传输静态文件并设置其属性。接下来,你将使用 lineinfileblockinfile 修改文件内容,并使用 ansible.builtin.template 模块生成自定义的 MOTD。本实验还涵盖了创建符号链接、使用 stat 验证文件状态、使用 fetch 检索日志以及清理已管理的文件,全面概述了 Ansible 的文件管理能力。

使用 ansible.builtin.copy 模块复制静态文件并设置属性

在本步骤中,你将学习如何使用最基础的 Ansible 模块之一:ansible.builtin.copy。此模块用于将文件从你的控制节点(LabEx VM)传输到托管主机上的指定位置。在我们的例子中,托管主机将是 localhost 本身。除了复制文件,copy 模块还允许你精确控制文件的属性,例如其所有者、组和权限模式,这对于正确的系统配置至关重要。

首先,让我们设置项目环境。我们所有的工作都将在 ~/project 目录下进行。

  1. 导航到项目目录并为源文件创建一个子目录。 这是一个保持项目组织的常见做法。

    安装 ansible-core 包。

    sudo dnf install -y ansible-core

    然后,导航到项目目录并为源文件创建一个子目录。

    cd ~/project
    mkdir files
  2. 接下来,创建一个简单的文本文件供我们复制。 我们将使用 cat 命令配合“here document”在 files 目录中创建 info.txt 文件。

    cat << EOF > ~/project/files/info.txt
    This file was deployed by Ansible.
    It contains important system information.
    EOF
  3. 现在,创建一个 Ansible inventory 文件。 inventory 文件告诉 Ansible 要管理哪些主机。对于本次实验,我们将管理本地机器。创建一个名为 inventory.ini 的文件。

    cat << EOF > ~/project/inventory.ini
    localhost ansible_connection=local
    EOF

    在此 inventory 中,localhost 是我们要定位的主机。变量 ansible_connection=local 指示 Ansible 直接在控制节点上执行任务,而无需使用 SSH。

  4. 创建你的第一个 Ansible playbook。 这个 playbook 将包含复制文件的指令。使用 nanocat 创建一个名为 copy_file.yml 的文件。

    nano ~/project/copy_file.yml

    将以下内容添加到文件中。这个 playbook 定义了一个任务:将 info.txt 复制到 /tmp/ 目录并设置其属性。

    ---
    - name: Deploy a static file to localhost
      hosts: localhost
      tasks:
        - name: Copy info.txt and set attributes
          ansible.builtin.copy:
            src: files/info.txt
            dest: /tmp/info.txt
            owner: labex
            group: labex
            mode: "0640"

    让我们分解一下 copy 任务中的参数:

    • src: files/info.txt: 源文件在控制节点上的路径,相对于 playbook 的位置。
    • dest: /tmp/info.txt: 文件将在托管主机上放置的绝对路径。
    • owner: labex: 将文件的所有者设置为 labex 用户。
    • group: labex: 将文件的组设置为 labex 组。
    • mode: '0640': 设置文件的权限。0640 表示所有者可读/写,组可读,其他人无权限。
  5. 使用 ansible-playbook 命令执行 playbook。 -i 标志指定了我们的 inventory 文件。

    ansible-playbook -i inventory.ini copy_file.yml

    你应该会看到类似以下的 playbook 执行成功的输出:

    PLAY [Deploy a static file to localhost] ***************************************
    
    TASK [Gathering Facts] *********************************************************
    ok: [localhost]
    
    TASK [Copy info.txt and set attributes] ****************************************
    changed: [localhost]
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  6. 最后,验证文件是否已正确复制 并具有正确的属性。使用 ls -l 命令检查权限、所有者和组。

    ls -l /tmp/info.txt

    输出应显示 labex 是所有者和组,权限为 -rw-r-----

    -rw-r----- 1 labex labex 72 Jul 10 14:30 /tmp/info.txt

    你还可以查看文件的内容,以确保它已完整复制。

    cat /tmp/info.txt
    This file was deployed by Ansible.
    It contains important system information.

你已成功使用 ansible.builtin.copy 模块在本地系统上部署了文件并配置了其属性。

使用 lineinfileblockinfile 修改文件内容

在本步骤中,你将学习如何在不替换整个文件的情况下修改托管主机上的现有文件。Ansible 为此目的提供了强大的模块:ansible.builtin.lineinfile 用于管理单行,ansible.builtin.blockinfile 用于管理多行文本块。这些模块在更改配置文件或向日志文件添加条目等任务中非常有用。

我们将继续使用你在上一步创建的 info.txt 文件,该文件位于 /tmp/info.txt

  1. 首先,确保你在项目目录中。

    cd ~/project
  2. 创建一个名为 modify_file.yml 的新 playbook。这个 playbook 将包含两个任务:一个用于添加单行,另一个用于向现有文件添加文本块。

    nano ~/project/modify_file.yml
  3. 将以下内容添加到你的 modify_file.yml playbook 中。 这个 playbook 目标是 localhost,并使用 lineinfileblockinfile/tmp/info.txt 追加内容。

    ---
    - name: Modify an existing file
      hosts: localhost
      tasks:
        - name: Add a single line of text to a file
          ansible.builtin.lineinfile:
            path: /tmp/info.txt
            line: This line was added by the lineinfile module.
            state: present
    
        - name: Add a block of text to an existing file
          ansible.builtin.blockinfile:
            path: /tmp/info.txt
            block: |
              ## BEGIN ANSIBLE MANAGED BLOCK
              This block of text consists of two lines.
              They have been added by the blockinfile module.
              ## END ANSIBLE MANAGED BLOCK
            state: present

    让我们来分析一下使用的模块:

    • ansible.builtin.lineinfile: 此模块确保文件中存在特定行。如果该行已存在,Ansible 将不执行任何操作,从而使任务具有幂等性。
      • path: 要修改的文件。
      • line: 要确保存在于文件中的文本行。
      • state: present: 这确保行存在。你可以使用 state: absent 来删除它。
    • ansible.builtin.blockinfile: 此模块管理一个文本块,该文本块由标记行(例如 ## BEGIN ANSIBLE MANAGED BLOCK)包围。这对于管理配置部分非常理想。
      • path: 要修改的文件。
      • block: 要插入的多行字符串。| 是 YAML 中用于字面量块的语法,会保留换行符。
      • state: present: 确保块存在。
  4. 使用 ansible-playbook 命令和你的 inventory.ini 文件执行 playbook。

    ansible-playbook -i inventory.ini modify_file.yml

    输出将显示两个任务都对文件进行了更改。

    PLAY [Modify an existing file] *************************************************
    
    TASK [Gathering Facts] *********************************************************
    ok: [localhost]
    
    TASK [Add a single line of text to a file] *************************************
    changed: [localhost]
    
    TASK [Add a block of text to an existing file] *********************************
    changed: [localhost]
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  5. 最后,通过查看 /tmp/info.txt 的内容来验证更改。

    cat /tmp/info.txt

    你应该会看到原始内容,然后是新添加的行和新的文本块。

    This file was deployed by Ansible.
    It contains important system information.
    This line was added by the lineinfile module.
    ## BEGIN ANSIBLE MANAGED BLOCK
    This block of text consists of two lines.
    They have been added by the blockinfile module.
    ## END ANSIBLE MANAGED BLOCK

    如果你再次运行 playbook,Ansible 将报告 ok=3changed=0,因为内容已存在,这展示了这些模块的幂等性。

使用 ansible.builtin.template 模块生成自定义 MOTD

在本步骤中,你将从复制静态文件进阶到使用 ansible.builtin.template 模块生成动态文件。该模块利用 Jinja2 模板引擎,根据 Ansible 从托管主机收集的变量和系统信息(称为“facts”)来创建定制化文件。我们将创建一个动态的每日消息 (MOTD),以显示特定于系统的相关信息。

  1. 首先,确保你在 ~/project 目录中,并创建一个专门用于存放模板的子目录。 将 Jinja2 模板存储在 templates 目录中是 Ansible 的标准最佳实践。

    cd ~/project
    mkdir templates
  2. 接下来,创建 Jinja2 模板文件。 这个文件 motd.j2 将包含我们 MOTD 的结构,其中包含动态数据的占位符。.j2 扩展名是 Jinja2 模板的常用约定。

    nano ~/project/templates/motd.j2

    将以下内容添加到文件中。请注意 {{ ... }} 语法,它表示一个变量或 fact 的占位符。

    #################################################################
    ##          Welcome to {{ ansible_facts['fqdn'] }}
    #
    ## This is a {{ ansible_facts['distribution'] }} system.
    ## System managed by Ansible.
    #
    ## For support, contact: {{ admin_email }}
    #################################################################

    在此模板中:

    • {{ ansible_facts['fqdn'] }} 将被替换为主机的完全限定域名 (Fully Qualified Domain Name)。
    • {{ ansible_facts['distribution'] }} 将被替换为 Linux 发行版的名称(例如 RedHat)。
    • {{ admin_email }} 是一个自定义变量,我们将在 playbook 中定义它。
  3. 现在,创建一个名为 template_motd.yml 的新 playbook。此 playbook 将使用模板生成 /etc/motd 文件。

    nano ~/project/template_motd.yml

    添加以下内容。此 playbook 需要提升的权限 (become: true) 才能写入 /etc 目录。它还定义了自定义的 admin_email 变量。

    ---
    - name: Deploy a custom MOTD from a template
      hosts: localhost
      become: true
      vars:
        admin_email: [email protected]
      tasks:
        - name: Generate /etc/motd from template
          ansible.builtin.template:
            src: templates/motd.j2
            dest: /etc/motd
            owner: root
            group: root
            mode: "0644"

    此 playbook 中的关键参数:

    • become: true: 这告诉 Ansible 使用 sudo 来执行任务,这对于写入 /etc/motd 是必需的。
    • vars: 此部分是我们定义自定义变量(如 admin_email)的地方。
    • ansible.builtin.template: 处理 Jinja2 模板的模块。src 指向我们的 .j2 文件,而 dest 是托管主机上的目标文件。
  4. 执行 playbook。

    ansible-playbook -i inventory.ini template_motd.yml

    输出应确认任务已成功完成。

    PLAY [Deploy a custom MOTD from a template] ************************************
    
    TASK [Gathering Facts] *********************************************************
    ok: [localhost]
    
    TASK [Generate /etc/motd from template] ****************************************
    changed: [localhost]
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  5. 验证结果。 检查新生成的 /etc/motd 文件的内容。

    cat /etc/motd

    你将看到渲染后的输出,其中 Jinja2 占位符已被实际的系统 facts 和你定义的自定义变量替换。fqdn 将与你的实验环境的主机名匹配。

    #################################################################
    ##          Welcome to host.labex.io
    #
    ## This is a RedHat system.
    ## System managed by Ansible.
    #
    ## For support, contact: [email protected]
    #################################################################

你现在已经成功使用模板创建了一个定制化文件,这是基础设施自动化中的一项核心技能。

使用 copyfile 模块部署支持文件并创建符号链接

在本步骤中,你将结合 copy 模块的知识和一个新的、功能强大的模块:ansible.builtin.filecopy 用于传输内容,而 file 则用于管理托管主机上文件、目录和符号链接的状态。你将使用它来创建目录、设置权限,以及(在本实验中最重要的)创建符号链接。

我们的场景是配置系统显示的登录前消息。在许多 Linux 系统中,/etc/issue 会显示给本地终端用户,而 /etc/issue.net 会显示给远程用户(例如通过 SSH)。我们将部署一个单独的 issue 文件,然后创建一个符号链接,使 /etc/issue.net 指向 /etc/issue,从而确保它们始终显示相同的消息。

  1. 首先,确保你在 ~/project 目录中,并为你的 issue 消息创建源文件。 我们将把这个文件放在你之前创建的 files 子目录中。

    cd ~/project
    cat << EOF > ~/project/files/issue
    Authorized access only.
    All connections are logged and monitored.
    EOF
  2. 创建一个名为 deploy_issue.yml 的新 playbook。此 playbook 将包含两个任务:一个用于复制 issue 文件,另一个用于创建符号链接。

    nano ~/project/deploy_issue.yml
  3. 将以下内容添加到你的 deploy_issue.yml playbook 中。 此 playbook 需要提升的权限 (become: true) 才能管理 /etc/ 目录中的文件。

    ---
    - name: Configure system issue files
      hosts: localhost
      become: true
      tasks:
        - name: Copy custom /etc/issue file
          ansible.builtin.copy:
            src: files/issue
            dest: /etc/issue
            owner: root
            group: root
            mode: "0644"
    
        - name: Ensure /etc/issue.net is a symlink to /etc/issue
          ansible.builtin.file:
            src: /etc/issue
            dest: /etc/issue.net
            state: link
            force: yes

    让我们分析新的 ansible.builtin.file 任务:

    • src: /etc/issue: 当 statelink 时,src 指定符号链接应指向的文件。
    • dest: /etc/issue.net: 这是创建符号链接本身的目标路径。
    • state: link: 这个关键参数告诉 file 模块创建符号链接,而不是普通文件或目录。
    • force: yes: 这是一个有用的选项,可以确保幂等性。如果 /etc/issue.net 已作为普通文件存在,Ansible 将删除它并创建链接。如果没有 force: yes,在这种情况下 playbook 将会失败。
  4. 执行 playbook。

    ansible-playbook -i inventory.ini deploy_issue.yml

    输出将显示两个任务都成功地进行了更改。

    PLAY [Configure system issue files] ********************************************
    
    TASK [Gathering Facts] *********************************************************
    ok: [localhost]
    
    TASK [Copy custom /etc/issue file] *********************************************
    changed: [localhost]
    
    TASK [Ensure /etc/issue.net is a symlink to /etc/issue] ************************
    changed: [localhost]
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  5. 使用 ls -l 命令验证结果。 此命令提供详细列表,清晰地显示符号链接。

    ls -l /etc/issue /etc/issue.net

    输出应显示 /etc/issue 是一个普通文件,而 /etc/issue.net 是一个指向它的符号链接。/etc/issue.net 权限开头的 l 表示它是一个链接。

    -rw-r--r--. 1 root root 65 Jul 10 15:00 /etc/issue
    lrwxrwxrwx. 1 root root 10 Jul 10 15:00 /etc/issue.net -> /etc/issue

你现在已经成功部署了一个配置文件,并使用 ansible.builtin.file 模块创建了一个符号链接,这是管理系统配置的一种常见且强大的模式。

使用 stat 验证文件状态并使用 fetch 检索日志

在本步骤中,你将学习两个重要的数据收集模块:ansible.builtin.statansible.builtin.fetchstat 模块用于检查托管主机上文件或目录的状态——例如,查看它是否存在、其权限是什么,或者最后修改时间。它不会更改任何内容,因此非常适合用于检查和条件逻辑。fetch 模块的作用与 copy 相反:它从托管主机检索文件,并将它们保存在你的控制节点上,这对于备份配置或收集日志文件进行分析非常理想。

我们将创建一个 playbook,该 playbook 首先检查你之前创建的 /etc/motd 文件是否存在,然后将 DNF 包管理器日志文件 (/var/log/dnf.log) 检索到你的 LabEx VM 上的本地目录。

  1. 首先,确保你在 ~/project 目录中,并创建一个新的子目录来存储你将要检索的文件。

    cd ~/project
    mkdir fetched_logs
  2. 创建一个名为 check_and_fetch.yml 的新 playbook。此 playbook 将包含检查文件和检索日志的任务。

    nano ~/project/check_and_fetch.yml
  3. 将以下内容添加到你的 check_and_fetch.yml playbook 中。 此 playbook 使用 stat 获取文件详细信息,使用 register 将这些详细信息存储在变量中,使用 debug 显示变量,并使用 fetch 检索日志文件。

    ---
    - name: Check file status and fetch logs
      hosts: localhost
      become: true
      tasks:
        - name: Check if /etc/motd exists
          ansible.builtin.stat:
            path: /etc/motd
          register: motd_status
    
        - name: Display stat results
          ansible.builtin.debug:
            var: motd_status.stat
    
        - name: Fetch the dnf log file from managed host
          ansible.builtin.fetch:
            src: /var/log/dnf.log
            dest: fetched_logs/
            flat: yes

    让我们分解一下关键概念:

    • register: motd_status: 这是 Ansible 的一个关键功能。它获取任务的整个输出,并将其保存到一个名为 motd_status 的新变量中。
    • ansible.builtin.debug: 此模块用于在 playbook 运行期间打印值。在这里,我们打印了已注册变量 (motd_status.stat) 中的 stat 对象,以查看文件的属性。
    • ansible.builtin.fetch: 此模块从托管主机检索文件。
      • src: 要从托管主机检索的文件的路径。
      • dest: 控制节点(你的 LabEx VM)上保存文件的目录。
      • flat: yes: 默认情况下,fetch 会创建与主机和源路径匹配的子目录结构。flat: yes 通过将文件直接复制到 dest 目录而不添加任何额外的子目录来简化此过程。
  4. 执行 playbook。 由于我们要读取系统日志文件,因此使用 become: true 来获取必要的权限。

    ansible-playbook -i inventory.ini check_and_fetch.yml

    输出将显示 debug 任务中 stat 检查的结果,然后是 fetch 任务。

    PLAY [Check file status and fetch logs] ****************************************
    
    TASK [Gathering Facts] *********************************************************
    ok: [localhost]
    
    TASK [Check if /etc/motd exists] ***********************************************
    ok: [localhost]
    
    TASK [Display stat results] ****************************************************
    ok: [localhost] => {
        "motd_status.stat": {
            "exists": true,
            "gid": 0,
            "isreg": true,
            "mode": "0644",
            "path": "/etc/motd",
            ...
        }
    }
    
    TASK [Fetch the dnf log file from managed host] ********************************
    changed: [localhost]
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  5. 验证日志文件是否已成功检索。 列出 fetched_logs 目录的内容。

    ls -l ~/project/fetched_logs/

    你应该会看到 dnf.log 文件,现在它已本地存储在你的控制节点上。

    total 4
    -rw-r--r--. 1 labex labex 1234 Jul 10 15:30 dnf.log

你现在已经学会了如何在不进行更改的情况下检查文件属性,以及如何将托管系统中的重要文件检索回你的控制节点。

使用 file 模块清理托管主机文件

在最后这个步骤中,你将学习如何使用 ansible.builtin.file 模块来确保文件和目录 不存在 于系统上。配置管理的一个关键部分不仅在于创建和修改资源,还在于清理它们。通过将 state 参数设置为 absent,你可以指示 Ansible 删除文件、符号链接甚至整个目录。

为了完成本次实验,我们将编写一个单一的“清理”playbook,它将删除我们在前几个步骤中创建的所有文件:/tmp/info.txt/etc/motd/etc/issue 以及 /etc/issue.net 符号链接。

  1. 首先,确保你在 ~/project 目录中。

    cd ~/project
  2. 创建一个名为 cleanup.yml 的新 playbook。此 playbook 将包含恢复我们更改所需的所有任务。

    nano ~/project/cleanup.yml
  3. 将以下内容添加到你的 cleanup.yml playbook 中。 此 playbook 使用任务列表,每个任务都针对我们创建的文件之一。请注意,become: true 是在 play 级别设置的,因此所有任务都将以提升的权限运行。

    ---
    - name: Clean up managed files from the system
      hosts: localhost
      become: true
      tasks:
        - name: Remove the temporary info file
          ansible.builtin.file:
            path: /tmp/info.txt
            state: absent
    
        - name: Remove the custom MOTD file
          ansible.builtin.file:
            path: /etc/motd
            state: absent
    
        - name: Remove the custom issue file
          ansible.builtin.file:
            path: /etc/issue
            state: absent
    
        - name: Remove the issue.net symbolic link
          ansible.builtin.file:
            path: /etc/issue.net
            state: absent

    此 playbook 的关键在于每个任务中的 state: absent 参数。这告诉 file 模块确保指定 path 的项不存在。如果找到文件,它将删除它。如果文件已不存在,它将不做任何操作,从而保持幂等性。

  4. 执行清理 playbook。

    ansible-playbook -i inventory.ini cleanup.yml

    输出将显示每个任务通过删除文件成功进行了更改。

    PLAY [Clean up managed files from the system] **********************************
    
    TASK [Gathering Facts] *********************************************************
    ok: [localhost]
    
    TASK [Remove the temporary info file] ******************************************
    changed: [localhost]
    
    TASK [Remove the custom MOTD file] *********************************************
    changed: [localhost]
    
    TASK [Remove the custom issue file] ********************************************
    changed: [localhost]
    
    TASK [Remove the issue.net symbolic link] **************************************
    changed: [localhost]
    
    PLAY RECAP *********************************************************************
    localhost                  : ok=5    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  5. 验证文件是否已被删除。 你可以使用 ls 命令检查它们是否存在。该命令将报告无法访问它们,因为它们已不存在。

    ls /tmp/info.txt /etc/motd /etc/issue /etc/issue.net

    预期的输出是一系列错误,这证实了清理工作已成功完成。

    ls: cannot access '/tmp/info.txt': No such file or directory
    ls: cannot access '/etc/motd': No such file or directory
    ls: cannot access '/etc/issue': No such file or directory
    ls: cannot access '/etc/issue.net': No such file or directory

你现在已经成功使用 Ansible 删除了文件并清理了系统,完成了从创建到删除的文件管理全生命周期。

总结

在本实验中,你学习了使用 Ansible 在 RHEL 系统上进行文件管理的基础知识。你首先使用 ansible.builtin.copy 模块将静态文件传输到托管主机,同时设置了特定的所有权和权限。然后,你通过使用 lineinfile 确保特定行存在,并使用 blockinfile 管理多行文本块,探索了如何修改现有文件。涵盖的一项关键技能是使用 ansible.builtin.template 模块和 Jinja2 语法生成动态文件内容,以创建包含系统事实的自定义每日消息 (MOTD)。

此外,你还练习了使用 ansible.builtin.file 模块部署支持文件和创建符号链接。为了确保你的部署成功,你使用了 stat 模块来验证文件的状态和属性,并使用 fetch 模块从托管主机检索文件(如日志)到控制节点。最后,你学习了如何执行清理操作,通过使用带有 state: absentfile 模块来删除在整个实验过程中创建的文件和目录,从而确保托管主机处于干净的状态。