这是一篇关于 ansible 的速成课程,你可以用作小项目的模板,或者帮你深入了解这个神奇的工具。阅读了本指南之后,你将对自动化服务器配置、部署等有足够的了解。
<a target="_blank"></a>
ansible 简单的说是一个配置管理系统configuration management system。你只需要可以使用 ssh 访问你的服务器或设备就行。它也不同于其他工具,因为它使用推送的方式,而不是像 puppet 或 chef 那样使用拉取的方式。你可以将代码部署到任意数量的服务器上,配置网络设备或在基础架构中自动执行任何操作。
假设你使用 mac 或 linux 作为你的工作站,ubuntu trusty 作为你的服务器,并有一些安装软件包的经验。此外,你的计算机上将需要以下软件。所以,如果你还没有它们,请先安装:
<a href="https://www.virtualbox.org/" target="_blank">virtualbox</a>
<a href="https://www.vagrantup.com/downloads.html" target="_blank">vagrant</a>
我们将模拟 2 个连接到 mysql 数据库的 web 应用程序服务器。web 应用程序使用 rails 5 和 puma。
为这个项目创建一个文件夹,并将下面的内容保存到名为 <code>vagrantfile</code> 的文件。
<code>vms = [</code>
<code>[ "web1", "10.1.1.11"],</code>
<code>[ "web2", "10.1.1.12"],</code>
<code>[ "dbserver", "10.1.1.21"],</code>
<code>]</code>
<code></code>
<code>vagrant.configure(2) do |config|</code>
<code>vms.each { |vm|</code>
<code>config.vm.define vm[0] do |box|</code>
<code>box.vm.box = "ubuntu/trusty64"</code>
<code>box.vm.network "private_network", ip: vm[1]</code>
<code>box.vm.hostname = vm[0]</code>
<code>box.vm.provider "virtualbox" do |vb|</code>
<code>vb.memory = "512"</code>
<code>end</code>
<code>}</code>
我们希望我们的虚拟机能互相交互,但不要让流量流出到真实的网络,所以我们将在 virtualbox 中创建一个仅主机(host-only)的网络适配器。
打开 virtualbox
转到 preferences
转到 network
单击 host-only
单击添加网络
单击 adapter
将 ipv4 设置为 <code>10.1.1.1</code>,ipv4 网络掩码:<code>255.255.255.0</code>
单击 “ok”
在终端中,在存放 <code>vagrantfile</code> 的项目目录中,输入下面的命令:
<code>vagrant up</code>
它会创建你的虚拟机,因此会花费一会时间。输入下面的命令并验证输出内容以检查是否已经工作:
<code>$ vagrant status</code>
<code>current machine states:</code>
<code>web1 running (virtualbox)</code>
<code>web2 running (virtualbox)</code>
<code>master running (virtualbox)</code>
<code>this environment represents multiple vms. the vms are all listed</code>
<code>above with their current state. for more information about a specific</code>
<code>vm, run `vagrant status name`.</code>
现在使用 <code>vagrant</code> 的用户名和密码 ,按 <code>vagrantfile</code> 中的 ip 登录其中一台虚拟机,这将验证虚拟机并将它们的密钥添加到你的已知主机(<code>known_hosts</code>)文件中。
<code>ssh [email protected] # password is `vagrant`</code>
<code>ssh [email protected]</code>
<code>ssh [email protected]</code>
恭喜你!现在你已经有可以实验的服务器了。下面的剩下的部分!
对于 mac 用户:
<code>$ brew install ansible</code>
对于 ubuntu 用户:
<code>$ sudo apt install ansible</code>
确保你使用了ansible 最近的版本 2.1 或者更高的版本:
<code>$ ansible --version</code>
<code>ansible 2.1.1.0</code>
ansible 使用清单文件来了解要使用的服务器,以及如何将它们分组以并行执行任务。让我们为这个项目创建我们的清单文件 <code>inventory</code>,并将它放在与 <code>vagrantfile</code> 相同的文件夹中:
<code>[all:children]</code>
<code>webs</code>
<code>db</code>
<code>[all:vars]</code>
<code>ansible_user=vagrant</code>
<code>ansible_ssh_pass=vagrant</code>
<code>[webs]</code>
<code>web1 ansible_host=10.1.1.11</code>
<code>web2 ansible_host=10.1.1.12</code>
<code>[db]</code>
<code>dbserver ansible_host=10.1.1.21</code>
<code>[all:children]</code> 定义一个组的组(<code>all</code>)
<code>[all:vars]</code> 定义属于组 <code>all</code> 的变量
<code>[webs]</code> 定义一个组,就像 <code>[db]</code> 一样
文件的其余部分只是主机的声明,带有它们的名称和 ip
空行表示声明结束
现在我们有了一个清单,我们可以从命令行开始使用 ansible,指定一个主机或一个组来执行命令。以下是检查与服务器的连接的命令示例:
<code>$ ansible -i inventory all -m ping</code>
<code>-i</code> 指定清单文件
<code>all</code> 指定要操作的服务器或服务器组
<code>-m' 指定一个 ansible 模块,在这种情况下为</code>ping`
下面是命令输出:
<code>dbserver | success => {</code>
<code>"changed": false,</code>
<code>"ping": "pong"</code>
<code>web1 | success => {</code>
<code>web2 | success => {</code>
服务器以不同的顺序响应,这只取决于谁先响应,但是这个没有关系,因为 ansible 独立保持每台服务器的状态。
你也可以使用另外一个选项来运行任何命令:
<code>-a <command></code>
<code>$ ansible -i inventory all -a uptime</code>
<code>web1 | success | rc=0 >></code>
<code>21:43:27 up 25 min, 1 user, load average: 0.00, 0.01, 0.05</code>
<code>dbserver | success | rc=0 >></code>
<code>21:43:27 up 24 min, 1 user, load average: 0.00, 0.01, 0.05</code>
<code>web2 | success | rc=0 >></code>
这是只有一台服务器的另外一个例子:
<code>$ ansible -i inventory dbserver -a "df -h /"</code>
<code>filesystem size used avail use% mounted on</code>
<code>/dev/sda1 40g 1.4g 37g 4% /</code>
下面是一个运行 shell 命令的剧本示例,将其保存为 <code>playbook1.yml</code>:
<code>---</code>
<code>- hosts: all</code>
<code>tasks:</code>
<code>- shell: uptime</code>
<code>---</code> 是 yaml 文件的开始
<code>- hosts</code>:指定要使用的组
<code>tasks</code>:标记任务列表的开始
记住:yaml 需要缩进结构,确保你始终遵循剧本中的正确结构
用下面的命令运行它:
<code>$ ansible-playbook -i inventory playbook1.yml</code>
<code>play [all] *********************************************************************</code>
<code>task [setup] *******************************************************************</code>
<code>ok: [web1]</code>
<code>ok: [web2]</code>
<code>ok: [dbmaster]</code>
<code>task [command] *****************************************************************</code>
<code>changed: [web1]</code>
<code>changed: [web2]</code>
<code>changed: [dbmaster]</code>
<code>play recap *********************************************************************</code>
<code>dbmaster : ok=2 changed=1 unreachable=0 failed=0</code>
<code>web1 : ok=2 changed=1 unreachable=0 failed=0</code>
<code>web2 : ok=2 changed=1 unreachable=0 failed=0</code>
正如你所见,ansible 运行了 2 个任务,而不是只有剧本中的一个。<code>task [setup]</code> 是一个隐式任务,它会首先运行以捕获服务器的信息,如主机名、ip、发行版和更多详细信息,然后可以使用这些信息运行条件任务。
还有最后的 <code>play recap</code>,其中 ansible 显示了运行了多少个任务以及每个对应的状态。在我们的例子中,因为我们运行了一个 shell 命令,ansible 不知道结果的状态,它被认为是 <code>changed</code>。
<code>- hosts: webs</code>
<code>become_user: root</code>
<code>become: true</code>
<code>- apt: name=git state=present</code>
有一些语句可以应用于 ansible 中所有模块;一个是 <code>name</code> 语句,可以让我们输出关于正在执行的任务的更具描述性的文本。要使用它,保持任务内容一样,但是添加 <code>name :描述性文本</code> 作为第一行,所以我们以前的文本将改成:
<code>- name: this task will make sure git is present on the system</code>
<code>apt: name=git state=present</code>
当你要处理一个列表时,比如要安装的项目和软件包、要创建的文件,可以用 ansible 提供的<code>with_items</code>。下面是我们如何在 <code>playbook3.yml</code> 中使用它,同时添加一些我们已经知道的其他语句:
<code>- name: installing dependencies</code>
<code>apt: name={{item}} state=present</code>
<code>with_items:</code>
<code>- git</code>
<code>- mysql-client</code>
<code>- libmysqlclient-dev</code>
<code>- build-essential</code>
<code>- python-software-properties</code>
<code>vars:</code>
<code>- secret_key: vqnzcldcv9a3jk</code>
<code>- path_to_vault: /opt/very/deep/path</code>
<code>- name: setting a configuration file using template</code>
<code>template: src=myconfig.j2 dest={{path_to_vault}}/app.conf</code>
正如你看到的,我可以使用 <code>{{path_to_vault}}</code> 作为剧本的一部分,但也因为我使用了 <code>template</code>语句,我可以使用 <code>myconfig.j2</code> 中的任何变量,该文件必须存在一个名为 <code>templates</code> 的子文件夹中。你项目树应该如下所示:
<code>├── vagrantfile</code>
<code>├── inventory</code>
<code>├── playbook1.yml</code>
<code>├── playbook2.yml</code>
<code>└── templates</code>
<code>└── myconfig.j2</code>
当 ansible 找到一个 <code>template</code> 语句后它会在 <code>templates</code> 文件夹内查找,并将把被 <code>{{</code> 和 <code>}}</code> 括起来的变量展开来。
示例模板:
<code>this is just an example vault_dir: {{path_to_vault}} secret_password: {{secret_key}}</code>
即使你不扩展变量你也可以使用 <code>template</code>。考虑到将来会添加所以我先做了。比如创建一个<code>hosts.j2</code> 模板并加入主机名和 ip。
<code>10.1.1.11 web1</code>
<code>10.1.1.12 web2</code>
<code>10.1.1.21 dbserver</code>
这里要用像这样的语句:
<code>- name: installing the hosts file in all servers</code>
<code>template: src=hosts.j2 dest=/etc/hosts mode=644</code>
你应该尽量使用模块,因为 ansible 可以跟踪任务的状态,并避免不必要的重复,但有时 shell 命令是不可避免的。 对于这些情况,ansible 提供两个选项:
<code>- name: 'run db:migrate'</code>
<code>shell: cd {{appdir}};rails db:migrate</code>
<code>run_once: true</code>
通过指定 <code>ignore_errors:true</code>,你可以运行可能会失败的任务,但不会影响剧本中剩余的任务完成。这是非常有用的,例如,当删除最初并不存在的日志文件时。
<code>- name: 'delete logs'</code>
<code>shell: rm -f /var/log/nginx/errors.log</code>
<code>ignore_errors: true</code>
现在用我们先前学到的,这里是每个文件的最终版:
<code>vagrantfile</code>:
<code>inventory</code>:
<code>templates/hosts.j2</code>:
<code>templates/my.cnf.j2</code>:
<code>[client]</code>
<code>port = 3306</code>
<code>socket = /var/run/mysqld/mysqld.sock</code>
<code>[mysqld_safe]</code>
<code>nice = 0</code>
<code>[mysqld]</code>
<code>server-id = 1</code>
<code>user = mysql</code>
<code>pid-file = /var/run/mysqld/mysqld.pid</code>
<code>basedir = /usr</code>
<code>datadir = /var/lib/mysql</code>
<code>tmpdir = /tmp</code>
<code>lc-messages-dir = /usr/share/mysql</code>
<code>skip-external-locking</code>
<code>bind-address = 0.0.0.0</code>
<code>key_buffer = 16m</code>
<code>max_allowed_packet = 16m</code>
<code>thread_stack = 192k</code>
<code>thread_cache_size = 8</code>
<code>myisam-recover = backup</code>
<code>query_cache_limit = 1m</code>
<code>query_cache_size = 16m</code>
<code>log_error = /var/log/mysql/error.log</code>
<code>expire_logs_days = 10</code>
<code>max_binlog_size = 100m</code>
<code>[mysqldump]</code>
<code>quick</code>
<code>quote-names</code>
<code>[mysql]</code>
<code>[isamchk]</code>
<code>!includedir /etc/mysql/conf.d/</code>
<code>final-playbook.yml</code>:
<code>- name: 'install common software on all servers'</code>
<code>- name: 'install hosts file'</code>
<code>- hosts: db</code>
<code>- name: 'software for db server'</code>
<code>- mysql-server</code>
<code>- percona-xtrabackup</code>
<code>- mytop</code>
<code>- mysql-utilities</code>
<code>- name: 'mysql config file'</code>
<code>template: src=my.cnf.j2 dest=/etc/mysql/my.cnf</code>
<code>- name: 'restart mysql'</code>
<code>service: name=mysql state=restarted</code>
<code>- name: 'grant access to web app servers'</code>
<code>shell: echo 'grant all privileges on *.* to "root"@"%" with grant option;flush privileges;'|mysql -u root mysql</code>
<code>- appdir: /opt/dummyapp</code>
<code>- name: 'add ruby-ng repo'</code>
<code>apt_repository: repo='ppa:brightbox/ruby-ng'</code>
<code>- name: 'install rails software'</code>
<code>- ruby-dev</code>
<code>- ruby-all-dev</code>
<code>- ruby2.2</code>
<code>- ruby2.2-dev</code>
<code>- ruby-switch</code>
<code>- libcurl4-openssl-dev</code>
<code>- libssl-dev</code>
<code>- zlib1g-dev</code>
<code>- nodejs</code>
<code>- name: 'set ruby to 2.2'</code>
<code>shell: ruby-switch --set ruby2.2</code>
<code>- name: 'install gems'</code>
<code>shell: gem install bundler rails</code>
<code>- name: 'kill puma if running'</code>
<code>shell: file /run/puma.pid >/dev/null && kill `cat /run/puma.pid` 2>/dev/null</code>
<code>- name: 'clone app repo'</code>
<code>git:</code>
<code>repo=https://github.com/c0d5x/rails_dummyapp.git</code>
<code>dest={{appdir}}</code>
<code>version=staging</code>
<code>force=yes</code>
<code>- name: 'run bundler'</code>
<code>shell: cd {{appdir}};bundler</code>
<code>- name: 'run db:setup'</code>
<code>shell: cd {{appdir}};rails db:setup</code>
<code>- name: 'run rails server'</code>
<code>shell: cd {{appdir}};rails server -b 0.0.0.0 -p 80 --pid /run/puma.pid -d</code>
将这些文件放在相同的目录,运行下面的命令打开你的开发环境:
<code>ansible-playbook -i inventory final-playbook.yml</code>
确保修改了代码并推送到了仓库中。接下来,确保你 git 语句中使用了正确的分支:
作为一个例子,你可以修改 <code>version</code> 字段为 <code>master</code>,再次运行剧本:
检查所有的 web 服务器上的页面是否已更改:<code>http://10.1.1.11</code> 或 <code>http://10.1.1.12</code>。将其更改为<code>version = staging</code> 并重新运行剧本并再次检查页面。
你还可以创建只包含与部署相关的任务的替代剧本,以便其运行更快。
这只是可以做的很小一部分。我们没有接触角色role、过滤器filter、调试等许多其他很棒的功能,但我希望它给了你一个良好的开始!所以,请继续学习并使用它。
原文发布时间为:2017-01-12
本文来自云栖社区合作伙伴“linux中国”