自动化运维工具Ansible实战(六)playbook常用的模块

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

(一)简述

    playbook的模块与在ansible命令行下使用的模块有一些不同。这主要是因为在playbook中会使用到一些facts变量和一些通过setup模块从远程主机上获取到的变量。有些模块没法在命令行下运行,就是因为它们需要这些变量。而且即使那些可以在命令行下工作的模块也可以通过playbook的模块获取一些更高级的功能。


(二)常用的模块

1,template模块

    在实际应用中,我们的配置文件有些地方可能会根据远程主机的配置的不同而有稍许的不同,template可以使用变量来接收远程主机上setup收集到的facts信息,针对不同配置的主机,定制配置文件。用法大致与copy模块相同。

常见的参数如下:

1
2
3
4
5
6
7
8
9
10
11
12
backup:如果原目标文件存在,则先备份目标文件
dest:目标文件路径
force:是否强制覆盖,默认为 yes
group:目标文件属组
mode:目标文件的权限
owner:目标文件属主
src:源模板文件路径
validate:在复制之前通过命令验证目标文件,如果验证通过则复制
官方简单示例:
- template: src= /mytemplates/foo .j2 dest= /etc/file .conf owner=bin group=wheel mode=0644
- template: src= /mytemplates/foo .j2 dest= /etc/file .conf owner=bin group=wheel mode= "u=rw,g=r,o=r"
- template: src= /mine/sudoers  dest= /etc/sudoers  validate= 'visudo -cf %s'

通过以下的例子来讲解template模块与copy模块的区别。

有一个配置文件named.conf需要通过playbook的template模块来分发到主机组web1中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
下面是named.conf的简单配置:
[root@Server5 etc] # cat named.conf 
options {
listen-on port 53 {
127.0.0.1;
192.168.180.5;
};
listen-on-v6 port 53 { ::1; };
directory  "/var/named" ;
dump- file  "/var/named/data/cache_dump.db" ;
statistics- file  "/var/named/data/named_stats.txt" ;
memstatistics- file  "/var/named/data/named_mem_stats.txt" ;
};
zone  "."  IN {
type  hint;
file  "named.ca" ;
};
include  "/etc/named.rfc1912.zones" ;
include  "/etc/named.root.key" ;
zone  "internal.example.com"  IN {
type  slave;
file  "slaves/internal.example.com" ;
masters { 192.168.180.4; };
};
{ {group_names } }

下面开始编写templated.yml的配置

1
2
3
4
5
6
[root@Monitor ansible] # vim templated.yml              
- name: copy configure  file  to server web1 group
   hosts: web1
   tasks:          
       - template: src= /etc/ansible/named .conf dest= /etc/named .conf
       - copy: src= /etc/ansible/named .conf dest= /etc/named2 .conf

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@Monitor ansible] # ansible-playbook templated.yml 
PLAY [copy configure  file  to server web1 group] ********************************
TASK [setup] *******************************************************************
ok: [Server6]
ok: [Server5]
TASK [template] ****************************************************************
ok: [Server6]
ok: [Server5]
TASK [copy] ********************************************************************
changed: [Server6]
changed: [Server5]
PLAY RECAP *********************************************************************
Server5                    : ok=3    changed=1    unreachable=0    failed=0   
Server6                    : ok=3    changed=1    unreachable=0    failed=0

去server5和server6上查看刚才拷贝的文件,发现name2.conf通过copy的没有任何改变,通过template模块拷贝的name.conf会有变量的更改。下面的是server5的name.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@Server5 etc] # cat /etc/named.conf 
options {
listen-on port 53 {
127.0.0.1;
192.168.180.5;
};
listen-on-v6 port 53 { ::1; };
directory  "/var/named" ;
dump- file  "/var/named/data/cache_dump.db" ;
statistics- file  "/var/named/data/named_stats.txt" ;
memstatistics- file  "/var/named/data/named_mem_stats.txt" ;
};
zone  "."  IN {
type  hint;
file  "named.ca" ;
};
include  "/etc/named.rfc1912.zones" ;
include  "/etc/named.root.key" ;
zone  "internal.example.com"  IN {
type  slave;
file  "slaves/internal.example.com" ;
masters { 192.168.180.4; };
};
{ {group_names }}


2,set_fact模块

    set_fact模块可以自定义facts,这些自定义的facts可以通过template或者变量的方式在playbook中使用。如果你想要获取一个进程使用的内存的百分比,则必须通过set_fact来进行计算之后得出其值,并将其值在playbook中引用。

#####下面是一个配置mysql innodb buffer size的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@Monitor ansible] # vim set_fact.yml 
 
- name: Configure MySQL
   hosts: web1
   tasks:
     - name:  install  MySql
       yum: name=mysql-server state=installed
 
     - name: Calculate InnoDB buffer pool size
       set_fact: innodb_buffer_pool_size_mb= "{{ ansible_memtotal_mb / 2 }}"
 
     - name: Configure MySQL 
       template: src= /etc/ansible/my .conf dest= /etc/my .cnf owner=root group=root mode=0644 
       notify: restart mysql 
 
     - name: Start MySQL 
       service: name=mysqld state=started enabled= yes 
   handlers: 
     - name: restart mysql 
       service: name=mysqld state=restarted

####配置mysql的配置文件my.cnf

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Monitor ansible] # vim /etc/ansible/my.conf
# {{ ansible_managed }}
[mysqld]
datadir= /var/lib/mysql
socket= /var/lib/mysql/mysql .sock
# Disabling symbolic-links is recommended to prevent assorted
security risks
symbolic-links=0
# Configure the buffer pool
innodb_buffer_pool_size = {{ innodb_buffer_pool_size_mb|int }}M
[mysqld_safe]
log-error= /var/log/mysqld .log
pid- file = /var/run/mysqld/mysqld .pid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#######查看执行结果
[root@Monitor ansible] # ansible-playbook set_fact.yml 
PLAY [Configure MySQL] *********************************************************
TASK [setup] *******************************************************************
ok: [Server5]
ok: [Server6]
TASK [ install  MySql] ***********************************************************
ok: [Server5]
ok: [Server6]
TASK [Calculate InnoDB buffer pool size] ***************************************
ok: [Server6]
ok: [Server5]
TASK [Configure MySQL] *********************************************************
ok: [Server6]
ok: [Server5]
TASK [Start MySQL] *************************************************************
fatal: [Server5]: FAILED! => { "changed" false "failed" true "msg" "MySQL Daemon failed to start.\nStarting mysqld:  [FAILED]\r\n" }
fatal: [Server6]: FAILED! => { "changed" false "failed" true "msg" "MySQL Daemon failed to start.\nStarting mysqld:  [FAILED]\r\n" }
         to retry, use: --limit @ /etc/ansible/set_fact .retry
PLAY RECAP *********************************************************************
Server5                    : ok=4    changed=0    unreachable=0    failed=1   
Server6                    : ok=4    changed=0    unreachable=0    failed=1

在server5和server6服务器上查看该配置

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@Server5 etc] # vim my.cnf 
# Ansible managed
[mysqld]
datadir= /var/lib/mysql
socket= /var/lib/mysql/mysql .sock
# Disabling symbolic-links is recommended to prevent assorted
security risks
symbolic-links=0
# Configure the buffer pool
innodb_buffer_pool_size = 934M
[mysqld_safe]
log-error= /var/log/mysqld .log
pid- file = /var/run/mysqld/mysqld .pid


3,pause模块

    pause模块是在playbook执行的过程中暂停一定时间或者提示用户进行某些操作

1
2
3
4
常用参数:
minutes:暂停多少分钟
seconds:暂停多少秒
prompt:打印一串信息提示用户操作

简单的实例:

1
2
3
4
5
[root@Monitor ansible] # vim pause.yml
  - name: wait on user input
    pause: prompt= "Warning! Detected slight issue. ENTER to continue CTRL-C a to quit" 
  - name: timed wait
   pause: seconds=30

4,wait_for模块


在playbook的执行过程中,等待某些操作完成以后再进行后续操作

1
2
3
4
5
6
7
8
常用参数:
connect_timeout:在下一个任务执行之前等待连接的超时时间
delay:等待一个端口或者文件或者连接到指定的状态时,默认超时时间为300秒,在这等待的300s的时间里,wait_for模块会一直轮询指定的对象是否到达指定的状态,delay即为多长时间轮询一次状态。
host:wait_for模块等待的主机的地址,默认为127.0.0.1
port:wait_for模块待待的主机的端口
path:文件路径,只有当这个文件存在时,下一任务才开始执行,即等待该文件创建完成
state:等待的状态,即等待的文件或端口或者连接状态达到指定的状态时,下一个任务开始执行。当等的对象为端口时,状态有started,stoped,即端口已经监听或者端口已经关闭;当等待的对象为文件时,状态有present或者started,absent,即文件已创建或者删除;当等待的对象为一个连接时,状态有drained,即连接已建立。默认为started
timeout:wait_for的等待的超时时间,默认为300秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#####示例
[root@Monitor ansible] # cat /etc/ansible/wait_for.yml 
- name: create task
   hosts: 192.168.180.2
   tasks:
  
         - wait_for: port=8080 state=started      #等待8080端口已正常监听,才开始下一个任务,直到超时
         - wait_for: port=8081 delay=10     #等待8000端口正常监听,每隔10s检查一次,直至等待超时
         - wait_for: host=0.0.0.0 port=8000 delay=10 state=drained     #等待8000端口直至有连接建立
         - wait_for: host=0.0.0.0 port=8000 state=drained exclude_hosts=10.2.1.2,10.2.1.3     #等待8000端口有连接建立,如果连接来自10.2.1.2或者10.2.1.3,则忽略。
         - wait_for: path= /tmp/foo     #等待/tmp/foo文件已创建
         - wait_for: path= /tmp/foo  search_regex=completed     #等待/tmp/foo文件已创建,而且该文件中需要包含completed字符串
         - wait_for: path= /var/lock/file .lock state=absent     #等待/var/lock/file.lock被删除
         - wait_for: path= /proc/3466/status  state=absent         #等待指定的进程被销毁
         - local_action: wait_for port=22 host= "{{ ansible_ssh_host | default(inventory_hostname) }}"  search_regex=OpenSSH delay=10     #等待openssh启动,10s检查一次

执行过程为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@Monitor ansible] # ansible-playbook wait_for.yml
PLAY [create task] *************************************************************
TASK [setup] *******************************************************************
ok: [192.168.180.2]
TASK [wait_for] ****************************************************************
ok: [192.168.180.2]
TASK [wait_for] ****************************************************************
ok: [192.168.180.2]
TASK [wait_for] ****************************************************************
ok: [192.168.180.2]
TASK [wait_for] ****************************************************************
ok: [192.168.180.2]
TASK [wait_for] ****************************************************************
ok: [192.168.180.2]
TASK [wait_for] ****************************************************************
ok: [192.168.180.2]
TASK [wait_for] ****************************************************************
ok: [192.168.180.2]
TASK [wait_for] ****************************************************************
ok: [192.168.180.2]
TASK [wait_for] ****************************************************************
ok: [192.168.180.2 -> localhost]
PLAY RECAP *********************************************************************
192.168.180.2              : ok=10   changed=0    unreachable=0    failed=0


5,assemble模块

用于组装文件,即将多个零散的文件,合并一个大文件

1
2
3
4
5
6
7
8
常用参数:
src:原文件(即零散文件)的路径
dest:合并后的大文件路径
group:合并后的大文件的属组
owner:合并后的大文件的属主
mode:合并后的大文件的权限
validate:与template的validate相同,指定命令验证文件
ignore_hidden:组装时,是否忽略隐藏文件,默认为no,该参数在2.0版本中新增

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@Monitor ansible] # vim assemble.yml 
- hosts: web1
   tasks:
     - name: Make a Directory  in  /opt
       file : path= /opt/sshkeys  state=directory owner=root group=root mode=0700
     - name: Copy SSH keys over
       copy: src=playbook/{{ item }}.pub dest= /opt/sshkeys/ {{ item }}.pub owner=root group=root mode=0600
       with_items:
         - dan
         - kate
         - mal
     - name: Make the root  users  SSH config directory
       file : path= /root/ . ssh  state=directory owner=root group=root mode=0700
     - name: Build the authorized_keys  file
       assemble: src= /opt/static/  dest= /root/ . ssh /authorized_keys  owner=root group=root mode=0700    #将/opt/sshkeys目录里所
有的文件合并到 /root/ . ssh /authorized_keys 一个文件中

######执行过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@Monitor ansible] # ansible-playbook assemble.yml 
PLAY [web1] ********************************************************************
TASK [setup] *******************************************************************
ok: [Server5]
ok: [Server6]
TASK [Make a Directory  in  /opt ] ************************************************
ok: [Server6]
ok: [Server5]
TASK [Copy SSH keys over] ******************************************************
failed: [Server6] (item=mal) => { "failed" true "item" "mal" "msg" "Unable to find 'playbook/mal.pub' in expected paths." }
failed: [Server6] (item=kate) => { "failed" true "item" "kate" "msg" "Unable to find 'playbook/kate.pub' in expected paths." }
failed: [Server6] (item=dan) => { "failed" true "item" "dan" "msg" "Unable to find 'playbook/dan.pub' in expected paths." }
failed: [Server5] (item=kate) => { "failed" true "item" "kate" "msg" "Unable to find 'playbook/kate.pub' in expected paths." }
failed: [Server5] (item=dan) => { "failed" true "item" "dan" "msg" "Unable to find 'playbook/dan.pub' in expected paths." }
failed: [Server5] (item=mal) => { "failed" true "item" "mal" "msg" "Unable to find 'playbook/mal.pub' in expected paths." }
         to retry, use: --limit @ /etc/ansible/assemble .retry
PLAY RECAP *********************************************************************
Server5                    : ok=2    changed=0    unreachable=0    failed=1   
Server6                    : ok=2    changed=0    unreachable=0    failed=1


6,add_host模块

在playbook执行的过程中,动态的添加主机到指定的主机组中

1
2
3
常用参数:
groups :添加主机至指定的组
name:要添加的主机名或IP地址

示例

1
2
3
4
5
[root@Monitor ansible] # vim add_host.yml
- name: add a host to group webservers
   hosts: web1
   tasks:
     - add_host name={{ ip_from_ec2 }} group=web1 foo=23     #添加主机到webservers组中,主机的变量foo的值为42


7,group_by模块

playbook执行的过程中,动态的创建主机组

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- name: Create operating system group
   hosts: all
   tasks:
     - group_by: key=os_{{ ansible_distribution }}            #在playbook中设置一个新的主机组
- name: Run on CentOS hosts only
   hosts: os_CentOS
   tasks:
     - name: Install Apache
       yum: name=httpd state=latest
- name: Run on Ubuntu hosts only
   hosts: os_Ubuntu
   tasks:
     - name: Install Apache
       apt: pkg=apache2 state=latest

执行过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
root@Monitor ansible] # ansible-playbook group_by.yml 
PLAY [Create operating system group] *******************************************
TASK [setup] *******************************************************************
ok: [Server6]
ok: [192.168.180.6]
ok: [192.168.180.5]
ok: [Server5]
ok: [192.168.180.4]
ok: [192.168.180.23]
ok: [192.168.180.2]
ok: [192.168.180.10]
TASK [group_by] ****************************************************************
ok: [Server6]
ok: [Server5]
ok: [192.168.180.4]
ok: [192.168.180.5]
ok: [192.168.180.6]
ok: [192.168.180.23]
ok: [192.168.180.2]
ok: [192.168.180.10]
PLAY [Run on CentOS hosts only] ************************************************
TASK [setup] *******************************************************************
ok: [Server6]
ok: [192.168.180.6]
ok: [Server5]
ok: [192.168.180.4]
ok: [192.168.180.5]
ok: [192.168.180.23]
ok: [192.168.180.2]
ok: [192.168.180.10]
TASK [Install Apache] **********************************************************
changed: [Server5]
changed: [Server6]
ok: [192.168.180.5]
ok: [192.168.180.6]
ok: [192.168.180.23]
ok: [192.168.180.4]
changed: [192.168.180.10]


8,debug模块

调试模块,用于在调试中输出信息

1
2
3
4
常用参数:
msg:调试输出的消息
var:将某个任务执行的输出作为变量传递给debug模块,debug会直接将其打印输出
verbosity:debug的级别

示例:

1
2
3
4
5
6
7
8
9
10
[root@Monitor ansible] # vim debug.yml              
# Example that prints the loopback address and gateway for each host- debug: msg="System {{ inventory_hostname }} has uuid
  {{ ansible_product_uuid }}"
- debug: msg= "System {{ inventory_hostname }} has gateway {{ ansible_default_ipv4.gateway }}"
         when: ansible_default_ipv4.gateway is defined
- shell:  /usr/bin/uptime 
         register: result
- debug: var=result verbosity=2     #直接将上一条指令的结果作为变量传递给var,由debug打印出result的值
- name: Display all variables /facts  known  for  a host
         debug: var=hostvars[inventory_hostname] verbosity=4


9,fail模块

用于终止当前playbook的执行,通常与条件语句组合使用,当满足条件时,终止当前play的运行。可以直接由failed_when取代。

1
2
选项只有一个:
msg:终止前打印出信息

示例:

1
2
3
[root@Monitor ansible] # vim fail.yml 
- fail: msg= "The system may not be provisioned according to the CMDB status."
   when: cmdb_status !=  "to-be-staged"


备注:以后会继续修改有不妥之处

本文转自 lqbyz 51CTO博客,原文链接:http://blog.51cto.com/liqingbiao/1968916


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
运维 Linux Apache
,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具
【10月更文挑战第7天】随着云计算和容器化技术的发展,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具,通过定义资源状态和关系,确保系统始终处于期望配置状态。本文介绍Puppet的基本概念、安装配置及使用示例,帮助读者快速掌握Puppet,实现高效自动化运维。
57 4
|
18天前
|
运维 Ubuntu 应用服务中间件
自动化运维工具Ansible的实战应用
【10月更文挑战第36天】在现代IT基础设施管理中,自动化运维已成为提升效率、减少人为错误的关键手段。本文通过介绍Ansible这一流行的自动化工具,旨在揭示其在简化日常运维任务中的实际应用价值。文章将围绕Ansible的核心概念、安装配置以及具体使用案例展开,帮助读者构建起自动化运维的初步认识,并激发对更深入内容的学习兴趣。
41 4
|
16天前
|
机器学习/深度学习 数据采集 人工智能
智能运维:从自动化到AIOps的演进与实践####
本文探讨了智能运维(AIOps)的兴起背景、核心组件及其在现代IT运维中的应用。通过对比传统运维模式,阐述了AIOps如何利用机器学习、大数据分析等技术,实现故障预测、根因分析、自动化修复等功能,从而提升系统稳定性和运维效率。文章还深入分析了实施AIOps面临的挑战与解决方案,并展望了其未来发展趋势。 ####
|
25天前
|
机器学习/深度学习 数据采集 运维
智能化运维:机器学习在故障预测和自动化响应中的应用
智能化运维:机器学习在故障预测和自动化响应中的应用
49 4
|
2月前
|
存储 运维 监控
高效运维:从基础架构到自动化管理的全面指南
【10月更文挑战第11天】 本文将深入探讨如何通过优化基础架构和引入自动化管理来提升企业IT运维效率。我们将从服务器的选择与配置、存储解决方案的评估,到网络的设计与监控,逐一解析每个环节的关键技术点。同时,重点讨论自动化工具在现代运维中的应用,包括配置管理、持续集成与部署(CI/CD)、自动化测试及故障排除等方面。通过实际案例分析,展示这些技术如何协同工作,实现高效的运维管理。无论是IT初学者还是经验丰富的专业人员,都能从中获得有价值的见解和实操经验。
77 1
|
2月前
|
运维 关系型数据库 MySQL
自动化运维工具Ansible的实战应用
【10月更文挑战第9天】在现代IT运维领域,效率和可靠性是衡量一个系统是否健康的重要指标。自动化运维工具Ansible因其简洁、易用的特性,成为了众多企业和开发者的首选。本文将通过实际案例,展示如何利用Ansible进行日常的运维任务,包括配置管理、软件部署以及批量操作等,帮助读者深入理解Ansible的应用场景及其带来的效益。
|
2月前
|
运维 监控 测试技术
构建高效运维体系:从监控到自动化的实践之路
【10月更文挑战第9天】 在当今信息技术飞速发展的时代,运维作为保障系统稳定性与效率的关键角色,正面临前所未有的挑战。本文将探讨如何通过构建一个高效的运维体系来应对这些挑战,包括监控系统的搭建、自动化工具的应用以及故障应急处理机制的制定。我们将结合具体案例,分析这些措施如何帮助提升系统的可靠性和运维团队的工作效率。
54 1
|
2月前
|
存储 运维 监控
高效运维管理:从基础架构优化到自动化实践
在当今数字化时代,高效运维管理已成为企业IT部门的重要任务。本文将探讨如何通过基础架构优化和自动化实践来提升运维效率,确保系统的稳定性和可靠性。我们将从服务器选型、存储优化、网络配置等方面入手,逐步引导读者了解运维管理的核心内容。同时,我们还将介绍自动化工具的使用,帮助运维人员提高工作效率,降低人为错误的发生。通过本文的学习,您将掌握高效运维管理的关键技巧,为企业的发展提供有力支持。
|
20天前
|
运维 应用服务中间件 网络安全
自动化运维的新篇章:使用Ansible进行服务器配置管理
【10月更文挑战第34天】在现代IT基础设施的快速迭代中,自动化运维成为提升效率、确保一致性的关键手段。本文将通过介绍Ansible工具的使用,展示如何实现高效的服务器配置管理。从基础安装到高级应用,我们将一步步揭开自动化运维的神秘面纱,让你轻松掌握这一技术,为你的运维工作带来革命性的变化。
|
15天前
|
运维 应用服务中间件 Linux
自动化运维的利器:Ansible在配置管理中的应用
【10月更文挑战第39天】本文旨在通过深入浅出的方式,向读者展示如何利用Ansible这一强大的自动化工具来优化日常的运维工作。我们将从基础概念讲起,逐步深入到实战操作,不仅涵盖Ansible的核心功能,还会分享一些高级技巧和最佳实践。无论你是初学者还是有经验的运维人员,这篇文章都会为你提供有价值的信息,帮助你提升工作效率。