自动化运维Ansible
目录
- 一、介绍
- 1. 自动化运维工具对比
- 2. Ansible 简介与特性
- 3. 变量优先级(从高到低)
- 二、Ansible 安装(CentOS 7)
- 1. 环境准备
- 2. 安装步骤
- 3. 基础配置与 Inventory
- 三、Ad-Hoc 命令与常用模块
- 命令格式与常用选项
- ping 探活
- shell vs command
- copy 复制与备份
- user 用户管理
- yum 包管理
- service 服务管理
- file 文件/目录/链接
- script 远程执行本地脚本
- setup 收集 Facts
- archive 打包
- unarchive 解压
- cron 定时任务
- get_url 下载文件
- yum_repository 管理仓库源
- lineinfile 修改/插入行
- debug 调试输出
- 四、Ansible Playbook 剧本
- Playbook 基础与结构
- 基础命令/输出颜色含义
- Tasks 示例
- Handlers/Notify 触发器
- 循环/迭代(with_items/loop)
- 自定义变量与 vars_files
- 分组与多 Play 示例(group 模块)
- gather_facts 与变量示例
- debug 调试 Playbook
- 条件判断 when
- 五、项目综合实战:Nginx + PHP Web 部署(CentOS 7)
- 1. 目标与架构
- 2. 目录结构
- 3. 基础配置与清单
- 4. 角色实现(common/nginx/php/app)
- 5. site.yml 编排与运行
- 6. 回滚与验证要点
一、介绍
1. 自动化运维工具对比
- Puppet:基于 Ruby,C/S 架构,可扩展强;远程命令执行偏弱。
- SaltStack:基于 Python,C/S 架构,较轻量,配置用 YAML;需要在每台被控端安装 agent。
- Ansible:基于 Python,分布式,无客户端,轻量;YAML + Jinja2;远程命令执行强。默认通过 SSH 连接。
通俗理解:Ansible 更容易“开箱即用”,无需在被控端安装东西(免 agent),非常适合快速批量操作与配置编排。
2. Ansible 简介与特性
- Ansible 是自动化运维工具,基于模块工作(核心是模块,Ansible 负责编排和分发调用)。
- 特性:
- no agents:被控端无需安装客户端;升级只需升级控制端。
- no server:无中心服务端,直接用命令/剧本即可。
- modules in any languages:模块可用任意语言实现。
- yaml, not code:用 YAML 写剧本(Playbook),更易读。
- ssh by default:默认基于 SSH 连接。
- 组件简述:
- connection plugins:连接插件,默认 SSH。
- host inventory:主机清单,定义被控主机与分组。
- modules:模块(command、copy、user…)。
- plugins:插件(连接、回调、邮件等)。
- playbook:编排多任务的 YAML 定义。
- 默认并发数:5(可在 ansible.cfg 中调整 forks)。
3. 变量优先级(从高到低)
- 命令行变量:
-e/--extra-vars
(最高)。 - Inventory 中的主机/组变量。
- Playbook 内的
vars
与vars_files
。 - Facts(setup 收集)。
- 角色的默认变量:
roles/*/defaults/main.yml
最低。
注:新写法建议使用 ansible_user/ansible_password/ansible_port/ansible_ssh_private_key_file
,不再推荐旧名 ansible_ssh_user/ansible_ssh_pass
(兼容但逐步淘汰)。
二、Ansible 安装(CentOS 7)
1. 环境准备
- 关闭防火墙与 SELinux(生产建议按需放行端口而非直接关闭,这里为教学简化)。
- 控制节点 1 台,被控节点若干。示例:
# /etc/hosts(各机均添加,便于主机名解析)
192.168.1.10 ansible-web1
192.168.1.11 ansible-web2
192.168.1.12 ansible-web3
192.168.1.9 ansible-server # 控制端
- SSH 免密:在控制端生成密钥并分发至各被控端。
ssh-keygen -t rsa -b 4096 -C "ansible@server"
ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.1.10
ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.1.11
ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.1.12
提示:无免密也可用密码登录,但不便于自动化;建议配置免密。
2. 安装步骤
CentOS 7 默认 Python 2.7,但新版本 Ansible 更推荐 Python 3。常见两种方式:
- 方式 A(简单):EPEL + yum 安装 Ansible(一般为 2.9.x),可在 Python 2.7 下运行。
yum install -y epel-release
yum install -y ansible
ansible --version
ansible --help
- 方式 B(推荐):安装 Python 3(EPEL 或 IUS),用 pip 安装较新 Ansible。
yum install -y epel-release
yum install -y python36 python3-pip
pip3 install --upgrade pip
pip3 install "ansible<8" # 在 CentOS 7 上兼容较佳的版本区间
ansible --version
说明:CentOS 7 年代较老,新版 Ansible 可能依赖较新 Python;如遇依赖冲突,选择方式 A 或锁定版本。
3. 基础配置与 Inventory
- 查看默认配置文件位置:
rpm -qc ansible
# /etc/ansible/ansible.cfg
# /etc/ansible/hosts
- 主配置:
/etc/ansible/ansible.cfg
(日志、forks、模块路径、回调插件等)。 - 主机清单:
/etc/ansible/hosts
(建议直接使用 IP,避免 DNS 依赖)。
示例(单主机与分组):
# 直接添加主机
ansible-web1# 添加主机组
[webservers]
192.168.1.11
ansible-web2# 组合组(children)
[webservers1]
ansible-web1[webservers2]
ansible-web2[weball:children]
webservers1
webservers2# 组变量(组内主机均生效)
[weball:vars]
ansible_user=root
ansible_port=22
# 使用私钥(如路径非默认时设置)
# ansible_ssh_private_key_file=/root/.ssh/id_rsa
# 若无免密,也可用密码(不安全,演示用)
# ansible_password=your_password
查看组内主机:
ansible weball --list-hosts
使用自定义 Inventory(文件 /opt/hostlist
):
[all:vars]
ansible_user=root
ansible_port=22
# ansible_password=your_password[all]
ansible-web1
ansible-web2
执行时指定:
ansible -i /opt/hostlist all -m ping -o
提示:ping
模块是 SSH 探活(非 ICMP),等价于“能否连上 22 端口并执行模块”。
三、Ad-Hoc 命令与常用模块
命令格式与常用选项
ansible <pattern> -m <module_name> -a <arguments> [其他选项]
- pattern:主机/组名/IP/别名,
all
为全部;支持通配与正则。 -m
:模块名,默认command
。-a
:模块参数。-o
:单行输出(更紧凑)。
ping 探活
# 单台
ansible ansible-web1 -m ping -o
# 多台
ansible ansible-web1,ansible-web2 -m ping -o
# 组
ansible webservers1 -m ping -o
shell vs command
- shell:支持管道、重定向、通配符;适合复杂命令链。
- command:直接执行命令,不经 shell;更高效/安全。
# 两者等效示例(简单命令)
ansible webservers1 -m shell -a 'uptime'
ansible webservers1 -a 'uptime' # 默认 command
copy 复制与备份
常用参数:src/dest/owner/group/mode/backup
。
# 控制端 /root/a.txt -> 远端 /opt/ (保留权限)
ansible weball -m copy -a 'src=/root/a.txt dest=/opt owner=root group=root mode=0644' -o
# 覆盖时备份
ansible weball -m copy -a 'src=/root/a.txt dest=/opt/ owner=root group=root mode=0644 backup=yes' -o
user 用户管理
# 添加/删除用户
ansible ansible-web1 -m user -a "name=qianfeng"
ansible ansible-web1 -m user -a "name=qianfeng state=absent" -o
ansible ansible-web1 -m user -a "name=qianfeng state=absent remove=yes" # 连同家目录/邮件删除# 设置密码(需哈希值,而非明文)
python - <<'PY'
import crypt
print(crypt.crypt('12345678'))
PY
# 假设输出为 $6$.... 形如 /etc/shadow 的哈希
ansible ansible-web1 -m user -a "name=tom password='$6$...'"# 生成 SSH 密钥对(在被控端为该用户创建 ~/.ssh/id_rsa*)
ansible ansible-web1 -m user -a "name=tom generate_ssh_key=yes"
yum 包管理
# 安装 httpd(最新)
ansible webservers1 -m yum -a "name=httpd state=latest" -o
# 卸载 httpd
ansible webservers1 -m yum -a "name=httpd state=removed" -o
常见 state
:
latest
:安装最新present
/installed
:确保已安装absent
/removed
:卸载
service 服务管理
ansible webservers1 -m service -a "name=httpd state=started" # 启动
ansible webservers1 -m service -a "name=httpd state=stopped" # 停止
ansible webservers1 -m service -a "name=httpd state=restarted" # 重启
ansible webservers1 -m service -a "name=httpd state=started enabled=yes" # 开机自启
提示:CentOS 7 使用 systemd;某些模块也支持 enabled
单独设置。
file 文件/目录/链接
# 创建空文件
ansible webservers1 -m file -a 'path=/tmp/88.txt mode=0777 state=touch'
# 创建目录
ansible webservers1 -m file -a 'path=/tmp/99 mode=0777 state=directory'
更多 Playbook 写法(更可读):
- name: Ensure testfile existsfile:path: /tmp/testfilestate: touch- name: Ensure testdir existsfile:path: /tmp/testdirstate: directoryowner: myusergroup: mygroupmode: '0755'- name: Remove testfilefile:path: /tmp/testfilestate: absent- name: Create a symlinkfile:src: /tmp/originaldest: /tmp/linkstate: link- name: Recursively chmodfile:path: /tmp/testdirstate: directorymode: '0755'recurse: yes
script 远程执行本地脚本
适合“控制端有脚本,但被控端没有”的场景。Ansible 会把脚本临时传到远端执行。
# 本地脚本
cat > /root/test.sh <<'SH'
#!/usr/bin/env bash
touch test{1..50}
SH
chmod +x /root/test.sh# 在远端 /mnt 目录下执行该脚本
ansible webservers1 -m script -a "chdir=/mnt /root/test.sh"# 条件执行示例(存在某文件才执行/或不存在才执行)
cat > /root/awk.sh <<'SH'
#!/usr/bin/env bash
awk -F: '{print $1,$2}' /etc/passwd
SH
chmod +x /root/awk.shansible webservers1 -m script -a "/root/awk.sh removes=/etc/passwd"
说明:removes=/path
表示当该路径存在时才执行;creates=/path
表示当该路径存在时跳过执行。
setup 收集 Facts
# 收集全部 facts
ansible webservers1 -m setup
# 仅过滤 IPv4 地址
ansible webservers1 -m setup -a 'filter=ansible_all_ipv4_addresses'
常用 facts:
ansible_all_ipv4_addresses
、ansible_default_ipv4
ansible_distribution/ansible_kernel
ansible_processor_cores/ansible_mem_total
ansible_python_version/ansible_pkg_mgr
Playbook 收集示例:
- hosts: alltasks:- name: 收集硬件信息setup:gather_subset: hardware- name: 收集网络信息setup:gather_subset: network
archive 打包
ansible webservers1 -m archive -a "path=/etc dest=/mnt/$(date +%F)-etc.tar.gz format=gz"
支持 tar/zip/gz/bz2
等格式,常用参数:path/dest/format/owner/mode
。
unarchive 解压
# 从控制端复制并解压到远端
ansible all -m unarchive -a "src=/root/arc.tar.gz dest=/root/" --become# 远端已有压缩包(不复制,只解压)
ansible all -m unarchive -a "src=/path/file.tar.gz dest=/path/ remote_src=yes"# 覆盖已有
ansible all -m unarchive -a "src=/root/a.tar.gz dest=/root/ extra_opts=--overwrite"
cron 定时任务
# 新增定时任务
ansible webservers -m cron -a 'name="deletefile" minute="53" hour="20" day="7" month="5" job="rm -rf /mnt/*"'
# 删除定时任务
ansible webservers -m cron -a "name='deletefile' state=absent"
更多 Playbook 写法:
- name: 每日 2 点备份cron:name: "daily backup"minute: "0"hour: "2"job: "/usr/local/bin/backup.sh"
get_url 下载文件
ansible webservers -m get_url -a "url=https://download.redis.io/releases/redis-7.0.10.tar.gz dest=/mnt force=yes"
常用参数:url/dest/backup/force/url_username/url_password
。
yum_repository 管理仓库源
# 创建本地源
ansible webserver -m yum_repository -a "name='Centos Base' file=base description='test' baseurl=file:///mnt/centos enabled=yes gpgcheck=no"# 配置阿里 EPEL 源
ansible webserver -m yum_repository -a "name=aliepel baseurl=https://mirrors.aliyun.com/epel/7/x86_64/ enabled=yes gpgcheck=yes gpgcakey=https://mirrors.aliyun.com/epel/RPM-GPG-KEY-EPEL-7 state=present file=AlicloudEpel description=alepel"# 删除仓库
ansible webserver -m yum_repository -a "file=base name='Centos Base' state=absent"
lineinfile 修改/插入行
关键参数:path/line/state/regexp/insertafter/create/backup
。
用法场景:批量改配置文件的某行,如 PermitRootLogin yes
。
debug 调试输出
# 输出字符串
ansible webservers -m debug -a "msg=hello,beijing"
# 输出变量(通过 -e 传入)
ansible webservers -m debug -a "var=a" -e "a=1234567"
Playbook 中常配合 register
把任务结果存变量后再 debug
打印。
四、Ansible Playbook 剧本
Playbook 基础与结构
- Playbook 用 YAML 描述,适合多步骤批量操作。
- 一个 Playbook = 若干个 Play;一个 Play = 在一组主机上按序执行多个任务(tasks)。
示例:
---
- name: first playgather_facts: false # 跳过 facts 可加速hosts: webserversremote_user: root # 建议改用 become 提权tasks:- name: test connectionping:- name: disable selinuxcommand: '/sbin/setenforce 0'ignore_errors: true- name: disable firewalldservice: name=firewalld state=stopped- name: install httpdyum: name=httpd state=latest- name: install configuration file for httpdcopy: src=/opt/httpd.conf dest=/etc/httpd/conf/httpd.confnotify: "restart httpd" # 若变更则触发 handler- name: start httpd serviceservice: enabled=true name=httpd state=startedhandlers:- name: restart httpdservice: name=httpd state=restarted
提示(最佳实践):
- 生产环境更推荐
become: yes
以普通用户登录再提权,少用 root 直连。 - 文件路径/模板请用
templates/
+template
模块,便于变量化。
基础命令/输出颜色含义
ansible-playbook test.yml # 运行
ansible-playbook test.yml --syntax-check # 语法检查
ansible-playbook test.yml --list-tasks # 列出 tasks
ansible-playbook test.yml --list-hosts # 列出主机
ansible-playbook test.yml --start-at-task 'install httpd'
颜色:绿色 success;黄色 changed(状态有变更);红色 failed(需排查)。
Tasks 示例
- hosts: webservers1user: roottasks:- name: create filefile: state=touch mode=0777 path=/tmp/playbook.txt- name: create dirfile: path=/mnt/dir12 state=directory
Handlers/Notify 触发器
- 只有当某任务状态为 changed 时才触发对应 handler(并且在本 play 的所有普通任务结束后执行)。
- hosts: webservers1tasks:- name: test copycopy: src=/root/a.txt dest=/mntnotify: test handlershandlers:- name: test handlersshell: echo "abcd" >> /mnt/a.txt
常见用法:配置文件变更后“重启服务”。
循环/迭代(with_items/loop)
- 字符串列表:
- hosts: websrvstasks:- name: install packagesyum:name: "{{ item }}"state: latestwith_items:- httpd- httpd-tools- php- php-mysql- php-mbstring- php-gd
- loop(推荐新写法)创建用户/组:
- hosts: testtasks:- name: Create Groupsgroup:name: "{{ item }}"loop:- group1- group2- group3- name: Create Usersuser:name: "{{ item.user }}"group: "{{ item.group }}"uid: "{{ item.uid }}"loop:- { user: jack, group: group1, uid: 2001 }- { user: tom, group: group2, uid: 2002 }- { user: alice,group: group3, uid: 2003 }
- 批量安装/卸载:
- hosts: 192.168.157.129tasks:- name: install epelyum: name=epel-release state=latest- name: install packagesyum:name: "{{ item }}"state: latestloop:- httpd- mysql- nginx- redis
自定义变量与 vars_files
- 优势:更清晰、可复用、一处修改全局生效。
- 变量文件示例:
/etc/ansible/vars/file.yml
src_path: /root/test/a.txt
dest_path: /opt/test/
- Playbook 引用:
- hosts: ansible-web1vars_files:- /etc/ansible/vars/file.ymltasks:- name: create directoryfile: path={{ dest_path }} mode=0755 state=directory- name: copy filecopy: src={{ src_path }} dest={{ dest_path }}
分组与多 Play 示例(group 模块)
- hosts: webserver1tasks:- name: create a groupgroup: name=mygrp system=yes- name: create a useruser: name=tom group=mygrp system=yes- hosts: webserver2tasks:- name: install apacheyum: name=httpd state=latest- name: start httpd serviceservice: name=httpd state=started
gather_facts 与变量示例
- hosts: ansible-web1gather_facts: falsevars:user: jacksrc_path: /root/a.txtdest_path: /mnt/tasks:- name: create useruser: name={{ user }}- name: copy filecopy: src={{ src_path }} dest={{ dest_path }}
debug 调试 Playbook
- hosts: webservertasks:- name: create filefile: path=/mnt/debug.txt state=touchregister: create_file- name: 输出创建过程debug:msg: "{{ create_file }}"- hosts: webservervars:user1: jacktasks:- name: 打印变量debug:var: user1
条件判断 when
- 作用:按条件决定 task 执行与否。常用比较:
> >= < <= == !=
。
- 根据主机名条件创建文件:
- hosts: webservertasks:- name: create file when hostname is localhostfile: path=/mnt/test1.txt state=touchregister: create_filewhen: ansible_hostname == "localhost"
- 仅在 CentOS 上安装包:
- hosts: webservertasks:- name: install packageignore_errors: trueyum:name: "{{ item }}"state: latestloop:- nginx- rediswhen: ansible_distribution == "CentOS"
- 文件为空则写入内容:
- hosts: webservertasks:- name: ensure filefile: path=/opt/file.txt state=touch- name: check file contentshell: cat /opt/file.txtregister: check_file- name: insert hello when emptyshell: echo "hello" >> /opt/file.txtwhen: check_file.stdout == ""
- 服务未启动则启动(以 mysqld 为例):
- hosts: webservertasks:- name: check mysql statusshell: systemctl is-active mysqldregister: mysql_statusignore_errors: true- name: start mysql when inactiveservice: name=mysqld state=startedwhen: mysql_status.rc != 0
五、项目综合实战:Nginx + PHP Web 部署(CentOS 7)
本章节给出一套可直接运行的“多机 Web 部署”案例,涵盖目录规范、配置、角色拆分与一键部署。
1. 目标与架构
- 目标:在一组
web
主机上部署 Nginx + PHP-FPM,发布一个简单 PHP 应用。 - 架构:
- 控制端:Ansible(CentOS 7)
- 被控端:CentOS 7(组名
web
) - 组件:Nginx 作为前端,转发到 PHP-FPM。
2. 目录结构
建议项目结构(便于环境区分与复用):
ansible-project/
├─ ansible.cfg
├─ site.yml # 顶层编排
├─ inventories/
│ ├─ prod/
│ │ ├─ hosts.ini
│ │ └─ group_vars/
│ │ └─ web.yml
│ └─ dev/
│ └─ hosts.ini
└─ roles/├─ common/│ └─ tasks/main.yml├─ nginx/│ ├─ tasks/main.yml│ ├─ handlers/main.yml│ └─ templates/│ ├─ nginx.conf.j2│ └─ vhost.conf.j2├─ php/│ ├─ tasks/main.yml│ ├─ handlers/main.yml│ └─ templates/www.conf.j2└─ app/├─ tasks/main.yml└─ templates/index.php.j2
快速初始化(在控制端执行,示意):
mkdir -p ansible-project/inventories/{prod,dev}/group_vars
mkdir -p ansible-project/roles/{common,nginx,php,app}/{tasks,handlers,templates}
touch ansible-project/{ansible.cfg,site.yml}
touch ansible-project/inventories/prod/hosts.ini
touch ansible-project/inventories/prod/group_vars/web.yml
3. 基础配置与清单
ansible-project/ansible.cfg
:
[defaults]
inventory = inventories/prod/hosts.ini
forks = 10
host_key_checking = False
timeout = 30
deprecation_warnings = False
log_path = ./ansible.log
inventories/prod/hosts.ini
:
[web]
192.168.1.10 ansible_user=root
192.168.1.11 ansible_user=root[web:vars]
http_port=80
server_name=www.example.com
app_root=/var/www/app
inventories/prod/group_vars/web.yml
(组变量):
http_port: 80
server_name: www.example.com
app_root: /var/www/app
php_fpm_listen: 127.0.0.1:9000
nginx_worker_processes: auto
4. 角色实现(common/nginx/php/app)
roles/common/tasks/main.yml
:基础环境与常用工具。
---
- name: Ensure EPELyum: name=epel-release state=present- name: Install base toolsyum:name:- vim-enhanced- curl- unzip- gitstate: present- name: Set SELinux permissive (runtime)command: setenforce 0ignore_errors: true- name: Disable firewalld (demo)service: name=firewalld state=stopped enabled=noignore_errors: true
roles/nginx/tasks/main.yml
:
---
- name: Install nginxyum: name=nginx state=present- name: Deploy nginx.conftemplate: src=nginx.conf.j2 dest=/etc/nginx/nginx.confnotify: Restart nginx- name: Deploy vhosttemplate: src=vhost.conf.j2 dest=/etc/nginx/conf.d/app.confnotify: Restart nginx- name: Enable and start nginxservice: name=nginx state=started enabled=yes
roles/nginx/handlers/main.yml
:
---
- name: Restart nginxservice: name=nginx state=restarted
roles/nginx/templates/nginx.conf.j2
(最小可用):
user nginx;
worker_processes {{ nginx_worker_processes | default('auto') }};
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events { worker_connections 1024; }
http {include /etc/nginx/mime.types;default_type application/octet-stream;log_format main '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';access_log /var/log/nginx/access.log main;sendfile on;keepalive_timeout 65;include /etc/nginx/conf.d/*.conf;
}
roles/nginx/templates/vhost.conf.j2
:
server {listen {{ http_port }};server_name {{ server_name }};root {{ app_root }};index index.php index.html;location / {try_files $uri $uri/ /index.php?$args;}location ~ \.php$ {include fastcgi_params;fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;fastcgi_pass {{ php_fpm_listen }};}
}
roles/php/tasks/main.yml
:
---
- name: Install PHP-FPM and extensionsyum:name:- php- php-fpm- php-cli- php-json- php-mbstring- php-xmlstate: present- name: Configure php-fpm pooltemplate: src=www.conf.j2 dest=/etc/php-fpm.d/www.confnotify: Restart php-fpm- name: Enable and start php-fpmservice: name=php-fpm state=started enabled=yes
roles/php/handlers/main.yml
:
---
- name: Restart php-fpmservice: name=php-fpm state=restarted
roles/php/templates/www.conf.j2
(关键监听修改为变量):
[www]
user = nginx
group = nginx
listen = {{ php_fpm_listen }}
listen.owner = nginx
listen.group = nginx
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
roles/app/tasks/main.yml
:应用目录与首页。
---
- name: Ensure app root existsfile: path={{ app_root }} state=directory owner=nginx group=nginx mode=0755- name: Deploy index.phptemplate: src=index.php.j2 dest={{ app_root }}/index.php owner=nginx group=nginx mode=0644
roles/app/templates/index.php.j2
:
<?php
phpinfo();
5. site.yml 编排与运行
site.yml
:
---
- hosts: webbecome: yesroles:- common- php- nginx- app
运行:
cd ansible-project
ansible-playbook site.yml # 使用默认 prod 清单
# 或指定清单
ansible-playbook -i inventories/prod/hosts.ini site.yml
6. 回滚与验证要点
- 回滚建议:
- 配置文件用
template
+backup=yes
或copy backup=yes
; - 应用发布建议采用版本目录(releases/2025xxxx)+
current
符号链接模式; - 使用
unarchive
的creates
防重复解压,或在任务中加 checksum 校验。
- 配置文件用
- 验证:
- 语法检查:
nginx -t
- 端口监听:
ss -lntp | egrep ':80|:9000'
- 服务状态:
systemctl status nginx php-fpm
- 页面验证:
curl -I http://<web_ip>/
或浏览器访问http://server_name/
。
- 语法检查: