部署文件到受管主机
使用JINJA2模板部署文件
示例1:部署web服务器,主页内容显示为Welcome to HOSTNAME。HOSTNAME为受管主机完全主机名。
playbook内容如下:
[yuxb@controller web 09:39:37]$ cat playbook.yml
---
- name: Enable intranet services
hosts: node1
tasks:
- name: ensure latest version of httpd
yum:
name: httpd
state: latest
- name: test html page is installed
template:
src: index.html.j2
dest: /var/www/html/index.html
- name: httpd enabled and running
service:
name: httpd
enabled: true
state: restarted
...
index.html.j2 内容如下:
[yuxb@controller web 09:39:40]$ vim index.html.j2
[yuxb@controller web 09:40:55]$ cat index.html.j2
Welcome to {{ ansible_fqdn }}
执行:
[yuxb@controller web 09:41:22]$ ansible-playbook playbook.yml
PLAY [Enable intranet services] *********************************************************************
TASK [Gathering Facts] ******************************************************************************
ok: [node1]
TASK [ensure latest version of httpd] ***************************************************************
ok: [node1]
TASK [test html page is installed] ******************************************************************
changed: [node1]
TASK [httpd enabled and running] ********************************************************************
changed: [node1]
PLAY RECAP ******************************************************************************************
node1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
示例2:推送 ssh 服务配置文件。
sshd_config.j2内容:
[yuxb@controller web 09:50:47]$ cat sshd_config.j2
# {{ ansible_managed }}
# DO NOT MAKE LOCAL MODIFICATIONS TO THIS FILE AS THEY WILL BE LOST
Port {{ ssh_port }}
ListenAddress {{ ansible_facts['default_ipv4']['address'] }}
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
SyslogFacility AUTHPRIV
PermitRootLogin {{ root_allowed }}
AllowGroups {{ groups_allowed }}
AuthorizedKeysFile /etc/.rht_authorized_keys .ssh/authorized_keys
PasswordAuthentication {{ passwords_allowed }}
playbook
[yuxb@controller web 09:41:00]$ vim playbook.yml
[yuxb@controller web 09:50:07]$ cat playbook.yml
---
- name: config sshd service
hosts: node1
vars:
ssh_port: 1022
root_allowed: "yes"
groups_allowed: wheel
passwords_allowed: "yes"
ansible_managed: "Ansible managed"
tasks:
- name: config sshd service
template:
src: sshd_config.j2
dest: /root/sshd_config
# 执行
[yuxb@controller web 09:51:00]$ ansible-playbook playbook.yml
PLAY [config sshd service] **************************************************************************
TASK [Gathering Facts] ******************************************************************************
ok: [node1]
TASK [config sshd service] **************************************************************************
changed: [node1]
PLAY RECAP ******************************************************************************************
node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[yuxb@controller web 10:32:15]$ cat playbook.yml
---
- name: 部署web服务器
hosts: node1
tasks:
- name: 安装软件包 httpd
yum:
name: httpd
state: latest
- name: 启动并启用 httpd 服务
service:
name: httpd
state: started
- name: 准备 web 服务器主页内容
copy:
src: index.html
dest: /var/www/html/index.html
- name: 启动并启用 firewalld 服务
service:
name: firewalld
state: started
- name: 配置防火墙 放行 http
firewalld:
service: http
permanent: yes
state: enabled
Jinja2 模板语法
for 语句
Jinja2使用for语句来提供循环功能。
示例1:
[yuxb@controller web 10:41:56]$ vim playbook.yml
[yuxb@controller web 10:46:24]$ cat playbook.yml
---
- name: test template
hosts: node1
vars:
users:
- tom
- jack
- snoopy
- lucy
tasks:
- name: test template
template:
src: testfile.j2
dest: /tmp/testfile
testfile.j2内容
[yuxb@controller web 10:45:16]$ cat testfile.j2
{% for user in users %}
username is {{ user }}
{% endfor %}
执行并验证
# 执行
[yuxb@controller web 10:45:19]$ ansible-playbook playbook.yml
PLAY [test template] *******************************************************************************
TASK [Gathering Facts] *****************************************************************************
ok: [node1]
TASK [test template] *******************************************************************************
changed: [node1]
PLAY RECAP *****************************************************************************************
node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# 验证
[yuxb@node1 ~ 10:33:13]$ cat /tmp/testfile
tom
jack
snoopy
lucy
LNMP + WordPress 自动化部署实验
一、实验目标
通过 Ansible 自动化工具 ,在目标服务器(lnmp
主机组)上搭建 LNMP 架构(Linux 系统 + Nginx 网页服务器 + MariaDB 数据库 + PHP 脚本语言),并部署 WordPress 博客系统,最终实现通过浏览器访问博客。
二、前置知识说明
术语 / 工具 | 作用说明 |
---|---|
Ansible | 自动化运维工具,通过 "剧本(YAML 文件)" 批量执行命令,无需在目标机装客户端 |
LNMP | Web 服务架构,Nginx 负责接收用户请求,PHP 处理动态代码,MariaDB 存储数据 |
MariaDB | 开源数据库(MySQL 分支),用于存储 WordPress 的文章、用户等数据 |
Nginx | 轻量级网页服务器,负责转发请求、展示静态页面、对接 PHP |
PHP-FPM | PHP 的进程管理器,处理 Nginx 转发的 PHP 动态请求 |
WordPress | 开源博客系统,需依赖 LNMP 架构运行 |
Ansible Vault | Ansible 自带的加密工具,用于保护数据库密码等敏感信息 |
三、实验环境准备
3.1 环境说明
角色 | 主机名 | 系统 | 核心软件 | IP 地址(示例) |
---|---|---|---|---|
控制节点(执行命令) | controller | CentOS 7 | Ansible、LNMP 配置文件 | 10.1.8.10(示例) |
目标节点(部署服务) | node1(属于 lnmp 组) | CentOS 7 | 无(Ansible 自动安装软件) | 10.1.8.11(示例) |
客户端(访问博客) | 个人电脑(Windows) | Windows 10/11 | 浏览器 | - |
3.2 控制节点前置检查
在 controller
主机上执行以下命令,确认 Ansible 已安装且能连接目标节点:
# 1. 检查Ansible是否安装(出现版本号即正常)
[yuxb@controller ~]$ ansible --version
# 2. 检查能否连通目标节点(lnmp是主机组名,从inventory文件读取)
[yuxb@controller ~]$ ansible lnmp -m ping
# 若返回“SUCCESS”,说明控制节点与目标节点连通正常;若失败,需先解决SSH登录问题
四、实验步骤
阶段 1:初始化控制节点工作目录(存放配置文件)
4.1.1 复制并进入工作目录(避免干扰原有文件)
[yuxb@controller ~ 11:21:36]$ cp -r web lnmp
-
这步做什么? :把原有
web
目录复制一份,命名为lnmp
,作为本次实验的 "工作目录"(所有配置文件都放在这里,不影响其他文件)。 -
为什么这么做? :工作目录独立,后续操作只在
lnmp
目录内进行,方便管理和回溯。
[yuxb@controller ~ 13:49:03]$ cd lnmp/
- 这步做什么? :进入
lnmp
工作目录,后续所有命令都在这个目录下执行。
4.1.2 清空工作目录(删除原有冗余文件)
[yuxb@controller lnmp 13:49:15]$ rm -rf *
-
这步做什么? :删除
lnmp
目录里所有原有文件(从web
复制过来的旧文件)。 -
为什么这么做?:避免旧文件(如旧配置、旧脚本)干扰本次实验,保证环境 "干净"。
4.1.3 拷贝 Ansible 核心配置文件(让 Ansible 知道怎么工作)
[yuxb@controller lnmp 13:49:44]$ cp ../web/ansible.cfg .
[yuxb@controller lnmp 13:49:46]$ cp ../web/inventory .
-
这步做什么?
:从上级目录web拷贝两个关键文件到当前lnmp目录:
-
ansible.cfg
:Ansible 的主配置文件(告诉 Ansible 用哪个用户登录目标机、敏感文件怎么解密等); -
inventory
:主机清单文件(告诉 Ansible 目标节点是哪些,比如lnmp
组包含node1
)。
-
-
为什么这么做?:这两个文件是 Ansible 运行的基础,必须放在工作目录,否则 Ansible 不知道 "操作哪个机器""用什么身份操作"。
阶段 2:准备变量文件(存储配置,保护敏感信息)
4.2.1 创建变量目录(Ansible 规定的目录结构)
[yuxb@controller lnmp 14:03:33]$ mkdir host_vars
[yuxb@controller lnmp 14:03:57]$ mkdir host_vars/lnmp
-
这步做什么?
:创建host_vars/lnmp目录:
-
host_vars
:是 Ansible 约定的 "主机变量目录",专门存放不同主机 / 主机组的配置; -
host_vars/lnmp
:专门存放lnmp
主机组(目标节点)的变量(比如数据库密码、用户名)。
-
-
为什么这么做?:变量和剧本分离,后续修改配置(如改密码)不用动核心剧本,更灵活。
4.2.2 编写敏感变量文件(存储密码等重要信息)
[yuxb@controller lnmp 14:04:02]$ vim host_vars/lnmp/vaults.yml
- 这步做什么? :用
vim
编辑器创建vaults.yml
文件,内容如下(直接复制粘贴):
mysql_root_password: 123
app_user: wordpress
app_password: 123
app_host: '%'
app_priv: '*.*:ALL'
-
每个配置项是什么意思?:
配置项 含义 作用 mysql_root_password: 123
MariaDB 数据库的 root 密码(最高权限密码) 用于初始化数据库、创建用户和库 app_user: wordpress
给 WordPress 用的数据库用户名 WordPress 只能用这个用户操作数据库,避免用 root 提权风险 app_password: 123
WordPress 数据库用户的密码 验证 WordPress 对数据库的访问权限 app_host: '%'
允许该用户从 "任意主机" 连接数据库 这里是本地部署, %
表示目标机自身可以访问app_priv: '*.*:ALL'
该用户拥有 "所有数据库、所有权限" 让 WordPress 能创建表、插入数据(实际生产可缩小权限,实验简化用 ALL) -
为什么要单独放这个文件?:这些是敏感信息(密码),后续要加密保护,不能明文暴露。
4.2.3 编写普通变量文件(存储非敏感配置)
[yuxb@controller lnmp 14:10:23]$ vim host_vars/lnmp/vars.yml
- 这步做什么? :用
vim
创建vars.yml
文件,内容如下:
db_name: webapp
-
配置项含义 :
db_name: webapp
表示给 WordPress 专门创建的数据库名是webapp
。 -
为什么和敏感变量分开?:数据库名不是敏感信息,不用加密,单独存放更清晰。
4.2.4 加密敏感变量文件(保护密码不泄露)
[yuxb@controller lnmp 15:03:40]$ ansible-vault encrypt host_vars/lnmp/vaults.yml
New Vault password:
Confirm New Vault password:
Encryption successful
-
这步做什么?:
用 Ansible Vault 加密1vaults.yml文件:
-
输入 "加密密码"(实验用
123
,生产环境用复杂密码); -
再次确认密码;
-
提示 "Encryption successful" 表示加密成功。
-
-
为什么要加密? :如果不加密,
vaults.yml
里的密码是明文,别人看到就能登录数据库,不安全。 -
加密后怎么用?:后续 Ansible 执行剧本时,需要解密密码,所以要存一个 "解密密码文件"。
4.2.5 创建解密密码文件(避免每次输入密码)
[yuxb@controller lnmp 15:04:53]$ echo 123 > secret.txt
-
这步做什么? :把 "加密密码(123)" 写入
secret.txt
文件。 -
为什么这么做?:如果不存这个文件,每次执行 Ansible 剧本都要手动输入解密密码,麻烦;存了之后,Ansible 会自动从这个文件读密码解密。
4.2.6 验证加密文件能否正常解密(确保后续能用)
[yuxb@controller lnmp 15:06:06]$ ansible-vault view host_vars/lnmp/vaults.yml
-
这步做什么? :查看加密后的
vaults.yml
内容(验证解密是否正常)。 -
正常结果 :会显示
vaults.yml
的原始内容(和 4.2.2 里写的一致),说明解密成功;如果失败,检查secret.txt
的密码是否和加密时一致。
阶段 3:准备核心配置文件(Ansible 剧本、Nginx/PHP 配置)
4.3.1 编写 Ansible 主配置文件(告诉 Ansible 怎么执行命令)
[yuxb@controller lnmp 16:25:31]$ vim ansible.cfg
- 这步做什么? :用
vim
编辑ansible.cfg
文件,内容如下(直接复制,注释已解释每一行):
[defaults]
inventory = ./inventory # 告诉Ansible:主机清单文件在当前目录的inventory里
remote_user = yuxb # 告诉Ansible:用“yuxb”用户登录目标节点(需提前配置该用户sudo免密)
vault_password_file=./secret.txt # 告诉Ansible:解密Vault文件的密码在当前目录的secret.txt里
#vault_password_file = ./password-for-vault # 注释:备用密码文件路径(不用管)
#ask_pass = True # 注释:是否每次登录目标机都问SSH密码(实验已配置免密,所以注释)
#module_name = command # 注释:Ansible默认执行的模块(不用改)
#private_key_file = /opt/id_rsa # 注释:如果用SSH密钥登录,填密钥路径(实验用密码免密,注释)
#host_key_checking = False # 注释:是否检查目标机的SSH密钥(新手可取消注释,避免首次登录弹确认)
[privilege_escalation]
become=True # 告诉Ansible:执行命令时需要提升权限(即sudo)
become_method=sudo # 告诉Ansible:提升权限的方式是sudo
become_user=root # 告诉Ansible:提升权限后用root用户执行命令
become_ask_pass=False # 告诉Ansible:sudo时不用问密码(需提前在目标机配置yuxb用户sudo免密)
- 关键注意点 :目标节点必须配置
yuxb
用户的 sudo 免密(否则 Ansible 执行安装命令会失败),配置方法:在目标机node1
上执行visudo
,添加yuxb ALL=(ALL) NOPASSWD: ALL
。
4.3.2 编写 PHP 配置文件(让 Nginx 能处理 PHP 请求)
[yuxb@controller lnmp 14:11:18]$ vim php.conf
- 这步做什么? :用
vim
创建php.conf
文件,内容如下(Nginx 解析 PHP 的规则):
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
-
每一行是什么意思?:
-
location ~ \.php$
:匹配所有以.php
结尾的请求(比如index.php
); -
try_files $uri =404
:检查请求的 PHP 文件是否存在,不存在就返回 404 错误(避免恶意请求); -
fastcgi_pass 127.0.0.1:9000
:把 PHP 请求转发到本地 9000 端口(PHP-FPM 默认端口); -
fastcgi_index index.php
:如果请求目录(比如http://blog.yuxb.cloud/
),默认找index.php
; -
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name
:告诉 PHP-FPM 脚本文件的实际路径(比如/usr/share/nginx/html/index.php
); -
include fastcgi_params
:加载 FastCGI 的通用参数(比如请求方法、用户 IP 等)。
-
-
为什么要这个文件?:Nginx 本身不能处理 PHP 动态代码,必须通过这个配置把 PHP 请求转发给 PHP-FPM 处理。
4.3.3 编写 WordPress 虚拟主机模板(让 Nginx 识别博客域名)
[yuxb@controller lnmp 16:25:38]$ vim vhost-wordpress.conf.j2
- 这步做什么? :用
vim
创建vhost-wordpress.conf.j2
模板文件(Jinja2 格式,Ansible 会自动替换变量),内容如下:
server {
listen 80;
server_name {{ blog_vhost }};
root /usr/share/nginx/html/{{ blog_vhost }}/wordpress;
index index.php;
include /etc/nginx/default.d/*.conf;
access_log /var/log/nginx/access-{{ blog_vhost }}.log;
error_log /var/log/nginx/error-{{ blog_vhost }}.log;
}
-
变量和配置项解释:
内容 含义 作用 {``{ blog_vhost }}
模板变量,后续会被替换为 blog.yuxb.cloud
(博客域名)避免硬编码,方便修改域名 listen 80
Nginx 监听 80 端口(HTTP 协议默认端口) 用户访问域名时,默认走 80 端口 server_name {``{ blog_vhost }}
绑定域名,Nginx 收到该域名的请求就用这个配置 区分不同网站(比如一个服务器跑多个博客) root /usr/share/nginx/html/{``{ blog_vhost }}/wordpress
WordPress 代码存放的根目录 Nginx 从这个目录找博客的文件 index index.php
默认首页文件,优先加载 index.php
用户访问域名时,自动打开 index.php
access_log/error_log
博客的访问日志和错误日志路径 后续排查问题(比如访问失败)时看日志 -
为什么用模板(.j2 后缀)?:如果后续要改域名,只需改变量,不用改配置文件,更灵活。
4.3.4 本地安装 Nginx(仅为编辑模板,非必须但建议做)
[yuxb@controller lnmp 14:34:30]$ sudo yum install -y nginx
-
这步做什么? :在控制节点
controller
上安装 Nginx。 -
为什么这么做? :不是必须的,但安装后可以本地测试
vhost-wordpress.conf.j2
模板是否正确(避免目标机部署后才发现配置错误),实验简化可跳过。
阶段 4:编写 Ansible 核心剧本(自动化部署的 "大脑")
4.4.1 创建并编辑部署剧本
[yuxb@controller lnmp 15:50:25]$ vim deploy_lnmp.yml
- 这步做什么? :用
vim
创建deploy_lnmp.yml
剧本文件(Ansible 执行的核心,按 "Play" 分阶段部署),内容如下(每个步骤都有详细注释):
---
# ==================== Play 1:部署MariaDB数据库(LNMP的“M”)====================
- name: deploy mariadb # Play名称(描述这个阶段做什么)
hosts: lnmp # 目标主机组(从inventory文件读取,即node1)
tasks: # 该阶段要执行的具体任务(按顺序执行)
# 任务1:安装MariaDB服务和PHP连接数据库的依赖
- name: install mariadb-server
yum: # Ansible的yum模块(用于在CentOS上安装软件)
name: # 要安装的软件列表
- mariadb-server # MariaDB数据库服务(核心)
- python2-PyMySQL # Python2连接MySQL的模块(Ansible操作MariaDB需要)
state: present # 确保软件“已安装”(如果没装就装,装了就不操作)
# 任务2:启动MariaDB服务,并设置开机自启
- name: enable and start mariadb
service: # Ansible的service模块(管理系统服务)
name: mariadb # 服务名(MariaDB的服务名就是mariadb)
enabled: yes # 开机自启(yes=启用,no=禁用)
state: started # 服务状态(started=启动,stopped=停止,restarted=重启)
# 任务3:首次设置MariaDB的root本地密码(忽略首次执行的错误)
- name: set root@localhost password
shell: mysqladmin password {{ mysql_root_password }} # 执行系统命令设置密码
ignore_errors: yes # 忽略错误(首次执行时MariaDB默认无密码,执行该命令可能报错,所以忽略)
# 任务4:为不同主机地址(本机域名、回环地址)设置root密码
- name: set root password
mysql_user: # Ansible的mysql_user模块(管理数据库用户)
name: root # 要操作的数据库用户(root)
password: "{{ mysql_root_password }}" # 密码(从vaults.yml读取,即123)
host: "{{ item }}" # 允许登录的主机地址(循环变量)
state: present # 确保用户“存在”
login_user: root # 登录数据库的用户(用root登录)
login_password: "{{ mysql_root_password }}" # 登录密码(root密码)
with_items: # 循环执行(为3个地址分别设置密码)
- "{{ ansible_fqdn }}" # 目标机的完整域名(比如node1.yuxb.cloud)
- 127.0.0.1 # IPv4回环地址(本机访问)
- ::1 # IPv6回环地址(本机访问)
# 任务5:删除数据库匿名用户(提升安全性,避免别人空密码登录)
- name: delete user anonymous
mysql_user:
name: "" # 匿名用户的名称是空字符串(MariaDB默认有匿名用户)
host_all: yes # 所有主机上的匿名用户都删除
state: absent # 确保用户“不存在”(即删除)
login_user: root
login_password: "{{ mysql_root_password }}"
#login_unix_socket: /var/lib/mysql/mysql.sock # 注释:可选的socket登录方式(不用管)
# 任务6:删除默认的test数据库(提升安全性,test库默认任何人可访问)
- name: delete database test
mysql_db: # Ansible的mysql_db模块(管理数据库)
name: test # 要删除的数据库名(test是默认测试库)
state: absent # 确保数据库“不存在”(即删除)
login_user: root
login_password: "{{ mysql_root_password }}"
# ==================== Play 2:为WordPress准备数据库(创建专用用户和库)====================
- name: prepare db for webapp # Play名称(为Web应用准备数据库)
hosts: lnmp
tasks:
# 任务1:创建WordPress专用的数据库用户(避免用root提权)
- name: create user {{ user }}
mysql_user:
name: "{{ app_user }}" # 用户名(从vaults.yml读取,即wordpress)
password: "{{ app_password }}" # 密码(从vaults.yml读取,即123)
host: "{{ app_host }}" # 允许登录的主机(从vaults.yml读取,即%)
priv: "{{ app_priv }}" # 权限(从vaults.yml读取,即*.*:ALL)
state: present
login_user: root
login_password: "{{ mysql_root_password }}"
# 任务2:创建WordPress专用的数据库(webapp)
- name: create database db_name
mysql_db:
name: "{{ db_name }}" # 数据库名(从vars.yml读取,即webapp)
state: present # 确保数据库“存在”(即创建)
login_user: root
login_password: "{{ mysql_root_password }}"
# ==================== Play 3:部署Nginx服务(LNMP的“N”)====================
- name: deploy web server # Play名称(部署网页服务器)
hosts: lnmp
tasks:
# 任务1:安装Nginx服务
- name: install nginx
yum:
name: nginx
state: present
# 任务2:启动Nginx服务,并设置开机自启
- name: enable and start nginx
service:
name: nginx
enabled: yes
state: started
# 任务3:创建Nginx测试页面(验证Nginx是否正常运行)
- name: prepare test file for web server
copy: # Ansible的copy模块(复制内容到文件)
content: hello world from nginx # 文件内容(测试文本)
dest: /usr/share/nginx/html/index.html # 目标文件路径(Nginx默认首页路径)
# ==================== Play 4:部署PHP服务(LNMP的“P”)====================
- name: php # Play名称(部署PHP)
hosts: lnmp
tasks:
# 任务1:安装PHP及依赖(PHP-FPM、MySQL连接驱动)
- name: install php
yum:
name: php,php-fpm,php-mysqlnd # 要安装的软件(用逗号分隔)
state: present
# 软件说明:
# php:PHP核心
# php-fpm:PHP进程管理器(处理Nginx转发的PHP请求)
# php-mysqlnd:PHP连接MySQL/MariaDB的驱动(WordPress需要)
# 任务2:修改PHP-FPM的运行用户和组为nginx(权限一致)
- name: modify running user for php
lineinfile: # Ansible的lineinfile模块(修改文件中的某一行)
path: /etc/php-fpm.d/www.conf # 要修改的文件(PHP-FPM的用户配置文件)
regexp: "{{ item}} = " # 匹配行的正则(找“user = ”或“group = ”)
line: "{{ item }} = nginx" # 替换后的内容(把用户/组改成nginx)
loop: # 循环修改两个配置项(user和group)
- user
- group
# 为什么改?Nginx默认用nginx用户运行,PHP-FPM也用nginx用户,避免文件权限问题(比如PHP写文件时权限不够)
# 任务3:启动PHP-FPM服务,并设置开机自启(重启确保配置生效)
- name: enable and start php-fpm.service
service:
name: php-fpm
enabled: yes
state: restarted # 用restarted(重启),因为刚改了配置,需要生效
# 任务4:把本地的php.conf复制到目标机(让Nginx能解析PHP)
- name: config php for nginx
copy:
src: php.conf # 本地的php.conf文件(3.2步创建的)
dest: /etc/nginx/default.d/php.conf # 目标机路径(Nginx会自动加载这个目录的配置)
# 任务5:重启Nginx(加载PHP配置,让Nginx能处理PHP请求)
- name: restart nginx
service:
name: nginx
state: restarted
# ==================== Play 5:部署WordPress博客系统====================
- name: deploy web app # Play名称(部署Web应用,即WordPress)
hosts: lnmp
vars: # 该Play的局部变量(只在这个Play里生效)
blog_vhost: blog.yuxb.cloud # 博客的域名(后续会替换模板里的{{ blog_vhost }})
tasks:
# 任务1:用模板生成WordPress的Nginx虚拟主机配置
- name: prepare vhost for wordpress
template: # Ansible的template模块(处理Jinja2模板,替换变量)
src: vhost-wordpress.conf.j2 # 本地模板文件(3.3步创建的)
dest: /etc/nginx/conf.d/vhost-wordpress.conf # 目标机路径(Nginx虚拟主机配置目录)
# 任务2:创建WordPress的网站根目录(按域名命名,方便管理)
- name: create /usr/share/nginx/html/{{ blog_vhost }}
file: # Ansible的file模块(管理文件/目录)
path: /usr/share/nginx/html/{{ blog_vhost }} # 目录路径(替换变量后是/blog.yuxb.cloud)
state: directory # 确保目录“存在”(即创建)
# 任务3:解压WordPress压缩包到网站根目录
- name: Unarchive a wordpress file
unarchive: # Ansible的unarchive模块(解压压缩包)
src: wordpress-4.9.4-zh_CN.zip # 本地WordPress压缩包(需提前下载到lnmp目录)
dest: /usr/share/nginx/html/{{ blog_vhost }}/ # 解压目标路径(到博客目录下)
owner: nginx # 解压后文件的所有者(nginx,和Nginx/PHP用户一致)
group: nginx # 解压后文件的所属组(nginx)
# 注意:必须提前把wordpress-4.9.4-zh_CN.zip下载到控制节点的lnmp目录,否则会失败!
# 任务4:重启Nginx(加载虚拟主机配置,让Nginx识别博客域名)
- name: restart nginx
service:
name: nginx
state: restarted
-
剧本关键注意点:
-
必须提前下载
wordpress-4.9.4-zh_CN.zip
到控制节点的lnmp
目录(下载地址:https://wordpress.org/wordpress-4.9.4-zh_CN.zip); -
剧本按 "数据库→Web 服务器→PHP→博客" 顺序执行,不能乱序(比如先装 PHP 再装数据库,PHP 连接数据库会失败)。
-
阶段 5:执行自动化部署(跑剧本,让 Ansible 干活)
5.1.1 执行 Ansible 剧本
[yuxb@controller lnmp 15:18:48]$ ansible-playbook deploy_lnmp.yml
-
这步做什么? :执行
deploy_lnmp.yml
剧本,Ansible 会自动连接目标节点node1
,按剧本顺序执行所有任务。 -
执行过程说明:
-
输出中会显示每个任务的状态(
ok
= 已执行过,changed
= 刚执行完,failed
= 失败); -
如果某个任务失败(比如软件安装失败),先看错误信息(通常是网络问题或权限问题),解决后重新执行命令即可(Ansible 支持幂等性,重复执行不会有问题)。
-
5.1.2 关闭目标机冲突服务(避免端口被占)
# 注意:这步要在目标节点node1上执行,不是控制节点!
[root@node1 ~ 15:32:00]# systemctl disable httpd --now
-
这步做什么?:
-
systemctl disable httpd
:禁用 httpd 服务(CentOS 默认的网页服务器),避免开机自启; -
--now
:立即停止 httpd 服务。
-
-
为什么这么做?:httpd 和 Nginx 都默认用 80 端口,同时运行会导致 Nginx 无法使用 80 端口(用户访问博客会失败),所以必须关闭。
阶段 6:验证部署结果(确保每个组件都正常)
6.1.1 验证 WordPress 文件是否部署成功(目标节点 node1)
# 在node1上执行,查看WordPress文件是否存在
[root@node1 ~ 15:57:26]$ ls /usr/share/nginx/html/blog.yuxb.cloud/wordpress/
- 正常结果 :会显示 WordPress 的核心文件(如
index.php
、wp-admin
目录),和下面一致:
index.php wp-admin wp-content wp-load.php wp-signup.php
license.txt wp-blog-header.php wp-cron.php wp-login.php wp-trackback.php
readme.html wp-comments-post.php wp-includes wp-mail.php xmlrpc.php
wp-activate.php wp-config-sample.php wp-links-opml.php wp-settings.php
- 如果失败 :检查 Ansible 剧本中 "解压 WordPress" 的任务是否成功,或压缩包是否在控制节点的
lnmp
目录。
# 继续在node1上执行,查看文件权限(确保所有者是nginx)
[root@node1 ~ 15:58:41]$ ll /usr/share/nginx/html/blog.yuxb.cloud/wordpress/
-
正常结果 :所有文件的所有者(
owner
)和所属组(group
)都是nginx
(如-rw-r--r-- 1 nginx nginx 418 9月 25 2013 index.php
); -
如果权限不对 :检查 Ansible 剧本中 "解压 WordPress" 任务的
owner
和group
是否配置为nginx
。
6.2.1 验证 MariaDB 数据库是否正常(目标节点 node1)
# 在node1上执行,用WordPress专用用户登录数据库
[root@node1 ~ 16:04:31]$ mysql -uwordpress -p123
-
登录成功标志 :显示 MariaDB 命令行提示符(
MariaDB [(none)]>
),无报错; -
如果登录失败 :检查
vaults.yml
中的app_user
、app_password
是否正确,或 Ansible 剧本中 "创建数据库用户" 的任务是否成功。
# 登录后执行,查看数据库列表
MariaDB [(none)]> show databases;
- 正常结果 :显示
webapp
数据库(WordPress 专用库),如下:
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| webapp |
+--------------------+
4 rows in set (0.00 sec)
- 如果没有 webapp 库:检查 Ansible 剧本中 "创建数据库" 的任务是否成功。
# 退出数据库(可选)
MariaDB [(none)]> exit
Bye
阶段 7:浏览器访问博客(完成 WordPress 安装)
7.1.1 配置 Windows 客户端 Hosts(让浏览器识别博客域名)
-
打开 Windows 文件管理器,进入路径:
C:\Windows\System32\drivers\etc
; -
找到
hosts
文件,右键用 "记事本" 打开(需管理员权限,否则无法保存); -
在文件末尾添加一行:
10.1.8.11 blog.yuxb.cloud
(10.1.8.11
是目标节点node1
的 IP,blog.yuxb.cloud
是博客域名); -
保存并关闭文件。
- 为什么这么做? :Windows 不知道
blog.yuxb.cloud
对应哪个 IP,添加后浏览器会把该域名指向10.1.8.11
(目标节点)。
7.1.2 访问 WordPress 并完成安装
-
打开浏览器(Chrome、Edge 等),在地址栏输入
blog.yuxb.cloud
,按回车; -
进入 WordPress 安装向导,选择 "中文",点击 "继续";
-
点击 "现在就开始!",填写数据库信息(和vaults.yml、vars.yml一致):
-
数据库名:
webapp
-
用户名:
wordpress
-
密码:
123
-
数据库主机:
localhost
(本地数据库,不用改) -
表前缀:默认
wp_
(不用改)
-
-
点击 "提交",再点击 "运行安装程序";
-
填写博客信息(自定义):
-
站点标题:比如 "我的第一个博客"
-
用户名:自定义(比如
admin
) -
密码:自定义(比如
123456
,生产环境用复杂密码) -
电子邮件:自定义(比如
test@123.com
)
-
-
点击 "安装 WordPress",安装完成后用刚才的用户名密码登录;
-
登录后进入博客后台,或点击 "访问站点" 查看博客首页,至此实验完成!
五、常见问题排查
问题现象 | 可能原因 | 解决方法 |
---|---|---|
执行剧本时 "yum 安装软件失败" | 目标节点网络不通,或 yum 源有问题 | 1. 在 node1 上 ping 百度(ping baidu.com )确认网络;2. 更换 yum 源为阿里云(yum install -y wget && wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo ) |
浏览器访问 blog.yuxb.cloud 显示 "无法访问" |
1. 目标节点 Nginx 没启动;2. 客户端 Hosts 配置错误;3. 防火墙拦截 80 端口 | 1. 在 node1 上执行 systemctl status nginx 确认 Nginx 已启动;2. 检查 Windows Hosts 配置的 IP 和域名是否正确;3. 在 node1 上关闭防火墙(systemctl stop firewalld && systemctl disable firewalld ) |
WordPress 安装时 "无法连接数据库" | 1. 数据库密码输错;2. MariaDB 没启动;3. 数据库用户权限不够 | 1. 确认密码和 vaults.yml 一致;2. 在 node1 上执行 systemctl status mariadb 确认数据库已启动;3. 检查剧本中 app_priv 是否为 *.*:ALL |
访问 PHP 文件显示 "下载文件" 而非执行 | Nginx 的 PHP 配置没生效,或 PHP-FPM 没启动 | 1. 在 node1 上执行 systemctl status php-fpm 确认 PHP-FPM 已启动;2. 检查 /etc/nginx/default.d/php.conf 文件是否存在且内容正确 |
六、实验总结
-
本次实验通过 Ansible 实现了 LNMP + WordPress 的自动化部署,核心是 "剧本驱动",避免手动在目标机敲命令;
-
敏感信息用 Ansible Vault 加密,普通配置用变量文件,符合 "安全 + 灵活" 的原则;
-
每个组件的作用:Nginx 收请求、PHP 处理动态代码、MariaDB 存数据,三者协同支撑 WordPress 运行;
-
零基础同学需重点理解 "剧本的执行顺序" 和 "各组件的联动关系",遇到问题先看日志(Nginx 日志、Ansible 执行日志)。
管理大项目
配置并行
概念
并行配置就是把原本串行执行的任务拆分,使得多个任务可以同时进行,从而加快整体完成速度。这在大项目中尤为重要,比如:
-
软件开发:编译、测试、打包可以并行。
-
数据处理:大数据分析任务可以按分区或模块并行。
-
运维/部署:批量服务器操作可以并行执行。
并行策略
-
任务级并行
-
不同任务之间独立执行。
-
例:在CI/CD流水线中,同时运行单元测试和代码扫描。
-
-
数据级并行
-
将数据拆分成多个部分,各自独立处理。
-
例:大文件分块处理、多节点分布式计算。
-
-
混合并行
-
结合任务级和数据级。
-
例:多模块服务并行编译 + 各模块单元测试并行执行。
-
配置 forks
[yuxb@controller web 16:47:05]$ vim inventory
[yuxb@controller web 16:47:48]$ cat playbook.yml
---
- name: connection
hosts: all
tasks:
- name: conneciton 1
shell: sleep 5
- name: conneciton 2
debug:
msg: connection 2
[yuxb@controller web 16:47:52]$ cat inventory
[controller]
[webs]
node1
node3
[dbs]
node2
node4
执行
# 两人一组执行
[yuxb@controller web 16:48:10]$ ansible-playbook playbook.yml -f 2
PLAY [connection] ***********************************************************************************
TASK [Gathering Facts] ******************************************************************************
ok: [node1]
ok: [node3]
ok: [node2]
ok: [node4]
TASK [conneciton 1] *********************************************************************************
changed: [node3]
changed: [node1]
changed: [node2]
changed: [node4]
TASK [conneciton 2] *********************************************************************************
ok: [node1] => {
"msg": "connection 2"
}
ok: [node3] => {
"msg": "connection 2"
}
ok: [node2] => {
"msg": "connection 2"
}
ok: [node4] => {
"msg": "connection 2"
}
PLAY RECAP ******************************************************************************************
node1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node4 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
配置 serial
[yuxb@controller web 16:49:34]$ vim playbook.yml
[yuxb@controller web 16:49:57]$ cat playbook.yml
---
- name: Rolling update
hosts: all
serial: 2
tasks:
- name: latest apache httpd package is installed
yum:
name: httpd
state: latest
notify: restart apache
handlers:
- name: restart apache
service:
name: httpd
state: restarted
执行
[yuxb@controller web 16:50:51]$ ansible-playbook playbook.yml
PLAY [Rolling update] *******************************************************************************
TASK [Gathering Facts] ******************************************************************************
ok: [node3]
ok: [node1]
TASK [latest apache httpd package is installed] *****************************************************
ok: [node3]
ok: [node1]
PLAY [Rolling update] *******************************************************************************
TASK [Gathering Facts] ******************************************************************************
ok: [node4]
ok: [node2]
TASK [latest apache httpd package is installed] *****************************************************
ok: [node2]
ok: [node4]
PLAY RECAP ******************************************************************************************
node1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node4 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
配置 async
示例1: 任务执行失败,在规定时间内容任务没有执行完成。
[yuxb@controller web 16:50:01]$ vim playbook.yml
[yuxb@controller web 16:52:54]$ cat playbook.yml
---
- name: connection
hosts: node1
tasks:
- name: conneciton
shell: sleep 10
async: 5
poll: 2
# 执行
# 中间会卡一会
[yuxb@controller web 16:51:14]$ ansible-playbook playbook.yml
PLAY [connection] ***********************************************************************************
TASK [Gathering Facts] ******************************************************************************
ok: [node1]
TASK [conneciton] ***********************************************************************************
fatal: [node1]: FAILED! => {"changed": false, "msg": "async task did not complete within the requested time - 5s"}
PLAY RECAP ******************************************************************************************
node1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
示例2: 放入后台下载,立刻执行下一个任务。
[yuxb@controller web 16:52:58]$ vim playbook.yml
[yuxb@controller web 16:54:11]$ cat playbook.yml
---
- name: connection
hosts: node1
tasks:
- name: download
get_url:
url: http://192.168.48.100/ISOS/openEuler-24.03-LTS-x86_64-dvd.iso
dest: /home/laoma
async: 100
poll: 0
# 执行
# 很快的就执行完毕
[yuxb@controller web 16:53:14]$ ansible-playbook playbook.yml
PLAY [connection] ***********************************************************************************
TASK [Gathering Facts] ******************************************************************************
ok: [node1]
TASK [download] *************************************************************************************
changed: [node1]
PLAY RECAP ******************************************************************************************
node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
示例3: ansible 默认行为,等该任务执行完,再执行下一个任务。
[yuxb@controller web 16:54:13]$ vim playbook.yml
[yuxb@controller web 16:55:18]$ cat playbook.yml
---
- name: connection
hosts: node1
tasks:
- name: conneciton
shell: sleep 10
async: 0
poll: 2
# 执行
# 中间会卡一会
[yuxb@controller web 16:54:26]$ ansible-playbook playbook.yml
PLAY [connection] ***********************************************************************************
TASK [Gathering Facts] ******************************************************************************
ok: [node1]
TASK [conneciton] ***********************************************************************************
changed: [node1]
PLAY RECAP ******************************************************************************************
node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0