Ansible 起步指南

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

这是一篇关于 Ansible 的速成课程,你可以用作小项目的模板,或者帮你深入了解这个神奇的工具。阅读了本指南之后,你将对自动化服务器配置、部署等有足够的了解。

Ansible 是什么,为什么你该了解?

Ansible 简单的说是一个配置管理系统configuration management system。你只需要可以使用 ssh 访问你的服务器或设备就行。它也不同于其他工具,因为它使用推送的方式,而不是像 puppet 或 chef 那样使用拉取的方式。你可以将代码部署到任意数量的服务器上,配置网络设备或在基础架构中自动执行任何操作。

前置要求

假设你使用 Mac 或 Linux 作为你的工作站,Ubuntu Trusty 作为你的服务器,并有一些安装软件包的经验。此外,你的计算机上将需要以下软件。所以,如果你还没有它们,请先安装:

情景

我们将模拟 2 个连接到 MySQL 数据库的 Web 应用程序服务器。Web 应用程序使用 Rails 5 和 Puma。

准备

Vagrantfile

为这个项目创建一个文件夹,并将下面的内容保存到名为 Vagrantfile 的文件。

 
  1. VMs = [
  2. [ "web1", "10.1.1.11"],
  3. [ "web2", "10.1.1.12"],
  4. [ "dbserver", "10.1.1.21"],
  5. ]
  6. Vagrant.configure(2) do |config|
  7. VMs.each { |vm|
  8. config.vm.define vm[0] do |box|
  9. box.vm.box = "ubuntu/trusty64"
  10. box.vm.network "private_network", ip: vm[1]
  11. box.vm.hostname = vm[0]
  12. box.vm.provider "virtualbox" do |vb|
  13. vb.memory = "512"
  14. end
  15. end
  16. }
  17. end

配置你的虚拟网络

我们希望我们的虚拟机能互相交互,但不要让流量流出到真实的网络,所以我们将在 Virtualbox 中创建一个仅主机(HOST-Only)的网络适配器。

  1. 打开 Virtualbox
  2. 转到 Preferences
  3. 转到 Network
  4. 单击 Host-Only
  5. 单击添加网络
  6. 单击 Adapter
  7. 将 IPv4 设置为 10.1.1.1,IPv4 网络掩码:255.255.255.0
  8. 单击 “OK”

测试虚拟机及虚拟网络

在终端中,在存放 Vagrantfile 的项目目录中,输入下面的命令:

 
  1. vagrant up

它会创建你的虚拟机,因此会花费一会时间。输入下面的命令并验证输出内容以检查是否已经工作:

 
  1. $ vagrant status
  2. Current machine states:
  3. web1 running (virtualbox)
  4. web2 running (virtualbox)
  5. master running (virtualbox)
  6. This environment represents multiple VMs. The VMs are all listed
  7. above with their current state. For more information about a specific
  8. VM, run `vagrant status NAME`.

现在使用 vagrant 的用户名和密码 ,按 Vagrantfile 中的 IP 登录其中一台虚拟机,这将验证虚拟机并将它们的密钥添加到你的已知主机(known_hosts)文件中。

 
  1. ssh vagrant@10.1.1.11 # password is `vagrant`
  2. ssh vagrant@10.1.1.12
  3. ssh vagrant@10.1.1.21

恭喜你!现在你已经有可以实验的服务器了。下面的剩下的部分!

安装 Ansible

对于 Mac 用户:

 
  1. $ brew install ansible

对于 Ubuntu 用户:

 
  1. $ sudo apt install ansible

确保你使用了ansible 最近的版本 2.1 或者更高的版本:

 
  1. $ ansible --version
  2. ansible 2.1.1.0

清单

Ansible 使用清单文件来了解要使用的服务器,以及如何将它们分组以并行执行任务。让我们为这个项目创建我们的清单文件 inventory,并将它放在与 Vagrantfile 相同的文件夹中:

 
  1. [all:children]
  2. webs
  3. db
  4. [all:vars]
  5. ansible_user=vagrant
  6. ansible_ssh_pass=vagrant
  7. [webs]
  8. web1 ansible_host=10.1.1.11
  9. web2 ansible_host=10.1.1.12
  10. [db]
  11. dbserver ansible_host=10.1.1.21
  • [all:children] 定义一个组的组(all
  • [all:vars] 定义属于组 all 的变量
  • [webs] 定义一个组,就像 [db] 一样
  • 文件的其余部分只是主机的声明,带有它们的名称和 IP
  • 空行表示声明结束

现在我们有了一个清单,我们可以从命令行开始使用 ansible,指定一个主机或一个组来执行命令。以下是检查与服务器的连接的命令示例:

 
  1. $ ansible -i inventory all -m ping
  • -i 指定清单文件
  • all 指定要操作的服务器或服务器组
  • -m' 指定一个 ansible 模块,在这种情况下为ping`

下面是命令输出:

 
  1. dbserver | SUCCESS => {
  2. "changed": false,
  3. "ping": "pong"
  4. }
  5. web1 | SUCCESS => {
  6. "changed": false,
  7. "ping": "pong"
  8. }
  9. web2 | SUCCESS => {
  10. "changed": false,
  11. "ping": "pong"
  12. }

服务器以不同的顺序响应,这只取决于谁先响应,但是这个没有关系,因为 ansible 独立保持每台服务器的状态。

你也可以使用另外一个选项来运行任何命令:

  • -a <command>
 
  1. $ ansible -i inventory all -a uptime
  2. web1 | SUCCESS | rc=0 >>
  3. 21:43:27 up 25 min, 1 user, load average: 0.00, 0.01, 0.05
  4. dbserver | SUCCESS | rc=0 >>
  5. 21:43:27 up 24 min, 1 user, load average: 0.00, 0.01, 0.05
  6. web2 | SUCCESS | rc=0 >>
  7. 21:43:27 up 25 min, 1 user, load average: 0.00, 0.01, 0.05

这是只有一台服务器的另外一个例子:

 
  1. $ ansible -i inventory dbserver -a "df -h /"
  2. dbserver | SUCCESS | rc=0 >>
  3. Filesystem Size Used Avail Use% Mounted on
  4. /dev/sda1 40G 1.4G 37G 4% /

剧本

剧本(playbook)只是个 YAML 文件,它将清单文件中的服务器组与命令关联。在 ansible 中的对于关键字是 tasks,它可以是一个预期的状态、shell 命令或许多其它的选项。有关 ansible 可做的所有事情列表,可以查看所有模块的列表

下面是一个运行 shell 命令的剧本示例,将其保存为 playbook1.yml

 
  1. ---
  2. - hosts: all
  3. tasks:
  4. - shell: uptime
  • --- 是 YAML 文件的开始
  • - hosts:指定要使用的组
  • tasks:标记任务列表的开始
  • - shell:指定第一个任务使用 shell 模块
  • 记住:YAML 需要缩进结构,确保你始终遵循剧本中的正确结构

用下面的命令运行它:

 
  1. $ ansible-playbook -i inventory playbook1.yml
  2. PLAY [all] *********************************************************************
  3. TASK [setup] *******************************************************************
  4. ok: [web1]
  5. ok: [web2]
  6. ok: [dbmaster]
  7. TASK [command] *****************************************************************
  8. changed: [web1]
  9. changed: [web2]
  10. changed: [dbmaster]
  11. PLAY RECAP *********************************************************************
  12. dbmaster : ok=2 changed=1 unreachable=0 failed=0
  13. web1 : ok=2 changed=1 unreachable=0 failed=0
  14. 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. - hosts: webs
  3. become_user: root
  4. become: true
  5. tasks:
  6. - apt: name=git state=present

有一些语句可以应用于 ansible 中所有模块;一个是 name 语句,可以让我们输出关于正在执行的任务的更具描述性的文本。要使用它,保持任务内容一样,但是添加 name :描述性文本 作为第一行,所以我们以前的文本将改成:

 
  1. ---
  2. - hosts: webs
  3. become_user: root
  4. become: true
  5. tasks:
  6. - name: This task will make sure git is present on the system
  7. apt: name=git state=present

使用 with_items

当你要处理一个列表时,比如要安装的项目和软件包、要创建的文件,可以用 ansible 提供的with_items。下面是我们如何在 playbook3.yml 中使用它,同时添加一些我们已经知道的其他语句:

 
  1. ---
  2. - hosts: all
  3. become_user: root
  4. become: true
  5. tasks:
  6. - name: Installing dependencies
  7. apt: name={{item}} state=present
  8. with_items:
  9. - git
  10. - mysql-client
  11. - libmysqlclient-dev
  12. - build-essential
  13. - python-software-properties

使用 template 和 vars

vars 是一个定义变量语句,可以在 task 语句或 template 文件中使用。 Jinja2 是 Ansible 中使用的模板引擎,但是关于它你不需要学习很多。在你的剧本中定义变量,如下所示:

 
  1. ---
  2. - hosts: all
  3. vars:
  4. - secret_key: VqnzCLdCV9a3jK
  5. - path_to_vault: /opt/very/deep/path
  6. tasks:
  7. - name: Setting a configuration file using template
  8. template: src=myconfig.j2 dest={{path_to_vault}}/app.conf

正如你看到的,我可以使用 {{path_to_vault}} 作为剧本的一部分,但也因为我使用了 template语句,我可以使用 myconfig.j2 中的任何变量,该文件必须存在一个名为 templates 的子文件夹中。你项目树应该如下所示:

 
  1. ├── Vagrantfile
  2. ├── inventory
  3. ├── playbook1.yml
  4. ├── playbook2.yml
  5. └── templates
  6. └── myconfig.j2

当 ansible 找到一个 template 语句后它会在 templates 文件夹内查找,并将把被 {{ 和 }} 括起来的变量展开来。

示例模板:

 
  1. this is just an example vault_dir: {{path_to_vault}} secret_password: {{secret_key}}

即使你不扩展变量你也可以使用 template。考虑到将来会添加所以我先做了。比如创建一个hosts.j2 模板并加入主机名和 IP。

 
  1. 10.1.1.11 web1
  2. 10.1.1.12 web2
  3. 10.1.1.21 dbserver

这里要用像这样的语句:

 
  1. - name: Installing the hosts file in all servers
  2. template: src=hosts.j2 dest=/etc/hosts mode=644

shell 命令

你应该尽量使用模块,因为 Ansible 可以跟踪任务的状态,并避免不必要的重复,但有时 shell 命令是不可避免的。 对于这些情况,Ansible 提供两个选项:

  • command:直接运行一个命令,没有环境变量或重定向(|<> 等)
  • shell:运行 /bin/sh 并展开变量和支持重定向

其他有用的模块

  • apt_repository - 在 Debian 系的发行版中添加/删除包仓库
  • yum_repository - 在 RedHat 系的发行版中添加/删除包仓库
  • service - 启动/停止/重新启动/启用/禁用服务
  • git - 从 git 服务器部署代码
  • unarchive - 从 Web 或本地源解开软件包

只在一台服务器中运行任务

Rails 使用 migrations 来逐步更改数据库,但由于你有多个应用程序服务器,因此这些迁移任务不能被分配为组任务,而我们只需要一个服务器来运行迁移。在这种情况下,当使用 run_once时,run_once 将分派任务到一个服务器,并直到这个任务完成继续下一个任务。你只需要在你的任务中设置 run_once:true

 
  1. - name: 'Run db:migrate'
  2. shell: cd {{appdir}};rails db:migrate
  3. run_once: true

会失败的任务

通过指定 ignore_errors:true,你可以运行可能会失败的任务,但不会影响剧本中剩余的任务完成。这是非常有用的,例如,当删除最初并不存在的日志文件时。

 
  1. - name: 'Delete logs'
  2. shell: rm -f /var/log/nginx/errors.log
  3. ignore_errors: true

放到一起

现在用我们先前学到的,这里是每个文件的最终版:

Vagrantfile

 
  1. VMs = [
  2. [ "web1", "10.1.1.11"],
  3. [ "web2", "10.1.1.12"],
  4. [ "dbserver", "10.1.1.21"],
  5. ]
  6. Vagrant.configure(2) do |config|
  7. VMs.each { |vm|
  8. config.vm.define vm[0] do |box|
  9. box.vm.box = "ubuntu/trusty64"
  10. box.vm.network "private_network", ip: vm[1]
  11. box.vm.hostname = vm[0]
  12. box.vm.provider "virtualbox" do |vb|
  13. vb.memory = "512"
  14. end
  15. end
  16. }
  17. end

inventory

 
  1. [all:children]
  2. webs
  3. db
  4. [all:vars]
  5. ansible_user=vagrant
  6. ansible_ssh_pass=vagrant
  7. [webs]
  8. web1 ansible_host=10.1.1.11
  9. web2 ansible_host=10.1.1.12
  10. [db]
  11. dbserver ansible_host=10.1.1.21

templates/hosts.j2:

 
  1. 10.1.1.11 web1
  2. 10.1.1.12 web2
  3. 10.1.1.21 dbserver

templates/my.cnf.j2

 
  1. [client]
  2. port = 3306
  3. socket = /var/run/mysqld/mysqld.sock
  4. [mysqld_safe]
  5. socket = /var/run/mysqld/mysqld.sock
  6. nice = 0
  7. [mysqld]
  8. server-id = 1
  9. user = mysql
  10. pid-file = /var/run/mysqld/mysqld.pid
  11. socket = /var/run/mysqld/mysqld.sock
  12. port = 3306
  13. basedir = /usr
  14. datadir = /var/lib/mysql
  15. tmpdir = /tmp
  16. lc-messages-dir = /usr/share/mysql
  17. skip-external-locking
  18. bind-address = 0.0.0.0
  19. key_buffer = 16M
  20. max_allowed_packet = 16M
  21. thread_stack = 192K
  22. thread_cache_size = 8
  23. myisam-recover = BACKUP
  24. query_cache_limit = 1M
  25. query_cache_size = 16M
  26. log_error = /var/log/mysql/error.log
  27. expire_logs_days = 10
  28. max_binlog_size = 100M
  29. [mysqldump]
  30. quick
  31. quote-names
  32. max_allowed_packet = 16M
  33. [mysql]
  34. [isamchk]
  35. key_buffer = 16M
  36. !includedir /etc/mysql/conf.d/

final-playbook.yml

 
  1. - hosts: all
  2. become_user: root
  3. become: true
  4. tasks:
  5. - name: 'Install common software on all servers'
  6. apt: name={{item}} state=present
  7. with_items:
  8. - git
  9. - mysql-client
  10. - libmysqlclient-dev
  11. - build-essential
  12. - python-software-properties
  13. - name: 'Install hosts file'
  14. template: src=hosts.j2 dest=/etc/hosts mode=644
  15. - hosts: db
  16. become_user: root
  17. become: true
  18. tasks:
  19. - name: 'Software for DB server'
  20. apt: name={{item}} state=present
  21. with_items:
  22. - mysql-server
  23. - percona-xtrabackup
  24. - mytop
  25. - mysql-utilities
  26. - name: 'MySQL config file'
  27. template: src=my.cnf.j2 dest=/etc/mysql/my.cnf
  28. - name: 'Restart MySQL'
  29. service: name=mysql state=restarted
  30. - name: 'Grant access to web app servers'
  31. shell: echo 'GRANT ALL PRIVILEGES ON *.* TO "root"@"%" WITH GRANT OPTION;FLUSH PRIVILEGES;'|mysql -u root mysql
  32. - hosts: webs
  33. vars:
  34. - appdir: /opt/dummyapp
  35. become_user: root
  36. become: true
  37. tasks:
  38. - name: 'Add ruby-ng repo'
  39. apt_repository: repo='ppa:brightbox/ruby-ng'
  40. - name: 'Install rails software'
  41. apt: name={{item}} state=present
  42. with_items:
  43. - ruby-dev
  44. - ruby-all-dev
  45. - ruby2.2
  46. - ruby2.2-dev
  47. - ruby-switch
  48. - libcurl4-openssl-dev
  49. - libssl-dev
  50. - zlib1g-dev
  51. - nodejs
  52. - name: 'Set ruby to 2.2'
  53. shell: ruby-switch --set ruby2.2
  54. - name: 'Install gems'
  55. shell: gem install bundler rails
  56. - name: 'Kill puma if running'
  57. shell: file /run/puma.pid >/dev/null && kill `cat /run/puma.pid` 2>/dev/null
  58. ignore_errors: True
  59. - name: 'Clone app repo'
  60. git:
  61. repo=https://github.com/c0d5x/rails_dummyapp.git
  62. dest={{appdir}}
  63. version=staging
  64. force=yes
  65. - name: 'Run bundler'
  66. shell: cd {{appdir}};bundler
  67. - name: 'Run db:setup'
  68. shell: cd {{appdir}};rails db:setup
  69. run_once: true
  70. - name: 'Run db:migrate'
  71. shell: cd {{appdir}};rails db:migrate
  72. run_once: true
  73. - name: 'Run rails server'
  74. shell: cd {{appdir}};rails server -b 0.0.0.0 -p 80 --pid /run/puma.pid -d

放在你的环境中

将这些文件放在相同的目录,运行下面的命令打开你的开发环境:

 
  1. vagrant up
  2. ansible-playbook -i inventory final-playbook.yml

部署新的代码

确保修改了代码并推送到了仓库中。接下来,确保你 git 语句中使用了正确的分支:

 
  1. - name: 'Clone app repo'
  2. git:
  3. repo=https://github.com/c0d5x/rails_dummyapp.git
  4. dest={{appdir}}
  5. version=staging
  6. force=yes

作为一个例子,你可以修改 version 字段为 master,再次运行剧本:

 
  1. ansible-playbook -i inventory final-playbook.yml

检查所有的 web 服务器上的页面是否已更改:http://10.1.1.11 或 http://10.1.1.12。将其更改为version = staging 并重新运行剧本并再次检查页面。

你还可以创建只包含与部署相关的任务的替代剧本,以便其运行更快。

接下来是什么 ?!

这只是可以做的很小一部分。我们没有接触角色role、过滤器filter、调试等许多其他很棒的功能,但我希望它给了你一个良好的开始!所以,请继续学习并使用它。

原文发布时间为:2017-01-12

本文来自云栖社区合作伙伴“Linux中国”


相关文章
|
6月前
|
运维 关系型数据库 Shell
运维自动化之 ansible
运维自动化之 ansible
|
6月前
|
消息中间件 缓存 关系型数据库
云计算|OpenStack|社区版OpenStack安装部署文档(二---OpenStack运行环境搭建)
云计算|OpenStack|社区版OpenStack安装部署文档(二---OpenStack运行环境搭建)
419 0
|
Kubernetes 负载均衡 Docker
ansible学习之旅(ansible依托kubeadm安装一个简单的k8s集群)
ansible学习之旅(ansible依托kubeadm安装一个简单的k8s集群)
159 0
|
运维 Devops jenkins
自动化工具后起之秀Ansible的部署实践
本文讲的是自动化工具后起之秀Ansible的部署实践,从早期手动加脚本的部署方式,到后来自动化工具(chef, puppet, saltstack, ansible等)的出现,再到如今DevOps的盛行,企业应用部署正式进入平台部署阶段,CD(持续部署)已经成为企业对应用部署的标准需求,运维的交付也不再是以周或天为单位,而是以分钟为单位。
4216 0
|
应用服务中间件 Shell nginx
|
运维 Shell 应用服务中间件
|
Web App开发 Shell 应用服务中间件
|
Web App开发 网络协议 网络安全