作者邮箱时间
潘顾昌guchang.pan@hand-china.com2020/01/29

1.什么是ELK

E: elasticsearch

L:logstash

K:kibana

Elasticsearch是一个开源的分布式搜索和分析引擎,可以用于全文检索、结构化检索和分析,它构建在Lucene搜索引擎库之上,是当前使用较为广泛的开源搜索引擎之一。

Logstash是一个开源的数据搜集引擎是一个用来搜集、分析、过滤日志的工具,使用它可以将搜集来的日志信息进行处理然后进行输出。Logstash支持许多功能强大的插件,可以合理使用这些插件来对搜集到 的日志信息进行过滤和处理。

Kibana是一个基于Web的图形界面,可以使用它对Elasticsearch索引中的数据进行搜索、查看、交互操作。还可以很方便的利用图表、表格及地图对数据进行多元化的分析和呈现。

Filebeat是一个轻量级的日志搜集器,用于搜集和转发日志数据,Filebeat一般都会安装在需要搜集日志的服务器上,指定需要搜集日志的日志文件的位置,搜集日志信息并转发到Elasticsearch 或Logstash上进行索引。

2.ELK架构

ELK日志监控平台架构图如下:

在这里插入图片描述

其中, Filebeat 主要用于在服务器中采集服务日志信息并输出到 Logstash, Logstash 负责接收所有 Filebeat 传递过来的日志数据,并对数据进行过滤筛选处理后输出到 Elasticsearch, Elasticsearch 主要用于日志消息的存储,搜索和分析,在 Elasticsearch 中存储的索引信息最终会通过 Kibana 在页面上直观的进行展示。最终用户可以在 Kibana 页面中操作索引,查询日志, 监控 Elasticsearch 和 Kibana 的健康状态,以及配置多种 Dashboard 来展示聚合数据信息。

总结来说,ELK 日志监控大体上分为两部分,Elasticsearch、Logstash 和 Filebeat 负责数据的搜集,过滤,存储和分析,Kibana 负责操作存储在 Elasticsearch 中的索引信息,监控 Elasticsearch 和 Kibana 的健康状态以及管理用户信息。 同时用户还可以配置多种 Dashboard 来展示数据聚合信息。

3.为什么采用ELK架构

1.Elasticsearch、Logstash、Kibana、Filebeat 均为开源组件,并且使用 ELK 搭建日志监控系统的方案也比较成熟。

2.一般的日志监控系统架构都是采用 ELK(Elasticsearch、Logstash、Kibana)架构去搭建的,但是由于 Logstash 本身是基于 jdk 的,且它集成了许多插件,所以占用内存较大,在每台服务器上都部署一 个 Logstash 有些占用资源,因此我们使用轻量级的 Filebeat 组件来完成搜集日志的操作。但是 Filebeat 只能采集日志数据却无法对数据进行过滤,因此我们在将日志信息输出到 Elasticsearch 之前先使 用 Logstash 对数据进行过滤处理,再将处理好的数据输出到 Elasticsearch。

3.在微服务场景下,可能存在如下两种情况:

(1)开发或生产环境的微服务项目部署在多台服务器上,此时需要监控多台服务器下的日志信息情况;

(2)一台服务器下既存在开发环境的微服务项目,又存在生产环境的微服务项目,此时需要将搜集到的日志信息加以区分。

这种情况下我们可以在 Filebeat 采集日志时为日志信息指定每个不同环境的唯一前缀后再输出。例如:采集融合开发环境的日志时,我们可以在 Filebeat 中进行配置,将采集到的日志添加统一的前缀 (integration_dev-服务名称)加以区分。这样我们可以针对不同的环境,在采集日志输出之前都为其添加唯一的前缀,最终在 Kibana 中使用我们配置的唯一前缀(integration_dev-*)来创建索引模式来获取我们所需环境的日志信息。 而在这种场景下,使用 FileBeat 就要比使用 Logstash 更加节约资源了。

4.环境准备

Elasticsearch运行时要求vm.max_map_count内核参数必须大于262144,因此开始之前需要确保这个参数正常调整过。

$ sysctl -w vm.max_map_count=262144

也可以在ES的的编排文件中增加一个initContainer来修改内核参数,但这要求kublet启动的时候必须添加了--allow-privileged参数,但是一般生产中不会给加这个参数,因此最好在系统供给的时候要求这个参数修改完成。

ES的配置方式

  • 使用Cluster Update Setting API动态修改配置
  • 使用配置文件的方式,配置文件默认在 config 文件夹下,具体位置取决于安装方式。
    • elasticsearch.yml 配置Elasticsearch
    • jvm.options 配置ES JVM参数
    • log4j.properties 配置ES logging参数
  • 使用Prompt方式在启动时输入

最常使用的配置方式为使用配置文件,ES的配置文件为yaml格式,格式要求和Kubernetes的编排文件一样。配置文件中可以引用环境变量,例如node.name: ${HOSTNAME}

ES的节点

ES的节点Node可以分为几种角色:

  • Master-eligible node,是指有资格被选为Master节点的Node,可以统称为Master节点。设置node.master: true
  • Data node,存储数据的节点,设置方式为node.data: true
  • Ingest node,进行数据处理的节点,设置方式为node.ingest: true
  • Trible node,为了做集群整合用的。

对于单节点的Node,默认是master-eligible和data,对于多节点的集群,就要仔细规划每个节点的角色。

5.单实例方式部署ELK

单实例部署ELK的方法非常简单,可以参考Github上的elk-single.yaml文件,整体就是创建一个ES的部署,创建一个Kibana的部署,创建一个ES的Headless服务,创建一个Kiana的NodePort服务,本地通过节点的NodePort访问Kibana。

创建elk-single.yaml

kind: List
apiVersion: v1
items:
- apiVersion: apps/v1beta1
  kind: Deployment
  metadata:
    name: kb-single
  spec:
    replicas: 1
    template:
      metadata:
        name: kb-single
        labels:
          app: kb-single
      spec:
        containers:
        - image: docker.elastic.co/kibana/kibana:6.4.0
          name: kb
          env:
          - name: ELASTICSEARCH_URL
            value: "http://es-single:9200"
          ports:
          - name: http
            containerPort: 5601
- apiVersion: v1
  kind: Service
  metadata:
    name: kb-single-svc
  spec:
    type: NodePort
    ports:
    - name: http
      port: 5601
      targetPort: 5601 
      nodePort: 32601
    selector:
      app: kb-single            
- apiVersion: apps/v1beta1
  kind: Deployment
  metadata:
    name: es-single
  spec:
    replicas: 1
    template:
      metadata:
        name: es-single
        labels:
          app: es-single
      spec:
        containers:
        - image: docker.elastic.co/elasticsearch/elasticsearch:6.4.0
          name: es
          env:
          - name: network.host
            value: "_site_"
          - name: node.name
            value: "${HOSTNAME}"
          - name: discovery.zen.ping.unicast.hosts
            value: "${ES_SINGLE_NODEPORT_SERVICE_HOST}"
          - name: cluster.name
            value: "test-single"
          - name: ES_JAVA_OPTS
            value: "-Xms128m -Xmx128m"
          volumeMounts:
          - name: es-single-data
            mountPath: /usr/share/elasticsearch/data
        volumes:
          - name: es-single-data
            emptyDir: {}
- apiVersion: v1
  kind: Service
  metadata: 
    name: es-single-nodeport
  spec:
    type: NodePort
    ports:
    - name: http
      port: 9200
      targetPort: 9200
      nodePort: 31200
    - name: tcp
      port: 9300
      targetPort: 9300
      nodePort: 31300
    selector:
      app: es-single
- apiVersion: v1
  kind: Service
  metadata:
    name: es-single
  spec:
    clusterIP: None
    ports:
    - name: http
      port: 9200
    - name: tcp
      port: 9300
    selector:
      app: es-single
[root@hzero_dev3 elk]# kubectl apply -f elk-single.yaml
deployment.apps/kb-single created
service/kb-single-svc created
deployment.apps/es-single created
service/es-single-nodeport created
service/es-single created

6. 集群部署ELK

6.1 不区分集群中的节点角色

创建文件:elk-cluster.yaml

kind: List
apiVersion: v1
items:
- apiVersion: apps/v1beta1
  kind: Deployment
  metadata:
    name: kb-single
    labels:
      author: shiqiang
  spec:
    replicas: 1
    template:
      metadata:
        name: kb-single
        labels:
          app: kb-single
          author: shiqiang
      spec:
        containers:
        - image: docker.elastic.co/kibana/kibana:6.4.0
          name: kb
          env:
          - name: ELASTICSEARCH_URL
            value: "http://es-cluster:9200"
          ports:
          - name: http
            containerPort: 5601
- apiVersion: v1
  kind: Service
  metadata:
    name: kb-single-svc
    labels:
      author: shiqiang
  spec:
    type: NodePort
    ports:
    - name: http
      port: 5601
      targetPort: 5601 
      nodePort: 32601
    selector:
      app: kb-single            
- apiVersion: apps/v1beta1
  kind: StatefulSet
  metadata:
    name: es-cluster
  spec:
    serviceName: es-cluster
    replicas: 3
    template:
      metadata:
        name: es-cluster
        labels:
          app: es-cluster
          author: shiqiang
      spec:
        containers:
        - image: docker.elastic.co/elasticsearch/elasticsearch:6.4.0
          name: es
          resources:
            limits:
              cpu: 300m
              memory: 512Mi
            requests:
              cpu: 200m
              memory: 256Mi
          env:
          - name: network.host
            value: "_site_"
          - name: node.name
            value: "${HOSTNAME}"
          - name: discovery.zen.ping.unicast.hosts
            value: "es-cluster"
          - name: discovery.zen.minimum_master_nodes
            value: "2"
          - name: cluster.name
            value: "test-cluster"
          - name: ES_JAVA_OPTS
            value: "-Xms128m -Xmx128m"
          volumeMounts:
          - name: es-cluster-data
            mountPath: /usr/share/elasticsearch/data
        volumes:
          - name: es-cluster-data
            emptyDir: {}
- apiVersion: v1
  kind: Service
  metadata: 
    name: es-cluster-nodeport
    labels:
      author: shiqiang
  spec:
    type: NodePort
    ports:
    - name: http
      port: 9200
      targetPort: 9200
      nodePort: 31200
    - name: tcp
      port: 9300
      targetPort: 9300
      nodePort: 31300
    selector:
      app: es-cluster
- apiVersion: v1
  kind: Service
  metadata:
    name: es-cluster
    labels:
      author: shiqiang
  spec:
    clusterIP: None
    ports:
    - name: http
      port: 9200
    - name: tcp
      port: 9300
    selector:
      app: es-cluster
[root@hzero_dev3 elk]# kubectl apply -f elk-cluster.yaml
deployment.apps/kb-single created
service/kb-single-svc created
deployment.apps/es-single created
service/es-single-nodeport created
service/es-single created

效果如下
在这里插入图片描述

6.2 区分集群中节点角色

如果需要区分节点的角色,就需要建立两个StatefulSet部署,一个是Master集群,一个是Data集群。Data集群的存储我这里为了简单使用了emptyDir,可以使用localStorage或者hostPath,关于存储的介绍,可以参考Kubernetes存储系统介绍。这样就可以避免Data节点在本机重启时发生数据丢失而重建索引,但是如果发生迁移的话,如果想保留数据,只能采用共享存储的方案了。具体的编排文件在这里elk-cluster-with-role

创建:elk-cluster-with-role.yaml

kind: List
apiVersion: v1
items:
- apiVersion: apps/v1beta1
  kind: Deployment
  metadata:
    name: kb-single
    labels:
      author: shiqiang
  spec:
    replicas: 1
    template:
      metadata:
        name: kb-single
        labels:
          app: kb-single
          author: shiqiang
      spec:
        containers:
        - image: docker.elastic.co/kibana/kibana:6.4.0
          name: kb
          env:
          - name: ELASTICSEARCH_URL
            value: "http://es-cluster:9200"
          ports:
          - name: http
            containerPort: 5601
- apiVersion: v1
  kind: Service
  metadata:
    name: kb-single-svc
    labels:
      author: shiqiang
  spec:
    type: NodePort
    ports:
    - name: http
      port: 5601
      targetPort: 5601 
      nodePort: 32601
    selector:
      app: kb-single            
- apiVersion: apps/v1beta1
  kind: StatefulSet
  metadata:
    name: es-cluster
  spec:
    serviceName: es-cluster
    replicas: 3
    selector:
      matchLabels:
        app: es-cluster
    template:
      metadata:
        name: es-cluster
        labels:
          app: es-cluster
          author: shiqiang
          role: master
      spec:
        containers:
        - image: docker.elastic.co/elasticsearch/elasticsearch:6.4.0
          name: es
          resources:
            limits:
              cpu: 300m
              memory: 512Mi
            requests:
              cpu: 200m
              memory: 256Mi
          env:
          - name: network.host
            value: "_site_"
          - name: node.name
            value: "${HOSTNAME}"
          - name: discovery.zen.ping.unicast.hosts
            value: "es-cluster"
          - name: discovery.zen.minimum_master_nodes
            value: "2"
          - name: cluster.name
            value: "test-cluster"
          - name: node.master
            value: "true"
          - name: node.data
            value: "false"
          - name: node.ingest
            value: "false"
          - name: ES_JAVA_OPTS
            value: "-Xms128m -Xmx128m"
          volumeMounts:
          - name: es-cluster-storage
            mountPath: /usr/share/elasticsearch/data
        volumes:
          - name: es-cluster-storage
            emptyDir: {}
- apiVersion: apps/v1beta1
  kind: StatefulSet
  metadata:
    name: es-cluster-data
  spec:
    serviceName: es-cluster-data
    replicas: 3
    selector:
      matchLabels:
        app: es-cluster-data
    template:
      metadata:
        name: es-cluster-data
        labels:
          app: es-cluster-data
          author: shiqiang
          role: master
      spec:
        containers:
        - image: docker.elastic.co/elasticsearch/elasticsearch:6.4.0
          name: es-data
          resources:
            limits:
              cpu: 300m
              memory: 512Mi
            requests:
              cpu: 200m
              memory: 256Mi
          env:
          - name: network.host
            value: "_site_"
          - name: node.name
            value: "${HOSTNAME}"
          - name: discovery.zen.ping.unicast.hosts
            value: "es-cluster"
          - name: discovery.zen.minimum_master_nodes
            value: "2"
          - name: cluster.name
            value: "test-cluster"
          - name: node.master
            value: "false"
          - name: node.data
            value: "true"
          - name: node.ingest
            value: "false"
          - name: ES_JAVA_OPTS
            value: "-Xms128m -Xmx128m"
          volumeMounts:
          - name: es-cluster-storage
            mountPath: /usr/share/elasticsearch/data
        volumes:
          - name: es-cluster-storage
            emptyDir: {}
- apiVersion: v1
  kind: Service
  metadata: 
    name: es-cluster-nodeport
    labels:
      author: shiqiang
  spec:
    type: NodePort
    ports:
    - name: http
      port: 9200
      targetPort: 9200
      nodePort: 31200
    - name: tcp
      port: 9300
      targetPort: 9300
      nodePort: 31300
    selector:
      app: es-cluster
- apiVersion: v1
  kind: Service
  metadata:
    name: es-cluster
    labels:
      author: shiqiang
  spec:
    clusterIP: None
    ports:
    - name: http
      port: 9200
    - name: tcp
      port: 9300
    selector:
      app: es-cluster
[root@hzero_dev3 elk]# kubectl apply -f elk-cluster-with-role.yaml
deployment.apps/kb-single created
service/kb-single-svc created
deployment.apps/es-single created
service/es-single-nodeport created
service/es-single created

效果如下
在这里插入图片描述

7.使用Filebeat监控收集容器日志

使用Logstash,可以监测具有一定命名规律的日志文件,但是对于容器日志,很多文件名都是没有规律的,这种情况比较适合使用Filebeat来对日志目录进行监测,发现有更新的日志后上送到Logstash处理或者直接送入到ES中。

每个Node节点上的容器应用日志,默认都会在/var/log/containers目录下创建软链接,这里我遇到了两个小问题,第一个就是当时挂载hostPath的时候没有挂载软链接的目的文件夹,导致在容器中能看到软链接,但是找不到对应的文件;第二个问题是宿主机上这些日志权限都是root,而Pod默认用filebeat用户启动的应用,因此要单独设置下。

8.统一部署

1.安装elasticsearch

docker pull elasticsearch:6.8.2

运行elasticsearch

docker run -p 9200:9200 -p 9300:9300 --name elasticsearch \
-e "discovery.type=single-node" \
-e "cluster.name=elasticsearch" \
-d elasticsearch:6.8.2

测试:

curl -XGET 'localhost:9200'

2.安装elasticsearch-head

docker pull mobz/elasticsearch-head:5

启动服务

docker run -d --name elasticsearch-head -p 9100:9100 docker.io/mobz/elasticsearch-head:5

进入容器配置elasticsearch跨域

docker exec -it 9188378a1ad9 /bin/bash

安装vim

yum -y install vim*

进入到/config/elasticsearch.yml配置文件,添加一下两行代码

http.cors.enabled: true
http.cors.allow-origin: "*"

3.logstash

安装包:

链接:https://pan.baidu.com/s/1MiS2vTvQue4pm54r-HzMlw 
提取码:kbrh

解压,调整配置文件,参数修改完成后保存即可

tar -zxvf logstash-6.8.2.tar.gz
mv logstash-6.8.2 logstash
cd logstash/config
cp logstash-sample.conf syslog.conf
vim syslog.conf

示例配置如下:

input {
  beats {
    # 端口信息
    port => 5044
  }
}

# 对filebeat中传输过来的信息进行过滤
filter {

   mutate {
      rename => { "[host][name]" => "host" }
   }

   # 排除tag中无用的参数,避免生成的ES索引格式有问题。注意:若不操作filebeat的tag时则无需该if条件及里面的mutate配置
   if "beats_input_codec_plain_applied" in [tags] {
        mutate {
            remove_tag => ["beats_input_codec_plain_applied"]
        }
    }
   # 标准配置,若无特殊需求可不做修改
   grok {
       match => { "message" => [
            "\[%{TIMESTAMP_ISO8601:logtime}\]\s*\[%{LOGLEVEL:loglevel}\]\s*\[%{IPORHOST:instancehost}\]\s*\[%{DATA:module}\]\s*%{JAVACLASS:class}\s*\[pic:%{DATA:pic}\]\s*\[%{DATA:keys}\]\s*%{GREEDYDATA:content}",

            "\[%{TIMESTAMP_ISO8601:logtime}\]\s*\[%{LOGLEVEL:loglevel}\]\s*\[%{IPORHOST:instancehost}\]\s*\[%{DATA:module}\]\s*%{JAVACLASS:class}\s*\[pic:%{DATA:pic}\]\s*%{GREEDYDATA:content}",

            "\[%{TIMESTAMP_ISO8601:logtime}\]\s*%{GREEDYDATA:content}",

            "%{GREEDYDATA:content}"
       ]}
    }
    # 标准配置,若无特殊需求可不做修改
    date {
        match => [ "logtime", "ISO8601", "yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd HH:mm:ss,SSS" ]
        target => "@timestamp"
        timezone => "Asia/Shanghai"
    }
}
# 配置输出到ES中的索引格式
output {
  elasticsearch {
    # elasticsearch的hosts地址。注意:需将localhost修改为实际的地址
    hosts => ["http://172.28.8.104:9200"]
    # 以filebeat定义的tags为前缀,日期为后缀组成索引,例如:(integration_dev-hzero-platform-2019.11.02)。注意:该配置需修改为实际想要的索引格式。
    index => "%{[tags]}-%{+YYYY.MM.dd}"
    # elastic用户名
    user => "elastic"
    # 注意:此处的密码需修改为实际elastic用户的密码,未修改时默认为changeMe,建议统一进行修改
    password => "changeMe"
  }
}

启动logstash

# 直接启动
./logstash -f ../config/syslog.conf 
# 后台启动
nohup ./bin/logstash -f ./config/syslog.conf &

4.kibana

下载

链接:https://pan.baidu.com/s/1HDBwF8bXHnTKs8dDAqwUwQ 
提取码:tdhh

解压,调整配置文件

tar -zxvf kibana-6.8.3.tar.gz
mv kibana-6.8.3 kibana
cd kibana/config
vim kibana.yml

示例配置如下:

server.port: 5601 # Kibana端口号
server.host: "localhost" # kibana地址。注意:该配置需修改为实际kibana的host地址。
elasticsearch.hosts: ["http://localhost:9200"] # kibana连接es的host。注意:该配置需修改为实际es的host地址。
elasticsearch.username: "elastic" # es用户名,当es开启认证授权时需要配置。
elasticsearch.password: "changeMe" # es密码,当es开启认证授权时需要配置,该配置需修改为实际elastic账户的密码。
i18n.locale: "zh-CN" # 配置中文Kibana环境。注意:该配置按需进行配置,不加时默认为英文操作环境。

启动Kibana

# 直接启动
./bin/kibana
# 后台启动
nohup ./bin/kibana &

访问:http://172.28.8.104:5601

在这里插入图片描述

5.filebeat

安装

链接:https://pan.baidu.com/s/1E0o-0qKRJ0m1SnidtZrGYQ 
提取码:jwkh

解压,调整配置文件

tar -zxvf filebeat-6.8.3-linux-x86_64.tar.gz
mv filebeat-6.8.3 filebeat
cd filebeat
vim filebeat.yml

示例配置如下:

force_close_files: true # 当文件改名或删除时,会自动关闭文件
# 配置filebeat传输到logstash中的参数,其中tags字段中的内容是我们定义的最终es的索引格式,在logstash中会在其后面拼接时间戳来区分相同服务不同日期的日志信息。
filebeat.prospectors:

- type: log

  enabled: true
  # 指定需要搜集的日志文件路径。注意:该配置需修改为实际需搜集的服务日志文件路径。
  paths:
    - /hzero-repo/hzero-platform/target/app.log
  # 标记tag,用于logstash中设置索引。注意:该配置需修改为实际要设置的索引前缀,推荐格式:环境类型+服务名称,例如融合开发环境平台服务则为integration_dev-hzero-platform。
  tags: ["integration_dev-hzero-platform"]
  multiline.pattern: ^\[
  multiline.negate: true
  multiline.match: after

# 开启filebeat输出到logstash。注意:在此之前需要禁用掉默认开启的es输出
output.logstash:
  # Logstash hosts。注意:该配置需修改为实际logstash的hosts地址
  hosts: ["localhost:5044"]
  
processors:
  - add_host_metadata:
      netinfo.enabled: true
      cache.ttl: 5m
  - add_cloud_metadata: ~

启动filebeat

nohup ./filebeat &

在这里插入图片描述
实时查看日志

在这里插入图片描述

在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐