标签 配置管理 下的文章

了解一下配置管理工具,以找出哪个最适合你的 DevOps 组织。

DevOps 正因为有提高产品质量、缩短产品开发时间等优势,目前备受业界关注,同时也在长足发展当中。

DevOps 的核心价值观 团队文化 Culture 自动化 Automation 评估 Measurement 分享 Sharing (CAMS),同时,团队对 DevOps 的执行力也是 DevOps 能否成功的重要因素。

  • 团队文化让大家团结一致;
  • 自动化是 DevOps 的基础;
  • 评估保证了及时的改进;
  • 分享让 CAMS 成为一个完整的循环过程。

DevOps 的另一个思想是任何东西,包括服务器、数据库、网络、日志文件、应用配置、文档、自动化测试、部署流程等,都可以通过代码来管理。

在本文中,我主要介绍配置管理的自动化。配置管理工具作为 基础架构即代码 Infrastructure as Code (IaC)的一部分,支持使用经过测试和验证的软件开发实践,通过明文定义文件管理和配置数据中心。

DevOps 团队只需要通过操作简单的配置文件,就可以实现应用开发中包括版本控制、测试、小型部署、设计模式在内的这些最佳实践。总而言之,配置管理工具实现了通过编写代码来使基础架构的配置和管理变得自动化。

为什么要使用配置管理工具?

配置管理工具可以提高应用部署和变更的效率,还可以让这些流程变得可重用、可扩展、可预测,甚至让它们维持在期望的状态,从而让资产的可控性提高。

使用配置管理工具的优势还包括:

  • 让代码遵守编码规范,提高代码可读性;
  • 具有 幂等性 Idempotency ,也就是说,无论执行多少次重复的配置管理操作,得到的结果都是一致的;
  • 分布式的设计可以方便地管理大量的远程服务器。

配置管理工具主要分为 拉取 pull 模式和 推送 push 模式。拉取模式是指安装在各台服务器上的 代理 agent 定期从 中央存储库 central repository 拉取最新的配置并应用到对应的服务器上;而推送模式则由 中央服务器 central server 的中央服务器会触发其它受管服务器的更新。

五大最流行的配置管理工具

目前配置管理工具有很多,不同的配置管理工具都有自己最适合的使用场景。而对于下面五个我按照字母顺序列出的配置管理工具,都对 DevOps 有明显的帮助:全都具有开源许可证、使用外部配置文件、支持无人值守运行、可以通过脚本自定义运行。下面对它们的介绍都来源于它们的软件库和官网内容。

Ansible

“Ansible 是一个极其简洁的 IT 自动化平台,可以让你的应用和系统以更简单的方式部署。不需要安装任何代理,只需要使用 SSH 的方式和简单的语言,就可以免去脚本或代码部署应用的过程。”——GitHub Ansible 代码库

Ansible 是我最喜欢的工具之一,我在几年前就开始使用了。你可以使用 Ansible 在命令行中让多个服务器执行同一个命令,也可以使用 YAML 格式的 剧本 playbook 来让它自动执行特定的操作,这促进了技术团队和非技术团队之间的沟通。简洁、无代理、配置文件对非技术人员友好是它的几个主要优点。

由于 Ansible 不需要代理,因此对服务器的资源消耗会很少。Ansible 默认使用的推送模式需要借助 SSH 连接,但 Ansible 也支持拉取模式。剧本 可以使用最少的命令集编写,当然也可以扩展为更加精细的自动化任务,包括引入角色、变量和其它人写的模块。

你可以将 Ansible 和其它工具(包括 Ansible Works、Jenkins、RunDeck、ARA 等)结合起来使用,因为这些工具 提供了运行剧本时的可追溯性,这样就可以创建控制流程的中央控制台。

CFEngine

“CFEngine 3 是一个流行的开源配置管理系统,它主要用于为大规模的系统提供自动化配置和维护。”——GitHub CFEngine 代码库

CFEngine 最早在 1993 年由 Mark Burgess 作为自动配置管理的科学方法提出,目的是降低计算机系统配置中的熵,最终收敛到期望的配置状态,同时还阐述了幂等性是让系统达到期望状态的能力。Burgess 在 2004 年又提出了 承诺理论 Promise Theory ,这个理论描述了代理之间自发合作的模型。

CFEngine 的最新版本已经用到了承诺理论,在各个服务器上的代理程序会从中央存储库拉取配置。CFEngine 的配置对专业技能要求较高,因此它比较适合技术团队使用。

Chef

“为整个基础架构在配置管理上带来便利的一个系统集成框架。”——GitHub Chef 代码库

Chef 通过由 Ruby 编写的“ 菜谱 recipe ”来让你的基础架构保持在最新、最兼容的状态,这些“菜谱”描述了一系列应处于某种状态的资源。Chef 既可以通过客户端-服务端的模式运行,也可以在 chef-solo 这种独立配置的模式下运行。大部分云提供商都很好地集成了 Chef,因此可以使用它为新机器做自动配置。

Chef 有广泛的用户基础,同时也提供了完备的工具包,让不同技术背景的团队可以通过“菜谱”进行沟通。尽管如此,它仍然算是一个技术导向的工具。

Puppet

“Puppet 是一个可以在 Linux、Unix 和 Windows 系统上运行的自动化管理引擎,它可以根据集中的规范来执行诸如添加用户、安装软件包、更新服务器配置等等管理任务。”——GitHub Puppet 代码库

Puppet 作为一款面向运维工程师和系统管理员的工具,在更多情况下是作为配置管理工具来使用。它通过客户端-服务端的模式工作,使用代理从主服务器获取配置指令。

Puppet 使用 声明式语言 declarative language 或 Ruby 来描述系统配置。它包含了不同的模块,并使用 清单文件 manifest files 记录期望达到的目标状态。Puppet 默认使用推送模式,但也支持拉取模式。

Salt

“为大规模基础结构或应用程序实现自动化管理的软件。”——GitHub Salt 代码库

Salt 的专长就是快速收集数据,即使是上万台服务器也能够轻松完成任务。它使用 Python 模块来管理配置信息和执行特定的操作,这些模块可以让 Salt 实现所有远程操作和状态管理。但配置 Salt 模块对技术水平有一定的要求。

Salt 使用客户端-服务端的结构(Salt minions 是客户端,而 Salt master 是服务端),并以 Salt 状态文件记录需要达到的目标状态。

总结

DevOps 工具领域一直在发展,因此必须时刻关注其中的最新动态。希望这篇文章能够鼓励读者进一步探索相关的概念和工具。为此, 云原生计算基金会 Cloud Native Computing Foundation (CNCF)在 Cloud Native Landscape Project 中也提供了很好的参考案例。


via: https://opensource.com/article/18/12/configuration-management-tools

作者:Marco Bravo 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Chef是面对IT专业人员的一款配置管理和自动化工具,它可以配置和管理你的基础设施(设备),无论它在本地还是在云上。它可以用于加速应用部署并协调多个系统管理员和开发人员的工作,这包括可支持大量的客户群的成百上千的服务器和程序。chef最有用的是让基础设施变成代码。一旦你掌握了Chef,你可以获得自动化管理你的云端基础设施或者终端用户的一流的网络IT支持。

下面是我们将要在本篇中要设置和配置Chef的主要组件。

安装Chef的要求和版本

我们将在下面的基础环境下设置Chef配置管理系统。

管理和配置工具:Chef
基础操作系统Ubuntu 14.04.1 LTS (x86\_64)
Chef ServerVersion 12.1.0
Chef ManageVersion 1.17.0
Chef Development KitVersion 0.6.2
内存和CPU4 GB , 2.0+2.0 GHz

Chef服务端的安装和配置

Chef服务端是核心组件,它存储配置以及其他和工作站交互的配置数据。让我们在他们的官网https://www.chef.io下载最新的安装文件。

我使用下面的命令来下载和安装它。

1) 下载Chef服务端

root@ubuntu-14-chef:/tmp# wget https://web-dl.packagecloud.io/chef/stable/packages/ubuntu/trusty/chef-server-core_12.1.0-1_amd64.deb

2) 安装Chef服务端

root@ubuntu-14-chef:/tmp# dpkg -i chef-server-core_12.1.0-1_amd64.deb

3) 重新配置Chef服务端

现在运行下面的命令来启动所有的chef服务端服务,这一步也许会花费一些时间,因为它需要由许多不同一起工作的服务组成一个可正常运作的系统。

root@ubuntu-14-chef:/tmp# chef-server-ctl reconfigure

chef服务端启动命令'chef-server-ctl reconfigure'需要运行两次,这样就会在安装后看到这样的输出。

Chef Client finished, 342/350 resources updated in 113.71139964 seconds
opscode Reconfigured!

4) 重启系统

安装完成后重启系统使系统能最好的工作,不然我们或许会在创建用户的时候看到下面的SSL连接错误。

ERROR: Errno::ECONNRESET: Connection reset by peer - SSL_connect

5) 创建新的管理员

运行下面的命令来创建一个新的管理员账户及其配置。创建过程中,用户的RSA私钥会自动生成,它需要保存到一个安全的地方。--file选项会保存RSA私钥到指定的路径下。

root@ubuntu-14-chef:/tmp# chef-server-ctl user-create kashi kashi kashi [email protected] kashi123 --filename /root/kashi.pem

Chef服务端的管理设置

Chef Manage是一个针对企业级Chef用户的管理控制台,它提供了可视化的web用户界面,可以管理节点、数据包、规则、环境、Cookbook 和基于角色的访问控制(RBAC)。

1) 下载Chef Manage

从官网复制链接并下载chef manage的安装包。

root@ubuntu-14-chef:~# wget https://web-dl.packagecloud.io/chef/stable/packages/ubuntu/trusty/opscode-manage_1.17.0-1_amd64.deb

2) 安装Chef Manage

使用下面的命令在root的家目录下安装它。

root@ubuntu-14-chef:~# chef-server-ctl install opscode-manage --path /root

3) 重启Chef Manage和服务端

安装完成后我们需要运行下面的命令来重启chef manage和服务端。

root@ubuntu-14-chef:~# opscode-manage-ctl reconfigure
root@ubuntu-14-chef:~# chef-server-ctl reconfigure

Chef Manage网页控制台

我们可以使用localhost或它的域名来访问网页控制台,并用已经创建的管理员登录

chef amanage

1) Chef Manage创建新的组织

你或许被要求创建新的组织,或者也可以接受其他组织的邀请。如下所示,使用缩写和全名来创建一个新的组织。

Create Org

2) 用命令行创建新的组织

我们同样也可以运行下面的命令来创建新的组织。

root@ubuntu-14-chef:~# chef-server-ctl org-create linux Linoxide Linux Org. --association_user kashi --filename linux.pem

设置工作站

我们已经完成安装chef服务端,现在我们可以开始创建任何recipes(基础配置元素)、cookbooks(基础配置集)、attributes(节点属性),以及做一些其他修改。

1) 在Chef服务端上创建新的用户和组织

为了设置工作站,我们需要用命令行创建一个新的用户和组织。

root@ubuntu-14-chef:~# chef-server-ctl user-create bloger Bloger Kashif [email protected] bloger123 --filename bloger.pem

root@ubuntu-14-chef:~# chef-server-ctl org-create blogs Linoxide Blogs Inc. --association_user bloger --filename blogs.pem

2) 下载工作站入门套件

在工作站的网页控制台中下载并保存入门套件,它用于与服务端协同工作

Starter Kit

3) 下载套件后,点击"Proceed"

starter kit

用于工作站的Chef开发套件设置

Chef开发套件是一款包含开发chef所需的所有工具的软件包。它捆绑了由Chef开发的带Chef客户端的工具。

1) 下载 Chef DK

我们可以从它的官网链接中下载开发包,并选择操作系统来下载chef开发包。

Chef DK

复制链接并用wget下载

root@ubuntu-15-WKS:~# wget https://opscode-omnibus-packages.s3.amazonaws.com/ubuntu/12.04/x86_64/chefdk_0.6.2-1_amd64.deb

2) Chef开发套件安装

使用dpkg命令安装开发套件

root@ubuntu-15-WKS:~# dpkg -i chefdk_0.6.2-1_amd64.deb

3) Chef DK 验证

使用下面的命令验证客户端是否已经正确安装。

root@ubuntu-15-WKS:~# chef verify

Running verification for component 'berkshelf'
Running verification for component 'test-kitchen'
Running verification for component 'chef-client'
Running verification for component 'chef-dk'
Running verification for component 'chefspec'
Running verification for component 'rubocop'
Running verification for component 'fauxhai'
Running verification for component 'knife-spork'
Running verification for component 'kitchen-vagrant'
Running verification for component 'package installation'
Running verification for component 'openssl'
..............
---------------------------------------------
Verification of component 'rubocop' succeeded.
Verification of component 'knife-spork' succeeded.
Verification of component 'openssl' succeeded.
Verification of component 'berkshelf' succeeded.
Verification of component 'chef-dk' succeeded.
Verification of component 'fauxhai' succeeded.
Verification of component 'test-kitchen' succeeded.
Verification of component 'kitchen-vagrant' succeeded.
Verification of component 'chef-client' succeeded.
Verification of component 'chefspec' succeeded.
Verification of component 'package installation' succeeded.

4) 连接Chef服务端

我们将创建 ~/.chef目录,并从chef服务端复制两个用户和组织的pem文件到该目录下。

root@ubuntu-14-chef:~# scp bloger.pem blogs.pem kashi.pem linux.pem [email protected]:/.chef/

[email protected]'s password:
bloger.pem 100% 1674 1.6KB/s 00:00
blogs.pem 100% 1674 1.6KB/s 00:00
kashi.pem 100% 1678 1.6KB/s 00:00
linux.pem 100% 1678 1.6KB/s 00:00

5) 编辑配置来管理chef环境 **

现在使用下面的内容创建"~/.chef/knife.rb"。

root@ubuntu-15-WKS:/.chef# vim knife.rb
current_dir = File.dirname(__FILE__)

log_level :info
log_location STDOUT
node_name "kashi"
client_key "#{current_dir}/kashi.pem"
validation_client_name "kashi-linux"
validation_key "#{current_dir}/linux.pem"
chef_server_url "https://172.25.10.173/organizations/linux"
cache_type 'BasicFile'
cache_options( :path => "#{ENV['HOME']}/.chef/checksums" )
cookbook_path ["#{current_dir}/../cookbooks"]

创建knife.rb中指定的“~/cookbooks”文件夹。

root@ubuntu-15-WKS:/# mkdir cookbooks

6) 测试Knife配置

运行“knife user list”和“knife client list”来验证knife是否工作。

root@ubuntu-15-WKS:/.chef# knife user list

第一次运行的时候可能会看到下面的错误,这是因为工作站上还没有chef服务端的SSL证书。

ERROR: SSL Validation failure connecting to host: 172.25.10.173 - SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
ERROR: Could not establish a secure connection to the server.
Use `knife ssl check` to troubleshoot your SSL configuration.
If your Chef Server uses a self-signed certificate, you can use
`knife ssl fetch` to make knife trust the server's certificates.

要解决上面的命令的错误,运行下面的命令来获取ssl证书,并重新运行knife user和client list,这时候应该就可以了。

root@ubuntu-15-WKS:/.chef# knife ssl fetch
WARNING: Certificates from 172.25.10.173 will be fetched and placed in your trusted_cert
directory (/.chef/trusted_certs).

knife没有办法验证这些是否是有效的证书。你应该在下载时验证这些证书的真实性。

在/.chef/trusted\_certs/ubuntu-14-chef\_test\_com.crt下面添加ubuntu-14-chef.test.com的证书。

在上面的命令取得ssl证书后,接着运行下面的命令。

root@ubuntu-15-WKS:/.chef#knife client list
kashi-linux

配置与chef服务端交互的新节点

节点是执行所有基础设施自动化的chef客户端。因此,在配置完chef-server和knife工作站后,通过配置与chef-server交互的新节点,来将新的服务端添加到我们的chef环境下。

我们使用下面的命令来添加与chef服务端协同工作的新节点。

root@ubuntu-15-WKS:~# knife bootstrap 172.25.10.170 --ssh-user root --ssh-password kashi123 --node-name mydns

Doing old-style registration with the validation key at /.chef/linux.pem...
Delete your validation key in order to use your user credentials instead

Connecting to 172.25.10.170
172.25.10.170 Installing Chef Client...
172.25.10.170 --2015-07-04 22:21:16-- https://www.opscode.com/chef/install.sh
172.25.10.170 Resolving www.opscode.com (www.opscode.com)... 184.106.28.91
172.25.10.170 Connecting to www.opscode.com (www.opscode.com)|184.106.28.91|:443... connected.
172.25.10.170 HTTP request sent, awaiting response... 200 OK
172.25.10.170 Length: 18736 (18K) [application/x-sh]
172.25.10.170 Saving to: ‘STDOUT’
172.25.10.170
100%[======================================>] 18,736 --.-K/s in 0s
172.25.10.170
172.25.10.170 2015-07-04 22:21:17 (200 MB/s) - written to stdout [18736/18736]
172.25.10.170
172.25.10.170 Downloading Chef 12 for ubuntu...
172.25.10.170 downloading https://www.opscode.com/chef/metadata?v=12&prerelease=false&nightlies=false&p=ubuntu&pv=14.04&m=x86_64
172.25.10.170 to file /tmp/install.sh.26024/metadata.txt
172.25.10.170 trying wget...

之后我们可以在knife节点列表下看到创建的新节点,它也会在新节点下创建新的客户端。

root@ubuntu-15-WKS:~# knife node list
mydns

类似地我们只要通过给上面的knife命令提供ssh证书,就可以在chef设施上创建多个节点。

总结

本篇我们学习了chef管理工具,并通过安装和配置设置基本了解了它的组件。我希望你在学习安装和配置Chef服务端以及它的工作站和客户端节点中获得乐趣。


via: http://linoxide.com/ubuntu-how-to/install-configure-chef-ubuntu-14-04-15-04/

作者:Kashif Siddique 译者:geekpi 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出

我在搜索Puppet的替代品时,偶然间碰到了Salt。我喜欢puppet,但是我又爱上Salt了:)。我发现Salt在配置和使用上都要比Puppet简单,当然这只是一家之言,你大可不必介怀。另外一个爱上Salt的理由是,它可以让你从命令行管理服务器配置,比如:

要通过Salt来更新所有服务器,你只需运行以下命令即可

salt '*' pkg.upgrade

安装SaltStack到Linux上

如果你是在CentOS 6/7上安装的话,那么Salt可以通过EPEL仓库获取到。而对于Pi和Ubuntu Linux用户,你可以从这里添加Salt仓库。Salt是基于python的,所以你也可以使用‘pip’来安装,但是你得用yum-utils或是其它包管理器来自己处理它的依赖关系。

Salt采用服务器-客户端模式,服务器端称为领主,而客户端则称为下属。

安装并配置Salt领主

[root@salt-master~]# yum install salt-master

Salt配置文件位于/etc/salt和/srv/salt。Salt虽然可以开箱即用,但我还是建议你将日志配置得更详细点,以方便日后排除故障。

[root@salt-master ~]# vim /etc/salt/master
# 默认是warning,修改如下
log_level: debug
log_level_logfile: debug

[root@salt-master ~]# systemctl start salt-master

安装并配置Salt下属

[root@salt-minion~]#yum install salt-minion

# 添加你的Salt领主的主机名
[root@salt-minion~]#vim /etc/salt/minion
master: salt-master.com
# 启动下属
[root@salt-minion~] systemctl start salt-minion

在启动时,下属客户机会生成一个密钥和一个id。然后,它会连接到Salt领主服务器并验证自己的身份。Salt领主服务器在允许下属客户机下载配置之前,必须接受下属的密钥。

在Salt领主服务器上列出并接受密钥

# 列出所有密钥
[root@salt-master~] salt-key -L
Accepted Keys:
Unaccepted Keys:
minion.com
Rejected Keys:

# 使用id 'minion.com'命令接受密钥
[root@salt-master~]salt-key -a minion.com

[root@salt-master~] salt-key -L
Accepted Keys:
minion.com
Unaccepted Keys:
Rejected Keys:

在接受下属客户机的密钥后,你可以使用‘salt’命令来立即获取信息。

Salt命令行实例

# 检查下属是否启动并运行
[root@salt-master~]  salt 'minion.com' test.ping
minion.com:
    True
# 在下属客户机上运行shell命令
 [root@salt-master~]#  salt 'minion.com' cmd.run 'ls -l'
minion.com:
    total 2988
    -rw-r--r--. 1 root root 1024 Jul 31 08:24 1g.img
    -rw-------. 1 root root     940 Jul 14 15:04 anaconda-ks.cfg
    -rw-r--r--. 1 root root 1024  Aug 14 17:21 test
# 安装/更新所有服务器上的软件
[root@salt-master ~]# salt '*' pkg.install git

salt命令需要一些组件来发送信息,其中之一是下属客户机的id,而另一个是下属客户机上要调用的函数。

在第一个实例中,我使用‘test’模块的‘ping’函数来检查系统是否启动。该函数并不是真的实施一次ping,它仅仅是在下属客户机作出回应时返回‘真’。

‘cmd.run’用于执行远程命令,而‘pkg’模块包含了包管理的函数。本文结尾提供了全部内建模块的列表。

颗粒实例

Salt使用一个名为颗粒(Grains)的界面来获取系统信息。你可以使用颗粒在指定属性的系统上运行命令。

[root@vps4544 ~]# salt -G 'os:Centos' test.ping
minion:
    True

更多颗粒实例,请访问http://docs.saltstack.com/en/latest/topics/targeting/grains.html

通过状态文件系统进行包管理

为了使软件配置自动化,你需要使用状态系统,并创建状态文件。这些文件使用YAML格式和python字典、列表、字符串以及编号来构成数据结构。将这些文件从头到尾研读一遍,这将有助于你更好地理解它的配置。

VIM状态文件实例

[root@salt-master~]# vim /srv/salt/vim.sls
vim-enhanced:
  pkg.installed
/etc/vimrc:
  file.managed:
    - source: salt://vimrc
    - user: root
    - group: root
    - mode: 644

该文件的第一和第三行称为状态id,它们必须包含有需要管理的包或文件的确切名称或路径。在状态id之后是状态和函数声明,‘pkg’和‘file’是状态声明,而‘installed’和‘managed’是函数声明。函数接受参数,用户、组、模式和源都是函数‘managed’的参数。

要将该配置应用到下属客户端,请移动你的‘vimrc’文件到‘/src/salt’,然后运行以下命令。

[root@salt-master~]# salt 'minion.com' state.sls vim
minion.com:
----------
          ID: vim-enhanced
    Function: pkg.installed
      Result: True
     Comment: The following packages were installed/updated: vim-enhanced.
     Started: 09:36:23.438571
    Duration: 94045.954 ms
     Changes:
              ----------
              vim-enhanced:
                  ----------
                  new:
                      7.4.160-1.el7
                  old:


Summary
------------
Succeeded: 1 (changed=1)
Failed:    0
------------
Total states run:     1

你也可以添加依赖关系到你的配置中。

[root@salt-master~]# vim /srv/salt/ssh.sls
openssh-server:
  pkg.installed


/etc/ssh/sshd_config:
  file.managed:
    - user: root
    - group: root
    - mode: 600
    - source: salt://ssh/sshd_config

sshd:
  service.running:
    - require:
      - pkg: openssh-server

这里的‘require’声明是必须的,它在‘service’和‘pkg’状态之间创建依赖关系。该声明将首先检查包是否安装,然后运行服务。

但是,我更偏向于使用‘watch’声明,因为它也可以检查文件是否修改和重启服务。

[root@salt-master~]# vim /srv/salt/ssh.sls
openssh-server:
  pkg.installed


/etc/ssh/sshd_config:
  file.managed:
    - user: root
    - group: root
    - mode: 600
    - source: salt://sshd_config

sshd:
  service.running:
    - watch:
      - pkg: openssh-server
      - file: /etc/ssh/sshd_config

[root@vps4544 ssh]# salt 'minion.com' state.sls ssh
seven.leog.in:
     Changes:
----------
          ID: openssh-server
    Function: pkg.installed
      Result: True
     Comment: Package openssh-server is already installed.
     Started: 13:01:55.824367
    Duration: 1.156 ms
     Changes:
----------
          ID: /etc/ssh/sshd_config
    Function: file.managed
      Result: True
     Comment: File /etc/ssh/sshd_config updated
     Started: 13:01:55.825731
    Duration: 334.539 ms
     Changes:
              ----------
              diff:
                  ---
                  +++
                  @@ -14,7 +14,7 @@
                   # SELinux about this change.
                   # semanage port -a -t ssh_port_t -p tcp #PORTNUMBER
                   #
                  -Port 22
                  +Port 422
                   #AddressFamily any
                   #ListenAddress 0.0.0.0
                   #ListenAddress ::

----------
          ID: sshd
    Function: service.running
      Result: True
     Comment: Service restarted
     Started: 13:01:56.473121
    Duration: 407.214 ms
     Changes:
              ----------
              sshd:
                  True

Summary
------------
Succeeded: 4 (changed=2)
Failed:    0
------------
Total states run:     4

在单一目录中维护所有的配置文件是一项复杂的大工程,因此,你可以创建子目录并在其中添加配置文件init.sls文件。

[root@salt-master~]# mkdir /srv/salt/ssh
[root@salt-master~]# vim /srv/salt/ssh/init.sls
openssh-server:
  pkg.installed

/etc/ssh/sshd_config:
  file.managed:
    - user: root
    - group: root
    - mode: 600
    - source: salt://ssh/sshd_config

sshd:
  service.running:
    - watch:
      - pkg: openssh-server
      - file: /etc/ssh/sshd_config

[root@vps4544 ssh]# cp /etc/ssh/sshd_config /srv/salt/ssh/
[root@vps4544 ssh]# salt 'minion.com' state.sls ssh

Top文件和环境

top文件(top.sls)是用来定义你的环境的文件,它允许你映射下属客户机到包,默认环境是‘base’。你需要定义在基本环境下,哪个包会被安装到哪台服务器。

如果对于一台特定的下属客户机而言,有多个环境,并且有多于一个的定义,那么默认情况下,基本环境将取代其它环境。

要定义环境,你需要将它添加到领主配置文件的‘file\_roots’指针。

[root@salt-master ~]# vim /etc/salt/master
file_roots:
  base:
    - /srv/salt
  dev:
    - /srv/salt/dev

现在,添加一个top.sls文件到/src/salt。

[root@salt-master ~]# vim /srv/salt/top.sls
base:
  '*':
    - vim
  'minion.com':
     - ssh

应用top文件配置

[root@salt-master~]# salt '*' state.highstate
minion.com:
----------
          ID: vim-enhanced
    Function: pkg.installed
      Result: True
     Comment: Package vim-enhanced is already installed.
     Started: 13:10:55
    Duration: 1678.779 ms
     Changes:
----------
          ID: openssh-server
    Function: pkg.installed
      Result: True
     Comment: Package openssh-server is already installed.
     Started: 13:10:55.
    Duration: 2.156 ms

下属客户机将下载top文件并搜索用于它的配置,领主服务器也会将配置应用到所有下属客户机。


这仅仅是一个Salt的简明教程,如果你想要深入学习并理解,你可以访问下面的链接。如果你已经在使用Salt,那么请告诉我你的建议和意见吧。

更新: Foreman 已经通过插件支持salt。

阅读链接

颗粒

Salt和Puppet的充分比较

内建执行模块的完全列表


via: http://techarena51.com/index.php/getting-started-with-saltstack/

作者:Leo G 译者:GOLinux 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出

今天我来谈谈 ansible,一个由 Python 编写的强大的配置管理解决方案。尽管市面上已经有很多可供选择的配置管理解决方案,但他们各有优劣,而 ansible 的特点就在于它的简洁。让 ansible 在主流的配置管理系统中与众不同的一点便是,它并不需要你在想要配置的每个节点上安装自己的组件。同时提供的一个优点在于,如果需要的话,你可以在不止一个地方控制你的整个基础架构。最后一点是它的正确性,或许这里有些争议,但是我认为在大多数时候这仍然可以作为它的一个优点。说得足够多了,让我们来着手在 RHEL/CentOS 和基于 Debian/Ubuntu 的系统中安装和配置 Ansible。

准备工作

  1. 发行版:RHEL/CentOS/Debian/Ubuntu Linux
  2. Jinja2:Python 的一个对设计师友好的现代模板语言
  3. PyYAML:Python 的一个 YAML 编码/反编码函数库
  4. paramiko:纯 Python 编写的 SSHv2 协议函数库 (译者注:原文对函数库名有拼写错误)
  5. httplib2:一个功能全面的 HTTP 客户端函数库
  6. 本文中列出的绝大部分操作已经假设你将在 bash 或者其他任何现代的 shell 中以 root 用户执行。

Ansible 如何工作

Ansible 工具并不使用守护进程,它也不需要任何额外的自定义安全架构,因此它的部署可以说是十分容易。你需要的全部东西便是 SSH 客户端和服务器了。

 +-----------------+                    +---------------+
 |安装了 Ansible 的|       SSH          | 文件服务器1   |
 |Linux/Unix 工作站|<------------------>| 数据库服务器2 | 在本地或远程
 +-----------------+       模块         | 代理服务器3   | 数据中心的
    192.168.1.100                       +---------------+ Unix/Linux 服务器

其中:

  1. 192.168.1.100 - 在你本地的工作站或服务器上安装 Ansible。
  2. 文件服务器1到代理服务器3 - 使用 192.168.1.100 和 Ansible 来自动管理所有的服务器。
  3. SSH - 在 192.168.1.100 和本地/远程的服务器之间设置 SSH 密钥。

Ansible 安装教程

ansible 的安装轻而易举,许多发行版的第三方软件仓库中都有现成的软件包,可以直接安装。其他简单的安装方法包括使用 pip 安装它,或者从 github 里获取最新的版本。若想使用你的软件包管理器安装,在基于 RHEL/CentOS Linux 的系统里你很可能需要 EPEL 仓库

在基于 RHEL/CentOS Linux 的系统中安装 ansible

输入如下 yum 命令:

$ sudo yum install ansible

在基于 Debian/Ubuntu Linux 的系统中安装 ansible

输入如下 apt-get 命令:

$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible

使用 pip 安装 ansible

pip 命令是一个安装和管理 Python 软件包的工具,比如它能管理 Python Package Index 中的那些软件包。如下方式在 Linux 和类 Unix 系统中通用:

$ sudo pip install ansible

从源代码安装最新版本的 ansible

你可以通过如下命令从 github 中安装最新版本:

$ cd ~
$ git clone git://github.com/ansible/ansible.git
$ cd ./ansible
$ source ./hacking/env-setup

当你从一个 git checkout 中运行 ansible 的时候,请记住你每次用它之前都需要设置你的环境,或者你可以把这个设置过程加入你的 bash rc 文件中:

# 加入 BASH RC
$ echo "export ANSIBLE_HOSTS=~/ansible_hosts" >> ~/.bashrc
$ echo "source ~/ansible/hacking/env-setup" >> ~/.bashrc

ansible 的 hosts 文件包括了一系列它能操作的主机。默认情况下 ansible 通过路径 /etc/ansible/hosts 查找 hosts 文件,不过这个行为也是可以更改的,这样当你想操作不止一个 ansible 或者针对不同的数据中心的不同客户操作的时候也是很方便的。你可以通过命令行参数 -i 指定 hosts 文件:

$ ansible all -m shell -a "hostname" --ask-pass -i /etc/some/other/dir/ansible_hosts

不过我更倾向于使用一个环境变量,这可以在你想要通过 source 一个不同的文件来切换工作目标的时候起到作用。这里的环境变量是 $ANSIBLE\_HOSTS,可以这样设置:

$ export ANSIBLE_HOSTS=~/ansible_hosts

一旦所有需要的组件都已经安装完毕,而且你也准备好了你的 hosts 文件,你就可以来试一试它了。为了快速测试,这里我把 127.0.0.1 写到了 ansible 的 hosts 文件里:

$ echo "127.0.0.1" > ~/ansible_hosts

现在来测试一个简单的 ping:

$ ansible all -m ping

或者提示 ssh 密码:

$ ansible all -m ping --ask-pass

我在刚开始的设置中遇到过几次问题,因此这里强烈推荐为 ansible 设置 SSH 公钥认证。不过在刚刚的测试中我们使用了 --ask-pass,在一些机器上你会需要安装 sshpass 或者像这样指定 -c paramiko:

$ ansible all -m ping --ask-pass -c paramiko

当然你也可以安装 sshpass,然而 sshpass 并不总是在标准的仓库中提供,因此 paramiko 可能更为简单。

设置 SSH 公钥认证

于是我们有了一份配置,以及一些基础的其他东西。现在让我们来做一些实用的事情。ansible 的强大很大程度上体现在 playbooks 上,后者基本上就是一些写好的 ansible 脚本(大部分来说),不过在制作一个 playbook 之前,我们将先从一些一句话脚本开始。现在让我们创建和配置 SSH 公钥认证,以便省去 -c 和 --ask-pass 选项:

$ ssh-keygen -t rsa

样例输出:

Generating public/private rsa key pair.
Enter file in which to save the key (/home/mike/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/mike/.ssh/id_rsa.
Your public key has been saved in /home/mike/.ssh/id_rsa.pub.
The key fingerprint is:
94:a0:19:02:ba:25:23:7f:ee:6c:fb:e8:38:b4:f2:42 [email protected]
The key's randomart image is:
+--[ RSA 2048]----+
|... . .          |
|.  . + . .       |
|= . o   o        |
|.*     .         |
|. . .   S        |
| E.o             |
|.. ..            |
|o o+..           |
| +o+*o.          |
+-----------------+

现在显然有很多种方式来把它放到远程主机上应该的位置。不过既然我们正在使用 ansible,就用它来完成这个操作吧:

$ ansible all -m copy -a "src=/home/mike/.ssh/id_rsa.pub dest=/tmp/id_rsa.pub" --ask-pass -c paramiko

样例输出:

SSH password:
127.0.0.1 | success >> {
    "changed": true,
    "dest": "/tmp/id_rsa.pub",
    "gid": 100,
    "group": "users",
    "md5sum": "bafd3fce6b8a33cf1de415af432774b4",
    "mode": "0644",
    "owner": "mike",
    "size": 410,
    "src": "/home/mike/.ansible/tmp/ansible-tmp-1407008170.46-208759459189201/source",
    "state": "file",
    "uid": 1000
}

下一步,把公钥文件添加到远程服务器里。输入:

$ ansible all -m shell -a "cat /tmp/id_rsa.pub >> /root/.ssh/authorized_keys" --ask-pass -c paramiko

样例输出:

SSH password:
127.0.0.1 | FAILED | rc=1 >>
/bin/sh: /root/.ssh/authorized_keys: Permission denied

矮油,我们需要用 root 来执行这个命令,所以还是加上一个 -u 参数吧:

$ ansible all -m shell -a "cat /tmp/id_rsa.pub >> /root/.ssh/authorized_keys" --ask-pass -c paramiko -u root

样例输出:

SSH password:
127.0.0.1 | success | rc=0 >>

请注意,我刚才这是想要演示通过 ansible 来传输文件的操作。事实上 ansible 有一个更加方便的内置 SSH 密钥管理支持:

$ ansible all -m authorized_key -a "user=mike key='{{ lookup('file', '/home/mike/.ssh/id_rsa.pub') }}' path=/home/mike/.ssh/authorized_keys manage_dir=no" --ask-pass -c paramiko

样例输出:

SSH password:
127.0.0.1 | success >> {
    "changed": true,
    "gid": 100,
    "group": "users",
    "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCq+Z8/usprXk0aCAPyP0TGylm2MKbmEsHePUOd7p5DO1QQTHak+9gwdoJJavy0yoUdi+C+autKjvuuS+vGb8+I+8mFNu5CvKiZzIpMjZvrZMhHRdNud7GuEanusTEJfi1pUd3NA2iXhl4a6S9a/4G2mKyf7QQSzI4Z5ddudUXd9yHmo9Yt48/ASOJLHIcYfSsswOm8ux1UnyeHqgpdIVONVFsKKuSNSvZBVl3bXzhkhjxz8RMiBGIubJDBuKwZqNSJkOlPWYN76btxMCDVm07O7vNChpf0cmWEfM3pXKPBq/UBxyG2MgoCGkIRGOtJ8UjC/daadBUuxg92/u01VNEB [email protected]",
    "key_options": null,
    "keyfile": "/home/mike/.ssh/authorized_keys",
    "manage_dir": false,
    "mode": "0600",
    "owner": "mike",
    "path": "/home/mike/.ssh/authorized_keys",
    "size": 410,
    "state": "file",
    "uid": 1000,
    "unique": false,
    "user": "mike"
}

现在这些密钥已经设置好了。我们来试着随便跑一个命令,比如 hostname,希望我们不会被提示要输入密码

$ ansible all -m shell -a "hostname" -u root

样例输出:

127.0.0.1 | success | rc=0 >>

成功!!!现在我们可以用 root 来执行命令,并且不会被输入密码的提示干扰了。我们现在可以轻易地配置任何在 ansible hosts 文件中的主机了。让我们把 /tmp 中的公钥文件删除:

$ ansible all -m file -a "dest=/tmp/id_rsa.pub state=absent" -u root

样例输出:

127.0.0.1 | success >> {
    "changed": true,
    "path": "/tmp/id_rsa.pub",
    "state": "absent"
}

下面我们来做一些更复杂的事情,我要确定一些软件包已经安装了,并且已经是最新的版本:

$ ansible all -m zypper -a "name=apache2 state=latest" -u root

样例输出:

127.0.0.1 | success >> {
    "changed": false,
    "name": "apache2",
    "state": "latest"
}

很好,我们刚才放在 /tmp 中的公钥文件已经消失了,而且我们已经安装好了最新版的 apache。下面我们来看看前面命令中的 -m zypper,一个让 ansible 非常灵活,并且给了 playbooks 更多能力的功能。如果你不使用 openSuse 或者 Suse enterprise 你可能还不熟悉 zypper, 它基本上就是 suse 世界中相当于 yum 的存在。在上面所有的例子中,我的 hosts 文件中都只有一台机器。除了最后一个命令外,其他所有命令都应该在任何标准的 *nix 系统和标准的 ssh 配置中使用,这造成了一个问题。如果我们想要同时管理多种不同的机器呢?这便是 playbooks 和 ansible 的可配置性闪闪发光的地方了。首先我们来少许修改一下我们的 hosts 文件:

$ cat ~/ansible_hosts

样例输出:

[RHELBased]
10.50.1.33
10.50.1.47

[SUSEBased]
127.0.0.1

首先,我们创建了一些分组的服务器,并且给了他们一些有意义的标签。然后我们来创建一个为不同类型的服务器执行不同操作的 playbook。你可能已经发现这个 yaml 的数据结构和我们之前运行的命令行语句中的相似性了。简单来说,-m 是一个模块,而 -a 用来提供模块参数。在 YAML 表示中你可以先指定模块,然后插入一个冒号 :,最后指定参数。

---
- hosts: SUSEBased
  remote_user: root
  tasks:
    - zypper: name=apache2 state=latest
- hosts: RHELBased
  remote_user: root
  tasks:
    - yum: name=httpd state=latest

现在我们有一个简单的 playbook 了,我们可以这样运行它:

$ ansible-playbook testPlaybook.yaml -f 10

样例输出:

PLAY [SUSEBased] **************************************************************

GATHERING FACTS ***************************************************************
ok: [127.0.0.1]

TASK: [zypper name=apache2 state=latest] **************************************
ok: [127.0.0.1]

PLAY [RHELBased] **************************************************************

GATHERING FACTS ***************************************************************
ok: [10.50.1.33]
ok: [10.50.1.47]

TASK: [yum name=httpd state=latest] *******************************************
changed: [10.50.1.33]
changed: [10.50.1.47]

PLAY RECAP ********************************************************************
10.50.1.33                 : ok=2    changed=1    unreachable=0    failed=0
10.50.1.47                 : ok=2    changed=1    unreachable=0    failed=0
127.0.0.1                  : ok=2    changed=0    unreachable=0    failed=0

注意,你会看到 ansible 联系到的每一台机器的输出。-f 参数让 ansible 在多台主机上同时运行指令。除了指定全部主机,或者一个主机分组的名字以外,你还可以把导入 ssh 公钥的操作从命令行里转移到 playbook 中,这将在设置新主机的时候提供很大的方便,甚至让新主机直接可以运行一个 playbook。为了演示,我们把我们之前的公钥例子放进一个 playbook 里:

---
- hosts: SUSEBased
  remote_user: mike
  sudo: yes
  tasks:
    - authorized_key: user=root key="{{ lookup('file', '/home/mike/.ssh/id_rsa.pub') }}" path=/root/.ssh/authorized_keys manage_dir=no
- hosts: RHELBased
  remote_user: mdonlon
  sudo: yes
  tasks:
    - authorized_key: user=root key="{{ lookup('file', '/home/mike/.ssh/id_rsa.pub') }}" path=/root/.ssh/authorized_keys manage_dir=no

除此之外还有很多可以做的事情,比如在启动的时候把公钥配置好,或者引入其他的流程来让你按需配置一些机器。不过只要 SSH 被配置成接受密码登陆,这些几乎可以用在所有的流程中。在你准备开始写太多 playbook 之前,另一个值得考虑的事情是,代码管理可以有效节省你的时间。机器需要不断变化,然而你并不需要在每次机器发生变化时都重新写一个 playbook,只需要更新相关的部分并提交这些修改。与此相关的另一个好处是,如同我之前所述,你可以从不同的地方管理你的整个基础结构。你只需要将你的 playbook 仓库 git clone 到新的机器上,就完成了管理所有东西的全部设置流程。

现实中的 ansible 例子

我知道很多用户经常使用 pastebin 这样的服务,以及很多公司基于显而易见的理由配置了他们内部使用的类似东西。最近,我遇到了一个叫做 showterm 的程序,巧合之下我被一个客户要求配置它用于内部使用。这里我不打算赘述这个应用程序的细节,不过如果你感兴趣的话,你可以使用 Google 搜索 showterm。作为一个合理的现实中的例子,我将会试图配置一个 showterm 服务器,并且配置使用它所需要的客户端应用程序。在这个过程中我们还需要一个数据库服务器。现在我们从配置客户端开始:

---
- hosts: showtermClients
  remote_user: root
  tasks:
    - yum: name=rubygems state=latest
    - yum: name=ruby-devel state=latest
    - yum: name=gcc state=latest
    - gem: name=showterm state=latest user_install=no

这部分很简单。下面是主服务器:

---
- hosts: showtermServers
  remote_user: root
  tasks:
    - name: ensure packages are installed
      yum: name={{item}} state=latest
      with_items:
        - postgresql
        - postgresql-server
        - postgresql-devel
        - python-psycopg2
        - git
        - ruby21
        - ruby21-passenger
    - name: showterm server from github
      git: repo=https://github.com/ConradIrwin/showterm.io dest=/root/showterm
    - name: Initdb
      command: service postgresql initdb
               creates=/var/lib/pgsql/data/postgresql.conf

    - name: Start PostgreSQL and enable at boot
      service: name=postgresql
               enabled=yes
               state=started
    - gem: name=pg state=latest user_install=no
  handlers:
   - name: restart postgresql
     service: name=postgresql state=restarted

- hosts: showtermServers
  remote_user: root
  sudo: yes
  sudo_user: postgres
  vars:
    dbname: showterm
    dbuser: showterm
    dbpassword: showtermpassword
  tasks:
    - name: create db
      postgresql_db: name={{dbname}}

    - name: create user with ALL priv
      postgresql_user: db={{dbname}} name={{dbuser}} password={{dbpassword}} priv=ALL
- hosts: showtermServers
  remote_user: root
  tasks:
    - name: database.yml
      template: src=database.yml dest=/root/showterm/config/database.yml
- hosts: showtermServers
  remote_user: root
  tasks:
    - name: run bundle install
      shell: bundle install
      args:
        chdir: /root/showterm
- hosts: showtermServers
  remote_user: root
  tasks:
    - name: run rake db tasks
      shell: 'bundle exec rake db:create db:migrate db:seed'
      args:
        chdir: /root/showterm
- hosts: showtermServers
  remote_user: root
  tasks:
    - name: apache config
      template: src=showterm.conf dest=/etc/httpd/conf.d/showterm.conf

还凑合。请注意,从某种意义上来说这是一个任意选择的程序,然而我们现在已经可以持续地在任意数量的机器上部署它了,这便是配置管理的好处。此外,在大多数情况下这里的定义语法几乎是不言而喻的,wiki 页面也就不需要加入太多细节了。当然在我的观点里,一个有太多细节的 wiki 页面绝不会是一件坏事。

扩展配置

我们并没有涉及到这里所有的细节。Ansible 有许多选项可以用来配置你的系统。你可以在你的 hosts 文件中内嵌变量,而 ansible 将会把它们应用到远程节点。如:

[RHELBased]
10.50.1.33  http_port=443
10.50.1.47  http_port=80  ansible_ssh_user=mdonlon

[SUSEBased]
127.0.0.1  http_port=443

尽管这对于快速配置来说已经非常方便,你还可以将变量分成存放在 yaml 格式的多个文件中。在你的 hosts 文件路径里,你可以创建两个子目录 groupvars 和 hostvars。在这些路径里放置的任何文件,只要能对得上一个主机分组的名字,或者你的 hosts 文件中的一个主机名,它们都会在运行时被插入进来。所以前面的一个例子将会变成这样:

ultrabook:/etc/ansible # pwd
/etc/ansible
ultrabook:/etc/ansible # tree
.
├── group_vars
│   ├── RHELBased
│   └── SUSEBased
├── hosts
└── host_vars
├── 10.50.1.33
└── 10.50.1.47

2 directories, 5 files
ultrabook:/etc/ansible # cat hosts
[RHELBased]
10.50.1.33
10.50.1.47

[SUSEBased]
127.0.0.1
ultrabook:/etc/ansible # cat group_vars/RHELBased
ultrabook:/etc/ansible # cat group_vars/SUSEBased
---
http_port: 443
ultrabook:/etc/ansible # cat host_vars/10.50.1.33
---
http_port: 443
ultrabook:/etc/ansible # cat host_vars/10.50.1.47
---
http_port:80
ansible_ssh_user: mdonlon

改善 Playbooks

组织 playbooks 也已经有很多种现成的方式。在前面的例子中我们用了一个单独的文件,因此这方面被大幅地简化了。组织这些文件的一个常用方式是创建角色。简单来说,你将一个主文件加载为你的 playbook,而它将会从其它文件中导入所有的数据,这些其他的文件便是角色。举例来说,如果你有了一个 wordpress 网站,你需要一个 web 前端,和一个数据库。web 前端将包括一个 web 服务器,应用程序代码,以及任何需要的模块。数据库有时候运行在同一台主机上,有时候运行在远程的主机上,这时候角色就可以派上用场了。你创建一个目录,并对每个角色创建对应的小 playbook。在这个例子中我们需要一个 apache 角色,mysql 角色,wordpress 角色,mod\_php,以及 php 角色。最大的好处是,并不是每个角色都必须被应用到同一台机器上。在这个例子中,mysql 可以被应用到一台单独的机器。这同样为代码重用提供了可能,比如你的 apache 角色还可以被用在 python 和其他相似的 php 应用程序中。展示这些已经有些超出了本文的范畴,而且做一件事总是有很多不同的方式,我建议搜索一些 ansible 的 playbook 例子。有很多人在 github 上贡献代码,当然还有其他一些网站。

模块

在 ansible 中,对于所有完成的工作,幕后的工作都是由模块主导的。Ansible 有一个非常丰富的内置模块仓库,其中包括软件包安装,文件传输,以及我们在本文中做的所有事情。但是对一部分人来说,这些并不能满足他们的配置需求,ansible 也提供了方法让你添加自己的模块。Ansible 的 API 有一个非常棒的事情是,它并没有限制模块也必须用编写它的语言 Python 来编写,也就是说,你可以用任何语言来编写模块。Ansible 模块通过传递 JSON 数据来工作,因此你只需要用想用的语言生成一段 JSON 数据。我很确定任何脚本语言都可以做到这一点,因此你现在就可以开始写点什么了。在 Ansible 的网站上有很多的文档,包括模块的接口是如何工作的,以及 Github 上也有很多模块的例子。注意一些小众的语言可能没有很好的支持,不过那只可能是因为没有多少人在用这种语言贡献代码。试着写点什么,然后把你的结果发布出来吧!

总结

总的来说,虽然在配置管理方面已经有很多解决方案,我希望本文能显示出 ansible 简单的设置过程,在我看来这是它最重要的一个要点。请注意,因为我试图展示做一件事的不同方式,所以并不是前文中所有的例子都是适用于你的个别环境或者对于普遍情况的最佳实践。这里有一些链接能让你对 ansible 的了解进入下一个层次:


via: http://www.cyberciti.biz/python-tutorials/linux-tutorial-install-ansible-configuration-management-and-it-automation-tool/

作者:Nix Craft 译者:felixonmars 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出