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: no
dhcp6: no
addresses: [192.168.221.21/24]
gateway4: 192.168.221.2
nameservers:
addresses: [114.114.114.114,8.8.8.8]
version: 2
yaml格式要求:
1.严格区分大小写
2.缩进统一,同一级别配置要对齐
-
缩进不允许使用Tab制表符,只允许使用空格字符;
-
缩进的空格数不重要,但官方推荐使用2个空格字符;
-
冒号后面至少要有一个空格
Ansible案例
前提条件:Ubuntu配置好四台设备
# 前提是每台机子都apt update
192.168.221.120 manage01
192.168.221.21 node1
192.168.221.22 node2
192.168.221.23 node3
#(仅管理机)
# ssh免密(输入yes和密码)
ssh-keygen
ssh-copy-id root@node1
ssh-copy-id root@node2
ssh-copy-id root@node3
#下载ansible
apt update
apt install ansible
#查看版本
ansible --version
#创建ansible
mkdir /etc/ansible
cd /etc/ansible
vim hosts
--------------------------------------------
[group1]
node1 # 对应 /etc/hosts 中的 192.168.221.21
node2 # 对应 192.168.221.22
node3 # 对应 192.168.221.23
--------------------------------------------
# ping节点
ansible -m ping group1
node3 | 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: group1
become: true
tasks:
- name: 安装Nginx
apt:
name: nginx
state: present
- name: 安装MySQL服务器
apt:
name: mysql-server
state: present
- name: 安装PHP及相关扩展
apt:
name: "{{ packages }}"
state: present
vars:
packages:
- php-fpm
- php-mysql
- php-cli
- php-gd
- php-curl
- php-mbstring
- name: 启动并启用Nginx服务
service:
name: nginx
state: started
enabled: true
- name: 启动并启用MySQL服务
service:
name: mysql
state: started
enabled: true
- name: 启动并启用PHP-FPM服务
service:
name: php8.1-fpm
state: started
enabled: true
# 执行剧本
# 执行剧本但不真正生效,用于检测脚本是否正确
ansible-playbook -C lnmp.yaml
# 正常执行剧本
ansible-playbook lnmp.yaml
课后练习:
1.Ansible剧本配Yum源或APT源(Ubuntu已完成,Centos未尝试)
vim apt.yml
---
- name: 配置Yum/APT软件源
hosts: node1
become: yes
gather_facts: yes
vars:
# 通用配置
use_mirrors: true
backup_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" and
ansible_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.d
state: directory
mode: '0755'
- name: 配置CentOS基础源
copy:
dest: /etc/yum.repos.d/CentOS-Base.repo
mode: '0644'
content: |
# CentOS-Base.repo
[base]
name=CentOS-$releasever - Base
baseurl={{ centos_mirror }}/$releasever/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}
[updates]
name=CentOS-$releasever - Updates
baseurl={{ centos_mirror }}/$releasever/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}
[extras]
name=CentOS-$releasever - Extras
baseurl={{ centos_mirror }}/$releasever/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}
[centosplus]
name=CentOS-$releasever - Plus
baseurl={{ centos_mirror }}/$releasever/centosplus/$basearch/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}
- name: 配置EPEL源
copy:
dest: /etc/yum.repos.d/epel.repo
mode: '0644'
content: |
[epel]
name=Extra Packages for Enterprise Linux $releasever - $basearch
baseurl={{ epel_mirror }}/$releasever/$basearch
enabled=1
gpgcheck=0
- name: 清理Yum缓存
command: yum clean all
- name: 更新Yum缓存
command: yum makecache
when: ansible_os_family == "RedHat"
# Debian系列配置块
- name: 为Debian系统配置APT源
block:
- name: 备份现有sources.list
command: >
cp /etc/apt/sources.list /etc/apt/sources.list.backup_{{ ansible_date_time.epoch }}
when: backup_existing
- name: 配置Ubuntu源
copy:
dest: /etc/apt/sources.list
mode: '0644'
content: |
# Ubuntu sources list
deb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }} main restricted universe multiverse
deb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }}-security main restricted universe multiverse
deb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }}-updates main restricted universe multiverse
deb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }}-backports main restricted universe multiverse
when: ansible_distribution == "Ubuntu"
- name: 配置Debian源
copy:
dest: /etc/apt/sources.list
mode: '0644'
content: |
# Debian sources list
deb {{ debian_mirror }}/ {{ ansible_distribution_release }} main contrib non-free
deb {{ debian_mirror }}/ {{ ansible_distribution_release }}-updates main contrib non-free
deb {{ debian_mirror }}/ {{ ansible_distribution_release }}-security main contrib non-free
when: ansible_distribution == "Debian"
- name: 更新APT缓存
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
# 验证配置
- name: 验证RedHat系统软件源配置
command: yum repolist
register: yum_repos
when: ansible_os_family == "RedHat"
- name: 显示Yum仓库列表
debug:
msg: "Yum仓库配置成功,共 {{ yum_repos.stdout_lines[-1] }} 个仓库"
when: ansible_os_family == "RedHat"
- name: 验证Debian系统软件源配置
command: apt-cache policy
register: apt_sources
when: ansible_os_family == "Debian"
- name: 显示APT配置状态
debug:
msg: "APT源配置成功,缓存已更新"
when: ansible_os_family == "Debian"
2.Ansible剧本修改IP地址与主机名(没出错但也没生效)
---
- name: 配置Ubuntu系统网络和主机名
hosts: node1
become: true
gather_facts: true
vars:
# 网络配置
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: true
tasks:
- 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 lo
register: network_interfaces
changed_when: false
- name: 显示检测到的网卡
debug:
msg: "系统检测到的网卡: {{ network_interfaces.stdout_lines }}"
- name: 备份原始主机名配置
copy:
src: /etc/hostname
dest: /etc/hostname.backup_{{ ansible_date_time.epoch }}
remote_src: true
backup: "{{ backup_files }}"
when: backup_files
- name: 备份原始hosts文件
copy:
src: /etc/hosts
dest: /etc/hosts.backup_{{ ansible_date_time.epoch }}
remote_src: true
backup: "{{ backup_files }}"
when: backup_files
- name: 备份原始网络配置
copy:
src: "{{ item }}"
dest: "{{ item }}.backup_{{ ansible_date_time.epoch }}"
remote_src: true
backup: "{{ backup_files }}"
loop: "{{ query('fileglob', '/etc/netplan/*.yaml') + query('fileglob', '/etc/netplan/*.yml') }}"
ignore_errors: yes
when: backup_files
- name: 修改主机名
hostname:
name: "{{ new_hostname }}"
- name: 更新hosts文件中的主机名
blockinfile:
path: /etc/hosts
marker: "# {mark} ANSIBLE MANAGED BLOCK - Hostname"
block: |
127.0.0.1 localhost
127.0.1.1 {{ new_hostname }}.{{ new_domain }} {{ new_hostname }}
# IPv6配置
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
state: present
- name: 获取当前Netplan配置文件
find:
paths: /etc/netplan
patterns: "*.yaml,*.yml"
file_type: file
register: netplan_files
- name: 显示找到的Netplan文件
debug:
msg: "找到的Netplan配置文件: {{ netplan_files.files | map(attribute='path') | list }}"
- name: 禁用其他Netplan配置(避免冲突)
file:
path: "{{ item }}"
state: absent
loop: "{{ 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: 2
renderer: networkd
ethernets:
{{ network_interface }}:
dhcp4: no
dhcp6: no
addresses:
- {{ new_ip_address }}/24
routes:
- to: default
via: {{ new_gateway }}
nameservers:
addresses: [{{ new_dns_servers | join(', ') }}]
mode: '0644'
- name: 验证Netplan配置语法
command: netplan generate
changed_when: false
- name: 应用网络配置
command: netplan apply
- name: 重启网络服务
systemd:
name: systemd-networkd
state: restarted
when: ansible_distribution == 'Ubuntu'
- name: 等待网络服务稳定
wait_for_connection:
timeout: 60
- name: 强制系统重启(确保所有更改生效)
reboot:
msg: "应用网络和主机名配置后重启"
connect_timeout: 5
reboot_timeout: 300
pre_reboot_delay: 0
post_reboot_delay: 30
- name: 验证新的主机名(重启后)
shell: hostname
register: hostname_check
changed_when: false
- name: 验证新的IP地址(重启后)
shell: |
ip addr show {{ network_interface }} 2>/dev/null | grep "inet " | awk '{print $2}' || echo "未配置"
register: ip_check
changed_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: node1
become: yes
tasks:
- name: Set timezone to Asia/Shanghai
timezone:
name: Asia/Shanghai
第二个实验:时间同步
### 时间同步
---
- name: 基础时间同步配置
hosts: group1
become: yes
gather_facts: yes
vars:
ntp_servers: ["ntp.aliyun.com", "ntp1.aliyun.com"]
tasks:
- name: 设置时区
timezone:
name: Asia/Shanghai
- name: 安装chrony (RedHat)
package: name=chrony state=present
when: ansible_os_family == "RedHat"
- name: 安装chrony (Debian)
package: name=chrony state=present
when: ansible_os_family == "Debian"
- name: 配置chrony服务
systemd:
name: "{{ 'chronyd' if ansible_os_family == 'RedHat' else 'chrony' }}"
state: started
enabled: yes
- name: 显示当前时间
command: date
register: current_time
changed_when: false
- name: 显示配置结果
debug:
msg: "时间同步配置完成!当前时间: {{ current_time.stdout }}"
还没有写:更新软件、关闭防火墙、SELinux
4.Ansible剧本安装单机LNMP、LNMT(案例一已完成LNMP,LNMT还未排完错)
---
- name: 部署LNMT环境
hosts: node1
become: true
gather_facts: true
vars:
# Tomcat配置
tomcat_version: 9
java_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: yes
cache_valid_time: 3600
- name: 安装基础依赖
apt:
name: "{{ item }}"
state: present
loop:
- curl
- wget
- vim
- unzip
- name: 安装Nginx
apt:
name: nginx
state: present
- name: 安装MySQL服务器
apt:
name: "{{ item }}"
state: present
loop:
- mysql-server
- mysql-client
- python3-pymysql
- name: 安装Java环境
apt:
name: "{{ java_package }}"
state: present
- name: 设置Java环境变量
lineinfile:
path: /etc/environment
line: "JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64"
state: present
- name: 安装Tomcat
apt:
name: "{{ item }}"
state: present
loop:
- "tomcat{{ tomcat_version }}"
- "tomcat{{ tomcat_version }}-admin"
- "tomcat{{ tomcat_version }}-docs"
- "tomcat{{ tomcat_version }}-examples"
- name: 等待MySQL服务启动
systemd:
name: mysql
state: started
register: mysql_service_result
- name: 安全配置MySQL(使用debconf设置root密码)
debconf:
name: mysql-server
question: mysql-server/root_password
value: "{{ mysql_root_password }}"
vtype: password
- name: 确认MySQL root密码
debconf:
name: mysql-server
question: mysql-server/root_password_again
value: "{{ mysql_root_password }}"
vtype: password
- name: 重新配置MySQL
shell: dpkg-reconfigure -f noninteractive mysql-server
when: mysql_service_result.changed
- name: 安装PyMySQL模块
pip:
name: PyMySQL
state: present
become: yes
- name: 创建应用数据库
mysql_db:
name: "{{ mysql_app_database }}"
state: present
login_user: root
login_password: "{{ mysql_root_password }}"
ignore_errors: yes
- name: 创建应用数据库用户
mysql_user:
name: "{{ mysql_app_user }}"
host: localhost
password: "{{ mysql_app_password }}"
priv: "{{ mysql_app_database }}.*:ALL"
state: present
login_user: root
login_password: "{{ mysql_root_password }}"
ignore_errors: yes
- name: 确保Tomcat webapps目录存在
file:
path: "/var/lib/tomcat{{ tomcat_version }}/webapps/ROOT"
state: directory
owner: tomcat
group: tomcat
mode: '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: tomcat
group: tomcat
mode: '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: tomcat
group: tomcat
mode: '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: present
notify: 重启Tomcat服务
- name: 配置Nginx代理Tomcat
copy:
dest: /etc/nginx/sites-available/tomcat
content: |
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/tomcat
dest: /etc/nginx/sites-enabled/tomcat
state: link
notify: 重新加载Nginx配置
- name: 禁用Nginx默认站点
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: 重新加载Nginx配置
- name: 配置防火墙规则
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 80
- 8080
- 3306
ignore_errors: yes
- name: 启动并启用服务
systemd:
name: "{{ item }}"
state: started
enabled: true
loop:
- nginx
- mysql
- "tomcat{{ tomcat_version }}"
- name: 等待服务启动完成
wait_for:
port: "{{ item }}"
delay: 10
state: started
timeout: 120
loop:
- 80
- 8080
- 3306
- name: 验证服务状态
shell: systemctl is-active {{ item }}
register: service_status
changed_when: false
loop:
- 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/environment
changed_when: false
- name: 重启Tomcat服务
systemd:
name: "tomcat{{ tomcat_version }}"
state: restarted
- name: 重新加载Nginx配置
systemd:
name: nginx
state: reloaded
5.Ansible剧本安装集群:MySQL主从、Redis哨兵(未完成)
Ansible-role角色
1、核心
-
将剧本根据功能进行拆分成不同组件;
-
每个组件只完成一项简单任务;
-
复杂任务通过组合多个组件来实现;
2、优缺点
-
优点:灵活、低耦合、高复用;
-
缺点:编写的复杂性高,对技术要求高;
882

被折叠的 条评论
为什么被折叠?



