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 不会造成数据重复。"

相关推荐
有毒的教程4 天前
Ansible批量操作自动化完整教程:批量部署服务、配置同步、软件更新实战
运维·自动化·ansible
Hy行者勇哥9 天前
用Cursor智能编写Ansible/Terraform脚本,打通CI/CD链路
ci/cd·ansible·terraform
芷栀夏11 天前
飞牛NAS怎么部署Immich?家庭照片自动备份与远程访问教程
服务器·nginx·ansible
祺风挽楠1 个月前
ansible编辑
网络·ansible
芳心粽伙饭1 个月前
Ansible课后作业
ansible
烁3471 个月前
Ansible初识
ansible
烁3471 个月前
Ansible安装部署调试
ansible
烁3471 个月前
Ansible命令
ansible
小义_1 个月前
【Ansible】(三)基础配置与连接设置
云原生·ansible