教你在k8s中搭建日志监控系统

背景

在docker环境中搭建日志监控系统,技术栈为logpilot+elasticsearch+kibana。

logpilot的镜像版本为 registry.cn-hangzhou.aliyuncs.com/acs/log-pilot:0.9.5-filebeat,采用的是filebeat版本的。
elasticsearch的镜像版本为elasticsearch:6.8.9。
kibana的镜像版本为kibana:6.8.9。

特别需要注意的一点是logpilot只支持es6.x版本,不支持es7.x版本

logpilot

关于logpilot的介绍可以看我的另一篇文章,这里不在过多介绍。
logpilot的githup地址为https://github.com/AliyunContainerService/log-pilot,在readme中可以看到Run pilot如下

docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /etc/localtime:/etc/localtime \
    -v /:/host:ro \
    --cap-add SYS_ADMIN \
    registry.cn-hangzhou.aliyuncs.com/acs/log-pilot:0.9.5-filebeat

从上面可以看出以下几点注意的:

  • 需要使用宿主机的docker,所以将宿主机的docker.sock映射容器内。
  • 将宿主机的/目录下的所有文件映射到容器内的/host内,以readOnly的方式。pod内的标准输出日志和以emptyDir的方式映射的日志文件其实都存在于宿主机上,所以需要将宿主机上的文件映射到logpilot容器内,这样logpilot容器才可以正常采集到各个pod的日志,这也是为什么logpilot容器需要将宿主机的/目录下的所有文件映射到容器内的/host内原因,而且必须是/host目录,不能是自定义目录名。
  • 增加容器SYS_ADMIN权限。将宿主机的/目录下的所有文件映射到容器内的/host内,普通用户是没有这个权限的,所以需要给宿主机增加admin权限。
  • 在docker hup上看不到官方的log-pilot官方镜像,既然readme中给的镜像地址是registry.cn-hangzhou.aliyuncs.com/acs,那么该地址就可以理解为官方镜像地址了。

采集日志通常为sidecar模式和node模式,这里logpilot采用的是node模式,所以采用k8s资源类型DaemonSet,logpilot.yaml文件如下,

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: logpilot
  namespace: laravel
spec:
  selector:
    matchLabels:
      log.app: logpilot
  template:
    metadata:
      labels:
        log.app: logpilot
    spec:
      containers:
        - name: logpilot
          image: harbor.maigengduo.com/elk/log-pilot:0.9.7-filebeat
          env:
            - name: FILEBEAT_OUTPUT
              value: elasticsearch
            - name: ELASTICSEARCH_HOSTS
              value: es-cluster-api:9200
          volumeMounts:
            - name: docker-sock
              mountPath: /var/run/docker.sock
            - name: node-all
              mountPath: /host/
              readOnly: true
          securityContext:
            capabilities:
              add:
                - SYS_ADMIN
      restartPolicy: Always
      volumes:
        - name: docker-sock
          hostPath:
            path: /var/run/docker.sock
        - name: node-all
          hostPath:
            path: /

当pod处于running状态后,进入其中任意一个pod,查看下主进程/pilot/entrypoint,有下面这一段话,大概意思就是当logpilot采用的是filebeat插件的话执行/pilot/config.filebeat文件,否则的话,也就是采用fluentd插件的话执行文件/pilot/config.fluentd文件,很明显我们使用的是filebeat插件。

def config():                                                                                                 
    pilot_type = os.environ.get(ENV_PILOT_TYPE)                                                               
    if pilot_filebeat == pilot_type:                                                                          
        print "start log-pilot:", pilot_filebeat                                                              
        subprocess.check_call(['/pilot/config.filebeat'])
    else:                                                
        print "start log-pilot:", pilot_fluentd          
        subprocess.check_call(['/pilot/config.fluentd']) 

接着我们查看下/pilot/config.filebeat文件,内容如下,可以看出filebeat支持的后端存储有es,file,logstash,redis,kafka,LOGGING_OUTPUT环境变量决定了使用哪种存储后端。我们使用es做为存储后端,所以需要设置环境变量LOGGING_OUTPUT为elasticsearch,然后执行es(),可以看到有一个ELASTICSEARCH_HOSTS环境变量,该变量就是es的9200端口对外暴露的服务地址,也就是下面es章节中创建的clusterIp类型的service,还有看到ELASTICSEARCH_USER,ELASTICSEARCH_PASSWORD环境变量,如果你的es没有设置user和password这里将不需要设置这俩变量,其他的环境变量则不需要设置,所以想要使用es做为存储后端需要在logpilot的deployment中设置LOGGING_OUTPUTELASTICSEARCH_HOSTS俩环境变量,在上面yaml文件中可以看到。es()的作用就是将这些配置文件写入到filebeat的配置文件/etc/filebeat/filebeat.yml中。

#!/bin/sh

set -e

FILEBEAT_CONFIG=/etc/filebeat/filebeat.yml
if [ -f "$FILEBEAT_CONFIG" ]; then
    echo "$FILEBEAT_CONFIG has been existed"
    exit
fi

mkdir -p /etc/filebeat/prospectors.d

assert_not_empty() {
    arg=$1
    shift
    if [ -z "$arg" ]; then
        echo "$@"
        exit 1
    fi
}

cd $(dirname $0)

base() {
cat >> $FILEBEAT_CONFIG << EOF
path.config: /etc/filebeat
path.logs: /var/log/filebeat
path.data: /var/lib/filebeat/data
filebeat.registry_file: /var/lib/filebeat/registry
filebeat.shutdown_timeout: ${FILEBEAT_SHUTDOWN_TIMEOUT:-0}
logging.level: ${FILEBEAT_LOG_LEVEL:-info}
logging.metrics.enabled: ${FILEBEAT_METRICS_ENABLED:-false}
logging.files.rotateeverybytes: ${FILEBEAT_LOG_MAX_SIZE:-104857600}
logging.files.keepfiles: ${FILEBEAT_LOG_MAX_FILE:-10}
logging.files.permissions: ${FILEBEAT_LOG_PERMISSION:-0600}
${FILEBEAT_MAX_PROCS:+max_procs: ${FILEBEAT_MAX_PROCS}}
setup.template.name: "${FILEBEAT_INDEX:-filebeat}"
setup.template.pattern: "${FILEBEAT_INDEX:-filebeat}-*"
filebeat.config:
    prospectors:
        enabled: true
        path: \${path.config}/prospectors.d/*.yml
        reload.enabled: true
        reload.period: 10s
EOF
}

es() {
if [ -f "/run/secrets/es_credential" ]; then
    ELASTICSEARCH_USER=$(cat /run/secrets/es_credential | awk -F":" '{ print $1 }')
    ELASTICSEARCH_PASSWORD=$(cat /run/secrets/es_credential | awk -F":" '{ print $2 }')
fi

if [ -n "$ELASTICSEARCH_HOSTS" ]; then
    ELASTICSEARCH_HOSTS=$(echo $ELASTICSEARCH_HOSTS|awk -F, '{for(i=1;i<=NF;i++){printf "\"%s\",", $i}}')
    ELASTICSEARCH_HOSTS=${ELASTICSEARCH_HOSTS%,}
else
    assert_not_empty "$ELASTICSEARCH_HOST" "ELASTICSEARCH_HOST required"
    assert_not_empty "$ELASTICSEARCH_PORT" "ELASTICSEARCH_PORT required"
    ELASTICSEARCH_HOSTS="\"$ELASTICSEARCH_HOST:$ELASTICSEARCH_PORT\""
fi

cat >> $FILEBEAT_CONFIG << EOF
$(base)
output.elasticsearch:
    hosts: [$ELASTICSEARCH_HOSTS]
    index: ${ELASTICSEARCH_INDEX:-filebeat}-%{+yyyy.MM.dd}
    ${ELASTICSEARCH_SCHEME:+protocol: ${ELASTICSEARCH_SCHEME}}
    ${ELASTICSEARCH_USER:+username: ${ELASTICSEARCH_USER}}
    ${ELASTICSEARCH_PASSWORD:+password: ${ELASTICSEARCH_PASSWORD}}
    ${ELASTICSEARCH_WORKER:+worker: ${ELASTICSEARCH_WORKER}}
    ${ELASTICSEARCH_PATH:+path: ${ELASTICSEARCH_PATH}}
    ${ELASTICSEARCH_BULK_MAX_SIZE:+bulk_max_size: ${ELASTICSEARCH_BULK_MAX_SIZE}}
EOF
}

default() {
echo "use default output"
cat >> $FILEBEAT_CONFIG << EOF
$(base)
output.console:
    pretty: ${CONSOLE_PRETTY:-false}
EOF
}

file() {
assert_not_empty "$FILE_PATH" "FILE_PATH required"

cat >> $FILEBEAT_CONFIG << EOF
$(base)
output.file:
    path: $FILE_PATH
    ${FILE_NAME:+filename: ${FILE_NAME}}
    ${FILE_ROTATE_SIZE:+rotate_every_kb: ${FILE_ROTATE_SIZE}}
    ${FILE_NUMBER_OF_FILES:+number_of_files: ${FILE_NUMBER_OF_FILES}}
    ${FILE_PERMISSIONS:+permissions: ${FILE_PERMISSIONS}}
EOF
}

logstash() {
assert_not_empty "$LOGSTASH_HOST" "LOGSTASH_HOST required"
assert_not_empty "$LOGSTASH_PORT" "LOGSTASH_PORT required"

cat >> $FILEBEAT_CONFIG << EOF
$(base)
output.logstash:
    hosts: ["$LOGSTASH_HOST:$LOGSTASH_PORT"]
    index: ${FILEBEAT_INDEX:-filebeat}-%{+yyyy.MM.dd}
    ${LOGSTASH_WORKER:+worker: ${LOGSTASH_WORKER}}
    ${LOGSTASH_LOADBALANCE:+loadbalance: ${LOGSTASH_LOADBALANCE}}
    ${LOGSTASH_BULK_MAX_SIZE:+bulk_max_size: ${LOGSTASH_BULK_MAX_SIZE}}
    ${LOGSTASH_SLOW_START:+slow_start: ${LOGSTASH_SLOW_START}}
EOF
}

redis() {
assert_not_empty "$REDIS_HOST" "REDIS_HOST required"
assert_not_empty "$REDIS_PORT" "REDIS_PORT required"

cat >> $FILEBEAT_CONFIG << EOF
$(base)
output.redis:
    hosts: ["$REDIS_HOST:$REDIS_PORT"]
    key: "%{[fields.topic]:filebeat}"
    ${REDIS_WORKER:+worker: ${REDIS_WORKER}}
    ${REDIS_PASSWORD:+password: ${REDIS_PASSWORD}}
    ${REDIS_DATATYPE:+datatype: ${REDIS_DATATYPE}}
    ${REDIS_LOADBALANCE:+loadbalance: ${REDIS_LOADBALANCE}}
    ${REDIS_TIMEOUT:+timeout: ${REDIS_TIMEOUT}}
    ${REDIS_BULK_MAX_SIZE:+bulk_max_size: ${REDIS_BULK_MAX_SIZE}}
EOF
}

kafka() {
assert_not_empty "$KAFKA_BROKERS" "KAFKA_BROKERS required"
KAFKA_BROKERS=$(echo $KAFKA_BROKERS|awk -F, '{for(i=1;i<=NF;i++){printf "\"%s\",", $i}}')
KAFKA_BROKERS=${KAFKA_BROKERS%,}

cat >> $FILEBEAT_CONFIG << EOF
$(base)
output.kafka:
    hosts: [$KAFKA_BROKERS]
    topic: '%{[topic]}'
    ${KAFKA_VERSION:+version: ${KAFKA_VERSION}}
    ${KAFKA_USERNAME:+username: ${KAFKA_USERNAME}}
    ${KAFKA_PASSWORD:+password: ${KAFKA_PASSWORD}}
    ${KAFKA_WORKER:+worker: ${KAFKA_WORKER}}
    ${KAFKA_PARTITION_KEY:+key: ${KAFKA_PARTITION_KEY}}
    ${KAFKA_PARTITION:+partition: ${KAFKA_PARTITION}}
    ${KAFKA_CLIENT_ID:+client_id: ${KAFKA_CLIENT_ID}}
    ${KAFKA_METADATA:+metadata: ${KAFKA_METADATA}}
    ${KAFKA_BULK_MAX_SIZE:+bulk_max_size: ${KAFKA_BULK_MAX_SIZE}}
    ${KAFKA_BROKER_TIMEOUT:+broker_timeout: ${KAFKA_BROKER_TIMEOUT}}
    ${KAFKA_CHANNEL_BUFFER_SIZE:+channel_buffer_size: ${KAFKA_CHANNEL_BUFFER_SIZE}}
    ${KAFKA_KEEP_ALIVE:+keep_alive ${KAFKA_KEEP_ALIVE}}
    ${KAFKA_MAX_MESSAGE_BYTES:+max_message_bytes: ${KAFKA_MAX_MESSAGE_BYTES}}
    ${KAFKA_REQUIRE_ACKS:+required_acks: ${KAFKA_REQUIRE_ACKS}}
EOF
}

count(){
cat >> $FILEBEAT_CONFIG << EOF
$(base)
output.count:
EOF
}

if [ -n "$FILEBEAT_OUTPUT" ]; then
    LOGGING_OUTPUT=$FILEBEAT_OUTPUT
fi

case "$LOGGING_OUTPUT" in
    elasticsearch)
        es;;
    logstash)
        logstash;;
    file)
        file;;
    redis)
        redis;;
    kafka)
        kafka;;
    count)
        count;;
    *)
        default
esac

接着我们继续看/etc/filebeat/filebeat.yml文件,正是上面es()中写入的部分。

path.config: /etc/filebeat
path.logs: /var/log/filebeat
path.data: /var/lib/filebeat/data
filebeat.registry_file: /var/lib/filebeat/registry
filebeat.shutdown_timeout: 0
logging.level: info
logging.metrics.enabled: false
logging.files.rotateeverybytes: 104857600
logging.files.keepfiles: 10
logging.files.permissions: 0600

setup.template.name: "filebeat"
setup.template.pattern: "filebeat-*"
filebeat.config:
    prospectors:
        enabled: true
        path: ${path.config}/prospectors.d/*.yml
        reload.enabled: true
        reload.period: 10s

output.elasticsearch:
    hosts: ["es-cluster-api:9200"]
    index: filebeat-%{+yyyy.MM.dd}

如果这些都正常,则说明logpilot部署es成功。

日志目录

我们知道pod的日志分为标准输出日志和容器内的文件日志俩种,这俩种日志在宿主机上的存储目录是哪里呢?

  • pod的标准输出日志目录
    /var/lib/docker/containers/containerId/ *-json.log
  • pod内文件日志以emptyDir的方式映射到宿主机上的日志目录
    /var/lib/kubelet/pods/podId/*

elasticsearch

创建configmap

创建一个es的configmap,说白了就是创建es的配置文件,以volume的方式映射到宿主机内。

apiVersion: v1
kind: ConfigMap
metadata:
  name: es-config
  namespace: laravel
data:
  #此配置是针对es6.8中的配置,7.x版本的配置有很多变化,discovery参数变化也很多
  elasticsearch.yml: |
    node.name: ${HOSTNAME}
    cluster.name: "es-cluster"
    network.host: 0.0.0.0
    bootstrap.memory_lock: false
    discovery.zen.ping.unicast.hosts: es-cluster-0.es-cluster-discovery
    discovery.zen.minimum_master_nodes: 2
    discovery.zen.ping_timeout: 6s
    discovery.zen.fd.ping_interval: 3s

这里需要介绍一下上面这些参数

  • node.name

集群中节点的名称,如果不设置则在启动时给节点分配一个随机通用唯一标识符作为名称,这是设置成HOSTNAME的环境变量,在statefulset的pod中的hostName和podName是保持一致的。

ip             heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
172.22.235.135           67          99   1    0.56    0.43     0.35 mdi       -      es-cluster-0
172.22.189.119           62         100   1    2.46    1.00     0.48 mdi       *      es-cluster-1
172.22.235.138           72          99   1    0.56    0.43     0.35 mdi       -      es-cluster-2

可以看到es-cluster-1为master节点,*代表主节点,在node.role可以看到每个节点都是mdi,代表的意思就是master data和ingrest,候选节点,数据节点和协调节点。

  • cluster.name

ES 集群由多个节点组成,每个节点配置相同的 cluster.name 即可加入集群。

  • bootstrap.memory_lock

官方文档上有这个参数的解释,https://www.elastic.co/guide/en/elasticsearch/reference/7.10/_memory_lock_check.html
JVM执行垃圾回收时,它会触及堆的每个页面。如果将这些页面中的任何一个换出到磁盘,则必须将其换回内存。这导致大量磁盘崩溃,Elasticsearch宁愿使用它们来处理请求。为了禁止交换需要将 bootstrap.memory_lock设置为true,且需要设置memlock unlimited

在docker-compose.yaml可以设置如下,可参考官方文档

ersion: '2.2'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.8.13
    container_name: elasticsearch
    environment:
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata1:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - esnet

但是在k8s中并没有ulimits和memlock,所以我设置成了false了,在生产环境中这里强烈建设设置成true,如果有小伙伴知道怎么设置memlock,请留言。

查看节点是否启动memlock,可通过如下命令

GET _nodes?filter_path=**.mlockall
{
    "nodes": {
        "9giihmDNRdS136KT52Gl5g": {
            "process": {
                "mlockall": true
            }
        },
        "X0zQESeeT8uJ9kVXvHpl-w": {
            "process": {
                "mlockall": true
            }
        },
        "w4hYw86rQhqL1ayGyUK1Kw": {
            "process": {
                "mlockall": true
            }
        }
    }
}
  • discovery.zen.ping.unicast.hosts

ES 内部是如何通过一个相同的设置 cluster.name 就能将不同的节点连接到同一个集群的?答案是 Zen Discovery。
Elasticsearch 默认被配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。
如果集群的节点运行在不同的机器上,使用单播,你可以为 Elasticsearch 提供一些它应该去尝试连接的节点列表。
当一个节点联系到单播列表中的成员时,它就会得到整个集群所有节点的状态,然后它会联系 Master 节点,并加入集群。
这意味着单播列表不需要包含集群中的所有节点, 它只是需要足够的节点,当一个新节点联系上其中一个并且说上话就可以了,所以我们只设置es-cluster-0.es-cluster-discovery即可,由于statefulset的pod的顺序性,es-cluster-0肯定是最先running的。

  • master和data

每个节点既可以是候选主节点也可以是数据节点,不配置默认都为true,也就是说我们的这份配置使所有节点即候选节点节点,也是数据节点。

候选主节点可以被选举为主节点(Master 节点),集群中只有候选主节点才有选举权和被选举权,其他节点不参与选举的工作。

主节点负责创建索引、删除索引和追踪集群中节点的状态等,数据节点负责数据的存储和相关的操作,例如对数据进行增、删、改、查和聚合等操作,所以数据节点(Data 节点)对机器配置要求比较高,对 CPU、内存和 I/O 的消耗很大。

  • discovery.zen.minimum_master_nodes

选举master节点时,先从各节点认为的 Master 中选,规则很简单,按照 ID 的字典序排序,取第一个。如果各节点都没有认为的 Master ,则从所有节点中选择,规则同上。

这里有个限制条件就是 discovery.zen.minimum_master_nodes ,如果master节点数达不到最小值的限制,则循环上述过程,直到master节点数足够可以开始选举。
只要所有的节点都遵循同样的规则,得到的信息都是对等的,选出来的主节点肯定是一致的。

但分布式系统的问题就出在信息不对等的情况,这时候很容易出现脑裂(Split-Brain)的问题,大多数解决方案就是设置一个 Quorum 值,要求可用节点必须大于 Quorum(一般是超过半数节点),才能对外提供服务

discovery.zen.minimum_master_nodes,见名知意,最小的master nodes数量,也就是说集群中的候选节点数必须达到discovery.zen.minimum_master_nodes才可以选择主节点,通常设置为候选节点数的一半+1

本实验中es statefulset有3个pod,也就意味有3个节点,且有3个都为候选节点,我设置discovery.zen.minimum_master_nodes=1,最后发现集群为3个,3个节点组成了3个集群,可通过_cat/nodes?v查看。

为什么会出现这种情况呢?如果你了解了 discovery.zen.minimum_master_nodes参数的含义也就知道出现这种情况的原因了,当第一个es节点running之后,发现discovery.zen.minimum_master_nodes为1,且自身又是候选节点,所以组成了一个集群,当第二个集群running后发现discovery.zen.minimum_master_nodes为1,且自身又是候选节点,于是第二个节点也自己组成了一个集群,第三个节点也是同样如此。

由此看来discovery.zen.minimum_master_nodes很重要,一定要设置成候选节点数的一半+1

  • discovery.zen.ping_timeout

为了避免脑裂现象的发生,可以适当discovery的调大响应时间,减少误判。通过参数 discovery.zen.ping_timeout 设置节点状态的响应时间,默认为 3s,可以适当调大。

  • discovery.zen.fd.ping_interval

discovery上次ping节点到下次ping节点的间隔时间,适当调大可以避免脑裂的发生,但是如果真的集群发生故障,发现故障的时间也会加长。

创建service

kind: Service
apiVersion: v1
metadata:
  name: es-cluster-api
  namespace: laravel
spec:
  ports:
    - protocol: TCP
      name: web
      port: 9200
      targetPort: 9200
  selector:
    name: es-cluster
---
kind: Service
apiVersion: v1
metadata:
  name: es-cluster-discovery
  namespace: laravel
spec:
  clusterIP: None
  ports:
    - protocol: TCP
      name: web
      port: 9300
      targetPort: 9300
  selector:
    name: es-cluster

创建俩个service,这俩service的作用是什么呢?

  • es-cluster-api

es中的各个节点都为协调节点,虽然对节点做了角色区分,但是用户的请求可以发往任何一个节点,并由该节点负责分发请求、收集结果等操作,通过hash算法转到节点的主分片上即可,而不需要主节点转发。
es集群9200端口用于外部流量访问,所以我们可以创建一个监听9200端口的类型为clusterIp的service,在业务中访问该service时随机转发到任何一台pod都可以。

  • es-cluster-discovery

es集群各节点是分片存储的,就已经决定必须使用statefulset类型了,且es的9300端口是集群节点之间互相连通的,所以需要一个headless service,正好创建一个监听9300端口的headless service即可。

创建es

es使用statefulset资源创建

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: es-cluster
  namespace: laravel
spec:
  selector:
    matchLabels:
      name: es-cluster # has to match .spec.template.metadata.labels
  serviceName: es-cluster-discovery
  replicas: 3 # by default is 1
  template:
    metadata:
      labels:
        name: es-cluster # has to match .spec.selector.matchLabels
    spec:
      initContainers:
        - name: init-sysctl
          image: busybox
          imagePullPolicy: IfNotPresent
          securityContext:
            privileged: true
          #increase mmap limits
          command: ["sysctl","-w","vm.max_map_count=262144"]
      containers:
        - name: elasticsearch
          image: harbor.maigengduo.com/elk/elasticsearch:6.8.9
          resources:
            requests:
              memory: 500Mi
            limits:
              memory: 500Mi
          env:
            - name: ES_JAVA_OPTS
              value: "-Xms200m -Xmx200m"
          ports:
            - containerPort: 9200
              name: es-api
            - containerPort: 9300
              name: es-discovery
          volumeMounts:
            - name: es-data
              mountPath: /usr/share/elasticsearch/data
            #这里是通过configMap的方式将elasticsearch.yml映射进去,也可以通过环境变量的方式,https://www.elastic.co/guide/en/elasticsearch/reference/6.8/docker.html
            - name: es-config
              mountPath: /usr/share/elasticsearch/config/elasticsearch.yml
              subPath: elasticsearch.yml
      volumes:
        - name: es-config
          configMap:
            name: es-config
            items:
              - key: elasticsearch.yml
                path: elasticsearch.yml
  volumeClaimTemplates:
    - metadata:
        name: es-data
      spec:
        accessModes: [ "ReadWriteMany"]
        storageClassName: "es-storage"
        resources:
          requests:
            storage: 1Gi

需要注意几个地方

  • 初始化容器

看到初始化容器执行了一个命令sysctl","-w","vm.max_map_count=262144",可查看官网文章

  • 环境变量ES_JAVA_OPTS

属于es的JVM配置参数,可参考文章https://www.elastic.co/guide/en/elasticsearch/reference/6.8/jvm-options.html,确保堆内存最小值( Xms )与最大值( Xmx )的大小是相同的,防止程序在运行时改变堆内存大小。
Elasticsearch 默认安装后设置的堆内存是 1GB。可通过 ../config/jvm.option 文件进行配置,但是最好不要超过物理内存的50%和超过 32GB。

kibana

创建一个configmap,该configmap就是kibana的配置文件,通过volume的方式映射到pod容器种,注意一下elasticsearch.hosts这个参数就可以了,该参数就是上面esc创建的cluserIp类型的service。

创建configmap

apiVersion: v1
kind: ConfigMap
metadata:
  name: kibana-config
  namespace: laravel
data:
  #此配置是针对es6.8中的配置,7.x版本的配置有很多变化,discovery参数变化也很多
  kibana.yml: |
    server.name: kibana
    server.host: "0"
    elasticsearch.hosts: ["http://es-cluster-api:9200"]

创建kibana

kind: Deployment
apiVersion: apps/v1
metadata:
  name: kibana
  namespace: laravel
  labels:
    name: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      name: kibana
  template:
    metadata:
      labels:
        name: kibana
    spec:
      nodeName: worker2
      containers:
        - name: kibana
          image: harbor.maigengduo.com/elk/kibana:6.8.9
          resources:
            requests:
              memory: 450Mi
            limits:
              memory: 450Mi
          ports:
            - name: kibana-port
              containerPort: 5601
              protocol: TCP
          imagePullPolicy: Always
          #可以配置环境变量,https://www.elastic.co/guide/en/kibana/6.8/docker.html
          # 也可以像es那样创建一个configMap,然后volume kibana的配置文件
          volumeMounts:
            - name: kibana-config
              mountPath: /usr/share/kibana/config/kibana.yml
              subPath: kibana.yml
      volumes:
        - name: kibana-config
          configMap:
            name: kibana-config
            items:
              - key: kibana.yml
                path: kibana.yml
      restartPolicy: Always

创建service

kind: Service
apiVersion: v1
metadata:
  name: kibana
  namespace: laravel
  labels:
    run: kibana
spec:
  type: NodePort
  ports:
    - protocol: TCP
      name: web
      port: 5601
      targetPort: 5601
      nodePort: 30008
  selector:
    name: kibana

cerebro

创建cerebro

kind: Deployment
apiVersion: apps/v1
metadata:
  name: cerebro
  namespace: laravel
  labels:
    name: cerebro
spec:
  replicas: 1
  selector:
    matchLabels:
      name: cerebro
  template:
    metadata:
      labels:
        name: cerebro
    spec:
      nodeName: worker1
      enableServiceLinks: false
      containers:
        - name: cerebro
          # 当前latest版本为0.92
          image: harbor.maigengduo.com/elk/cerebro:latest
#          securityContext:
#            runAsUser: 0
          env:
            - name: "CEREBRO_PORT"
              value: "9000"
          resources:
            requests:
              cpu: 200m
              memory: 300Mi
            limits:
              cpu: 200m
              memory: 300Mi
          ports:
            - name: cerebro-port
              containerPort: 9000
          imagePullPolicy: Always
      restartPolicy: Always

创建cerebro时启动失败,查看log日志Invalid HTTP port 9000,经查询,需要增加俩参数

  • CEREBRO_PORT环境变量
env:
   - name: "CEREBRO_PORT"
   value: "9000"
  • enableServiceLinks
enableServiceLinks: false

具体原因这篇文章给出了解释https://github.com/lmenezes/cerebro/issues/441

创建service

kind: Service
apiVersion: v1
metadata:
  name: cerebro
  namespace: laravel
spec:
  ports:
    - protocol: TCP
      name: web
      port: 9000
      targetPort: 9000
  selector:
    name: cerebro

推荐阅读更多精彩内容