0%

前言

通过 helm 部署的 Loki 默认都是采用一个副本,如果想部署集群方案直接将 Loki 副本数改成 3 或者其它数是不行的,网上集群相关的文档也比较少,不过在 Loki 官方仓库 production 目录下有相关的配置样例,具体参考 production

Loki组件

loki

  • Distributor(分配器)
    Distributor主要负责分发从客户端上报的日志, 在进行完日志的校验之后便会分发给ingester处理. Distributor使用了一致性哈希以及一个可以配置的副本数来决定不同的日志流的后端ingester. 哈希环信息的存储使用了Consul. Ingester会根据自身的状态注册在哈希环上来标识自身可提供服务的状态. Distributor会使用注册租户id和标签集时作为哈希的输入, 计算出对应的哈希环位置, 并找到对应的ingester.
  • Ingester(采集器)
    Ingester是loki中比较核心的服务, 该服务将从Distributor发送来的日志进行转储到后端存储, 并给Querier提供仍在内存中未写盘的数据查询功能.
    Ingester注册在哈希环上的状态有PENDING, JOINING, ACTIVE, LEAVING, UNHEALTHY, 除了ACTIVE状态外, 其余状态都只会提供部分服务。
    在存储到真正的存储前, 日志流只有按顺序收到才会被处理, Ingester会按照标签的组合set构建若干压缩过的chunks, 间隔一段时间将chunks作为整体刷写到后端存储. 刷进后端存储的chunks被标记为只读.
  • Querier frontend
    该服务是一个可选组件,在一组查询器前面,来负责在它们之间公平地调度请求,尽可能地并行化它们并缓存请求。
  • Querier(查询器)
    Querier则是用来提供查询服务, 他使用了LogQL, 一种类似于PromQL的语言作为用户界面. Querier会查询已经写入后端存储的chunks以及仍在ingester内存中的数据, 根据label查找数据后, 再遍历数据查找到满足条件的日志
  • Chunk(块)存储
    Chunk Store是Loki的后端存储框架, 可以支持多种存储方式, 存储分为两部分:索引的存储(Index)和日志数据存储.
    loki

Loki集群方案

loki

  • loki 核心服务 distributor、ingester、querier 没有分离,而是启动在一个实例当中;
  • 是直接用 memberlist 在内存中维护集群状态;
  • 使用 boltdb-shipper 替代其他日志索引方案

Loki集群配置

config:
  ingester:
    lifecycler:
      ring:
        kvstore:
        # store 支持 consul, etcd, inmemory, memberlist
          store: memberlist
        # 设置副本数
        replication_factor: 1
  memberlist:
    # 加入集群的节点地址列表
    join_members: ["loki-1.loki-headless.loki", "loki-2.loki-headless.loki", "loki-0.loki-headless.loki"]

Loki helm安装

Promtail 配置注意事项

  • vim charts/promtail/values.yaml
# Extra volumes to scrape logs from
volumes:
  name: docker
  hostPath:
    # 修改为docker的实际路径
    path: /home/docker/containers
#  ...
volumeMounts:
  name: docker
  # 修改为docker的实际路径
  mountPath: /home/docker/containers
  readOnly: true

参考文档

  • 参考文档:1

常用命令

# 添加仓库, 如
helm repo add loki https://grafana.github.io/loki/charts
# 更新仓库
helm repo update
# 安装服务并设置自定义配置
helm install loki --namespace=loki loki/loki-stack --set grafana.enabled=true
# 下载chart到本地
helm pull loki/loki-stack
# 使用本地chart安装服务
helm install loki --namespace=loki  .
helm install opentelemetry-collector  .
helm install tempo ./tempo/  --values ./tempo/values.yaml -nloki
  • helm install 等价于 helm upgrade --install
  • 如果安装前没有创建 namespace 可以添加命令 --create-namespace 自动创建

Ingress-Nginx Annotation 简介

KubeSphere 基于 Nginx Ingress Controller 实现了项目的网关,作为项目对外的流量入口和项目中各个服务的反向代理。而 Ingress-Nginx 支持配置 Ingress Annotations 来实现不同场景下的灰度发布和测试,可以满足金丝雀发布蓝绿部署A/B 测试等业务场景。

Nginx Annotations 支持以下 5 种 Canary 规则:

  • nginx.ingress.kubernetes.io/canary-by-header:基于 Request Header 的流量切分,适用于灰度发布以及 A/B 测试。当 Request Header 设置为 always 时,请求将会被一直发送到 Canary 版本;当 Request Header 设置为 never 时,请求不会被发送到 Canary 入口;对于任何其他 Header 值,将忽略 Header,并通过优先级将请求与其他金丝雀规则进行优先级的比较。
  • nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口。该规则允许用户自定义 Request Header 的值,必须与上一个 annotation (即:canary-by-header) 一起使用。
  • nginx.ingress.kubernetes.io/canary-by-header-pattern:要匹配的 Request Header 值的 PCRE 正则表达式,作用同上面的 canary-by-header-value 一样,当 Request Header 设置的值满足该正则表达式时,它将被路由到 Canary 入口,必须与 canary-by-header 一起使用。
  • nginx.ingress.kubernetes.io/canary-weight:基于服务权重的流量切分,适用于蓝绿部署,权重范围 0 - 100按百分比将请求路由到 Canary Ingress 中指定的服务。权重为 0 意味着该金丝雀规则不会向 Canary 入口的服务发送任何请求,权重为 100 意味着所有请求都将被发送到 Canary 入口。
  • nginx.ingress.kubernetes.io/canary-by-cookie:基于 cookie 的流量切分,适用于灰度发布与 A/B 测试。用于通知 Ingress 将请求路由到Canary Ingress中指定的服务的 cookie。当 cookie 值设置为 always 时,它将被路由到 Canary 入口;当 cookie 值设置为 never 时,请求不会被发送到 Canary 入口;对于任何其他值,将忽略 cookie 并将请求与其他金丝雀规则进行优先级的比较。

注意:金丝雀规则按优先顺序进行如下排序:

canary-by-header - > canary-by-cookie - > canary-weight

使用

  • 创建两个不同版本的service及ingress
  • 两个ingress使用相关的域名,但是后端服务使用不同版本的
  • canary版本的ingress增加nginx.ingress.kubernetes.io/canary: "true" 启用 Canary
  • 其余策略参考nginx annotation

前提

  • 已部署k8s集群
  • 集群内部署prometheus

步骤

  • 在外部节点安装node-exporter
docker run -d --name node-exporter -p 9100:9100 prom/node-exporter

启动完成可以访问http://IP:9100/metrics.

  • 创建endpoints
apiVersion: v1
kind: Endpoints
metadata:
  name: node-data
  namespace: kubesphere-monitoring-system
  labels:
    app.kubernetes.io/name: node-data
subsets:
  - addresses:
      - ip: 192.168.3.17
      - ip: 192.168.3.19
      - ip: 192.168.3.20
    ports:
      - port: 9100
        name: http
  • 创建service
apiVersion: v1
kind: Service
metadata:
  name: node-data
  namespace: kubesphere-monitoring-system
  labels:
    app.kubernetes.io/name: node-data
spec:
  ports:
    - port: 9100
      name: http
  • 创建ServiceMonitor
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: node-exporter-data
  namespace: kubesphere-monitoring-system
spec:
  endpoints:
  - port: http
  namespaceSelector:
    matchNames:
    - kubesphere-monitoring-system
  selector:
    matchLabels:
      app.kubernetes.io/name: node-data

创建完成可以prometheus web页面查看是否有新建的targets.

PromQL

  1. 计算CPU使用率
# 1m查不到数据可以设置成3m,5m
(1-(sum(increase(node_cpu_seconds_total{mode="idle"}[1m]))by(instance))/(sum(increase(node_cpu_seconds_total[1m]))by(instance)))*100
  1. 内存使用率
(1-(node_memory_MemAvailable_bytes{}/(node_memory_MemTotal_bytes{})))*100
  1. 磁盘分区
# 注意mountpoint
100 - (node_filesystem_free_bytes{mountpoint="/",fstype=~"ext4|xfs"} / node_filesystem_size_bytes{mountpoint="/",fstype=~"ext4|xfs"} * 100)

docker stats

docker stats --no-stream

[root@data1 ~]# docker stats
CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
b4fb48ce6a23   magical_bell     147.29%   4.207GiB / 7.638GiB   55.08%    8.72GB / 7.79GB   2.46GB / 7.21GB   71
e189b149f025   kafka            3.94%     1.549GiB / 7.638GiB   20.27%    0B / 0B           637GB / 25GB      78
14cc0c468a14   zookeeper        0.24%     178.2MiB / 7.638GiB   2.28%     0B / 0B           381GB / 7.49MB    102

统计的结果和实际有出入.

TOP

# 获取容器的PID
docker inspect -f '{{.State.Pid}}' container_name
# 根据pid查询
[root@data1 ~]# top -p 2203
PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+  COMMAND 
2203 1001      20   0 6470744   1.3g   6076 S   0.0 16.6   2717:49 java

VmRSS

[root@data1 ~]# cat /proc/2203/status
VmPeak:	 6475388 kB
VmSize:	 6470744 kB
VmLck:	       0 kB
VmPin:	       0 kB
VmHWM:	 1336420 kB
VmRSS:	 1329516 kB # 此处是所要查询的内存大小

脚本

# 找出所有运行的容器
idNames=`docker ps --format "{{.ID}}|{{.Names}},"`

# 按,号分隔
OLD_IFS="$IFS"
IFS=","
arr=($idNames)
IFS="$OLD_IFS"

# 输出 Title
printf "%-15s %-30s %-15s\n" Id Name Mem

# 遍历所有容器
for item in ${arr[@]}
do
    # 容器ID和容器名字 按 | 分隔
    OLD_IFS="$IFS"
    IFS="|"
    array=($item)
    IFS="$OLD_IFS"
    
    # 当前容器的Pid
    pid=`docker inspect -f '{{.State.Pid}}' ${array[0]}`
    
    # 当前容器的内存
    mem=$(cat /proc/$pid/status|grep -e VmRSS| awk '{print $2}')

    # 输出结果
    printf "%-15s %-30s %-15s\n" ${array[0]} ${array[1]} $[$mem / 1024]M
done

节点离开延迟分配

index.unassigned.node_left.delayed_timeout默认为1m. 当一个节点出于某种原因离开集群时,无论是有意的还是其他的,主节点的反应是:

  • 将副本分片提升为主分片以替换节点上的任何主分片。
  • 分配副本分片以替换丢失的副本(假设有足够的节点)。
  • 在剩余节点上均匀地重新平衡分片。

如果一个节点被删除永远不会返回,希望 Elasticsearch 立即分配丢失的分片,只需将超时更新为0

PUT _all/_settings
{
  "settings": {
    "index.unassigned.node_left.delayed_timeout": "0"
  }
}

索引恢复优先级

尽可能按优先级顺序恢复未分配的分片。指数按优先级排序如下:

  • 可选index.priority设置(先高后低)
  • 索引创建日期(先高后低)
  • 索引名称(先高后低)
PUT index_4/_settings
{
  "index.priority": 1
}

节点分片总数限制

  • 为单个索引设置限制index.routing.allocation.total_shards_per_node默认为无限制;
  • 为集群统一设置cluster.routing.allocation.total_shards_per_node默认为-1无限制.

数据冷热节点角色

  • data_content
  • data_hot
  • data_warm
  • data_cold
  • data_frozen
# es 7.13之前用下面的语法,7.13之后过时
index.routing.allocation.include._tier: data_warm
index.routing.allocation.require._tier: data_warm
index.routing.allocation.exclude._tier: data_warm
# es 7.13版本之后使用
index.routing.allocation.include._tier_preference: data_warm,data_hot

索引块

# 设置索引和索引元数据只读
index.blocks.read_only: true
# 设置只读,不能删除索引内doc,但是允许删除索引
index.blocks.read_only_allow_delete: true
# 
index.blocks.read: true
index.blocks.write: true
index.blocks.metadata: true

操作

# <block>可以是metadata,read,read_only,write
PUT /my-index-000001/_block/<block>

慢日志

  • 系统级别
// 可以动态设置,threshold默认disabled,为-1
PUT /my-index-000001/_settings
{
  "index.search.slowlog.threshold.query.warn": "10s",
  "index.search.slowlog.threshold.query.info": "5s",
  "index.search.slowlog.threshold.query.debug": "2s",
  "index.search.slowlog.threshold.query.trace": "500ms",
  "index.search.slowlog.threshold.fetch.warn": "1s",
  "index.search.slowlog.threshold.fetch.info": "800ms",
  "index.search.slowlog.threshold.fetch.debug": "500ms",
  "index.search.slowlog.threshold.fetch.trace": "200ms"
}
  • 索引级别,文件名以_index_indexing_slowlog.log结尾
PUT /my-index-000001/_settings
{
  "index.indexing.slowlog.threshold.index.warn": "10s",
  "index.indexing.slowlog.threshold.index.info": "5s",
  "index.indexing.slowlog.threshold.index.debug": "2s",
  "index.indexing.slowlog.threshold.index.trace": "500ms",
  "index.indexing.slowlog.source": "1000"
}

存储

store模块允许控制索引数据在磁盘上的存储和访问方式,建议采用默认值.

  • 系统级别设置,elasticsearch.yml
index.store.type: hybridfs
  • 索引级别
PUT /my-index-000001
{
  "settings": {
    "index.store.type": "hybridfs"
  }
}

可选的值:fs,simplefs,niofs,mmapfs,hybridfs.

事务日志

ES提交到Lucene的索引、删除、分片拷贝、写等操作在未确认之前都会写入translog.

# 默认request意味所有操作(index, delete, update, bulk)只有同步到所有分片和副本后才会返回success
index.translog.durability: request
# 可以设置异步提交到磁盘
index.translog.durability: async
# 异步同步到磁盘的时间,最少100ms
index.translog.sync_interval: 5s
# 达到这个大小立即刷新磁盘
index.translog.flush_threshold_size: 512mb

历史保留-软删除

# Elasticsearch 6.5.0 之后可用,默认为true
index.soft_deletes.enabled: true
# 保留时间,默认12h
index.soft_deletes.retention_lease.period: 12h

索引排序

默认不排序.

# 支持boolean, numeric, date and keyword
index.sort.field: ["username"]
# 支持asc,desc
index.sort.order: ["asc"]
# 支持min,max
index.sort.mode: min
# 支持_last,_first
index.sort.missing: _last

示例

PUT my-index-000001
{
  "settings": {
    "index": {
      "sort.field": [ "username", "date" ], 
      "sort.order": [ "asc", "desc" ]       
    }
  },
  "mappings": {
    "properties": {
      "username": {
        "type": "keyword",
        "doc_values": true
      },
      "date": {
        "type": "date"
      }
    }
  }
}

indexing pressure

# Defaults to 10% of the heap.
indexing_pressure.memory.limit: 10%