Ansible

一行命令操纵“千军万马”:Ansible 自动化部署入门

2018-05-03  本文已影响192人  图雀社区

对于大部分开发者而言,第一次部署 Web 应用几乎都是痛苦的回忆。

当你在本地的开发服务器上欣赏完刚刚写完的项目时,你才完成了万里长征的第一步。首先你需要购买一台价格和性能合适的云主机,接着 SSH 远程登录到你的远程主机配置项目环境(例如 LAMP),然后把项目代码拷贝到远程主机,最后运行你的项目。

让我们来动手实践吧。本文所有的代码都可以在我的 GitHub 仓库 里面找到。

由于购买云主机有一定的经济成本,所以这里我使用 DevOps 界非常流行的 Vagrant 工具(点击这里下载)。Vagrant 是一个简单易用的虚拟机管理工具(在本文中,有时“虚拟机”会被称为“主机”),能够通过命令行轻松地配置、开启、关闭、登录虚拟机。这里我们用 Vagrant 搭建一个内网中的主机,并将一个静态网页(HTML文件)部署到这台机器上。

配置 Vagrant

首先,确保你的 Vagrant 已经安装完毕:

$ vagrant --version

如果下面显示了版本信息,则说明安装成功啦。

然后我们新建一个目录,所有的活动将在这个目录里面进行。

$ mkdir deploy-my-first-project
$ cd deploy-my-first-project

然后我们创建 Ubuntu 虚拟机:

$ vagrant init ubuntu/trusty64
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

然后发现当前目录里面多了个 Vagrantfile 文件!这个文件用于配置虚拟机,我们要想通过内网(Private Network)访问这台主机,就需要进行如下的配置(用下面的代码替换掉原来 Vagrantfile 里面的内容):

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.network "private_network", ip: "192.168.33.10"
end

配置改好了,接下来启动虚拟机:

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/trusty64'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'ubuntu/trusty64' is up to date...
...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
    default: The guest additions on this VM do not match the installed version of
    default: VirtualBox! In most cases this is fine, but in rare cases it can
    default: prevent things such as shared folders from working properly. If you see
    default: shared folder errors, please make sure the guest additions within the
    default: virtual machine match the version of VirtualBox you have installed on
    default: your host and reload your VM.
    default:
    default: Guest Additions Version: 4.3.36
    default: VirtualBox Version: 5.1
==> default: Configuring and enabling network interfaces...
==> default: Mounting shared folders...
    default: /vagrant => /Users/mRc/Desktop/deploy-my-first-project

初次启动可能会比较久,因为需要下载 Ubuntu 镜像。下载过程中,你可以选择读接下来的内容,也可以喝杯咖啡放松一下。启动成功后,我们就可以 SSH 登录我们的虚拟机啦:

$ vagrant ssh
Welcome to Ubuntu 14.04.5 LTS (GNU/Linux 3.13.0-145-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

  System information as of Thu May  3 02:12:01 UTC 2018

  System load:  0.91              Processes:           82
  Usage of /:   3.6% of 39.34GB   Users logged in:     0
  Memory usage: 29%               IP address for eth0: 10.0.2.15
  Swap usage:   0%

  Graph this data and manage this system at:
    https://landscape.canonical.com/

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

0 packages can be updated.
0 updates are security updates.

New release '16.04.4 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

vagrant@vagrant-ubuntu-trusty-64:~$

对于没有登录过远程主机的同学来说可能会有点惊讶。没错,我们已经进入了另一个系统,命令行提示符已经发生了变化。在这里你可以输入各种命令进行探索(Vagrant 虚拟机确实也很适合学习 Linux 操作系统),但是不要皮到 rm -rf /* (从删库到跑路的惨痛经历历历在目)。

(在全新的世界里面畅游了许久……)

喔!别忘了我们登进这个系统的是为了部署我们的网站!赶紧回到正题。

“手动”部署网站

Nginx 是一款极其流行的高性能 HTTP 和反向代理服务器,这里我们就用它来架设我们的网站。首先需要在主机中安装 Nginx(Vagrant 虚拟机 root 密码默认是 vagrant):

vagrant@vagrant-ubuntu-trusty-64:~$ sudo apt-get update
vagrant@vagrant-ubuntu-trusty-64:~$ sudo apt-get install -y nginx

装好之后,我们陷入了沉思:怎么把我们的项目代码从自己的电脑搬到需要部署的主机上?需要注意的是,Vagrant 提供了本地文件系统和虚拟机文件系统的“同步绑定”(行话叫 mount,详情请查看 Vagrant 文档中关于 Syncing Folders 的描述),但是实际部署到主机时通常采用如下四种方法:

  1. 直接使用 vim 等工具将项目代码拷贝进远程主机
  2. 使用 GitHub、GitLab 或 Bitbucket 等代码托管服务
  3. 使用 Chef、Puppet、Ansible 等自动化部署工具
  4. 使用容器技术(Docker, Kubernetes等)

很显然第一种方法非常愚蠢,但是我们这里偏偏用第一种方法,因为我们这次只需要部署一个静态 HTML 文件。以后我会写使用容器技术部署的教程,敬请期待。

下面是我们的网页源码(index.html),用它替换掉 /usr/share/nginx/html/index.html 里面的文件(记得要 sudo 喔):

<html>
  <head>
    <title>My Project</title>
  </head>
  <body>
  <h1>I Made It!</h1>
  <p>I Have Successfully Deployed My First Project!</p>
  </body>
</html>

然后我们重启 Nginx 服务:

vagrant@vagrant-ubuntu-trusty-64:~$ sudo service nginx restart
 * Restarting nginx nginx                                                [ OK ]

按 Ctrl + D 或输入 logout,退出虚拟机。

vagrant@vagrant-ubuntu-trusty-64:~$ logout
Connection to 127.0.0.1 closed.

打开我们的浏览器,输入设定的内网 IP:http://192.168.33.10/。

成功部署了我们的网页

哇塞,我们帅气的网页部署成功了!

以为这样就完了?并没有。时代在变化,你的项目代码也需要不断的修改,每次修改后如果想要让用户看到改变,都需要 SSH 登录到远程主机重复上述的部署流程。

如果你觉得这点劳动量还是不算什么,那么请设想一下业务扩展需要,需要 n 台主机来适应更大的用户访问量,并且我们的项目进化成了由多个微服务、数据库、消息队列、任务队列、负载均衡组成的庞然大物……更可怕的是,我们追求敏捷开发和快速迭代的产品经理要求每天都能部署……

总结一下,用传统的“手工”部署的方法存在以下问题:

科技界有个很流行的说法:偷懒是进步的动力。为了能够从繁琐的手动部署中解放出来,我们需要更先进的技术——没错,就是本文的主角 Ansible。

Ansible 介绍和安装

Ansible 是一款革命性的 IT 自动化工具,拥有平滑的学习曲线却又不失强大而又灵活的功能。和竞争对手 Chef、Puppet 或 Fabric 相比,拥有如下优势:

首先安装 Ansible,可以查看官方的安装说明。通常我们使用 Python 的包管理工具 pip 进行安装:

$ sudo pip install ansible

检验我们是否安装成功了:

$ ansible --version

如果有版本提示信息,那么就安装成功了!

光说不练假把式,我们来感受 Ansible 的力量吧!

用 Ansible 部署单台主机

首先,我们要从零开始部署,把之前的虚拟机删掉:

$ vagrant destroy --force
==> default: Forcing shutdown of VM...
==> default: Destroying VM and associated drives...

重新访问 http://192.168.33.10/,我们发现之前部署成功的网站已经不能访问了。然后重新创建虚拟机:

$ vagrant up

由于我们之前已经下载过 Ubuntu 镜像,所以这次会快很多。

接下来就是进行 Ansible 的配置。有两种配置方式:第一种是全局配置,在 /etc/ansible 里面编辑配置文件;第二种是局部配置,在当前项目里面编辑配置文件。如果检测到了局部配置文件,那么就会忽略全局配置。通常建议使用局部配置,并将配置文件和 Playbook (可以理解为 Ansible 脚本)加入源代码管理系统中。

在当前 deploy-my-first-project 目录中,生成以下文件:

$ tree
.
├── Vagrantfile
├── ansible.cfg
├── deploy.yml
├── hosts
└── index.html

Vagrantfile 之前在手动部署时已经存在了。ansible.cfg 是 Ansible 的配置文件,内容如下:

[defaults]
inventory = hosts
remote_user = vagrant
private_key_file = .vagrant/machines/default/virtualbox/private_key
host_key_checking = False

然后是 hosts 文件的内容,只有一条 IP 记录,对应我们需要部署的主机:

192.168.33.10

一切就绪!

使用 Ansible 通常有两种方式:

我们用一条 Ad-Hoc 指令检查是否能 SSH 登录到我们的虚拟机(192.168.33.10)。

$ ansible all -m ping
192.168.33.10 | SUCCESS => {
    "changed": false,
    "failed": false,
    "ping": "pong"
}

成功了!

我们来理解一下 ansible all -m ping 这条命令。all 是指 inventory 中的所有主机,-m 是用于指定执行模块(module),这里选择 ping 模块。

需要注意的是,Ansible 执行所有指令都是基于模块的,然后在实际执行时将我们所选择的模块生成并执行对应的 Python 脚本,因此一次部署流程就对应一系列模块的执行。Ansible 的强大正是由小巧的内核和 1600 多个模块构成,几乎你能想到的操作流程都要对应的模块。例如, git 模块能够管理 Git 仓库,yum 模块能够操作 yum 包管理工具,docker 模块能够轻松管理 Docker 镜像和容器。

如果要查看某个模块怎么使用,只需要使用 ansible-doc 工具:

$ ansible-doc git

接下来我们就来写 Playbook 来指定我们的部署流程。下面是 deploy.yml 的内容:

---
- name: Deploy Static Site with Nginx
  hosts: all
  become: True
  tasks:
    - name: Install Nginx
      apt: name=nginx update_cache=yes
    - name: Copy index.html
      copy: src=index.html dest=/usr/share/nginx/html/index.html mode=0644
    - name: Restart Nginx
      service: name=nginx state=restarted

Playbook 是熟练使用 Ansible 最重要的知识点。首先,你需要理解 YAML 这种格式的语法。如果不熟悉,建议读一下阮一峰的 YAML 语言教程。如果你对 JSON 比较熟悉的话,上面的 Playbook 等价于下面的 JSON:

[
  {
    "name": "Deploy Static Site with Nginx",
    "hosts": "all",
    "become": true,
    "tasks": [
      {
        "name": "Install Nginx",
        "apt": "name=nginx update_cache=yes"
      }, {
        "name": "Copy index.html",
        "copy": "src=index.html dest=/usr/share/nginx/html/index.html mode=0644"
      }, {
        "name": "Restart Nginx",
        "service": "name=nginx state=restarted"
      }
    ]
  }
]

我们来看一下 Playbook 的构成。

Playbook 的构成

一个 Playbook 由多个 Play 组成,每个 Play 由多个 Task 组成。其中 Play 必须要有 hosts(目标主机)和 tasks(至少一个 Task)组成,每个 Task 必须要指定执行模块及其参数。我们还注意到每个 Play 和 Task 都有一个 name 属性,这个属性并不是必须的,但它能极大地增加 Playbook 的可读性,并且它的功能不只是“注释”——有些功能支持以 name 为单位进行操作,例如 ansible-playbook 支持一个参数叫 —start-at-task,能够选择从哪个 Task 开始执行。

回到我们的 deploy.yml,里面只有一个名为 Deploy Static Site with Nginx 的 Play,指定执行主机为 allbecome 属性是可选的,用于执行 root 权限,然后 tasks 中包含三个任务: Install NginxCopy index.htmlRestart Nginx,分别调用了 aptcopyservice 模块。

执行我们的 Playbook:

$ ansible-playbook deploy.yml

PLAY [Deploy Static Site with Nginx] *****************************************

TASK [Gathering Facts] *******************************************************
ok: [192.168.33.10]

TASK [Install Nginx] *********************************************************
changed: [192.168.33.10]

TASK [Copy index.html] *******************************************************
changed: [192.168.33.10]

TASK [Restart Nginx] *********************************************************
changed: [192.168.33.10]

PLAY RECAP *******************************************************************
192.168.33.10              : ok=4    changed=3    unreachable=0    failed=0

我们在浏览器输入 192.168.33.10 试一下,果真部署成功了。等一下!我还想在网站里面加点东西。下面是新的网站代码(index.html),加了一行 I Added Something Interesting.

<html>
  <head>
    <title>My Project</title>
  </head>
  <body>
  <h1>I Made It!</h1>
  <p>I Have Successfully Deployed My First Project!</p>
  <p>I Added Something Interesting.</p>
  </body>
</html>

修改完成之后,我们重新部署:

$ ansible-playbook deploy.yml
...
PLAY RECAP *******************************************************************
192.168.33.10              : ok=4    changed=2    unreachable=0    failed=0

打开浏览器访问 http://192.168.33.10/,看到了我们刚才的改动:

我们的改动部署成功了

没错!每次修改之后只需一行命令即可将改动立刻部署到远程主机!

用 Ansible 部署多台主机

现在我们的网站一炮走红,全国各地的网友争相访问!于是领导决定在北京、上海、广州三地分别架设主机,我们现在要将网站同时部署到三个主机上。

我们还是用 Vagrant 来模拟多台主机。先删掉之前的主机:

$ vagrant destroy --force
==> default: Forcing shutdown of VM...
==> default: Destroying VM and associated drives...

然后将 Vagrantfile 改为如下代码:

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  # Use the same key for each machine
  config.ssh.insert_key = false

  config.vm.define "beijing" do |beijing|
    beijing.vm.box = "ubuntu/trusty64"
    beijing.vm.network "private_network", ip: "192.168.33.10"
  end
  config.vm.define "shanghai" do |shanghai|
    shanghai.vm.box = "ubuntu/trusty64"
    shanghai.vm.network "private_network", ip: "192.168.33.11"
  end
  config.vm.define "guangzhou" do |guangzhou|
    guangzhou.vm.box = "ubuntu/trusty64"
    guangzhou.vm.network "private_network", ip: "192.168.33.12"
  end
end

然后启动我们的“集群”!

$ vagrant up

来看一下当前我们的“集群”是否真的在运行:

$ vagrant status
Current machine states:

beijing                   running (virtualbox)
shanghai                  running (virtualbox)
guangzhou                 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`.

很好!北京节点、上海节点和广州节点都运行正常。

接下来开始部署到这三台主机。听起来主机数量是之前的三倍,是不是工作量也应该是三倍?完全不是!实际上只需要修改两个文件即可:ansible.cfg 和 hosts,而且都只需要改一两行代码哦!

先修改 ansible.cfg:

[defaults]
inventory = hosts
remote_user = vagrant
private_key_file = ~/.vagrant.d/insecure_private_key
host_key_checking = False

这里只修改了 private_key_file,由于三个主机都采用了同一个私钥(在 Vagrantfile 中配置的)。

然后在主机清单 hosts 里面加上三台主机:

192.168.33.10
192.168.33.11
192.168.33.12

接着还是执行熟悉的 Playbook:

$ ansible-playbook deploy.yml

PLAY [Deploy Static Site with Nginx] *****************************************

TASK [Gathering Facts] *******************************************************
ok: [192.168.33.11]
ok: [192.168.33.12]
ok: [192.168.33.10]

TASK [Install Nginx] *********************************************************
changed: [192.168.33.10]
changed: [192.168.33.12]
changed: [192.168.33.11]

TASK [Copy index.html] *******************************************************
changed: [192.168.33.12]
changed: [192.168.33.10]
changed: [192.168.33.11]

TASK [Restart Nginx] *********************************************************
changed: [192.168.33.11]
changed: [192.168.33.12]
changed: [192.168.33.10]

PLAY RECAP *******************************************************************
192.168.33.10              : ok=4    changed=3    unreachable=0    failed=0
192.168.33.11              : ok=4    changed=3    unreachable=0    failed=0
192.168.33.12              : ok=4    changed=3    unreachable=0    failed=0

最后打开浏览器分别访问三台主机,确认我们已经部署成功了。如果以后还想修改代码,还是一行命令执行 Playbook 即可。

总结

正如标题所言,一行命令真的可以操纵“千军万马”——只需执行特定的 Playbook,就可以让千万台主机自动化运行你想要的任务,而不再需要 SSH 远程登录手动执行繁琐的配置操作。有趣的是,Ansible 这个名字也是取自一本名为 Ender's Game 的科幻小说,在书中 Ansible 是一种能够同时控制一大群宇宙飞船的设备。

相关学习资源

上一篇 下一篇

猜你喜欢

热点阅读