Tuesday, September 22, 2015

用 Docker 搭建 Spark 集群

简介

Spark 是 Berkeley 开发的分布式计算的框架,相对于 Hadoop 来说,Spark 可以缓存中间结果到内存而提高某些需要迭代的计算场景的效率,目前收到广泛关注。
熟悉 Hadoop 的同学也不必担心,Spark 很多设计理念和用法都跟 Hadoop 保持一致和相似,并且在使用上完全兼容 HDFS。但是 Spark 的安装并不容易,依赖包括 Java、Scala、HDFS 等。
通过使用 Docker,可以快速的在本地搭建一套 Spark 环境,方便大家开发 Spark 应用,或者扩展到生产环境。
Spark 的设计理念很简单master 节点负责接收要执行的应用和指令worker 节点负责具体处理
除了自带的集群机制,还兼容集成到其他的集群框架,包括 mesos、yarn 等。

安装

其实官方的源码中已经提供了支持 mesos 的 Dockerfile 文件,在 docker\spark-mesos 下面,可以据此生成 Docker 镜像。
这里,我们采用比较热门的 sequenceiq/docker-spark 镜像,这个镜像已经安装了对 Spark 的完整依赖。
无论官方还是 sequenceiq/docker-spark 镜像,都是基于提前打包的二进制文件来制作的,都可以放心使用。
由于镜像比较大(2+ GB),首先下载该镜像到本地。
$ docker pull sequenceiq/spark:1.4.0
下载完毕后,从 https://github.com/yeasy/docker-compose-files/tree/master/spark_cluster 下载编写好的支持 Spark 集群的 docker-compose.yml 文件,并启动它
$ sudo pip install docker-compose==1.4.0
$ docker-compose up
可以看到类似如下的输出:
Creating sparkcompose_master_1...
Creating sparkcompose_slave_1...
Attaching to sparkcompose_master_1, sparkcompose_slave_1
master_1 | /
master_1 | Starting sshd: [  OK  ]
slave_1  | /
slave_1  | Starting sshd: [  OK  ]
master_1 | Starting namenodes on [master]
slave_1  | Starting namenodes on [5d0ea02da185]
master_1 | master: starting namenode, logging to /usr/local/hadoop/logs/hadoop-root-namenode-master.out
slave_1  | 5d0ea02da185: starting namenode, logging to /usr/local/hadoop/logs/hadoop-root-namenode-5d0ea02da185.out
master_1 | localhost: starting datanode, logging to /usr/local/hadoop/logs/hadoop-root-datanode-master.out
slave_1  | localhost: starting datanode, logging to /usr/local/hadoop/logs/hadoop-root-datanode-5d0ea02da185.out
master_1 | Starting secondary namenodes [0.0.0.0]
slave_1  | Starting secondary namenodes [0.0.0.0]
master_1 | 0.0.0.0: starting secondarynamenode, logging to /usr/local/hadoop/logs/hadoop-root-secondarynamenode-master.out
master_1 | starting yarn daemons
master_1 | starting resourcemanager, logging to /usr/local/hadoop/logs/yarn--resourcemanager-master.out
master_1 | localhost: starting nodemanager, logging to /usr/local/hadoop/logs/yarn-root-nodemanager-master.out
master_1 | starting org.apache.spark.deploy.master.Master, logging to /usr/local/spark-1.4.0-bin-hadoop2.6/sbin/../logs/spark--org.apache.spark.deploy.master.Master-1-master.out
slave_1  | 0.0.0.0: starting secondarynamenode, logging to /usr/local/hadoop/logs/hadoop-root-secondarynamenode-5d0ea02da185.out
slave_1  | starting yarn daemons
slave_1  | starting resourcemanager, logging to /usr/local/hadoop/logs/yarn--resourcemanager-5d0ea02da185.out
slave_1  | localhost: starting nodemanager, logging to /usr/local/hadoop/logs/yarn-root-nodemanager-5d0ea02da185.out
slave_1  | starting org.apache.spark.deploy.worker.Worker, logging to /usr/local/spark-1.4.0-bin-hadoop2.6/sbin/../logs/spark--org.apache.spark.deploy.worker.Worker-1-5d0ea02da185.out
docker-compose 服务起来后我们还可以用 scale 命令来动态扩展 Spark 的 worker 节点数例如
$ docker-compose scale worker=2
Creating and starting 2... done
注意,需要先用 pip 安装 docker-compose,1.4.1 版本有 bug,推荐使用 1.4.0 版本。

docker-compose 文件

我们来解析下 docker-compose 文件,核心内容很简单,包括 master 服务和 worker 服务。
master:
  image: sequenceiq/spark:1.4.0
  hostname: master
  ports:
  - "4040:4040"
  - "8042:8042"
  - "7077:7077"
  - "8080:8080"
  - "8088:8088"
  restart: always
  command: bash /usr/local/spark/sbin/start-master.sh && ping localhost > /dev/null

worker:
  image: sequenceiq/spark:1.4.0
  links:
  - master:master
  expose
  - "8081"
  restart: always
  command: bash /usr/local/spark/sbin/start-slave.sh spark://master:7077 && ping localhost >/dev/null

master 服务

首先,master 服务映射了好几组端口到本地,分别功能为: 4040:Spark 运行任务时候提供 web 界面观测任务的具体执行状况,包括执行到哪个阶段、在哪个 executor 上执行; 8042:Hadoop 的节点管理界面; 7077:Spark 主节点的监听端口,用户可以提交应用到这个端口,worker 节点也可以通过这个端口连接到主节点构成集群; 8080:Spark 的监控界面,可以看到所有的 worker、应用整体信息; * 8088:Hadoop 集群的整体监控界面。
master 服务启动后执行了 bash /usr/local/spark/sbin/start-master.sh 命令来配置自己为 master 节点然后通过 ping 来避免容器退出

worker 服务

类似 master 节点,启动后,执行了 /usr/local/spark/sbin/start-slave.sh spark://master:7077 命令来配置自己为 worker 节点,然后通过 ping 来避免容器退出。
注意,启动脚本后面需要提供 spark://master:7077 参数来指定 master 节点地址。
8081 端口提供的 web 界面,可以看到该 worker 节点上任务的具体执行情况。

执行应用

Spark 推荐用 spark-submit 命令来提交执行的命令基本语法为
spark-submit \
  --class your-class-name \
  --master master_url \
  your-jar-file
  app_params
例如,我们可以使用 spark 自带样例中的计算 Pi 的应用。
在 master 节点上执行命令
/urs/local/spark/bin/spark-submit --master spark://master:7077 --class org.apache.spark.examples.SparkPi /usr/local/spark/lib/spark-examples-1.4.0-hadoop2.6.0.jar 1000
最后的参数 1000 表示要计算的迭代次数为 1000 次。
任务运行中,可以用浏览器访问 4040 端口,看到任务被分配到了两个 worker 节点上执行。 
计算过程中也会输出结果,如下:
...
15/09/22 03:07:54 INFO scheduler.TaskSetManager: Finished task 998.0 in stage 0.0 (TID 998) in 201 ms on 172.17.0.49 (998/1000)
15/09/22 03:07:54 INFO scheduler.TaskSetManager: Finished task 999.0 in stage 0.0 (TID 999) in 142 ms on 172.17.0.49 (999/1000)
15/09/22 03:07:54 INFO scheduler.TaskSetManager: Finished task 997.0 in stage 0.0 (TID 997) in 220 ms on 172.17.0.49 (1000/1000)
15/09/22 03:07:54 INFO scheduler.TaskSchedulerImpl: Removed TaskSet 0.0, whose tasks have all completed, from pool
15/09/22 03:07:54 INFO scheduler.DAGScheduler: ResultStage 0 (reduce at SparkPi.scala:35) finished in 23.149 s
15/09/22 03:07:54 INFO scheduler.DAGScheduler: Job 0 finished: reduce at SparkPi.scala:35, took 23.544018 s
Pi is roughly 3.1417124
15/09/22 03:07:54 INFO handler.ContextHandler: stopped o.s.j.s.ServletContextHandler{/metrics/json,null}
15/09/22 03:07:54 INFO handler.ContextHandler: stopped o.s.j.s.ServletContextHandler{/stages/stage/kill,null}
15/09/22 03:07:54 INFO handler.ContextHandler: stopped o.s.j.s.ServletContextHandler{/api,null}
...

总结

集群系统作为现代 IT 基础架构的重要组成部分,一直是运维部门的“硬骨头”。上线、部署、扩容……规模大了后各方面都可能出问题。而结合 Docker 这样的容器技术,可以很大程度上解耦对周边环境的依赖。如果再配合 mesos 等资源管理系统,可以大大降低人工运维的风险和复杂性。
当然,优秀的系统一定要配合正确的流程。生产环境中如果缺乏一套成熟的 devops 流程,是不可能有高效的生产力的。加班,也就成了常态。
转载请注明。

Monday, August 17, 2015

用 consul + consul-template + registrator + nginx 打造真正可动态扩展的服务架构

在互联网应用领域,服务的动态性需求十分常见,这就对服务的自动发现和可动态扩展提出了很高的要求。
Docker 的出现,以及微服务架构的兴起,让众多开源项目开始关注在松耦合的架构前提下,如何基于 Docker 实现一套真正可动态扩展的服务架构。

基本需求

基本的需求包括:
  • 服务启动后要能自动被发现(vs 传统需要手动进行注册);
  • 负载要能动态在可用的服务实例上进行均衡(vs 传统需要静态写入配置);
  • 服务规模要方便进行快速调整(vs 传统需要较长时间的手动调整)。

相关项目

服务发现

服务发现的项目已经有不少,包括之前介绍的 consul,以及 skydnsserf、以及主要关注一致性的强大的 zookeeper 等。
这些项目各有优缺点,功能上大同小异,都是要通过某种机制来获取服务信息,然后通过维护一套(分布式)数据库来存储服务的信息。这也是为什么 etcd 受到大家关注和集成。
在这里,选用 HashiCorp 公司的 consul 作为服务发现的管理端,它的简介可以参考 这里

服务注册

服务注册的手段有很多,当然,从发起方是谁可以分为两大类,主动注册还是被动探测。
主动注册,顾名思义,服务启动后,向指定的服务发现管理端的 API 发送请求,给出自身的相关信息。这样做,对管理端的要求简单了很多,但意味着服务自身要完成注册工作,并且极端情况下,管理端比较难探测出真正存活的服务。
被动探测,则是服务发现管理端通过某种机制来探测存活的服务。这样可以获取真实的服务情况,但如何探测是个很难设计的点,特别当服务类型比较复杂的时候。
以上两种,都对网络连通性要求较高。从短期看,主动注册方式会比较容易实现一些,应用情形更广泛;但长期维护上,被动探测方式应该是更高效的设计。
这里,我们选用 gliderlabs 的 registrator,它可以通过跟本地的 docker 引擎通信,来获取本地启动的容器信息,并且注册到指定的服务发现管理端。

配置更新

服务被调整后,负载均衡器要想动态重新分配负载,就需要通过配置来获取更新。这样的方案也有不少,基本上都是要安装一些本地 agent 来监听服务发现管理端的信息,生成新的配置,并执行更新命令。
HashiCorp 公司 的consul-template,可以通过监听 consul 的注册信息,来完成本地应用的配置更新。

负载均衡

负载均衡对性能要求很高,其实并不是软件所擅长的领域,但软件方案胜在成本低、维护方便。包括 lvshaproxy 都是很优秀的设计方案。
这里,我们选用 nginx。nginx 不仅是个强大的 web 代理服务器,同时在负载均衡方面表现也不俗。更关键的,新版本的 nginx 对在线升级支持做到了极致。实时配置更新更是不在话下,可以保证服务的连续性。

实验过程

准备工作

首先,从 这里 下载模板文件。
主要内容如下:
#backend web application, scale this with docker-compose scale web=3
web:
  image: yeasy/simple-web:latest
  environment:
    SERVICE_80_NAME: http
    SERVICE_NAME: web
    SERVICE_TAGS: backend
  ports:
  - "80"

#load balancer will automatically update the config using consul-template
lb:
  image: yeasy/nginx-consul-template:latest
  hostname: lb
  links:
  - consulserver:consul
  ports:
  - "80:80"

consulserver:
  image: gliderlabs/consul-server:latest
  hostname: consulserver
  ports:
  - "8300"
  - "8400"
  - "8500:8500"
  - "53"
  command: -data-dir /tmp/consul -bootstrap -client 0.0.0.0

# listen on local docker sock to register the container with public ports to the consul service
registrator:
  image: gliderlabs/registrator:master
  hostname: registrator
  links:
  - consulserver:consul
  volumes:
  - "/var/run/docker.sock:/tmp/docker.sock"
  command: -internal consul://consul:8500
如果没有安装 docker 和 docker-compose,需要先进行安装,以 ubuntu 系统为例。
$ curl -sSL https://get.docker.com/ | sh
$ sudo pip install docker-compose

执行

docker-compose 模板所在目录,执行
$ sudo docker-compose up
相关镜像即可自动被下载,下载完毕后,容器就启动起来了。
访问 http://localhost 可以看到一个 web 页面,提示实际访问的目标地址。
多次刷新,可以看到目标地址没有变化,这是因为,目前我们只有一个 web 后端服务器。
2015-08-18 03:37:58: 6 requests from <LOCAL: 172.17.1.150> to WebServer <172.17.1.148>
调整后端为 3 个服务器。
$ sudo docker-compose scale web=3
然后,再次访问 http://localhost,多次刷新,可以看到访问的实际目标地址发生了变化,新启动的 web 服务器被自动注册,并且 nginx 自动对它们进行了负载均衡。
2015-08-18 03:37:58: 6 requests from <LOCAL: 172.17.1.150> to WebServer <172.17.1.148>
2015-08-18 03:38:17: 5 requests from <LOCAL: 172.17.1.150> to WebServer <172.17.1.152>
2015-08-18 03:38:20: 5 requests from <LOCAL: 172.17.1.150> to WebServer <172.17.1.153>

Tuesday, August 04, 2015

Consul 简介

概述

Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。
它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台,支持 Linux、Mac、Windows

安装

从 www.consul.io/downloads.html 下载二进制文件到本地即可。
consul 在每个节点上都只需要相同的单一二进制文件,部署十分简单。

基本概念

agent

组成 consul 集群的每个成员上都要运行一个 agent,可以通过 consul agent 命令来启动。agent 可以运行在 server 状态或者 client 状态。自然的,运行在 server 状态的节点被称为 server 节点;运行在 client 状态的节点被称为 client 节点。

client 节点

负责转发所有的 RPC 到 server 节点。本身无状态,且轻量级,因此,可以部署大量的 client 节点。

server 节点

负责组成 cluster 的复杂工作(选举、状态维护、转发请求到 lead),以及 consul 提供的服务(响应 RCP 请求)。考虑到容错和收敛,一般部署 3 ~ 5 个比较合适。
第一个节点:
$ consul agent -data-dir=/tmp/consul -server -bootstrap-expect 1 -bind $IP
其他节点:
$ consul agent -data-dir=/tmp/consul -server -join $IP
通过 $IP:8500/v1/catalog/nodes 可以访问到注册上的节点。

Gossip

基于 Serf 实现的 gossip 协议,负责成员、失败探测、事件广播等。通过 UDP 实现各个节点之间的消息。分为 LAN 上的和 WAN 上的两种情形。

架构

consul architecture

相关项目

registrator

可以监听主机上的容器信息,并把有暴露端口的容器注册到给定的 consul/etcd 等服务上。

consul-template

可以监听 consul 服务的内容变化,并根据变化实时更新应用的配置文件。

Thursday, July 16, 2015

配置 Docker 镜像下载的本地 mirror 服务

Docker registry 工具现在已经很好的支持了 mirror 功能,使用它可以配置一个本地的 mirror 服务,将 pull 过的镜像 cache 在本地,这样其它主机再次 pull 的时候会极大提高响应速度。

使用 docker-compose 启动 registry mirror 服务

以 ubuntu 为例,首先要安装 docker 和 docker-compose。

安装 docker

$ sudo wget -qO- https://get.docker.com/ | sh

安装 docker-compose

$ sudo pip install docker-compose
之后,在本地创建 /opt/data/registry 目录,作为镜像文件的存储位置;创建 /opt/data/redis 目录,作为 redis 数据的存放位置。
编写一个 docker-compose.yml 文件。
该文件将启动一个 registry 容器监听在本地的 5000 端口,并使用一个 redis 容器作为小文件的 cache。
内容如下:
# This compose file will start 2 containers: registry and redis.
# registry container will listen on host port 5000,
# and depend on the redis container as the cache scheme.


registry:
    image: registry:latest
    cpu_shares: 10
    environment:
        - STANDALONE=false
        - MIRROR_SOURCE=https://registry-1.docker.io
        - MIRROR_SOURCE_INDEX=https://index.docker.io
        - CACHE_REDIS_HOST=redis
        - CACHE_REDIS_PORT=6379
        - DEBUG=false
    hostname: docker-registry
    links:
        - redis:redis
    mem_limit: 512m
    ports:
        - "5000:5000"
    privileged: false
    restart: always
    user: root
    volumes:
        - /opt/data/registry:/tmp/registry

redis:
    image: redis:3.0
    cpu_shares: 10
    expose:
        - "6379"
    mem_limit: 512m
    restart: always
    volumes:
        - /opt/data/redis:/data
之后,启动服务。
$ docker-compose up -d

配置主机使用 mirror 服务

在其它主机上,配置 docker 的配置文件(例如 /etc/default/docker),添加一行:
DOCKER_OPTS="$DOCKER_OPTS --registry-mirror http://localmirror:5000"
其中 localmirror 替换为刚才配置了 mirror 服务的机器地址。
之后重启 docker 服务。
$ sudo service docker restart

测试

随便下载一个镜像,比如 ubuntu:14.04,正常需要十几分钟。
删除下载的镜像,再次下载,一分钟不到,就下载完毕了。

Tuesday, June 30, 2015

Mesos 热门框架

framework 是实际干活的,可以理解为 mesos 上跑的 应用,需要先注册到 master 上。

长期运行的服务

Aurora

利用 mesos 调度安排的任务,保证任务一直在运行。
提供 REST 接口,客户端和 webUI(8081 端口)

Marathon

一个 PaaS 平台。
保证任务一直在运行。如果停止了,会自动重启一个新的任务。
支持任务为任意 bash 命令,以及容器。
提供 REST 接口,客户端和 webUI(8080 端口)

Singularity

一个 PaaS 平台。
调度器,运行长期的任务和一次性任务。
提供 REST 接口,客户端和 webUI(7099、8080 端口),支持容器。

大数据处理

Cray Chapel

支持 Chapel 并行编程语言的运行框架。

Dpark

Spark 的 Python 实现。

Hadoop

经典的 map-reduce 模型的实现。

Spark

跟 Hadoop 类似,但处理迭代类型任务会更好的使用内存做中间状态缓存,速度要快一些。

Storm

分布式流计算,可以实时处理数据流。

批量调度

Chronos

Cron 的分布式实现,负责任务调度。

Jenkins

大名鼎鼎的 CI 引擎。使用 mesos-jenkins 插件,可以将 jenkins 的任务被 mesos 来动态调度执行。

ElasticSearch

功能十分强大的分布式数据搜索引擎。

数据存储

Cassandra

高性能分布式数据库。