基于 Ansible 的分布式爬虫自动化部署

使用 NodeJS + Redis + Ansible 实现分布式爬虫并且自动化批量部署上线。


本文章基于以下CC协议进行知识共享:署名(BY)-非商业性使用(NC)-禁止演绎(ND)


分布式爬虫

之前在爬取一个网站的数据时,为了保证可以稳定的访问站点,不被网站屏蔽。尝试了很多方法,除了伪造请求头的UA、Cookie,还要对请求频率进行限制,或者通过代理服务器访问来改变请求来源。但是限制频率和请求代理会导致爬虫的效率变得非常低。因此,决定改成分布式爬虫,由多台服务器同时进行爬取。

该爬虫主要使用了 NodeJS 开发,并使用了 Cheerioioredis 两个框架。使用 Redis 同步各个爬虫的工作进度,并存储主要数据。

使用分布式爬虫,就需要为每个服务器安装环境,初始化配置等等。2、3个服务器还好说,但是如果有100台服务器,我们肯定不能手动的为每台服务器都配置一遍,1是人工容易出错,2是效率太低。

Ansible 自动化运维工具

基于Python开发,集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。ansible是基于模块工作的,本身没有批量部署的能力。真正具有批量部署的是ansible所运行的模块,ansible只是提供一种框架。

除了 Ansible 以外,还有一款运维工具也是做服务器批量管理的 —— SaltStack。但是它的文档不够友好,并且由于通信机制的原因,需要在所有服务器上初始化一次 Salt Stack Minion,故没有选择此方案。(虽然 SaltStack 在后来也加入了 ssh 连接的支持)

安装 Ansible

在官方文档中有很多安装方法,但是在这里我选择了使用 pip 进行安装(懒,图省事)。

1
pip install ansible

注意:使用 Ansible 时,进行管理的宿主机,必须是 Linux/MacOS/Unix 系统。

服务器与分组

在很多教程中,对 Ansible 的服务器配置都是要修改 /etc/ansible/hosts 文件来实现的,但是通过 pip 安装是不会产生这个文件的。

当然我们可以手动去创建,但是尝试在终端中输入 ansible --help 就可以在帮助手册中看到能通过 -i 参数来指定 inventory 文件。

inventory 文件的格式简单的说就是用 [] 表示分组,用于批量管理,然后在分组下面写上服务器IP就可以了。IP后面写连接配置,例如一个服务器的登录用户名是root,密码是123456的配置写法是:

1
2
[master] # 分组名称
127.0.0.1 ansible_ssh_user=root ansible_ssh_pass=123456

注意:这种在配置中记录密码的方式并不安全,所以不推荐。最好是使用SSH密钥。

这个时候,我们就可以执行一些简单的命令,来测试配置是否正确了。在终端中输入 ansible master -i ./inventory -m ping 当你得到如下图所示的输出时,就算配置正确了。

使用 ping 命令测试与服务器的连接

最后,我们来分析一下真实的情况来写一个实际可用的配置。在这套爬虫系统中,除了每个服务器运行1个爬虫以外,还要有个服务器运行 redis 服务作为信息中枢。所以:所有服务器环境配置相同,单独拿出其中一个服务器再安装一个 redis 服务就可以了。同时,服务器连接使用 SSH 密钥的方式,最终配置如下。

1
2
3
4
5
6
7
8
9
10
[master]
0.0.0.0 ansible_ssh_private_key_file=./private.pem

[default]
0.0.0.0 ansible_ssh_private_key_file=./private.pem
0.0.0.0 ansible_ssh_private_key_file=./private.pem
0.0.0.0 ansible_ssh_private_key_file=./private.pem
0.0.0.0 ansible_ssh_private_key_file=./private.pem
......
# 配置中省略了服务器真实IP和数量

编写 playbook.yml

playbook.yml 是 Ansible 的主要配置文件,通过它我们可以安装环境、执行脚本、编排任务等等。我们先以一个最简单的例子上手 —— 安装 Python 运行环境(假定你的服务器是 CentOS 系统,使用 yum 软件包管理器)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
- hosts: default
# 服务器组
remote_user: root
# 登录服务器用的用户名
sudo: yes
# 是否使用 sudo 执行命令
tasks:
# 任务列表
- name: 安装 python 环境
# 任务名称
yum:
# 任务模块
name: python
# yum 模块中的 name 参数(安装的软件包)
state: installed
# yum 模块中的 state 参数(要达到的状态)

然后我们在终端中输入 ansible-playbook -i ./inventory playbook.yml -f 10(-f 的数值是启动的进程数),就可以完美的跑起来这个脚本了。最后我们手动连入服务器,运行 python --version 来检查是否安装成功。(PS:在部分系统镜像中,python可能已经内置了)

在之前的简介中我们也说过 Ansible是基于模块工作的,本身没有批量部署的能力。真正具有批量部署的是ansible所运行的模块,ansible只是提供一种框架。 所以,如果我们想通过 yum 安装软件包,那么就用 yum 模块;如果我们想管理 docker 容器,那么就用 docker_container 模块。当然,服务软件那么多,Ansible 的模块肯定不能做到完全覆盖。因此,我们还可以通过 command 模块写自定义命令来实现上述功能。

1
2
3
4
5
6
7
---
- hosts: default
remote_user: root
sudo: yes
tasks:
- name: 安装 python 环境
command: yum install -y python

那么这两种模式有什么区别?实际上我们观察 yum 模块的 state 的参数就会发现,他的值写的是 install 的过去式 installed 。这就表示 Ansible 追求的是任务达到了某个状态,而不是去做某个动作。这样做实际上是为了实现幂等性,也就是不管我们重复多少次操作,该操作的最终结果应该是一致的。

举个例子,假设 yum 包管理器不会为我们自动跳过已经安装的软件包,那么我们每执行一次这个配置,都会重复安装一遍 python,最后就会导致硬盘容量越来越小,浪费了很多空间。所以,在多数情况下,我们应该尽可能的使用 Ansible 的模块来保证幂等性

最后,根据我的项目的实际情况,写出如下的 playbook 配置:

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
---
- hosts: default
remote_user: root
sudo: yes
tasks:
- name: 安装 python 环境
yum:
name: python
state: installed
- name: 安装 git 版本管理工具
yum:
name: git
state: installed
- name: 安装 Docker 环境(yum-utils)
yum:
name: yum-utils
state: installed
- name: 安装 Docker 环境(device-mapper-persistent-data)
yum:
name: device-mapper-persistent-data
state: installed
- name: 安装 Docker 环境(lvm2)
yum:
name: lvm2
state: installed
- name: 安装 Docker 环境(add repo)
yum_repository:
name: docker-ce
description: Docker CE Stable
baseurl: https://download.docker.com/linux/centos/7/$basearch/stable
gpgkey: https://download.docker.com/linux/centos/gpg
gpgcheck: yes
- name: 安装 Docker 环境(docker-ce)
yum:
name: docker-ce
state: installed
enablerepo: docker-ce
- name: 启动 Docker
systemd:
name: docker
state: started
enabled:
- name: 安装 docker-py
pip:
name: docker-py
- name: 下拉 nodejs 镜像
docker_image:
name: "node:9.11-alpine"


- hosts: master
remote_user: root
tasks:
- name: 下拉 redis 镜像
docker_image:
name: "redis:3.2-alpine"
- name: 启动 redis
docker_container:
name: redis
image: "redis:3.2-alpine"
restart_policy: always
ports:
- "6379:6379"
volumes:
- /app/data/redis:/data
env:
appendonly: yes
state: started

# 配置中省略了 NodeJS 容器的安装、运行等操作,因为大体的配置基本一样。更多操作直接参考官方文档即可。

最后,我们在终端中输入 ansible-playbook -i ./inventory playbook.yml -f 10,就可以自动化的初始化系统环境,并运行相应服务了。

运行效果
服务器列表

分享到