这是一篇关于 Ansible 的速成课程,你可以用作小项目的模板,或者帮你深入了解这个神奇的工具。阅读了本指南之后,你将对自动化服务器配置、部署等有足够的了解。
Ansible 是什么,为什么你该了解? Ansible 简单的说是一个 配置管理系统 ( configuration management system ) 。你只需要可以使用 ssh 访问你的服务器或设备就行。它也不同于其他工具,因为它使用推送的方式,而不是像 puppet 或 chef 那样使用拉取的方式。你可以将代码部署到任意数量的服务器上,配置网络设备或在基础架构中自动执行任何操作。
前置要求 假设你使用 Mac 或 Linux 作为你的工作站,Ubuntu Trusty 作为你的服务器,并有一些安装软件包的经验。此外,你的计算机上将需要以下软件。所以,如果你还没有它们,请先安装:
情景 我们将模拟 2 个连接到 MySQL 数据库的 Web 应用程序服务器。Web 应用程序使用 Rails 5 和 Puma。
准备 Vagrantfile 为这个项目创建一个文件夹,并将下面的内容保存到名为 Vagrantfile
的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 VMs = [ [ "web1" , "10.1.1.11" ], [ "web2" , "10.1.1.12" ], [ "dbserver" , "10.1.1.21" ], ] Vagrant.configure(2 ) do |config | VMs .each { |vm | config .vm.define vm[0 ] do |box | box .vm.box = "ubuntu/trusty64" box.vm.network "private_network" , ip: vm[1 ] box.vm.hostname = vm[0 ] box.vm.provider "virtualbox" do |vb | vb .memory = "512" end end }end
配置你的虚拟网络 我们希望我们的虚拟机能互相交互,但不要让流量流出到真实的网络,所以我们将在 Virtualbox 中创建一个仅主机(HOST-Only)的网络适配器。
打开 Virtualbox
转到 Preferences
转到 Network
单击 Host-Only
单击添加网络
单击 Adapter
将 IPv4 设置为 10.1.1.1
,IPv4 网络掩码:255.255.255.0
单击 “OK”
测试虚拟机及虚拟网络 在终端中,在存放 Vagrantfile
的项目目录中,输入下面的命令:
它会创建你的虚拟机,因此会花费一会时间。输入下面的命令并验证输出内容以检查是否已经工作:
1 2 3 4 5 6 7 8 9 10 11 $ vagrant statusCurrent machine states: web1 running (virtualbox) web2 running (virtualbox) master running (virtualbox) This environment represents multiple VMs. The VMs are all listed above with their current state. For more information about a specific VM, run `vagrant status NAME`.
现在使用 vagrant
的用户名和密码 ,按 Vagrantfile
中的 IP 登录其中一台虚拟机,这将验证虚拟机并将它们的密钥添加到你的已知主机(known_hosts
)文件中。
1 2 3 4 ssh vagrant@10.1.1.11 ssh vagrant@10.1.1.12 ssh vagrant@10.1.1.21
恭喜你!现在你已经有可以实验的服务器了。下面的剩下的部分!
安装 Ansible 对于 Mac 用户:
对于 Ubuntu 用户:
1 2 $ sudo apt install ansible
确保你使用了ansible 最近的版本 2.1 或者更高的版本:
1 2 3 $ ansible ansible 2.1 .1.0
清单 Ansible 使用清单文件来了解要使用的服务器,以及如何将它们分组以并行执行任务。让我们为这个项目创建我们的清单文件 inventory
,并将它放在与 Vagrantfile
相同的文件夹中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [all:children] webs db [all:vars]ansible_user =vagrantansible_ssh_pass =vagrant [webs] web1 ansible_host =10.1.1.11 web2 ansible_host =10.1.1.12 [db] dbserver ansible_host =10.1.1.21
[all:children]
定义一个组的组(all
)
[all:vars]
定义属于组 all
的变量
[webs]
定义一个组,就像 [db]
一样
文件的其余部分只是主机的声明,带有它们的名称和 IP
空行表示声明结束
现在我们有了一个清单,我们可以从命令行开始使用 ansible,指定一个主机或一个组来执行命令。以下是检查与服务器的连接的命令示例:
1 2 $ ansible -i inventory all -m ping
-i
指定清单文件
all
指定要操作的服务器或服务器组
-m' 指定一个 ansible 模块,在这种情况下为
ping`
下面是命令输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 dbserver | SUCCESS => { "changed" : false , "ping" : "pong" } web1 | SUCCESS => { "changed" : false , "ping" : "pong" } web2 | SUCCESS => { "changed" : false , "ping" : "pong" }
服务器以不同的顺序响应,这只取决于谁先响应,但是这个没有关系,因为 ansible 独立保持每台服务器的状态。
你也可以使用另外一个选项来运行任何命令:
1 2 3 4 5 6 7 8 9 10 $ ansible -i inventory all -a uptime web1 | SUCCESS | rc=0 >> 21 :43 :27 up 25 min , 1 user, load average : 0.00 , 0.01 , 0.05 dbserver | SUCCESS | rc=0 >> 21 :43 :27 up 24 min , 1 user, load average : 0.00 , 0.01 , 0.05 web2 | SUCCESS | rc=0 >> 21 :43 :27 up 25 min , 1 user, load average : 0.00 , 0.01 , 0.05
这是只有一台服务器的另外一个例子:
1 2 3 4 5 $ ansible -i inventory dbserver -a "df -h /" dbserver | SUCCESS | rc=0 >> Filesystem Size Used Avail Use /dev/sda1 40 G 1.4 G 37 G 4
剧本 剧本(playbook)只是个 YAML 文件,它将清单文件中的服务器组与命令关联。在 ansible 中的对于关键字是 tasks
,它可以是一个预期的状态、shell 命令或许多其它的选项。有关 ansible 可做的所有事情列表,可以查看所有模块的列表 。
下面是一个运行 shell 命令的剧本示例,将其保存为 playbook1.yml
:
1 2 3 4 5 --- - hosts: all tasks: - shell: uptime
---
是 YAML 文件的开始
- hosts
:指定要使用的组
tasks
:标记任务列表的开始
- shell
:指定第一个任务使用 shell 模块
记住:YAML 需要缩进结构,确保你始终遵循剧本中的正确结构
用下面的命令运行它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ ansible-playbook -i inventory playbook1.yml PLAY [all] **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** * TASK [setup] **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** ok: [web1] ok: [web2] ok: [dbmaster] TASK [command] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** changed: [web1] changed: [web2] changed: [dbmaster] PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** dbmaster : ok=2 changed=1 unreachable=0 failed=0 web1 : ok=2 changed=1 unreachable=0 failed=0 web2 : ok=2 changed=1 unreachable=0 failed=0
正如你所见,ansible 运行了 2 个任务,而不是只有剧本中的一个。TASK [setup]
是一个隐式任务,它会首先运行以捕获服务器的信息,如主机名、IP、发行版和更多详细信息,然后可以使用这些信息运行条件任务。
还有最后的 PLAY RECAP
,其中 ansible 显示了运行了多少个任务以及每个对应的状态。在我们的例子中,因为我们运行了一个 shell 命令,ansible 不知道结果的状态,它被认为是 changed
。
安装软件 我们将使用 apt 在我们的服务器上安装软件,因为我们需要 root 权限,所以我们必须使用 become
语句,将这个内容保存在 playbook2.yml
中并运行它(ansible-playbook playbook2.yml
):
1 2 3 4 5 6 7 --- - hosts: webs become_user: root become: true tasks: - apt: name=git state=present
有一些语句可以应用于 ansible 中所有模块;一个是 name
语句,可以让我们输出关于正在执行的任务的更具描述性的文本。要使用它,保持任务内容一样,但是添加 name :描述性文本
作为第一行,所以我们以前的文本将改成:
1 2 3 4 5 6 7 8 --- - hosts: webs become_user: root become: true tasks: - name: This task will make sure git is present on the system apt: name=git state=present
使用 with_items
当你要处理一个列表时,比如要安装的项目和软件包、要创建的文件,可以用 ansible 提供的 with_items
。下面是我们如何在 playbook3.yml
中使用它,同时添加一些我们已经知道的其他语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 --- - hosts: all become_user: root become: true tasks: - name: Installing dependencies apt: name={{item}} state=present with_items: - git - mysql-client - libmysqlclient-dev - build-essential - python-software-properties
使用 template
和 vars
vars
是一个定义变量语句,可以在 task
语句或 template
文件中使用。 Jinja2 是 Ansible 中使用的模板引擎,但是关于它你不需要学习很多。在你的剧本中定义变量,如下所示:
1 2 3 4 5 6 7 8 9 --- - hosts: all vars: - secret_key: VqnzCLdCV9a3jK - path_to_vault: /opt/very/deep/path tasks: - name: Setting a configuration file using template template: src=myconfig.j2 dest={{path_to_vault}}/app.conf
正如你看到的,我可以使用 {{path_to_vault}}
作为剧本的一部分,但也因为我使用了 template
语句,我可以使用 myconfig.j2
中的任何变量,该文件必须存在一个名为 templates
的子文件夹中。你项目树应该如下所示:
1 2 3 4 5 6 7 ├── Vagrantfile ├── inventory ├── playbook1.yml ├── playbook2.yml └── templates └── myconfig.j2
当 ansible 找到一个 template
语句后它会在 templates
文件夹内查找,并将把被 {{` 和 `}}
括起来的变量展开来。
示例模板:
1 2 this is just an example vault_dir: {{path_to_vault }} secret_password: {{secret_key }}
即使你不扩展变量你也可以使用 template
。考虑到将来会添加所以我先做了。比如创建一个 hosts.j2
模板并加入主机名和 IP。
1 2 3 4 10.1.1.11 web110.1.1.12 web210.1.1.21 dbserver
这里要用像这样的语句:
1 2 3 - name: Installing the hosts file in all servers template: src =hosts.j2 dest =/etc/hosts mode =644
shell 命令 你应该尽量使用模块,因为 Ansible 可以跟踪任务的状态,并避免不必要的重复,但有时 shell 命令是不可避免的。 对于这些情况,Ansible 提供两个选项:
command :直接运行一个命令,没有环境变量或重定向(|
,<
,>
等)
shell :运行 /bin/sh
并展开变量和支持重定向
其他有用的模块
只在一台服务器中运行任务 Rails 使用 migrations 来逐步更改数据库,但由于你有多个应用程序服务器,因此这些迁移任务不能被分配为组任务,而我们只需要一个服务器来运行迁移。在这种情况下,当使用 run_once
时,run_once
将分派任务到一个服务器,并直到这个任务完成继续下一个任务。你只需要在你的任务中设置 run_once:true
。
1 2 3 4 - name: 'Run db :migrate' shell : cd {{appdir}};rails db :migrate run_once: true
会失败的任务 通过指定 ignore_errors:true
,你可以运行可能会失败的任务,但不会影响剧本中剩余的任务完成。这是非常有用的,例如,当删除最初并不存在的日志文件时。
1 2 3 4 - name: 'Delete logs' shell: rm -f /var /log /nginx/errors.log ignore_errors: true
放到一起 现在用我们先前学到的,这里是每个文件的最终版:
Vagrantfile
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 VMs = [ [ "web1" , "10.1.1.11" ], [ "web2" , "10.1.1.12" ], [ "dbserver" , "10.1.1.21" ], ] Vagrant.configure(2 ) do |config | VMs .each { |vm | config .vm.define vm[0 ] do |box | box .vm.box = "ubuntu/trusty64" box.vm.network "private_network" , ip: vm[1 ] box.vm.hostname = vm[0 ] box.vm.provider "virtualbox" do |vb | vb .memory = "512" end end }end
inventory
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [all:children] webs db [all:vars]ansible_user =vagrantansible_ssh_pass =vagrant [webs] web1 ansible_host =10.1.1.11 web2 ansible_host =10.1.1.12 [db] dbserver ansible_host =10.1.1.21
templates/hosts.j2
:
1 2 3 4 10.1.1.11 web110.1.1.12 web210.1.1.21 dbserver
templates/my.cnf.j2
:
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 39 40 41 42 43 [client] port = 3306 socket = /var/run/mysqld/mysqld.sock [mysqld_safe] socket = /var/run/mysqld/mysqld.sock nice = 0 [mysqld] server-id = 1 user = mysql pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock port = 3306 basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp lc-messages-dir = /usr/share/mysql skip-external-locking bind-address = 0.0.0.0 key_buffer = 16M max_allowed_packet = 16M thread_stack = 192K thread_cache_size = 8 myisam-recover = BACKUP query_cache_limit = 1M query_cache_size = 16M log_error = /var/log/mysql/error.log expire_logs_days = 10 max_binlog_size = 100M [mysqldump] quick quote-names max_allowed_packet = 16M [mysql] [isamchk] key_buffer = 16M !includedir /etc/mysql/conf.d/
final-playbook.yml
:
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 - hosts: all become_user : root become : true tasks : - name: 'Install common software on all servers' apt : name={{item}} state=present with_items : - git - mysql-client - libmysqlclient-dev - build-essential - python-software-properties - name: 'Install hosts file' template : src=hosts.j2 dest=/etc/hosts mode=644 - hosts: db become_user : root become : true tasks : - name: 'Software for DB server' apt : name={{item}} state=present with_items : - mysql-server - percona-xtrabackup - mytop - mysql-utilities - name: 'MySQL config file' template : src=my.cnf.j2 dest=/etc/mysql/my.cnf - name: 'Restart MySQL' service : name=mysql state=restarted - name: 'Grant access to web app servers' shell : echo 'GRANT ALL PRIVILEGES ON *.* TO "root"@"%" WITH GRANT OPTION;FLUSH PRIVILEGES;'|mysql -u root mysql - hosts: webs vars : - appdir: /opt/dummyapp become_user : root become : true tasks : - name: 'Add ruby-ng repo' apt_repository : repo='ppa:brightbox/ruby-ng' - name: 'Install rails software' apt : name={{item}} state=present with_items : - ruby-dev - ruby-all-dev - ruby2.2 - ruby2.2-dev - ruby-switch - libcurl4-openssl-dev - libssl-dev - zlib1g-dev - nodejs - name: 'Set ruby to 2.2' shell : ruby-switch --set ruby2.2 - name: 'Install gems' shell : gem install bundler rails - name: 'Kill puma if running' shell : file /run/puma.pid >/dev/null && kill `cat /run/puma.pid` 2>/dev/null ignore_errors : True - name: 'Clone app repo' git : repo=https://github.com/c0d5x/rails_dummyapp.git dest={{appdir}} version=staging force=yes - name : 'Run bundler' shell : cd {{appdir}};bundler - name: 'Run db:setup' shell : cd {{appdir}};rails db:setup run_once : true - name: 'Run db:migrate' shell : cd {{appdir}};rails db:migrate run_once : true - name: 'Run rails server' shell : cd {{appdir}};rails server -b 0.0.0.0 -p 80 --pid /run/puma.pid -d
放在你的环境中 将这些文件放在相同的目录,运行下面的命令打开你的开发环境:
1 2 3 vagrant up ansible-playbook -i inventory final-playbook.yml
部署新的代码 确保修改了代码并推送到了仓库中。接下来,确保你 git 语句中使用了正确的分支:
1 2 3 4 5 6 7 - name: 'Clone app repo' git: repo =https://github.com/c0d5x/rails_dummyapp.git dest={{appdir}} version =staging force =yes
作为一个例子,你可以修改 version
字段为 master
,再次运行剧本:
1 2 ansible-playbook -i inventory final-playbook.yml
检查所有的 web 服务器上的页面是否已更改:http://10.1.1.11
或 http://10.1.1.12
。将其更改为 version = staging
并重新运行剧本并再次检查页面。
你还可以创建只包含与部署相关的任务的替代剧本,以便其运行更快。
接下来是什么 ?! 这只是可以做的很小一部分。我们没有接触 角色 ( role ) 、 过滤器 ( filter ) 、调试等许多其他很棒的功能,但我希望它给了你一个良好的开始!所以,请继续学习并使用它。如果你有任何问题,你可以在 twitter 或评论栏联系我,让我知道你还想知道哪些关于 ansible 的东西!
via: https://gorillalogic.com/blog/getting-started-with-ansible/?utm_source=webopsweekly&utm_medium=email
作者:JOSE HIDALGO 译者:geekpi 校对:wxy
本文由 LCTT 组织编译,Linux中国 荣誉推出