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

对于大部分开发者而言,第一次部署 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 台主机来适应更大的用户访问量,并且我们的项目进化成了由多个微服务、数据库、消息队列、任务队列、负载均衡组成的庞然大物……更可怕的是,我们追求敏捷开发和快速迭代的产品经理要求每天都能部署……

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

  • 过程机械而重复,十分枯燥乏味,而且很可能容易出错
  • 大规模部署时效率非常低,无法适应持续部署/交付(CI/CD)的需求

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

Ansible 介绍和安装

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

  • 无需在部署主机上安装任何代理(Chef 和 Puppet 都需要安装)
  • 无需学习特定的语言(Chef 使用的是 Ruby,Fabric 则是纯 Python 代码)
  • 使用 YAML 记录执行流程,声明式语法,可读性好
  • 基于模块,具有高度的复用性
  • 能够和当前主流云计算平台、容器工具完美结合

首先安装 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
  • inventory 是主机清单,里面是我们需要部署的主机,这里我们指定为 hosts 文件(接下来会创建)
  • remote_user 是远程登录的用户名,Vagrant 为我们提供了一个叫 vagrant 的用户用于登录
  • private_key_file 指定了用于 SSH 登录的私钥文件,就相当于访问远程主机的一把钥匙(这里就不详细讲述 SSH 身份验证了,有兴趣可以自己了解一下)
  • host_key_checking 是用于检查所登录主机的 key 是否与 .ssh/known_hosts 里面对应主机的 key 相同,在这里我们把这个功能关闭掉(设置为 False),实际生产时不要关闭这个功能。

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

192.168.33.10

一切就绪!

使用 Ansible 通常有两种方式:

  • Ad-Hoc 模式,又称为临时命令模式,输入一条命令执行一个操作。通常用于执行一些非常简单的操作,例如检查是否能与主机连接、获取主机正常运行时间(uptime)等等。
  • Playbook 模式,执行一个 YAML 文件里面的全部指令,类似于执行脚本。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 是一种能够同时控制一大群宇宙飞船的设备。

相关学习资源

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容