Ansible常见模块总结及LDAP Role 编写与调试

一、Ansible 常见模块使用总结

1. command 模块

Ansible 的默认模块,用于在远程主机上执行简单的 Linux 命令。

特点:

  • 不通过 shell 解析,直接执行命令
  • 不支持管道符 |、重定向 ><、变量 $HOME、分号 ;、与符号 & 等特殊符号
  • 安全性较高,可防止命令注入
  • 不具备幂等性

常用参数:

参数 说明
chdir 执行命令前,先切换到指定目录
creates 如果指定文件存在,则不执行命令
removes 如果指定文件不存在,则不执行命令
free_form 要执行的命令本身

使用示例:

复制代码

bash

代码解读

复制代码

# 查看主机名 ansible all -m command -a "hostname" ​ # 创建目录(简单命令优先使用command) ansible node2 -m command -a 'mkdir -p /tmp/mydir'[reference:13] ​ # 先切换目录再执行 ansible all -m command -a "chdir=/tmp ls -l" ​ # 如果文件已存在则跳过 ansible all -m command -a "creates=/etc/my.conf touch /etc/my.conf"


2. shell 模块

command 模块的"升级版",通过远程主机的 /bin/sh 来执行命令。

特点:

  • 使用完整的 shell 解析,支持管道、重定向、环境变量等
  • 存在命令注入风险,需要严格验证输入
  • 不具备幂等性
  • 可通过 executable 参数指定其他 shell 解释器

使用示例:

复制代码

bash

代码解读

复制代码

# 使用管道统计(command无法实现) ansible node2 -m shell -a 'grep "error" /var/log/dmesg | wc -l'[reference:22] ​ # 使用环境变量 ansible web -m shell -a "cd $HOME; pwd"[reference:23] ​ # 使用重定向 ansible web -m shell -a "echo 'hello' > /tmp/test.txt" ​ # 指定解释器 ansible all -m shell -a "executable=/bin/bash echo $HOME"

Playbook 中使用: l

复制代码

yaml

代码解读

复制代码

- name: 统计日志错误行数 hosts: node2 tasks: - name: 使用shell模块统计 shell: grep 'error' /var/log/messages | wc -l register: error_count - name: 打印结果 debug: msg: "错误行数为:{``{ error_count.stdout }}"[reference:24]


3. 其他常用模块

模块 功能 示例
ping 测试主机连通性 ansible webservers -m ping
copy 从主控端拷贝文件到远程主机 ansible webservers -m copy -a "src=/root/tcp dest=/tmp/ mode=600"
file 管理文件/目录属性(权限、所有者等) ansible all -m file -a "path=/tmp/test state=directory mode=755"
template 使用 Jinja2 模板生成配置文件 ansible all -m template -a "src=nginx.conf.j2 dest=/etc/nginx/nginx.conf"
fetch 从远程主机拉取文件到本地 ansible all -m fetch -a "src=/var/log/syslog dest=/backup/"
script 在远程主机执行本地脚本 ansible all -m script -a "/server/scripts/test.sh"
hostname 修改远程主机的主机名 ansible 目标ip -m hostname -a 'name=my.cluster.com'
yum/apt 包管理(具备幂等性) ansible all -m yum -a "name=httpd state=present"
service 管理系统服务 ansible all -m service -a "name=httpd state=started enabled=yes"

4. command vs shell 选择建议

| 场景 | 推荐模块 | 原因 |
|-------------------------|-----------------------|-----------------------|-------------|
| 简单命令(ls、mkdir、hostname) | command | 更安全,防止注入 |
| 需要使用管道、重定向 | shell | command 不支持 |
| 需要使用环境变量 | shell | command 不支持 $HOME 等 |
| 命令中包含 ` | ><;&` | shell | command 不支持 |
| 不确定时 | shell | 兼容性更广,但需注意安全 |

注意 :shell 和 command 模块都不具备幂等性。如需要实现幂等,可结合 createsremoves 参数进行判断。


二、LDAP Role 编写与调试

以下是一个完整的 OpenLDAP 服务端部署 Role 示例,包含安装、配置、初始化等完整流程。

1. Role 目录结构

复制代码

bash

代码解读

复制代码

roles/openldap/ ├── tasks/ │ ├── main.yml # 主任务入口 │ ├── install.yml # 安装任务 │ ├── configure.yml # 配置任务 │ └── init.yml # 初始化任务 ├── handlers/ │ └── main.yml # 重启等服务处理器 ├── templates/ │ ├── slapd.conf.j2 # OpenLDAP 主配置文件模板 │ └── ldap.conf.j2 # 客户端配置文件模板 ├── vars/ │ └── main.yml # 变量定义(可覆盖) └── defaults/ └── main.yml # 默认变量


2. 默认变量 (defaults/main.yml)

复制代码

yaml

代码解读

复制代码

--- # OpenLDAP 服务端默认配置 ​ # LDAP 基础配置 ldap_domain: "example.com" ldap_organization: "Example Inc." ldap_admin_password: "admin123" # 生产环境建议使用 vault 加密 ​ # 根据域名自动生成 suffix 和 dc ldap_suffix: "dc={``{ ldap_domain.split('.') | join(',dc=') }}" ldap_rootdn: "cn=admin,{``{ ldap_suffix }}" ​ # 端口配置 ldap_port: 389 ldaps_port: 636 ​ # 安装包名称(不同OS不同) openldap_packages: - openldap - openldap-servers - openldap-clients - openldap-devel ​ # 配置文件路径 slapd_config_path: "/etc/openldap/slapd.conf" ldap_config_path: "/etc/openldap/ldap.conf"


3. 主任务入口 (tasks/main.yml)

复制代码

yaml

代码解读

复制代码

--- # 主任务文件:按顺序执行各阶段任务 ​ - name: 导入安装任务 import_tasks: install.yml tags: [ldap, install] ​ - name: 导入配置任务 import_tasks: configure.yml tags: [ldap, configure] ​ - name: 导入初始化任务 import_tasks: init.yml tags: [ldap, init]


4. 安装任务 (tasks/install.yml)

复制代码

yaml

代码解读

复制代码

--- # 安装 OpenLDAP 服务端及相关软件包 ​ - name: 安装 OpenLDAP 软件包 package: name: "{``{ openldap_packages }}" state: present become: yes # package 模块会根据系统自动选择 yum/apt 等包管理器 # 具备幂等性:已安装则跳过 register: install_result tags: [ldap, install] ​ - name: 创建 LDAP 用户和组 group: name: ldap state: present system: yes become: yes ​ - name: 创建 LDAP 用户 user: name: ldap group: ldap system: yes shell: /sbin/nologin home: /var/lib/ldap become: yes # 确保 slapd 进程以专用用户运行,提高安全性 ​ - name: 创建数据目录 file: path: /var/lib/ldap state: directory owner: ldap group: ldap mode: 0750 become: yes


5. 配置任务 (tasks/configure.yml)

复制代码

yaml

代码解读

复制代码

--- # 配置 OpenLDAP 服务端 ​ - name: 生成 slapd.conf 配置文件 template: src: slapd.conf.j2 dest: "{``{ slapd_config_path }}" owner: root group: ldap mode: 0640 become: yes # 使用模板动态生成配置,支持变量替换 # 配置文件变更时触发 handlers 重启服务 notify: restart slapd tags: [ldap, configure] ​ - name: 生成 ldap.conf 客户端配置文件 template: src: ldap.conf.j2 dest: "{``{ ldap_config_path }}" owner: root group: root mode: 0644 become: yes tags: [ldap, configure] ​ - name: 创建日志目录 file: path: /var/log/openldap state: directory owner: ldap group: ldap mode: 0750 become: yes tags: [ldap, configure] ​ - name: 启动并启用 slapd 服务 service: name: slapd state: started enabled: yes become: yes # 确保服务开机自启 tags: [ldap, configure]


6. 初始化任务 (tasks/init.yml)

复制代码

yaml

代码解读

复制代码

--- # 初始化 LDAP 数据库和基础条目 - name: 检查 LDAP 是否已初始化 command: ldapsearch -x -b "{``{ ldap_suffix }}" -LLL register: ldap_check failed_when: false changed_when: false # 通过查询判断是否已存在数据,避免重复初始化 - name: 创建基础 LDIF 文件 template: src: base.ldif.j2 dest: /tmp/base.ldif mode: 0644 become: yes when: ldap_check.rc != 0 # 仅在未初始化时执行 - name: 导入基础 LDIF 数据 command: > ldapadd -x -D "{``{ ldap_rootdn }}" -w "{``{ ldap_admin_password }}" -f /tmp/base.ldif become: yes when: ldap_check.rc != 0 # 使用 ldapadd 命令导入基础组织架构数据 # 注意:command 模块不支持管道,此处无需管道,使用安全 - name: 清理临时文件 file: path: /tmp/base.ldif state: absent become: yes


7. Handlers (handlers/main.yml)

复制代码

yaml

代码解读

复制代码

--- # 服务处理器:在配置文件变更时触发 - name: restart slapd service: name: slapd state: restarted become: yes # 当 slapd.conf 发生变化时,重启服务使配置生效 - name: reload slapd service: name: slapd state: reloaded become: yes # 部分配置变更可通过 reload 实现不中断服务


8. 配置模板 (templates/slapd.conf.j2)

复制代码

bash

代码解读

复制代码

# {``{ ansible_managed }} # OpenLDAP 服务端配置文件 # 基础配置 include /etc/openldap/schema/core.schema include /etc/openldap/schema/cosine.schema include /etc/openldap/schema/inetorgperson.schema include /etc/openldap/schema/nis.schema # 数据库配置 database bdb suffix "{``{ ldap_suffix }}" rootdn "{``{ ldap_rootdn }}" rootpw {``{ ldap_admin_password | password_hash('ssha') }} # 使用 password_hash 过滤器对密码进行 SSHA 加密 # 数据目录 directory /var/lib/ldap # 索引配置 index objectClass eq index uid eq,pres index cn eq,pres,sub index sn eq,pres,sub index mail eq,pres,sub # 访问控制 access to attrs=userPassword by self write by anonymous auth by * none access to * by * read by dn="cn=admin,{``{ ldap_suffix }}" write # 日志配置 loglevel 256


9. 调试与验证

调试方法:

复制代码

yaml

代码解读

复制代码

# 1. 语法检查(playbook 级别) ansible-playbook site.yml --syntax-check # 2. 只运行特定标签的任务 ansible-playbook site.yml --tags ldap -v # 3. 检查模式(dry-run,不实际执行) ansible-playbook site.yml --check # 4. 逐步调试(step 模式) ansible-playbook site.yml --step # 5. 使用 debug 模块打印变量 - name: 调试 LDAP 配置变量 debug: msg: "suffix: {``{ ldap_suffix }}, rootdn: {``{ ldap_rootdn }}" # 6. 验证 LDAP 服务是否正常 ldapsearch -x -b "{``{ ldap_suffix }}" -LLL # 7. 使用 LDAP 调试插件(如使用 microsoft.ad 集合) # microsoft.ad.debug_ldap_client 可检查 LDAP Python 包安装情况[reference:36]


10. 使用 Role 的 Playbook 示例

复制代码

yaml

代码解读

复制代码

--- # site.yml - 部署 OpenLDAP 服务 - name: 部署 OpenLDAP 服务端 hosts: ldap_servers become: yes vars: ldap_domain: "mycompany.com" ldap_organization: "My Company Ltd." ldap_admin_password: "{``{ vault_ldap_password }}" # 从 vault 读取 roles: - role: openldap tags: [ldap, openldap] post_tasks: - name: 验证 LDAP 服务状态 command: systemctl status slapd register: service_status changed_when: false - name: 打印服务状态 debug: msg: "SLAPD 服务状态: {``{ service_status.stdout_lines[0] }}"


关键代码注释总结

代码位置 关键点 说明
defaults/main.yml 变量分层设计 默认值与系统变量分离,便于覆盖
tasks/install.yml package 模块 跨平台包管理,自动适配 yum/apt
tasks/configure.yml template + notify 模板渲染配置,变更时触发 handler 重启
tasks/init.yml command + register + when 通过查询结果判断是否执行初始化,实现幂等
handlers/main.yml handler 分离 仅在变更时触发,避免不必要的服务重启
templates/slapd.conf.j2 password_hash 过滤器 对敏感密码进行 SSHA 加密存储
调试部分 --check / --step / debug 多种调试手段确保 Role 正确性

三、面试高频考察

模块一:核心模块辨析(必问!)

面试官经典三板斧: "command 和 shell 的区别?什么时候用哪个?"

| 对比维度 | command(默认模块) | shell(需显式调用) |
|--------------|--------------------------------------------------------------------------------------------------|-----------------------------------|---------|
| 执行环境 | 直接 fork 执行,不加载系统环境变量 | 通过远程主机的 /bin/sh 执行,加载用户环境 |
| 是否支持特殊符号 | 不支持:` | (管道)、>(重定向)、;&$HOME` | 全支持 |
| 安全性 | (命令注入风险低,参数被转义) | (拼接字符串极易产生注入漏洞) |
| 幂等性 | 不具备(除非结合 creates/removes) | 不具备(除非结合 creates/removes) |
| 面试推荐回答 | "能用 command 坚决不用 shell" 。在实现 chdircreates 等场景时优先选择 command,仅在必须使用管道、重定向或环境变量时降级为 shell。 | |


模块二:幂等性与状态模块(高级工程师的试金石)

面试官常问: "Ansible 如何保证反复执行不报错?"

  • 核心原理 :Ansible 的多数模块(如 copyfileyumservice)本身具备幂等性 ,即只有资源状态与预期不符时才会执行变更(changed 状态)。

  • 针对 Command/Shell 的处理: 由于这两个模块本身不幂等,必须通过参数人工干预:

    • creates:若指定文件已存在,则跳过执行。
    • removes:若指定文件不存在,则跳过执行。
  • LDAP Role 中的高级实践(关键代码注释):

    复制代码

    yaml

    代码解读

    复制代码
    # 通过 register 注册执行结果,再配合 when 条件判断实现幂等 - name: 检查 LDAP 是否已初始化 command: ldapsearch -x -b "{``{ ldap_suffix }}" -LLL register: ldap_check failed_when: false # 即使查询不到(返回非0)也不中断Playbook changed_when: false # 此步骤仅用于探测,不标记为变更 - name: 导入初始化数据 command: ldapadd -x -D "{``{ ldap_rootdn }}" -w "{``{ ldap_admin_password }}" -f /tmp/base.ldif when: ldap_check.rc != 0 # 只有当上一步检测到数据不存在时才执行导入


模块三:变量优先级与 Jinja2 过滤器(必考送分题)

面试官常问: "defaults 和 vars 里的同名变量,谁生效?"

  • 优先级顺序(从低到高)role defaults(最低) → inventory variablesplaybook varsextra vars(最高,命令行-e指定)。

  • LDAP Role 中的安全过滤器 (面试加分项): 在模板 slapd.conf.j2 中,使用 password_hash 过滤器对明文密码加密存储,避免配置文件泄露风险。

    复制代码

    scss

    代码解读

    复制代码
    rootpw {``{ ldap_admin_password | password_hash('ssha') }}

    面试话术 :"生产环境绝不存明文密码,我会结合 ansible-vault 加密变量文件,再配合 password_hash 过滤器写入配置。"


模块四:Handlers 与 notify 的触发机制(踩坑预警)

面试官高频追问: "如果 handler 被多次触发,它会执行几次?"

  • 正确答案只执行一次。Ansible 会在 Playbook 所有 Task 执行完毕后,统一触发 Handlers,且去重后只执行一次。

  • 注意陷阱 :如果某个 Task 使用了 listen 监听通用频道,或 handler 名称重复,只会合并执行一次。

  • Role 中的标准写法

    复制代码

    yaml

    代码解读

    复制代码
    # tasks/configure.yml - name: 生成 slapd.conf template: src=slapd.conf.j2 dest=/etc/openldap/slapd.conf notify: restart slapd # 配置改变时通知 # handlers/main.yml - name: restart slapd service: name=slapd state=restarted


模块五:调试与故障排查实战(体现经验值)

面试场景: "客户环境 Playbook 报错了,你怎么快速定位?"

调试级别 命令/参数 用途
语法检查 ansible-playbook site.yml --syntax-check 最先执行,检查 YAML 缩进和语法错误
干跑模式 ansible-playbook site.yml --check 模拟执行,展示会发生什么变更(需模块支持)
分步执行 ansible-playbook site.yml --step 每执行一个 Task 前都需人工确认,适合排查复杂逻辑
变量调试 在 Playbook 中插入 debug 模块 打印 {``{ ldap_suffix }} 等中间变量值
SSH 连接排错 ansible -m ping -vvv 三级 verbose 输出详细的 SSH 认证过程

模块六:Role 的企业级目录规范(架构能力)

面试官问: "你写的 Role 怎么保证团队协作和维护?"

优秀的 Role 结构必须职责分离(对应之前 LDAP Role 的拆分逻辑):

复制代码

bash

代码解读

复制代码

roles/openldap/ ├── tasks/ │ ├── main.yml # 仅做 import 调度(入口清晰) │ ├── install.yml # 只管 yum/apt 装包 │ ├── configure.yml # 只管 template 渲染 + notify │ └── init.yml # 只管 ldapadd 初始化(含幂等判断) ├── handlers/ # 仅存放重启/重载动作 ├── templates/ # 全部 .j2 后缀模板文件 ├── defaults/ # 最低优先级的默认变量(适合给用户覆盖) └── vars/ # 高优先级固定变量(适合操作系统差异映射)

核心思想 :将"安装"、"配置"、"初始化"解耦,配合 tags(如 --tags install)实现分阶段独立部署。


面试终极加分项(针对 LDAP 场景)

如果面试官追问你写的 LDAP Role,你可以抛出这个亮点:

"我在 init.yml 中没有使用 shell 模块去拼接 ldapadd 命令,而是使用了 command 模块 。因为该命令不涉及管道和重定向,使用 command 不仅更安全,还能避免 Shell 转义带来的特殊字符密码报错问题。 同时,我利用 ldapsearch 的返回码(rc)作为幂等性判断依据,确保 LDAP 基础数据只导入一次,重复执行 Playbook 不会造成数据重复。"