一、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 模块都不具备幂等性。如需要实现幂等,可结合
creates和removes参数进行判断。
二、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”。在实现chdir、creates等场景时优先选择 command,仅在必须使用管道、重定向或环境变量时降级为 shell。 |
模块二:幂等性与状态模块(高级工程师的试金石)
面试官常问:“Ansible 如何保证反复执行不报错?”
核心原理:Ansible 的多数模块(如
copy、file、yum、service)本身具备幂等性,即只有资源状态与预期不符时才会执行变更(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 variables→playbook vars→extra 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 不会造成数据重复。”