Ansible之剧本和角色
playbook剧本
YAML简介
YAML(YAML Ain't Markup Language的缩写)是一种人类可读的完整的数据序列化语言。
YAML 官网首页就很 YAML,这很有意思。 The Official YAML Web Site
通常用作软件的配置文件;
YAML基本语法规则(基于显式指示符的表示范围的Flow Style):
-
流序列:以逗号分隔的列表形式写入,并置于方括号[]内;
-
流映射:以逗号分隔的列表形式写入,并置于大括号{}内;
YAML支持3种基本数据类型:
-
标量(Scalar):原子数据类型,如:字符串(String)、数字(Numbers)、布尔值(Boolean)和空值(null)等;
-
序列(Sequence):节点列表,类似某些编程语言的数组(Array)、列表(List)等;
-
映射(Mapping):节点到节点的映射,键值对,类似某些编程语言的哈希(Hash)、哈希映射(Hash Map)、字典(Dict)、对象(Objects)等;
与许多编程语言不同,键key可以是序列或映射。 一个YAML文件或流可以包含多个文档Documents 文档可以显式地以---开头(可选的)。在规范中称此为指令结束标记,但实际使用中更多称为文档开始标记; 文档可以显式地以...结尾(可选的)。在规范中称此为文档结束标记,如果文档没有指令,可以省略---,仅用...来分割多个文档; 如果YAML文件或流仅包含一个文档,那么--- ...可以省略
YAML支持两个指令: 版本指令:指令放在---之前行,如 %YAML 1.2 用于告知 YAML 处理器使用哪个版本处理当前文档; 标签指令:为用户提供自定义处理程序的速记标记的方法,一般很少使用
-
yaml格式:以.yaml或者.yml结尾
root@server-21:~# cat /etc/netplan/00-installer-config.yaml# This is the network config written by 'subiquity'network:ethernets:ens33:dhcp4: nodhcp6: noaddresses: [192.168.221.21/24]gateway4: 192.168.221.2nameservers:addresses: [114.114.114.114,8.8.8.8]version: 2
yaml格式要求:
1.严格区分大小写
2.缩进统一,同一级别配置要对齐
-
缩进不允许使用Tab制表符,只允许使用空格字符;
-
缩进的空格数不重要,但官方推荐使用2个空格字符;
-
冒号后面至少要有一个空格
Ansible案例
前提条件:Ubuntu配置好四台设备
# 前提是每台机子都apt update192.168.221.120 manage01192.168.221.21 node1192.168.221.22 node2192.168.221.23 node3#(仅管理机)# ssh免密(输入yes和密码)ssh-keygenssh-copy-id root@node1ssh-copy-id root@node2ssh-copy-id root@node3#下载ansibleapt updateapt install ansible#查看版本ansible --version#创建ansiblemkdir /etc/ansiblecd /etc/ansiblevim hosts--------------------------------------------[group1]node1 # 对应 /etc/hosts 中的 192.168.221.21node2 # 对应 192.168.221.22node3 # 对应 192.168.221.23--------------------------------------------# ping节点ansible -m ping group1node3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}node1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}node2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
案例一:playbook部署LNMP环境
# Ubuntu部署LNMP环境vim lnmp.yaml---- name: 部署LNMP环境hosts: group1become: truetasks:- name: 安装Nginxapt:name: nginxstate: present- name: 安装MySQL服务器apt:name: mysql-serverstate: present- name: 安装PHP及相关扩展apt:name: "{{ packages }}"state: presentvars:packages:- php-fpm- php-mysql- php-cli- php-gd- php-curl- php-mbstring- name: 启动并启用Nginx服务service:name: nginxstate: startedenabled: true- name: 启动并启用MySQL服务service:name: mysqlstate: startedenabled: true- name: 启动并启用PHP-FPM服务service:name: php8.1-fpmstate: startedenabled: true# 执行剧本# 执行剧本但不真正生效,用于检测脚本是否正确ansible-playbook -C lnmp.yaml# 正常执行剧本ansible-playbook lnmp.yaml
课后练习:
1.Ansible剧本配Yum源或APT源(Ubuntu已完成,Centos未尝试)
vim apt.yml---- name: 配置Yum/APT软件源hosts: node1become: yesgather_facts: yesvars:# 通用配置use_mirrors: truebackup_existing: true# Yum源配置yum_mirror_url: "https://mirrors.aliyun.com"centos_mirror: "{{ yum_mirror_url }}/centos"epel_mirror: "{{ yum_mirror_url }}/epel"# APT源配置apt_mirror_url: "http://mirrors.aliyun.com"ubuntu_mirror: "{{ apt_mirror_url }}/ubuntu"debian_mirror: "{{ apt_mirror_url }}/debian"tasks:- name: 检测操作系统家族fail:msg: "不支持的操作系统: {{ ansible_distribution }}"when: >ansible_os_family != "RedHat" andansible_os_family != "Debian"# RedHat系列配置块- name: 为RedHat系统备份现有repo文件block:- name: 备份整个repo目录command: >mv /etc/yum.repos.d /etc/yum.repos.d.backup_{{ ansible_date_time.epoch }}when: backup_existing- name: 创建新的repo目录file:path: /etc/yum.repos.dstate: directorymode: '0755'- name: 配置CentOS基础源copy:dest: /etc/yum.repos.d/CentOS-Base.repomode: '0644'content: |# CentOS-Base.repo[base]name=CentOS-$releasever - Basebaseurl={{ centos_mirror }}/$releasever/os/$basearch/gpgcheck=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}[updates]name=CentOS-$releasever - Updatesbaseurl={{ centos_mirror }}/$releasever/updates/$basearch/gpgcheck=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}[extras]name=CentOS-$releasever - Extrasbaseurl={{ centos_mirror }}/$releasever/extras/$basearch/gpgcheck=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}[centosplus]name=CentOS-$releasever - Plusbaseurl={{ centos_mirror }}/$releasever/centosplus/$basearch/gpgcheck=1enabled=0gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}- name: 配置EPEL源copy:dest: /etc/yum.repos.d/epel.repomode: '0644'content: |[epel]name=Extra Packages for Enterprise Linux $releasever - $basearchbaseurl={{ epel_mirror }}/$releasever/$basearchenabled=1gpgcheck=0- name: 清理Yum缓存command: yum clean all- name: 更新Yum缓存command: yum makecachewhen: ansible_os_family == "RedHat"# Debian系列配置块- name: 为Debian系统配置APT源block:- name: 备份现有sources.listcommand: >cp /etc/apt/sources.list /etc/apt/sources.list.backup_{{ ansible_date_time.epoch }}when: backup_existing- name: 配置Ubuntu源copy:dest: /etc/apt/sources.listmode: '0644'content: |# Ubuntu sources listdeb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }} main restricted universe multiversedeb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }}-security main restricted universe multiversedeb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }}-updates main restricted universe multiversedeb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }}-backports main restricted universe multiversewhen: ansible_distribution == "Ubuntu"- name: 配置Debian源copy:dest: /etc/apt/sources.listmode: '0644'content: |# Debian sources listdeb {{ debian_mirror }}/ {{ ansible_distribution_release }} main contrib non-freedeb {{ debian_mirror }}/ {{ ansible_distribution_release }}-updates main contrib non-freedeb {{ debian_mirror }}/ {{ ansible_distribution_release }}-security main contrib non-freewhen: ansible_distribution == "Debian"- name: 更新APT缓存apt:update_cache: yescache_valid_time: 3600when: ansible_os_family == "Debian"# 验证配置- name: 验证RedHat系统软件源配置command: yum repolistregister: yum_reposwhen: ansible_os_family == "RedHat"- name: 显示Yum仓库列表debug:msg: "Yum仓库配置成功,共 {{ yum_repos.stdout_lines[-1] }} 个仓库"when: ansible_os_family == "RedHat"- name: 验证Debian系统软件源配置command: apt-cache policyregister: apt_sourceswhen: ansible_os_family == "Debian"- name: 显示APT配置状态debug:msg: "APT源配置成功,缓存已更新"when: ansible_os_family == "Debian"
2.Ansible剧本修改IP地址与主机名(没出错但也没生效)
---- name: 配置Ubuntu系统网络和主机名hosts: node1become: truegather_facts: truevars:# 网络配置new_ip_address: "192.168.221.100"new_netmask: "255.255.255.0"new_gateway: "192.168.221.2"new_dns_servers:- "8.8.8.8"- "8.8.4.4"network_interface: "ens33" # 必须根据实际情况修改!# 主机名配置new_hostname: "ubuntu-server"new_domain: "local.lan"# 备份配置backup_files: truetasks:- name: 显示当前系统信息debug:msg: |当前系统信息:主机名: {{ ansible_hostname }}IP地址: {{ ansible_default_ipv4.address }}网卡: {{ ansible_default_ipv4.interface }}所有网卡: {{ ansible_interfaces }}- name: 检测实际网卡名称shell: ip -o link show | awk -F': ' '{print $2}' | grep -v loregister: network_interfaceschanged_when: false- name: 显示检测到的网卡debug:msg: "系统检测到的网卡: {{ network_interfaces.stdout_lines }}"- name: 备份原始主机名配置copy:src: /etc/hostnamedest: /etc/hostname.backup_{{ ansible_date_time.epoch }}remote_src: truebackup: "{{ backup_files }}"when: backup_files- name: 备份原始hosts文件copy:src: /etc/hostsdest: /etc/hosts.backup_{{ ansible_date_time.epoch }}remote_src: truebackup: "{{ backup_files }}"when: backup_files- name: 备份原始网络配置copy:src: "{{ item }}"dest: "{{ item }}.backup_{{ ansible_date_time.epoch }}"remote_src: truebackup: "{{ backup_files }}"loop: "{{ query('fileglob', '/etc/netplan/*.yaml') + query('fileglob', '/etc/netplan/*.yml') }}"ignore_errors: yeswhen: backup_files- name: 修改主机名hostname:name: "{{ new_hostname }}"- name: 更新hosts文件中的主机名blockinfile:path: /etc/hostsmarker: "# {mark} ANSIBLE MANAGED BLOCK - Hostname"block: |127.0.0.1 localhost127.0.1.1 {{ new_hostname }}.{{ new_domain }} {{ new_hostname }}# IPv6配置::1 localhost ip6-localhost ip6-loopbackff02::1 ip6-allnodesff02::2 ip6-allroutersstate: present- name: 获取当前Netplan配置文件find:paths: /etc/netplanpatterns: "*.yaml,*.yml"file_type: fileregister: netplan_files- name: 显示找到的Netplan文件debug:msg: "找到的Netplan配置文件: {{ netplan_files.files | map(attribute='path') | list }}"- name: 禁用其他Netplan配置(避免冲突)file:path: "{{ item }}"state: absentloop: "{{ netplan_files.files | map(attribute='path') | list }}"when: item != '/etc/netplan/99-static-ip.yaml'- name: 配置静态IP地址copy:dest: "/etc/netplan/99-static-ip.yaml"content: |network:version: 2renderer: networkdethernets:{{ network_interface }}:dhcp4: nodhcp6: noaddresses:- {{ new_ip_address }}/24routes:- to: defaultvia: {{ new_gateway }}nameservers:addresses: [{{ new_dns_servers | join(', ') }}]mode: '0644'- name: 验证Netplan配置语法command: netplan generatechanged_when: false- name: 应用网络配置command: netplan apply- name: 重启网络服务systemd:name: systemd-networkdstate: restartedwhen: ansible_distribution == 'Ubuntu'- name: 等待网络服务稳定wait_for_connection:timeout: 60- name: 强制系统重启(确保所有更改生效)reboot:msg: "应用网络和主机名配置后重启"connect_timeout: 5reboot_timeout: 300pre_reboot_delay: 0post_reboot_delay: 30- name: 验证新的主机名(重启后)shell: hostnameregister: hostname_checkchanged_when: false- name: 验证新的IP地址(重启后)shell: |ip addr show {{ network_interface }} 2>/dev/null | grep "inet " | awk '{print $2}' || echo "未配置"register: ip_checkchanged_when: false- name: 显示最终配置结果debug:msg: |🎉 系统配置完成!最终配置信息:设置的主机名: {{ new_hostname }}实际主机名: {{ hostname_check.stdout | default('未知') }}设置的IP地址: {{ new_ip_address }}实际IP地址: {{ ip_check.stdout | default('未知') }}网卡接口: {{ network_interface }}如果配置不正确,请检查:1. 网卡名称是否正确2. IP地址是否在正确的网段3. 网关地址是否正确
3.Ansible剧本实现系统优化:设置时区、时间同步、更新软件、关闭防火墙、SELinux
第一个剧本:设置时区
### 设置时区vim time.yml---- name: 设置时区hosts: node1become: yestasks:- name: Set timezone to Asia/Shanghaitimezone:name: Asia/Shanghai
第二个实验:时间同步
### 时间同步---- name: 基础时间同步配置hosts: group1become: yesgather_facts: yesvars:ntp_servers: ["ntp.aliyun.com", "ntp1.aliyun.com"]tasks:- name: 设置时区timezone:name: Asia/Shanghai- name: 安装chrony (RedHat)package: name=chrony state=presentwhen: ansible_os_family == "RedHat"- name: 安装chrony (Debian)package: name=chrony state=presentwhen: ansible_os_family == "Debian"- name: 配置chrony服务systemd:name: "{{ 'chronyd' if ansible_os_family == 'RedHat' else 'chrony' }}"state: startedenabled: yes- name: 显示当前时间command: dateregister: current_timechanged_when: false- name: 显示配置结果debug:msg: "时间同步配置完成!当前时间: {{ current_time.stdout }}"
还没有写:更新软件、关闭防火墙、SELinux
4.Ansible剧本安装单机LNMP、LNMT(案例一已完成LNMP,LNMT还未排完错)
---- name: 部署LNMT环境hosts: node1become: truegather_facts: truevars:# Tomcat配置tomcat_version: 9java_package: "openjdk-11-jdk"# 数据库配置 - 生产环境请务必修改这些密码!mysql_root_password: "root123"mysql_app_database: "mysql"mysql_app_user: "root"mysql_app_password: "root123"# 应用配置app_context_path: "/myapp"tomcat_manager_user: "admin"tomcat_manager_password: "admin123!"# 网络配置server_host: "localhost"tasks:- name: 检查系统版本debug:msg: "检测到系统: {{ ansible_distribution }} {{ ansible_distribution_version }}"- name: 更新软件包缓存apt:update_cache: yescache_valid_time: 3600- name: 安装基础依赖apt:name: "{{ item }}"state: presentloop:- curl- wget- vim- unzip- name: 安装Nginxapt:name: nginxstate: present- name: 安装MySQL服务器apt:name: "{{ item }}"state: presentloop:- mysql-server- mysql-client- python3-pymysql- name: 安装Java环境apt:name: "{{ java_package }}"state: present- name: 设置Java环境变量lineinfile:path: /etc/environmentline: "JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64"state: present- name: 安装Tomcatapt:name: "{{ item }}"state: presentloop:- "tomcat{{ tomcat_version }}"- "tomcat{{ tomcat_version }}-admin"- "tomcat{{ tomcat_version }}-docs"- "tomcat{{ tomcat_version }}-examples"- name: 等待MySQL服务启动systemd:name: mysqlstate: startedregister: mysql_service_result- name: 安全配置MySQL(使用debconf设置root密码)debconf:name: mysql-serverquestion: mysql-server/root_passwordvalue: "{{ mysql_root_password }}"vtype: password- name: 确认MySQL root密码debconf:name: mysql-serverquestion: mysql-server/root_password_againvalue: "{{ mysql_root_password }}"vtype: password- name: 重新配置MySQLshell: dpkg-reconfigure -f noninteractive mysql-serverwhen: mysql_service_result.changed- name: 安装PyMySQL模块pip:name: PyMySQLstate: presentbecome: yes- name: 创建应用数据库mysql_db:name: "{{ mysql_app_database }}"state: presentlogin_user: rootlogin_password: "{{ mysql_root_password }}"ignore_errors: yes- name: 创建应用数据库用户mysql_user:name: "{{ mysql_app_user }}"host: localhostpassword: "{{ mysql_app_password }}"priv: "{{ mysql_app_database }}.*:ALL"state: presentlogin_user: rootlogin_password: "{{ mysql_root_password }}"ignore_errors: yes- name: 确保Tomcat webapps目录存在file:path: "/var/lib/tomcat{{ tomcat_version }}/webapps/ROOT"state: directoryowner: tomcatgroup: tomcatmode: '0755'recurse: yes- name: 创建测试JSP页面copy:dest: "/var/lib/tomcat{{ tomcat_version }}/webapps/ROOT/index.jsp"content: |<%@ page contentType="text/html;charset=UTF-8" language="java" %><!DOCTYPE html><html><head><title>LNMT环境测试</title></head><body><h1>✅ LNMT环境部署成功!</h1><p>服务器时间: <%= new java.util.Date() %></p><p>Java版本: <%= System.getProperty("java.version") %></p><p>Tomcat版本: <%= application.getServerInfo() %></p><p>服务器: <%= request.getServerName() %>:<%= request.getServerPort() %></p><h2>数据库连接测试</h2><%try {Class.forName("com.mysql.cj.jdbc.Driver");out.println("<p style='color: green;'>✅ MySQL驱动加载成功</p>");} catch (Exception e) {out.println("<p style='color: red;'>❌ MySQL驱动加载失败: " + e.getMessage() + "</p>");}%></body></html>owner: tomcatgroup: tomcatmode: '0644'- name: 配置Tomcat用户权限copy:dest: "/etc/tomcat{{ tomcat_version }}/tomcat-users.xml"content: |<?xml version='1.0' encoding='utf-8'?><tomcat-users xmlns="http://tomcat.apache.org/xml"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"version="1.0"><role rolename="manager-gui"/><role rolename="admin-gui"/><role rolename="manager-script"/><user username="{{ tomcat_manager_user }}"password="{{ tomcat_manager_password }}"roles="manager-gui,admin-gui,manager-script"/></tomcat-users>owner: tomcatgroup: tomcatmode: '0640'notify: 重启Tomcat服务- name: 允许外部访问Tomcat管理界面lineinfile:path: "/etc/tomcat{{ tomcat_version }}/server.xml"regexp: '<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve"'line: '<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve"'state: presentnotify: 重启Tomcat服务- name: 配置Nginx代理Tomcatcopy:dest: /etc/nginx/sites-available/tomcatcontent: |server {listen 80;server_name {{ server_host }};# 静态文件缓存location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {expires 1y;add_header Cache-Control "public, immutable";}# API代理location {{ app_context_path }}/ {proxy_pass http://localhost:8080{{ app_context_path }}/;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_connect_timeout 30s;proxy_read_timeout 60s;}# 根路径代理location / {proxy_pass http://localhost:8080/;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}# Tomcat管理界面location /manager/ {proxy_pass http://localhost:8080/manager/;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}}mode: '0644'notify: 重新加载Nginx配置- name: 启用Nginx Tomcat站点file:src: /etc/nginx/sites-available/tomcatdest: /etc/nginx/sites-enabled/tomcatstate: linknotify: 重新加载Nginx配置- name: 禁用Nginx默认站点file:path: /etc/nginx/sites-enabled/defaultstate: absentnotify: 重新加载Nginx配置- name: 配置防火墙规则ufw:rule: allowport: "{{ item }}"proto: tcploop:- 80- 8080- 3306ignore_errors: yes- name: 启动并启用服务systemd:name: "{{ item }}"state: startedenabled: trueloop:- nginx- mysql- "tomcat{{ tomcat_version }}"- name: 等待服务启动完成wait_for:port: "{{ item }}"delay: 10state: startedtimeout: 120loop:- 80- 8080- 3306- name: 验证服务状态shell: systemctl is-active {{ item }}register: service_statuschanged_when: falseloop:- nginx- mysql- "tomcat{{ tomcat_version }}"- name: 显示部署摘要debug:msg: |🎉 LNMT环境部署完成!服务状态:Nginx: {{ service_status.results[0].stdout }}MySQL: {{ service_status.results[1].stdout }}Tomcat: {{ service_status.results[2].stdout }}访问地址:🌐 网站: http://{{ ansible_default_ipv4.address }}:80🚀 Tomcat直接访问: http://{{ ansible_default_ipv4.address }}:8080📊 Tomcat管理: http://{{ ansible_default_ipv4.address }}:8080/manager/html安全信息:🔑 Tomcat管理员: {{ tomcat_manager_user }} / {{ tomcat_manager_password }}🔒 MySQL root密码: {{ mysql_root_password }}💾 应用数据库: {{ mysql_app_database }} (用户: {{ mysql_app_user }})handlers:- name: 重新加载环境变量command: source /etc/environmentchanged_when: false- name: 重启Tomcat服务systemd:name: "tomcat{{ tomcat_version }}"state: restarted- name: 重新加载Nginx配置systemd:name: nginxstate: reloaded
5.Ansible剧本安装集群:MySQL主从、Redis哨兵(未完成)
Ansible-role角色
1、核心
-
将剧本根据功能进行拆分成不同组件;
-
每个组件只完成一项简单任务;
-
复杂任务通过组合多个组件来实现;
2、优缺点
-
优点:灵活、低耦合、高复用;
-
缺点:编写的复杂性高,对技术要求高;