Month: 9月 2018

kubernetes 初探——部署集群

随着 docker cloud 的关闭,容器的编排工具之争似乎已经结束了,Docker Swarm 算是完了,Kubernetes 笑到了最后。然而 k8s 的组件众多,因此部署起来也很麻烦。为此,网上有不少的网上有不少的部署教程和脚本,包括但不限于:

  • kubesaz
  • minikube
  • kubespray
  • rke
  • kubernetes: the hard way

本文基于 ubuntu 18.04. CentOS 上好多默认设置都需要修改,因此建议大家基于 Ubuntu 部署 k8s。

使用 kubespray 安装 k8s

(零) 假设我们要在三台机器上安装,另外一台机器作为控制节点。其中每台都作为工作节点,两台作为 master 节点。

| IP | 角色 |
| ———– | ————————- |
| 10.4.17.165 | 控制节点,不参与 k8s 集群 |
| 10.4.17.167 | master, node |
| 10.4.17.168 | master, node |
| 10.4.17.169 | node |

(一) 下载 kubespray 安装包,这里我们使用最新版 (2018.10), 可能需要安装 python3, 如果没有安装的话

VERSION=2.7.0

# download kubespray
wget https://github.com/kubernetes-incubator/kubespray/archive/v${VERSION}.tar.gz
tar xzf v${VERSION}.tar.gz

# install dependencies
pip3 install -r kubespray-${VERSION}/requirements.txt

(二) 生成部署的 hosts.ini

kubespray 中有一个脚本叫做 inventory_builder 用来生成部署的 hosts.ini

cd kubespray-v${VERSION}
cp -r inventory/sample inventory/mycluster
declare -a IPS=(10.4.17.167 10.4.17.168 10.4.17.169)
CONFIG_FILE=inventory/mycluster/hosts.ini python3 contrib/inventory_builder/inventory.py ${IPS[@]}

生成之后,可以查看生成的文件

cat inventory/mycluster/host.ini

[k8s-cluster:children]
kube-master·▸   ·
kube-node·▸ ·

[all]
node1 ▸  ansible_host=10.4.17.167 ip=10.4.17.167
node2 ▸  ansible_host=10.4.17.168 ip=10.4.17.168
node3 ▸  ansible_host=10.4.17.169 ip=10.4.17.169

[kube-master]
node1·▸ ·
node2·▸ ·

[kube-node]
node1·▸ ·
node2·▸ ·
node3·▸ ·

[etcd]
node1·▸ ·
node2·▸ ·
node3·▸ ·

[calico-rr]

[vault]
node1·▸ ·
node2·▸ ·
node3·▸ ·

(三) 修改一些配置

代理:

由于众所周知的原因,k8s 依赖的 gcr.io 在中国大陆范围内无法访问,我们可以使用代理访问,关于如何搭建代理超出了本文的范围。
假设我们的代理是 http://proxy.com:10086, 修改 inventory/mycluster/group_vars/all/all.yml 文件,设置 httpproxy 和 httpsproxy 两个变量。

下载 kubectl 到本机:

设置 kubectllocalhost 和 kubeconfiglocalhost 两个变量为 true. 安装完成后会在本机安装 kubectl, 并且可以使用 inventory/mycluster/artifacts/admin.conf 使用 kubectl.

(四) 部署

这里我们需要使用 tiger 用户,这个用户需要满足如下条件:

  1. 可以无密码远程登录
  2. 在远程主机上具有无密码 sudo 权限
ansible-playbook -i inventory/mycluster/hosts.ini cluster.yml --become --user tiger -v

大概过十几分钟就部署好了

(五) 验证

cd inventory/mycluster/artifacts
./kubectl.sh get nodes

NAME      STATUS    ROLES         AGE       VERSION
node1     Ready     master,node   1d        v1.11.3
node2     Ready     master,node   1d        v1.11.3
node3     Ready     node          1d        v1.11.3

参考

  1. HN 上关于 Docker Cloud 关闭的讨论
  2. 使用 Kubespray 部署生产可用的 Kubernetes 集群
  3. https://1byte.io/developer-guide-to-docker-and-kubernetes/

kubernetes 初探——日志收集

在 K8S 中,默认情况下,日志分散在每个不同的 Pod, 可以使用 kubectl logs 命令查看,但是当 Pod 被删除之后,就无法查看该 Pod 的日志了,所以需要一种永久性的保存方式。

Grafana 公司推出的 Loki 看起来不错,完全为 Kubernetes 设计,直接和 grafana 集成。

requests cookies 为空的一个坑

有时候,requests 返回的 cookies 会为空,原因是链接发生了 301/302 跳转,而 cookies 是跟着第一个响应返回的,第二个响应没有返回 Set-Cookie header。所以直接读取 r.cookies 是空的,而在 session.cookies 中是有数据的。

解决方法是直接读 s.cookies。

s = requests.Session()
r = s.get("http://httpbin.org/cookies/set?foo=bar")
cookies = requests.utils.dict_from_cookiejar(s.cookies)
s.cookies.clear()

不过需要注意的是如果在多线程环境中使用 session 需要注意锁的问题,建议把 session 设置成 thread local 的类型。

influxdb+grafana+telegraf 监控系统搭建

本文基于 ubuntu 18.04

要不要用 docker?

这是一个哲学问题,用不用其实都有各自的好处。不过在这里我倾向于不用。因为 influxdb 和 grafana 都有好多的状态,而且不是都可以写到 mysql 中的,所以既然还得 mount 出来,何苦用 docker 呢?telegraf 需要采集系统的相关信息,更不适合放在 docker 里面。

  • InfluxDB,开源的时间序列数据库
  • Grafana,开源的数据可视化工具

搭建过程

influxdb && telegraf

因为这两个都是一家的产品,所以安装步骤都是一样的。按照 官网 给的安装步骤,其实很简单的。

curl -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add -
source /etc/lsb-release
echo "deb https://repos.influxdata.com/${DISTRIB_ID,,} ${DISTRIB_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/influxdb.list

sudo apt-get update && sudo apt-get install influxdb telegraf
sudo systemctl start influxdb telegraf

我们暂时就不配置了,直接使用默认配置。可以通过 systemctl status influxdb 来查看状态

grafana

同样参考 官网 的教程。

基本概念

Grafana 从数据源中读取数据,数据源一般是时间序列数据库。their data can be mixed in a single panel.

Grafana 使用 orginazations and users to manage permissions.

A dashboard consists of rows, rows consist of panels. for each panel, there is a query editor to edit which data the panel should show.

VERSION=5.1.4
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${VERSION}_amd64.deb
apt-get install -y adduser libfontconfig
dpkg -i grafana_${VERSION}_amd64.deb

sudo systemctl daemon-reload
sudo systemctl enable grafana-server
sudo systemctl start grafana-server

然后就 ok 啦,打开 http://ip:3000 就能访问 grafana 的界面了,默认用户名和密码是 admin。如果是在阿里云等云上面,注意要在安全组里面开一下 3000 端口。

配置

配置 telegraf 的插件

配置 grafana 的 datasource

未完待续

参考资料

  1. https://blog.csdn.net/w958660278/article/details/80484486
  2. https://juejin.im/post/5b4568c851882519790c72f3
  3. https://grafana.com/dashboards/928
  4. http://docs.grafana.org/guides/gettingstarted/
  5. http://docs.grafana.org/guides/basic_concepts/

监控系统基本概念与选型

时序数据库

监控系统的基础是时序数据库

现代的监控系统一般都有如下几部分组成:

时序数据库 + 前端显示 + 报警系统 + 指标收集

一般需要实现的功能:

  • 度量数据收集和可视化
  • 收集尽可能多的性能和状态数据
  • 图形化做有意义的展示
  • 如果发现可疑问题,可以关联其他图表找到原因
  • 错误检测
  • 按需告警,触发条件越宽松则告警应该越少
  • 避免误报

从监控的层次划分的话,一般包含三层监控:

  • 基础层:主机的 CPU, 内存,网络及 IO 等
  • 中间层:应用运行的中间件层,Nginx, Tomcat, MySQL, Redis
  • 应用层:服务端及客户端性能数据,如 API 访问次数和响应时间等

现代的监控越来越关注应用层和其他层数据的整合能力,具有快速找到系统瓶颈,方便扩容或代码优化。

时序数据库的选择

监控数据往往都带有时间戳,其实就是一种典型时间序列数据,而这方面已经有很多专门的存储系统,如 opentsdb,influxdb,prometheus 等。相比 mysql 这样的传统数据库,这类系统在存储、查询上针对时间序列数据都做了特别的优化。

其中 opentsdb 基于 hadoop 生态系统,考虑到搭建的复杂度,暂时不考虑了。influxdb 和 prometheus 都是 Golang 编写的,直接一个二进制文件就可以运行。两者的区别有:

  • prometheus 对于保存长时间的数据有一些问题,influxdb 似乎没有问题
  • 另外 influxdb 可以直接写入,而 prometheus 是基于拉 (pull) 模式的,也就是说程序不能直接写入 prometheus,而是需要由 prometheus 去定期拉监控数据,太反人类了。
  • influxdb 的查询类似 SQL,而 prometheus 的查询语法更加简洁,但是有学习成本,各有千秋吧

所以选用 influxdb 了。

前端显示

唯一的标准自然是越漂亮越好,所以我们选择 grafana。

当然另一需要考虑的是编写查询界面不要过于复杂,这方面 grafana 只需要拖拽空间和勾勾选选就可以了,显然不成问题。

报警系统

grafan 自带了一些报警,但是只能根据阈值报警,显然不能满足我们的需求。我们这里选择了 bosun,是 Stack Overflow 开源的一款监控系统。通过简单的几个语句就可以编写复杂的报警条件。

指标收集

按照前面的分析,对于应用层,也就是我们自己的代码,可以随意地添加代码打点,这里不再赘述。对于系统的 metrics 的收集,可以使用 influxdb 公司钦定的 telegraf。telegraf 也有一些不同的插件,可以很好地支持 mysql、redis 等的监控数据收集。

参考

  1. Logs and Metrics and Graphs, Oh my!
  2. Prometheus 和 influxdb 对比
  3. 360 基于 Prometheus 的在线服务监控实践
  4. 虎牙直播运维负责人张观石:基于时序数据库的直播业务监控实践
  5. 监控系统选型
  6. openTSDB/Bosun 报警语法 介绍 / 学习笔记
  7. https://zhuanlan.zhihu.com/p/28127748

为什么不使用 scrapy?

请参考更新版本: https://yifei.me/note/838

最近面了几家公司,每当我提到头条的爬虫都是自己写的时候,对方一个下意识的问题就是: “为什么不使用开源的 scrapy?”。实际上我在头条的 lead 就是 scrapy 的 contributor,而他自己也不用自己的框架,显然说明 scrapy 不适合大型项目,那么具体问题在哪儿呢?今天终于有时间了,详细写写这个问题。

爬虫并不需要一个框架

Web 服务器是一个爬虫可以抽象出来的是各种组件。而 scrapy 太简陋了,比如说去重,直接用的是内存中的一个集合。如果要依赖 scrapy 写一个大型的爬虫,几乎每个组件都要自己实现,那有何必用 scrapy 呢?

scrapy 不是完整的爬虫框架

一个完整的爬虫至少需要两部分,fetcher 和 frontier。其中 fetcher 用于下载网页,而 frontier 用于调度。scrapy 重点实现的是 fetcher 部分,也就是下载部分。

scrapy 依赖 twisted

这一点导致 scrapy 深入后曲线非常地陡峭,要想了解一些内部的机理,必须对 twisted 比较明了。而 twisted 正如它的名字一样,是非常扭曲的一些概念,虽然性能非常好,但是要理解起来是要花上不少时间的。

scrapy 适合的领域

scrapy 主要适合一次性地从指定的站点爬取一些数据

最重要的并不是你使用不使用 Scrapy,而是你不能为每一站点去单独写一个爬虫的脚本。代码的灵活度实在太大了,对于没有足够经验的工程师来说,写出来的脚本可能很难维护。重点是要把主循环掌握在爬虫平台的手中,而不是让每一个脚本都各行其是。

参考

  1. scrapy 源码解读

为什么不使用 scrapy,而是从头编写爬虫系统?

最近面了几家公司,每当我提到头条的爬虫都是自己写的时候,对方一个下意识的问题就是:“为什么不使用开源的 scrapy?”。实际上我在头条的 lead 就是 scrapy 的 contributor,而他自己也不用自己的框架,显然说明 scrapy 不适合大型项目。那么具体问题在哪儿呢?今天终于有时间了,详细写写这个问题。

个人不喜欢 scrapy 原因一言以蔽之:高不成,低不就,弊大于利。总的来说,需要使用代码来爬一些数据的大概分为两类人:

  1. 非程序员,需要爬一些数据来做毕业设计、市场调研等等,他们可能连 Python 都不是很熟;
  2. 程序员,需要设计大规模、分布式、高稳定性的爬虫系统,对他们来说,语言都无所谓的,更别说用不用框架了。

为什么不适合初学者?

对于初学者来说用不上 scrapy 的原因很简单:

  1. scrapy 太复杂了;
  2. scrapy 采用异步模式带来的高性能和在反爬面前实际上没有任何卵用;
  3. scrapy 项目冗余的代码结构对初学者完全是过度设计。

对于一个任何一个已经入门的程序员来说,Python 都算不上一个很复杂的语言,除了不用大括号可能让一些人感觉有些不适应之外,基本上看看语法上手就能写了。但是恰恰是因为我们都是老司机了,所以不能体会到每一行代码对于外行来说可能『比登天还难』。如果不用 scrapy,可能我只需要这样:

# 以下代码未经测试,可能有些许 bug
import requests

def main():
    for i in range(100):
        rsp = requests.get(f"http://www.example.com/{i}.html")
        with open("example-{i}.html", "w") as f:
            print(f"saving {i}")
            f.write(rsp.text)

if __name__ == "__main__":
    main()

就写好了一个简单的爬虫。而使用 scrapy 呢,大概需要这样吧:

# 以下代码未经测试,可能有些许 bug
import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        for i in range(100):
            yield scrapy.Request(url=f"http://www.example.com/{i}.html", callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        with open("example-%s.html" % page, "wb") as f:
            f.write(response.body)
        self.log("Save file %s" % page)

先不说代码行数增长了不少,初学者会问到这些问题:“什么是 class?为什么类还有参数?啊,什么是继承?yield 又是什么鬼,那个 scrapy.Request 又是啥?”这些都是心智负担。那么 scrapy 这些心智负担又给我们带来了什么好处呢?好处是性能和稍微统一的代码结构,但是其实这两个对初学者并没有什么卵用啊……

Scrapy 采用了 Twisted 作为基础,实现了基于协程的高并发。协程看着虽然挺好,但是对于非程序员来说,他们往往就想对一个站点做定向爬取,你说你蹭蹭蹭把并发涨上去了,无非两个后果:

  1. 对方承受不住你爬,挂掉了,你拿不到数据;
  2. 对方把你封禁了,疯狂弹验证码,你拿不到数据。

所以,对于非程序员做的一些定向爬取来说,速度是没有意义的,甚至往往是越慢越好。Scrapy out。

那么统一的代码结构有什么卵用吗?答案依然是没有。我们知道,在 web 开发领域,稍微有点规模的项目还是要使用框架的,哪怕是 flask 这种微框架。这是因为,在 web 开发领域,有经典的 MVC 模式,我们需要路由、模板、ORM 这些固定的组件,所以主循环是由框架和 web server 来控制的。而对于爬虫呢?其实没有什么固定的模式,scrapy 也仅仅是定义了几个钩子函数而已,反倒因为我们没有了主循环,在编写一些特定逻辑的时候非常受到掣肘。

另外,scrapy 提供的一些其他功能,比如说抓取的队列或者去重等等,个人感觉有过度封装的味道,而且也都是在内存里,在反爬导致爬虫挂掉这种故障面前没有什么卵用,不二次开发的话还是得重爬。对于小白来说,也不用想 redis 这些幺蛾子,其实可以用 Google 最开始使用的一个很简单的方法,就把每个新抓到的 url 写到一个 txt 文件就好了,爬虫每次重启的时候首先读取这个 txt 就好了,网上乱七八糟的教程大多是炫技的。

为什么不适合大型爬虫系统?

前面说到,scrapy 基于 twisted。twisted 是 Python 的一个异步框架,最大的问题就是太难懂了,而且现在官方应支持了 asyncio,所以 twisted 的未来堪忧,甚至比起 twisted 来说,我更愿意投入时间到 curio 这样新兴的有潜力的异步框架。第二点就是 scrapy 控制了主循环,所以二次开发相当于只能在他的框架内做一些修修补补,并且还要兼容 twisted。

scrapy 依赖 twisted 这一点导致 scrapy 深入后曲线非常地陡峭,要想了解一些内部的机理,必须对 twisted 比较明了。而 twisted 正如它的名字一样,是非常扭曲的一些概念,虽然性能非常好,但是要理解起来是要花上不少时间的。

Web 服务器是一个爬虫可以抽象出来的是各种组件。而 scrapy 太简陋了,比如说去重,直接用的是内存中的一个集合。如果要依赖 scrapy 写一个大型的爬虫,几乎每个组件都要自己实现,那有何必用 scrapy 呢?

既然要开发大型爬虫系统,那么其中很重要的一部分就是爬虫的调度了。一种比较简单的模式是 scheduler 作为 master,全局调度。另一种模式没有 master,所有的爬虫 worker 都是对等的。在实际生产中显然是第一种用的更多。Scrapy 作为一个框架,实际上只是实现了下载部分而已,很难称得上一个框架。

对于大型爬虫系统,最重要的并不是你使用不使用 Scrapy,而是你不能为每一站点去单独写一个爬虫的脚本。代码的灵活度实在太大了,对于没有足够经验的工程师来说,写出来的脚本可能很难维护。重点是要把主循环掌握在爬虫平台的手中,而不是让每一个脚本都各行其是。

显然 scheduler 这部分是不能再用一个爬虫框架来实现的,连主循环都没有怎么写逻辑呢?我们可能还要实现增量爬取,或者消费业务方发来的爬取请求等各种业务,这块显然是在 scheduler 里面的,那么这个爬虫系统无非是 scheduler 分发任务给各个 worker 来抓取。worker 还可以使用 scrapy 实现,但是呢,这个 worker 其实已经弱化为一层薄薄的 downloader 了,那我要他干嘛呢?scrapy 的核心逻辑也不过是个深度或者广度优先的遍历而已,少一个依赖不好么……

总结一下,爬虫的工作量要么在反爬,要么在调度等业务逻辑,本身只是一个 requests.get 而已,scrapy 提供的种种抽象对于初学者太复杂,大型系统又用不上,所以个人不推荐使用包括但不限于 scrapy 在内的所有爬虫框架

建议所有认为学习框架会使自己变强的人读读:Stop learning frameworks 和评论,中文翻译

以上仅代表个人观点,欢迎讨论,不要人身攻击。

参考

  1. scrapy 源码解读