通过docker swarm搭建ELK集群


介绍

用到的开源组件及其版本如下:

  • elasticsearch: 5.4.0
  • logstash: 5.0
  • kibana: 5.0
  • docker(swarm): 17.03

为什么要用docker?
ELK如果直接在vm上安装的话,需要去考虑一些服务的编排以及配置一致性问题。后期运维的时候服务的升级和回滚都会比较麻烦。使用docker来搭建的话,就可以使用swarm这样的集群管理工具来对整个集群进行管理,会带来很多有利的特性。不过使用docker的时候会有一些其他需要解决的问题,后面会做说明。

环境搭建

目前ELK集群在AWS上搭建,AWS上有支持docker的AMI,但是此处为了和其他的子系统保持一致,使用redhat的AMI,为了使用docker,需要在每个vm上安装docker, 并组建docker swarm集群。具体的步骤如下,为了更自动地完成这部分工作,可以通过ansible等vm 配置工具来完成。这里只列出来需要执行的命令:

安装docker

添加yum 仓库并更新:

sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

sudo yum makecache fast

安装并启动docker:

sudo yum -y install docker-ce
sudo systemctl enable docker
sudo systemctl start docker

赋予aws用户使用docker的权限:

sudo usermod -aG docker ec2-user

在所有的elk集群的vm上执行上面的命令,可以通过如下命令验证docker是否已经安装好:

[ec2-user@ip-10-0-2-26 ~]$ docker --version
Docker version 17.03.1-ce, build c6d412e

另外,为了能在vm上运行elasticsearch,需要在每个vm上做如下修改:

sudo sysctl -w vm.max_map_count=262144

创建swarm cluster

在1.12之后的docker版本上,swarm已经集成在docker engine上了,也就是说只要安装了docker,就已经默认支持swarm了,不用再单独安装swarm,这样使用起来更方便。在新版本上的swarm被称做docker swarm mode. 后续我们用到的docker service 等命令都是只能使用在docker swarm mode下的,不支持standlone方式的swarm.

在任意一台vm上执行如下命令docker swarm init命令:

[ec2-user@ip-10-0-2-26 ~]$ docker swarm init
Swarm initialized: current node (809b4sngwmbnfdvxntiplw95x) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join \
    --token SWMTKN-1-02jj7a820h9bxu9sw7jigku9j4hfmryb2md5oe1wugck7rkw8u-8mdgjxvuouq04ycz0jbeqiqc8 \
    10.0.2.191:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

上面的命令创建了一个docker swarm cluster,并且该vm作为docker swarm manage的角色,每一个集群可以有一个或者多个manage,manage作为cluster的管理者,可以在上面执行很多管理操作。

将elk集群的其他vm加入swarm cluster,在其余的vm上执行如下命令:

    docker swarm join \
    --token SWMTKN-1-02jj7a820h9bxu9sw7jigku9j4hfmryb2md5oe1wugck7rkw8u-8mdgjxvuouq04ycz0jbeqiqc8 \
    10.0.2.191:2377

创建好的集群,可以通过docker node ls来查看:

[ec2-user@ip-10-0-2-191 ~]$ docker node ls
ID                           HOSTNAME                    STATUS  AVAILABILITY  MANAGER STATUS
4t3tawqaq4v06fj2atdm36ofy *  ip-10-0-2-191.ec2.internal  Ready   Active        Leader
f0y7u7vo7oi9x0gp7cacf8dql    ip-10-0-2-15.ec2.internal   Ready   Active        
lrdl37bndm3tfu9my9o4x87tr    ip-10-0-2-81.ec2.internal   Ready   Active        
nev2gglqzv2izaeq5bzytnz28    ip-10-0-2-42.ec2.internal   Ready   Active        Reachable
x5d5xevqjr94h75tgbo9a4eh5    ip-10-0-2-26.ec2.internal   Ready   Active        

如上是一个5个node的swarm cluster,其中有3个worker,2个manager,其中一个manager作为leader,另外一个作为备份leader,这样,如果一个manager出现异常,另外一个manager可以作为leader继续工作,不会导致集群的崩溃。

部署elk集群

这里先将elk集群搭建起来,具体的技术细节和一些关键特性会在后面再做介绍。

创建docker network

这里不适用缺省的bridge网络,我们将elk集群放在一个隔离的网络里面,在swarm manager节点上通过docker命令新建一个网络:

docker network create --driver overlay --subnet 192.168.8.0/20  elknet

部署elasticsearch

1.创建本地目录用于存储数据

[ec2-user@ip-10-0-2-191 ~]$ mkdir es

[ec2-user@ip-10-0-2-191 ~]$ chmod 777 es

elasticsearch为了保存索引数据必须要永久化存储数据,docker本身并不支持数据的永久化存储,后续会对这部分做详细说明,这里的es目录属于用户ec2-user,而运行elasticsearch的是用户elasticsearch,因此需要将es的权限改成777或者666.

2.部署elasticsearch服务

docker service create --name es --network=elknet \
  --mode global \
  --mount type=bind,src=$PWD/es,dst=/usr/share/elasticsearch/data \
  --env SERVICE_NAME=es \
  --publish 9200:9200 \
  --env discovery.zen.minimum_master_nodes=3 \
  --env path.data=/usr/share/elasticsearch/data  \
  fangcha/swarm-elasticsearch

3.查看服务是否部署成功:

[ec2-user@ip-10-0-2-191 ~]$ docker service ls
ID            NAME        MODE        REPLICAS  IMAGE
8f8n4jo7lwkq  es          global      5/5       fangcha/swarm-elasticsearch:latest

[ec2-user@ip-10-0-2-191 ~]$ docker service ps es
ID            NAME                              IMAGE                               NODE                        DESIRED STATE  CURRENT STATE              ERROR                      PORTS
3smu1yflqg4s  es.f0y7u7vo7oi9x0gp7cacf8dql      fangcha/swarm-elasticsearch:latest  ip-10-0-2-15.ec2.internal   Running        Running about an hour ago                             
ob3ihbf3ot7d  es.4t3tawqaq4v06fj2atdm36ofy      fangcha/swarm-elasticsearch:latest  ip-10-0-2-191.ec2.internal  Running        Running about an hour ago                             
p6jpwgh9bhjg  es.x5d5xevqjr94h75tgbo9a4eh5      fangcha/swarm-elasticsearch:latest  ip-10-0-2-26.ec2.internal   Running        Running about an hour ago                             
r2m39v5mm6fz   es.x5d5xevqjr94h75tgbo9a4eh5   fangcha/swarm-elasticsearch:latest   ip-10-0-2-42.ec2.internal   Running        Running about an hour ago                             
17e96lcjvgpm  es.lrdl37bndm3tfu9my9o4x87tr      fangcha/swarm-elasticsearch:latest  ip-10-0-2-81.ec2.internal   Running        Running about an hour ago                             

从上面可以看出,我们在部署了5个elasticsearch的节点,服务已经部署成功了,服务的名称是es,后续可以通过这个名称来做负载均衡。现在可以通过curl命令验证elasticsearch节点的工作状态是否正常:

[ec2-user@ip-10-0-2-191 ~]$ curl 10.0.2.191:9200/_cat/nodes?v
ip          heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.0.8           10          91   0    0.00    0.02     0.05 mdi       *      192.168.0.8
192.168.0.3            6          97   0    0.00    0.01     0.05 mdi       -      192.168.0.3
192.168.0.4            6          97   0    0.02    0.03     0.05 mdi       -      192.168.0.4
192.168.0.7            8          92   0    0.00    0.01     0.05 mdi       -      192.168.0.7
192.168.0.5           11          93   0    0.05    0.04     0.05 mdi       -      192.168.0.5

elasticsearch集群已经可以正常工作了。

部署logstash

在swarm manage节点上执行如下命令:

docker service create --name logshipper --network=elknet \
  --replicas 2 \
  --publish 514:10514 \
  logstash -e 'input { syslog { port => 10514 } } output { elasticsearch { hosts => "es:9200"  index => "syslog-msg"} }'

部署kibana

在swarm manage节点上执行如下命令:

docker service create --name kibana --network=elknet \
  --replicas 1 \
  --publish 5601:5601 \
  --env  ELASTICSEARCH_URL=http://es:9200 \
  kibana

通过以上命令,一个有2个logstash节点、5个elasticsearch节点、1个kibana节点的elk集群就已经搭建成功了。查看整个服务的状态:

[ec2-user@ip-10-0-2-191 ~]$ docker service ls
ID            NAME        MODE        REPLICAS  IMAGE
8f8n4jo7lwkq  es          global      5/5       fangcha/swarm-elasticsearch:latest
n0i181kvsj3k  kibana      replicated  1/1       kibana:latest
s81iijgbv5gh  logshipper  replicated  2/2       logstash:latest

测试elk集群

以上搭建的集群是是通过514端口接收syslog消息,将接收到的消息发送给elasticsearch之后,kibana通过5601端口将syslog消息展示给用户。

在任意一台可以访问集群的计算机上执行如下命令:

root@zhbo-OptiPlex-9020:/home/zhbo# yes "test" | nc 52.176.215.132 514

52.176.215.132是集群中一台vm的public ip。我们可以通过elasticseach的api查看是否建立syslog-msg的索引:

[ec2-user@ip-10-0-2-191 ~]$ curl 10.0.2.191:9200/_cat/shards
.kibana    0 p STARTED     2   7.1kb 192.168.0.3 192.168.0.3
.kibana    0 r STARTED     2   7.1kb 192.168.0.4 192.168.0.4
syslog-msg 3 p STARTED 12516 595.2kb 192.168.0.3 192.168.0.3
syslog-msg 3 r STARTED 12516 595.2kb 192.168.0.4 192.168.0.4
syslog-msg 2 r STARTED 12503 597.7kb 192.168.0.5 192.168.0.5
syslog-msg 2 p STARTED 12503 597.7kb 192.168.0.7 192.168.0.7
syslog-msg 1 p STARTED 12370 591.6kb 192.168.0.5 192.168.0.5
syslog-msg 1 r STARTED 12370 591.6kb 192.168.0.7 192.168.0.7
syslog-msg 4 p STARTED 12445 586.3kb 192.168.0.8 192.168.0.8
syslog-msg 4 r STARTED 12445 586.3kb 192.168.0.4 192.168.0.4
syslog-msg 0 r STARTED 12395 589.6kb 192.168.0.3 192.168.0.3
syslog-msg 0 p STARTED 12395 589.6kb 192.168.0.8 192.168.0.8

从上面可以看出已经收到测试消息,并分成5片分别存储在不同的节点上。elk集群搭建成功。

关键特性

负载均衡

docker swarm mode 内置了负载均衡器,所以此处不需要再通过haproxy或者nginx做负载均衡。docker swarm mode的负载均衡是通过routing mesh来实现的。这里引用docker官方文档中的一个图做说明:

ingress-routing-mesh.png-60.2kB
ingress-routing-mesh.png-60.2kB

Docker Engine swarm mode makes it easy to publish ports for services to make them available to resources outside the swarm. All nodes participate in an ingress routing mesh. The routing mesh enables each node in the swarm to accept connections on published ports for any service running in the swarm, even if there’s no task running on the node. The routing mesh routes all incoming requests to published ports on available nodes to an active container.
In order to use the ingress network in the swarm, you need to have the following ports open between the swarm nodes before you enable swarm mode:

  1. Port 7946 TCP/UDP for container network discovery.
  2. Port 4789 UDP for the container ingress network.
    You must also open the published port between the swarm nodes and any external resources, such as an external load balancer, that require access to the port.

如上图所示,swarm集群作为一个整体对外提供服务,对集群中任何一个节点的访问,都会根据相应的端口信息,路由到可用的容器上执行相关操作。如elk集群中logstash有2个节点,对外监听514端口,则访问swarm cluster中的任意一台vm的514端口,都会被路由到2个可用的logstash节点中的一个。达到负载分担的效果。

弹性伸缩

docker swarm对服务的弹性伸缩支持得很好,尤其是对于无状态的服务。只需要一条命令就可以支持弹性伸缩了。如:将logstash的服务有2个节点扩展到3个几点,在swarm manage节点上执行如下命令:

[ec2-user@ip-10-0-2-191 ~]$ docker service scale logshipper=3
logshipper scaled to 3

[ec2-user@ip-10-0-2-191 ~]$ docker service ls 
ID            NAME        MODE        REPLICAS  IMAGE
8f8n4jo7lwkq  es          global      5/5       fangcha/swarm-elasticsearch:latest
n0i181kvsj3k  kibana      replicated  1/1       kibana:latest
s81iijgbv5gh  logshipper  replicated  3/3       logstash:latest

[ec2-user@ip-10-0-2-191 ~]$ docker service ps logshipper
ID            NAME          IMAGE            NODE                       DESIRED STATE  CURRENT STATE           ERROR  PORTS
xix4cretis74  logshipper.1  logstash:latest  ip-10-0-2-81.ec2.internal  Running        Running 18 hours ago           
d09u1h44exuv  logshipper.2  logstash:latest  ip-10-0-2-42.ec2.internal  Running        Running 18 hours ago           
e5gl09fymz9u  logshipper.3  logstash:latest  ip-10-0-2-26.ec2.internal  Running        Running 30 seconds ago

将logstash服务的节点数缩减为2个,也是很容易的:

[ec2-user@ip-10-0-2-191 ~]$ docker service scale logshipper=2
logshipper scaled to 2

[ec2-user@ip-10-0-2-191 ~]$ docker service ps logshipper
ID            NAME          IMAGE            NODE                       DESIRED STATE  CURRENT STATE         ERROR  PORTS
xix4cretis74  logshipper.1  logstash:latest  ip-10-0-2-81.ec2.internal  Running        Running 18 hours ago         
d09u1h44exuv  logshipper.2  logstash:latest  ip-10-0-2-42.ec2.internal  Running        Running 18 hours ago     

注意:这里进行服务扩展时,服务的参数会在新建的节点上执行,所以在创建服务的时候需要根据服务的特点合理的规划服务和服务的参数,最好是使用节点无关的参数。

服务发现

这里主要分为两部分来说明:服务间发现和服务内发现。在elk集群中都用到了这两个功能,logstash和es,kibana和es之间的交互要用到服务间发现。es本身就是一个集群,内部不同节点间是服务内发现。

服务间发现

docker swarm的架构决定了它非常适合做服务间发现,docker本身内置了dns的服务,可以将服务名称解析为对应的ip地址。

在上面部署elk集群的时候,logstash为了将日志发送给es,需要知道es的集群的访问地址,当时部署时候的命令如下:

docker service create --name logshipper --network=elknet \
  --replicas 2 \
  --publish 514:10514 \
  logstash -e 'input { syslog { port => 10514 } } output { elasticsearch { hosts => "es:9200"  index => "syslog-msg"} }'

直接用es:9200就可以指代es集群的地址了,这里es就是es集群的服务名称。事实上在elknet内部,通过dig tasks.es +short就可以找到所有运行es服务的node的ip地址。所以,对于服务间发现,docker swarm天然支持得很好,不需要做额外的工作,每个服务都可以通过服务名称进行访问。

但是docker swarm mode对于服务内发现支持得并不好,这是由于docker swarm本身的特点决定的。docker swarm将所有的集群作为一个整体来看,所有的node除了manage和worker的区分之外,没有其他的区分了。这种特性在不需要关心节点的状态的情况下,非常有用,如logstash就很适应这种情况。但是对于某些需要关注不同node状态的服务,或者说启动参数和节点有关的服务,这时候就有一些问题,比如elasticsearch。

服务内发现
elasticsearch节点分为master节点、data节点、ingress节点、tribe节点。正常es集群的建立方式是先启动一个或者多个master节点,通过network.publish_host(transport.publish_host)配置集群内通信的ip地址。然后其他的节点通过discovery.zen.ping.unicast.hosts配置集群的host(一般是master节点的network.publish_host)来加入es集群。

如果简单地通过docker service来创建es服务,则无法为master节点(master节点为了组成集群,也需要互相知道彼此的地址)和data节点分别配置不同的而且有关联的参数。有几种方式解决这个问题,这里结合docker swarm内置的dns解析功能,通过脚本的方式解决es之间的服务发现的问题。

这里需要新建一个elasticsearch的docker image,Dockerfile和全部的脚本可以参考github上如下路径的实现:
https://github.com/riverbaby/docker-swarm-elasticsearch

通过上述的Dockerfile生成的镜像可以在docker hub上的如下位置找到:
https://hub.docker.com/r/fangcha/swarm-elasticsearch/

使用这里的镜像就可以通过docker swarm mode建立es的集群了。对具体的实现细节感兴趣可以参考上面的两个链接。

服务回滚和升级

服务的回滚和升级可以通过docker service update来实现。docker swarm支持很多服务和升级的配置,缺省情况下,在服务回滚和升级的时候,会逐个将服务的节点shutdown,然后更换为新的服务节点,直到所有的节点全部替换完毕。

对于该功能的详细实现,可以查看如下链接:
https://docs.docker.com/engine/swarm/swarm-tutorial/rolling-update/

永久存储

docker本身是不支持永久存储的,也就是说当前正在运行的容器如果重启之后,先前的数据会丢失。对于单个docker实例,可以通过-v参数将host的存储路径映射到docker容器中,这样,需要永久化存储的数据就会被存储到host中。

elk集群中es的数据是需要永久存储的,有两个思路:

其一
使用公共的存储路径,将所有的节点的数据存储到这个公共的存储路径这里。这样再创建service的时候,只需要制定一个公共的volume就可以了。docker支持创建和使用这样的路径。这里以aws的EBS存储为例,具体的命令和步骤如下:

1.安装rexray plugin,在所有的vm上执行如下命令:

docker plugin install rexray/ebs \
  REXRAY_PREEMPT=true \
  EBS_ACCESSKEY=×× \
  EBS_SECRETKEY=××

其中EBS_ACCESSKEY和EBS_SECRETKEY是AWS的访问凭据。

2. 创建docker volume,在docker manage上执行如下命令:

docker volume create -d rexray/ebs --name esdisk --opt=size=50

3.可以通过docker volume查看创建的volume卷,这里创建的volume,也可以通过aws的网页页面的volume栏查看。
4.创建服务,指定数据存储的位置为esdisk volume:

docker service create --name esmaster --network=elknet \
  --replicas 5 \
  --mount type=volume,source=esdisk,target=/usr/share/elasticsearch/data,volume-driver=rexray/ebs \
  --env SERVICE_NAME=esmaster \
  --env discovery.zen.minimum_master_nodes=3 \
  --env path.data=/usr/share/elasticsearch/data  \
  fangcha/swarm-elasticsearch

如上,就可以通过公用的存储位置来存储整个集群的数据了。但是这种方式存储的时候,需要注意master节点和data节点的数据一致性的问题。目前在使用的时候还存在一些问题,es的官网上也不建议通过共享数据路径的方式存储es的数据,本身es集群就是通过在不同的位置存储数据的备份来达到数据的高可靠性的,如果共享了同一个位置,后续该volume发生问题的时候则所有数据都会出异常。这个后续需要再研究看看如何实现。

其二
在每个节点有独立的存储路径来存储数据。这样是符合elasticsearch的要求的,但是对于docker swarm来说这样的实现并不好,因为需要考虑每个node的存储路径,而且swarm并不能保证一个服务重启之后还在原来的vm上。一个解决方式是通过global mode来启动服务:

  • global mode的服务会在每个vm上启动服务
  • es服务通过bind的mount方式在每个vm上mount一个local路径,local路径需要提起创建好,local路径是host的路径,以后的数据都存储在host上了。
  • 服务重启的时候可以直接从每个节点上恢复出来数据。
  • 后续如果将es的节点功能独立出来(如单独的master节点、data节点等),则可以通过lable的方式将vm打上标签,然后在启动服务的时候分别启动master服务,data服务,通过filter指定服务在指定label的节点上启动。只要保证不同功能的节点不共用相同的路径即可。

目前的采取了“其二”的实现方式。

跨数据中心部署和访问

在实际的使用过程中,有可能数据被存储在不同的数据中心中,这时候需要能跨数据中心访问数据。es提供了相关的特性支持这种跨数据中心的操作。此时的部署架构图如下:

tribe-72.1kB
tribe-72.1kB

通过在DC边缘部署tribe节点,tribe节点绑定到不同DC的es cluster中,则可以通过tribe节点访问不同的数据中心的数据。

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

推荐阅读更多精彩内容