自动化运维工具之Saltstack

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
日志服务 SLS,月写入数据量 50GB 1个月
简介:

一、简介

what  is Saltstack?

Saltstack是一个具备puppet与func功能为一身的集中化管理平台,saltstack基于python实现,功能十分强大,适合大规模批量管理服务器,并且它比 Puppet 更容易配置。saltstack具有三种基本功能包括远程命令执行,配置管理(服务,文件,cron,用户,组),云管理。同时,saltstack具有三种运行方式Local、master/minon 、salt ssh。其中master/minion是其最传统的运行方式采用c/s模式,需要在管理端安装Master,被管理节点上安装Minion;而Saltstack salt ssh 运行方式,可以实现无需安Agent,通过SSH进行管理。

Saltstack部署架构

master->minion:master和所有minion连接,minion接收来自master的指令,完成命令执行或配置。如图:

28bd649378ba813ad2de8b202881e745.png-wh_

master->syndic->minion:master通过syndic对minion进行管理,该架构可以进行多级扩展。如图:

ccb8204d6409a4e5f3b696dc41e84c77.png-wh_

无master的minion:minion不受任何master控制,通过本地运行即可完成相关功能。

8b3e2f87fb4d6536b3c292cd85f375cf.png-wh_

Saltstack的两种主要设计理念是远程执行和配置管理。在远程执行系统中,salt用python通过函数调用来完成任务。salt中的配置管理系统可以称作state,也是基于远程执行系统之上,通过master的定规可以让对应的minion达到想要的系统状态。

salt远程执行底层原理:

Salt的底层通信是通过ZeroMQ完成的,采用了ZeroMQ的订阅发布模式(Pub和Sub)如下图所示:

salt远程执行原理图.png

    简单来讲,Pub/Sub模式类似于广播电台,在订阅发布模式中Pub将消息发送到总线,所有的Sub收到来自总线的消息后,根据自己的条件来接收特定的消息。对应到salt中就是master将事件发布到消息总线,minion订阅并监听事件,然后minion会查看事件是否和自己匹配以确定是否需要执行,匹配条件就是多种主机匹配方法。saltmaster和minion的通信过程中,会启动监听两个端口,默认是4505和4506。

    4506的作用:Salt Master Ret接口,支持认证(auth)、文件服务、结果收集等功能;

    4505的作用:Salt Master Pub接口,提供远程执行命令发送功能。

    Salt minion启动时从配置文件中获取master的地址,如果为域名,则进行解析。解析完成后,会连接master的4506(ret接口)进行key认证。认证通过,会获取到master的publish_port(默认是4505),然手连接publish_port订阅来自master pub接口任务。当master下发操作指令是,所有的minion都能接收到,然后minion会检查本机是否匹配。如果匹配,则执行。执行完毕后,把结果发送到master的4506(ret接口)由master进行处理,命令发送通信完成是异步的,并且命令包很小。此外,这些命令包通过maqpack进行序列化后数据会进一步压缩(Maqpack是一种高效的二进制序列化格式),所以salt的网络负载非常低。

二、Saltstack的安装

2.1 yum源安装

下载Centos6的epol源进行安装:

1
wget -O  /etc/yum .repos.d /epel .repo http: //mirrors .aliyun.com /repo/epel-6 .repo

如果使用Centos7,可以下载此epol源:

1
wget -O  /etc/yum .repos.d /epel .repo http: //mirrors .aliyun.com /repo/epel-7 .repo

安装master端:

1
yum  install  salt-master

启动master端服务:

1
/etc/init .d /salt-master  start

43fd178ff2fc4e0b809cdd1474b4065c.png-wh_

Agent安装minion端:

1
Yum  install  salt-minion

2.2 Agent简单配置Saltstack

Agent指定master:

1
2
3
4
5
vim  /etc/salt/mionion
 
# resolved, then the minion will fail to start.
#master: salt
master: 192.168.39.135

修改mionion的id号:

1
2
3
# clusters.
#id:
id : Agent1

注:id号用于唯一区分mionion的标示,可以不做指定,默认为主机名。

启动minon服务:

873ac98c4110b922c7a67abac2091985.png-wh_

2.3 master授权mionion密钥认证

master查看认证请求:

1
# salt-key

efbb1f99534bb6dfcb20628e9f4ae489.png-wh_

master授权请求:

1
# salt-key -a centos-test1

可以批量授权请求:

1
[root@centos salt] # salt-key -A

再次查看请求,可以看到都以授权成功:

5315774f7e5de67765b3f0c6ade9acd0.png-wh_

进行测试,可以看到agent端信息:

fc16e9796a89da270f46749de6382689.png-wh_

三、Saltstack的数据系统

Saltstack有两个数据系统,分别是Grains和Pillar。本质上它们都是key value型的数据库。

3.1 Grains

Grains是存储在minion上的数据,minion启动后就进行Grains计算。Grains是一种静态数据,包括很多诸如操作系统、操作系统版本或CPU内核数量、内存大小等数据。这些数据不

经常变,即使有所变化重启Minion也会重新计算生成。Grains让salt变得更加灵活。

Grains功能:1)信息查询 ;包括资产管理  2)用于目标选择   3)配置管理中使用

3.1.1 信息查询(包括资产管理)

1
[root@centos ~] # salt 'centos-test1*' grains.ls  #列出ID为"centos-test1"的主机,grains所有项

1
[root@centos ~] # salt 'centos-test1*' grains.items #列出主机的详细信息,可用于资产管理
1
[root@centos ~] # salt 'centos-test1*' grains.item host #查询centos-test1的主机名
1
[root@centos ~] # salt '*' grains.item fqdn_ip4 #查询左右主机的ip地址

3.1.2 目标选择

1
[root@centos ~] # salt -G os:CentOS test.ping #对所有主机系统为"centos"的主机进行Ping测试
1
[root@centos ~] # salt -G os:CentOS cmd.run 'w' #对所有主机系统为“centos”的主机查询负载状态

3.1.3 配置管理中的使用

方法一:

通过修改minion的配置文件可以自定义Grains。

1
2
3
4
5
6
7
8
# vim /etc/salt/minion
 
#  cabinet: 13
#  cab_u: 14-15
grains:
    roles:
      - webserver
      - memcache

重启minion服务:

1
# /etc/init.d/salt-minion restart

通过自定义的item,可以实现对所有角色为memche命令执行"date"命令

1
[root@centos ~] # salt '*' grains.item roles #查询所有主机的角色信息

屏幕快照 2017-11-27 下午5.42.47.png

1
[root@centos ~] # salt -G 'roles:memcache' cmd.run 'date' #对所有主机roles为memcache的主机,执行"date"命令

屏幕快照 2017-11-27 下午5.44.38.png

方法二:

也可以通过修改/etc/salt/grains文件来实现。

1
2
3
# vim /etc/salt/grains
 
minion: host1

1
[root@centos ~] # salt -G 'minion:host1' cmd.run 'w'[object Object]

修改/etc/salt/grains不重启服务的方法,刷新命令如下(备注:方式一和方式二修改配置文件,通过此命令都可以不用重启服务)

1
# salt '*' saltutil.sync_grains

方法三:

Grains在top file中使用(Grains module方式)。

1)创建文件系统路径:

1
[root@centos ~] # mkdir -p /srv/salt/

2)修改master配置文件指定top file文件路径

1
2
3
4
5
6
7
8
[root@centos ~] # vim /etc/salt/master
 
#     - /srv/salt/prod/services
#     - /srv/salt/prod/states
#
file_roots:
   base:
    /srv/salt

3) 编辑top file文件

1
[root@centos ~] # vim /srv/salt/top1.sls base:
1
2
3
4
base:
   'web:nginx' :
     - match: grains   #指定grains进行匹配
     - apache  #执行apache.sls

#所有值为nginx的主机,执行apache的状态

1
[root@centos ~] # vim /srv/salt/apache.sls

1
2
3
4
5
6
7
8
9
10
apache- install :
   pkg.installed:
     - names:
       - httpd
       - httpd-devel
apache-service:
   service.running:
     - name: httpd
     enable : True
     - reload: True

1
[root@centos ~] # salt '*' state.highstate  #执行[object Object]

Saltstart文件sls可以参考:http://www.jianshu.com/p/7f0b4857c8ac

案例:

1
2
3
4
5
6
7
/srv/salt/webserver .sls
apache:                  # 标签定义
   pkg:                   # state declaration
     - installed          # function declaration
第一行被称为(ID declaration) 标签定义,在这里被定义为安装包的名。注意:在不同发行版软件包命名不同,比如 fedora 中叫httpd的包 Debian /Ubuntu 中叫apache2
第二行被称为(state declaration)状态定义, 在这里定义使用(pkg state module)
第三行被称为( function  declaration)函数定义, 在这里定义使用(pkg state module)调用 installed 函数

3.2 Pillar

Grains很强大,但是其缺点是这些数据相对来说都是静态数据。如果有变化的数据如何处理呢?这时我们就用到了pillar。pillar数据存储在master上。指定的minion只能看到自己pillar数据,其他的minion看不到任何pillar数据,这一点与状态文件正好相反。所有通过认证的minion都可以获取状态文件,但是每隔minion却都有自己的一套pillar数据,而且每台minion的pillar都进行了加密,所以很适用于敏感数据。

3.2.1 开启pillar

现在saltstack已经默认关闭pillar,因此pillar功能需要开启。

1
2
3
4
5
[root@centos ~] # vim /etc/salt/master
 
# the pillar called "master". This is used to set simple configurations in the
# master config file that can then be used on minions.
pillar_opts: True

1
[root@centos ~] # service salt-master restart

列出minion所有pillar的详细信息。

1
[root@centos ~] # salt 'centos-test1' pillar.items

屏幕快照 2017-11-28 上午10.59.30.png

3.2.2 定义pillar目录

1
2
3
4
5
6
[root@centos ~] # vim /etc/salt/master
 
#    - /srv/pillar
pillar_roots:
   base:
     /srv/pillar

1
[root@centos ~] # mkdir -p /srv/pillar

1) 创建一个pillar文件(python jinjia2写法)

1
[root@centos ~] # vim /srv/pillar/apache.sls

1
2
3
4
5
{% if  grains [ 'os' ] ==  'CentOS'  %}
apache: httpd
{%  elif  grains[ 'os'  ==  'Debian' ] %}
apache: apache2
{% endif %}

2)创建top file文件

1
2
3
4
[root@centos ~] # vim /srv/pillar/top.sls
base:
   '*' :
    - apache

#让所有主机(*),读取apachepillar

1
[root@centos ~] # salt '*' saltutil.refresh_pillar  #执行刷新pillar
1
[root@centos ~] # salt '*' pillar.items

屏幕快照 2017-11-28 上午11.22.59.png

使用pillar定位主机:

1
[root@centos ~] # salt -I 'apache:httpd' test.ping

屏幕快照 2017-11-28 上午11.37.21.png

3.3 Grains和Pillar的不同

屏幕快照 2017-11-28 下午2.40.19.png

四、Saltstack远程执行

4.1 远程执行

在远程主机上运行预定义的或任意的命令,亦称为远程执行,是salt的核心功能。了解模块和返回值,是远程执行两个关键要素。

Salt命令由三个主要部分构成:

1
salt  '<target>'  < function > [arguments]

TARGET

target部分允许你指定哪些minion应该运行执行. 默认的规则是使用glob匹配minion id. 例如:

1
2
salt  '*'  test . ping
salt  '*.example.org'  test . ping

Targets可以使用Grains系统来通过minion的系统信息进行过滤:

1
salt -G  'os:Ubuntu'  test . ping

参见

Grains系统

ip地址或子网掩码进行检测:

1
salt -S  '192.168.39.200'  test . ping

Targets也可以使用正则表达式:

1
salt -E  'virtmach[0-9]'  test . ping

Targets也可以指定列表:

1
salt -L  'foo,bar,baz,quo'  test . ping

或者在一个命令中混合使用多target类型:

1
salt -C  'G@os:Ubuntu an webser* or E@database.*'  test . ping

FUNCTION

funcation是module提供的功能. Salt内置了大量有效的functions. 列出minions上的所有有效functions?

1
salt  '*'  sys.doc

这里有一些例子:

显示当前所有有效的minions:

1
salt  '*'  test . ping

运行一个任意的shell命令:

1
salt  '*'  cmd.run  'uname -a'

参见

所有模块列表

arguments 

function通过空格来界定参数:

1
salt  '*'  cmd.exec_code python  'import sys; sys.version'

可选的, 也支持keyword参数:

1
salt  '*'  pip. install  salt timeout=5 upgrade=True

他们常常在 kwargs=argument form中.

4.2 salt常用模块

saltstack提供了很多执行模块,可以从官方文档上查找模块具体使用方法。saltstack执行模块链接:http://docs.saltstack.cn/ref/modules/all/index.html#all-salt-modules

下面指列举service模块使用方法:

1
salt.modules.service.available(name)

判断指定服务是否存在,如果存在返回True,否则返回False。

1
2
3
4
5
6
7
[root@centos ~] # salt '*' service.available sshd
centos-test2:
     True
centos-test3:
     True
centos-test1:
     True

1
salt.modules.service.get_all()

返回所有服务可用的列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@centos ~] # salt '*' service.get_all
centos-test1:
     - abrt-ccpp
     - abrt-oops
     - abrtd
     - acpid
     - atd
     - auditd
     - blk-availability
     - control-alt-delete
     - cpuspeed
     - crond
     - haldaemon

1
salt.modules.service.run(name, action)

用动作运行指定的服务。

name --服务名 action --动作名称,如start,stop,reload,restart。

1
2
# salt '*' service.run apache2 reload
# salt '*' service.run postgresql initdb
1
salt.modules.service.start(name)

启动指定服务。

1
[root@centos ~] # salt '*' service.start httpd
1
salt.modules.service.status(name)

查询指定服务状态。服务启动返回True,服务未启动返回False。

1
2
3
4
5
6
7
[root@centos ~] # salt '*' service.status httpd
centos-test2:
     True
centos-test1:
     True
centos-test3:
     True

4.3 模块的ACL(访问控制)
4.3.1 对系统用户允许特定命令执行

1)修改相应目录权限

1
[root@centos ~] # chmod 755 /var/cache/salt /var/cache/salt/master /var/cache/salt/master/jobs /var/run/salt /var/run/salt/master/

2)修改master配置文件,开启acl访问控制

1
[root@centos ~] # vim /etc/salt/master

1
2
3
4
client_acl:
   jerry:     #用户jerry
     test . ping   #只允许使用test.ping和network下所有方法
     - network.*

3)重启服务

1
[root@centos ~] # /etc/init.d/salt-master restart

测试:

屏幕快照 2017-11-30 下午2.28.59.png

4.3.2 指定用户在某一台机器上执行特定命令

1)修改配置

1
[root@centos ~] # vim /etc/salt/master

1
2
3
4
5
6
7
client_acl:
   jerry:
     test . ping
     - network.*
   tom:   #用户
     - centos-test1*:  #主机centos-test1
       test . ping  #test.ping命令

2)重启服务

1
[root@centos ~] # /etc/init.d/salt-master restart

测试:

屏幕快照 2017-11-30 下午2.38.43.png

五、Saltstack配置管理

Saltstack包含一个健壮且灵活的配置管理框架,该框架建立在远程执行核心上。这个框架执行的minion端,允许轻松,同时可配置成千上万的主机,通过编写特定于语言的状态文件得以实现。saltstack的配置管理提供了很多的“状态模块”用于实现不同配置管理的需求。以下是配置管理的链接以方便查询:https://docs.saltstack.com/en/latest/topics/states/index.html

1)修改file_root文件目录

1
2
3
4
5
6
7
8
[root@centos base] # vim /etc/salt/master 
file_roots:
   base:
     /srv/salt/base
   test :
     /srv/salt/test
   prod:
     /srv/salt/prod

2) 创建文件目录

1
2
3
[root@centos base] # mkdir -p /srv/salt/base
[root@centos base] # mkdir -p /srv/salt/test
[root@centos base] # mkdir -p /srv/salt/prod

3)重启服务

1
[root@centos base] # /etc/init.d/salt-master restart

批量修改/etc/resolv.conf文件:

1
[root@centos ~] # cd /srv/salt/base
1
2
3
4
5
6
7
[root@centos base] # vim dns.sls
/etc/resolv .conf:     #标签
   file .managed:      #状态模块file的mamaged方法,用以实现
     source : salt: //files/resolv .conf   #源文件resolv.conf
     - user: root     #文件用户属主
     - group: root     #文件数组
     - mode: 644     #文件权限

文件目录状态为:

屏幕快照 2017-12-01 下午5.12.06.png

resolv.conf文件内容:

1
2
3
[root@centos base] # more files/resolv.conf 
# Generated by NetworkManager
nameserver 192.168.39.2

1
[root@centos base] # salt '*' state.sls dns #执行dns状态文件

屏幕快照 2017-12-01 下午5.12.55.png

也可以从top文件上执行。

1
2
3
4
[root@centos base] # vim top.sls 
base:
   '*' :
     - dns

1
[root@centos base] # salt '*' state.highstate  #state高级状态默认从top文件中执行

查看minion端的resolv.conf文件:

屏幕快照 2017-12-01 下午5.22.04.png

5.2 saltstack配置管理之YAML和jinjia

5.2.1 什么是YAML?

YAML是“另一种标记语言”的外语缩写(见前方参考资料原文内容);但为了强调这种语言以数据做为中心,而不是以置标语言为重点,而用返璞词重新命名。它是一种直观的能够被电脑识别的数据序列化格式,是一个可读性高并且容易被人类阅读,容易和脚本语言交互,用来表达资料序列的编程语言。

它是类似于标准通用标记语言的子集XML数据描述语言,语法比XML简单很多。

YAML语法规则:

规则一:缩紧

1)yaml使用一个固定的缩紧风格表示水层结构关系。salt需要每个缩紧分别由两个空格组成。

2)不要使用tabs。

规则二:冒号

YAML my_key: my_value

first_level dict_key:

  second_level dict_key: value_in_second_level_dict

IN Pyton {‘my_key’: ‘my_value’,

{

‘first_level_dict_key’:{

‘second_level_dict_key’: ‘value_in_second_level_dict’

}

}

}

规则三:短横线

想要表示列表项:使用一个短横杠加一个空格。多个项使用同样的缩紧级别作为同一列表的一部分。

my_dictionary:

  - list_value_one

  - list_value_two

  - list_value_three

{‘my_dictionary’: [‘list_value_one’,’list_value_two’,’list_value_three’]}

5.2.2 什么是jinja?

Jinja2是基于python模板引擎,功能比较类似于于PHPsmartyJ2eeFreemarkervelocity。 它能完全支持unicode,并具有集成的沙箱执行环境,应用广泛。jinja2使用BSD授权。jiaja2官网:http://jinja.pocoo.org/

1.file状态使用template参数

2.模版文件里面变量使用{{ 名称 }} {{ PORT }}

3.变量列表

-defaults:

PORT:8080

4.Jinjia if-else 语句

{% if grains[‘fqdn’] == ‘lb-node1.unixhot.com’ %}

  — ROUTEID: haproxy_master

  - STATEID: master

{% elif grains[‘fedn’] == ‘lb-node2.unixhot.com’ %}

  - ROUTEID: haproxy_backup

  - STATEID: backup

  - PRIORITYID: 100

{% endif %}

5.2.2 变量的使用

以上一个示例需改resolv.conf为例

1
2
3
4
5
6
7
8
9
10
[root@centos ~] # vim /srv/salt/base/dns.sls 
/etc/resolv .conf:
   file .managed:
     source : salt: //files/resolv .conf
     - user: root
     - group: root
     - mode: 644
     - template: jinja  #template指定为jinja模版
     - defaults:
       DNS_SERVER: 192.168.39.23  #变量:默认值

1
2
3
[root@centos ~] # vim /srv/salt/base/files/resolv.conf 
# Generated by NetworkManager
nameserver {{ DNS_SERVER }}  #{{ DNS_SERVER }} 作为变量,也可以使用单括号,这里使用便于区分
1
[root@centos base] # salt '*' state.highstate #执行进行修改minions端配置

可以看到已经修改完成。

屏幕快照 2017-12-04 下午5.44.00.png

参考资料:

https://docs.saltstack.com/en/latest/

http://outofmemory.cn/saltstack/salt










本文转自 SoulMio 51CTO博客,原文链接:http://blog.51cto.com/bovin/1984115,如需转载请自行联系原作者
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
24天前
|
运维 Linux Apache
Puppet 作为一款强大的自动化运维工具,被广泛应用于配置管理领域。通过定义资源的状态和关系,Puppet 能够确保系统始终处于期望的配置状态。
Puppet 作为一款强大的自动化运维工具,被广泛应用于配置管理领域。通过定义资源的状态和关系,Puppet 能够确保系统始终处于期望的配置状态。
48 3
|
29天前
|
运维 Linux Apache
,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具
【10月更文挑战第7天】随着云计算和容器化技术的发展,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具,通过定义资源状态和关系,确保系统始终处于期望配置状态。本文介绍Puppet的基本概念、安装配置及使用示例,帮助读者快速掌握Puppet,实现高效自动化运维。
47 4
|
27天前
|
运维 Linux Apache
Puppet这一强大的自动化运维工具,涵盖其基本概念、安装配置及使用示例
【10月更文挑战第8天】本文介绍了Puppet这一强大的自动化运维工具,涵盖其基本概念、安装配置及使用示例。Puppet通过定义资源状态和关系,确保系统配置始终如一,支持高效管理基础设施。文章详细讲解了Puppet的安装步骤、配置方法及DSL语言示例,帮助读者快速掌握Puppet的使用技巧。
54 2
|
1天前
|
运维 监控 数据安全/隐私保护
自动化运维工具的设计与实现
【10月更文挑战第34天】在现代IT基础设施管理中,自动化运维工具扮演着至关重要的角色。它们不仅提高了运维效率,还确保了服务的连续性和稳定性。本文将深入探讨如何设计并实现一个自动化运维工具,从需求分析到功能实现,再到最终的测试与部署。我们将通过一个简单的代码示例来展示如何自动执行常见的运维任务,如日志清理和性能监控。文章旨在为读者提供一套完整的方法论,以便他们能够构建自己的自动化运维解决方案。
|
6天前
|
机器学习/深度学习 数据采集 运维
智能化运维:机器学习在故障预测和自动化响应中的应用
智能化运维:机器学习在故障预测和自动化响应中的应用
25 4
|
27天前
|
运维 关系型数据库 MySQL
自动化运维工具Ansible的实战应用
【10月更文挑战第9天】在现代IT运维领域,效率和可靠性是衡量一个系统是否健康的重要指标。自动化运维工具Ansible因其简洁、易用的特性,成为了众多企业和开发者的首选。本文将通过实际案例,展示如何利用Ansible进行日常的运维任务,包括配置管理、软件部署以及批量操作等,帮助读者深入理解Ansible的应用场景及其带来的效益。
|
27天前
|
人工智能 运维 监控
自动化运维:从脚本到工具的演变之路
【10月更文挑战第8天】在数字化时代的浪潮中,运维不再是简单的硬件维护,它已经演变成一场关于效率、稳定性和创新的技术革命。本文将带您领略自动化运维的魅力,从最初的脚本编写到现代复杂的自动化工具,我们将一探究竟,看看这些工具如何帮助运维人员简化日常任务,提升工作效率,并最终推动业务发展。
|
22天前
|
JavaScript 前端开发 搜索推荐
Gulp:构建自动化与任务管理的强大工具
【10月更文挑战第13天】Gulp:构建自动化与任务管理的强大工具
56 0
|
1月前
|
机器学习/深度学习 人工智能 运维
构建高效运维体系:从自动化到智能化的演进
本文探讨了如何通过自动化和智能化手段,提升IT运维效率与质量。首先介绍了自动化在简化操作、减少错误中的作用;然后阐述了智能化技术如AI在预测故障、优化资源中的应用;最后讨论了如何构建一个既自动化又智能的运维体系,以实现高效、稳定和安全的IT环境。
58 4
|
29天前
|
运维 jenkins 持续交付
自动化部署的魅力:如何用Jenkins和Docker简化运维工作
【10月更文挑战第7天】在现代软件开发周期中,快速且高效的部署是至关重要的。本文将引导你理解如何使用Jenkins和Docker实现自动化部署,从而简化运维流程。我们将从基础概念开始,逐步深入到实战操作,让你轻松掌握这一强大的工具组合。通过这篇文章,你将学会如何利用这些工具来提升你的工作效率,并减少人为错误的可能性。
下一篇
无影云桌面