Loki 日志系统分布式部署实践三 redis

说明

这里支持 redis 主从、哨兵、集群三种模式,我这里选择主从即可,集群模式测试异常,没能解决

安装

# helm repo add bitnami https://charts.bitnami.com/bitnami

# helm search repo redis
NAME                    CHART VERSION   APP VERSION     DESCRIPTION                                       
bitnami/redis           12.1.1          6.0.9           Open source, advanced key-value store. It is of...
bitnami/redis-cluster   4.0.2           6.0.9           Open source, advanced key-value store. It is of...

安装 redis 主从模式:

# helm pull bitnami/redis --version=12.1.1
# helm show values redis-12.1.1.tgz

编写配置文件:

# cat > redis-config.yaml < <更新>  
  #  
  #   如果指定的秒数和数据库写操作次数都满足了就将数据库保存。  
  #   900 秒(15 分钟)内至少 1 个 key 值改变(则进行数据库保存--持久化)
  #save 900 1
  #   300 秒(5 分钟)内至少 10 个 key 值改变(则进行数据库保存--持久化)
  #save 300 10
  #   60 秒(1 分钟)内至少 10000 个 key 值改变(则进行数据库保存--持久化)
  #save 60 10000
  save ""
  
  # 默认情况下,如果 RDB 快照持久化操作被激活并且持久化操作失败,Redis 则会停止接受更新操作。这样会让用户了解到数据没有被正确的存储到磁盘上
  # 如果后台存储(持久化)操作进程再次工作,Redis 会自动允许更新操作。  
  stop-writes-on-bgsave-error yes  
    
  # 是否在导出 .rdb 数据库文件的时候采用 LZF 压缩字符串和对象?压缩会产生较高的 CPU
  rdbcompression yes
    
  # 从版本 RDB版本 5 开始,一个 CRC64 的校验就被放在了文件末尾。 这会让格式更加耐攻击,但是当存储或者加载 rbd 文件的时候会有一个 10% 左右的性能下降
  # 没有校验的 RDB 文件会有一个 0 校验位,来告诉加载代码跳过校验检查 
  rdbchecksum yes
  
  dbfilename dump.rdb

sysctlImage:
  enabled: true
  command: 
  - /bin/sh
  - -c
  - |
    # 正常运行的 docker 容器中,是不能修改任何 sysctl 内核参数的。因为 /proc/sys 是以只读方式挂载到容器里面的。可以使用 --privileged 参数解决,也可以挂为 rw 解决
    mount -o remount rw /proc/sys
    sysctl -w net.core.somaxconn=65535
    #sysctl -w net.ipv4.tcp_max_syn_backlog=262144
    sysctl -w net.ipv4.ip_local_port_range="1024 65535"
    sysctl -w fs.file-max=26234859
    sysctl -w fs.inotify.max_user_instances=26234859
    sysctl -w fs.inotify.max_user_watches=26234859
    sysctl -w fs.inotify.max_queued_events=26234859
    sysctl -w vm.max_map_count=262144
    echo never > /host-sys/kernel/mm/transparent_hugepage/enabled
    echo never > /host-sys/kernel/mm/transparent_hugepage/defrag
  registry: ops-harbor.hupu.io/base
  repository: alpine
  #repository: minideb
  tag: v3.10
  #tag: buster
  pullPolicy: Always
  mountHostSys: true
  resources: {}

podSecurityPolicy:
  create: false

podDisruptionBudget:
  enabled: false
  minAvailable: 1
EOF

安装:

# helm upgrade --install redis -f redis-config.yaml redis-12.1.1.tgz -n grafana

# kubectl get pod -n grafana |grep redis 
redis-master-0                        2/2     Running   0          73s
redis-slave-0                         2/2     Running   0          73s

# kubectl get svc -n grafana |grep redis
redis-headless                           ClusterIP   None                    6379/TCP         77s
redis-master                             ClusterIP   172.21.0.153            6379/TCP         77s
redis-metrics                            ClusterIP   172.21.1.60             9121/TCP         77s
redis-slave                              ClusterIP   172.21.7.185            6379/TCP         77s

# kubectl get sts -n grafana |grep redis   
redis-master                        1/1     81s
redis-slave                         2/2     81s

查看密码:

# kubectl get secret --namespace grafana redis -o jsonpath="{.data.redis-password}" | base64 --decode
kong62123

连接测试

连接 master:

# kubectl run -n grafana redis-redis-cluster-client --rm \
                                                    --tty -i \
                                                    --restart='Never' \
                                                    --env REDIS_PASSWORD=kong62123 \
                                                    --image ops-harbor.hupu.io/k8s/redis-cluster:6.0.9 \
                                                    -- redis-cli -c -h redis-master -a kong62123 info

连接 slave:

# kubectl run -n grafana redis-redis-cluster-client --rm \
                                                    --tty -i \
                                                    --restart='Never' \
                                                    --env REDIS_PASSWORD=kong62123 \
                                                    --image ops-harbor.hupu.io/k8s/redis-cluster:6.0.9 \
                                                    -- redis-cli -c -h redis-slave -a kong62123 info

读写分离:
读写:

# redis-master.grafana.svc.cluster.local

只读:

redis-slave.grafana.svc.cluster.local

错误处理

错误 1:

# kubectl logs -f -n grafana redis-master-0 -c redis
1:M 30 Nov 2020 15:08:29.257 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

解决:
参考:https://github.com/helm/charts/issues/10666
参考:https://docs.bitnami.com/kubernetes/infrastructure/redis/administration/kernel-settings/
参考:https://github.com/helm/charts/tree/master/stable/redis/#user-content-host-kernel-settings

sysctlImage:
  enabled: true
  command: 
  - /bin/sh
  - -c
  - |
    # 正常运行的 docker 容器中,是不能修改任何 sysctl 内核参数的。因为 /proc/sys 是以只读方式挂载到容器里面的。可以使用 --privileged 参数解决,也可以挂为 rw 解决
    mount -o remount rw /proc/sys
    sysctl -w net.core.somaxconn=65535
    #sysctl -w net.ipv4.tcp_max_syn_backlog=262144
    sysctl -w net.ipv4.ip_local_port_range="1024 65535"
    sysctl -w fs.file-max=26234859
    sysctl -w fs.inotify.max_user_instances=26234859
    sysctl -w fs.inotify.max_user_watches=26234859
    sysctl -w fs.inotify.max_queued_events=26234859
    sysctl -w vm.max_map_count=262144

注意:Kubernetes 1.12+ 可以使用 securityContext.sysctls 来设置 pod 的 sysctl,而不需要 initContainer 了:

securityContext:
  sysctls:
  - name: net.core.somaxconn
    value: "65535"

错误 2:

# kubectl logs -f -n grafana redis-master-0 -c init-sysctl 
mount: /proc/sys: mount point not mounted or bad option.

解决:
使用 initContainer 去修改 sysctl 的方案在生产环境正常,这里却报错了,应该是 securityContext 或 PSP 的问题,这里没有启用 PSP,那就剩下 securityContext 了:
我这里尝试写入如下权限,最终都被修改掉了:

containerSecurityContext:
  enabled: true
  runAsUser: 1001
  runAsNonRoot: false
  runAsUser: 0
  privileged: true
  capabilities:
    add:
    - SYS_ADMIN
    drop:
    - ALL

# kubectl get sts -n grafana redis-master -o yaml 
...
        securityContext:
          privileged: true
          runAsUser: 0
...
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext:
        fsGroup: 1001

这里暂时没有找到解决方案,但是发现虽然报错了,下面 sysctl 修改是生效了的,因为日志不再报错了

错误 3:

# kubectl logs -f -n grafana redis-master-0 -c init-sysctl 
/bin/sh: 3: sysctl: not found
/bin/sh: 5: sysctl: not found
/bin/sh: 6: sysctl: not found
/bin/sh: 7: sysctl: not found
/bin/sh: 8: sysctl: not found
/bin/sh: 9: sysctl: not found
/bin/sh: 10: sysctl: not found

解决:
因为 bitnami/minideb:buster 镜像里没有 sysctl

# docker run -it --rm bitnami/minideb:buster sysctl   
docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "exec: \"sysctl\": executable file not found in $PATH": unknown.

方法一:替换镜像

sysctlImage:
  registry: ops-harbor.hupu.io/base
  repository: alpine
  tag: v3.10

方法二:直接安装包,但是这个比较慢

sysctlImage:
  enabled: true
  mountHostSys: true
  command:
    - /bin/sh
    - -c
    - |-
      install_packages procps

错误 4:

# kubectl logs -f -n grafana redis-master-0 -c redis
1:M 30 Nov 2020 15:08:29.257 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').

解决:
参考:https://github.com/docker-library/redis/issues/55
参考:https://github.com/prometheus/node_exporter/issues/703
注意:它将修改调度了容器的节点的内核设置,从而影响该节点上运行的其他容器。这就是为什么您需要运行特权的 initContainer 或设置 securityContext.sysctls 的原因。

sysctlImage:
  enabled: true
  command: 
  - /bin/sh
  - -c
  - |
    echo never > /host-sys/kernel/mm/transparent_hugepage/enabled
    echo never > /host-sys/kernel/mm/transparent_hugepage/defrag
  mountHostSys: true

错误 5:

# kubectl logs -f -n grafana redis-master-0 -c init-sysctl 
/bin/sh: can't create /sys/kernel/mm/transparent_hugepage/enabled: Read-only file system
/bin/sh: can't create /sys/kernel/mm/transparent_hugepage/defrag: Read-only file system

解决:
因为将宿主机的 /sys 挂载到容器内的路径变成了 /host-sys

# kubectl describe pod -n grafana redis-master-0
    Mounts:
      /host-sys from host-sys (rw)
...
Volumes:
  host-sys:
    Type:          HostPath (bare host directory volume)
    Path:          /sys
    HostPathType:  

所以要修改路径:

sysctlImage:
  enabled: true
  command: 
  - /bin/sh
  - -c
  - |
    #echo never > /sys/kernel/mm/transparent_hugepage/enabled
    #echo never > /sys/kernel/mm/transparent_hugepage/defrag
    echo never > /host-sys/kernel/mm/transparent_hugepage/enabled
    echo never > /host-sys/kernel/mm/transparent_hugepage/defrag
  mountHostSys: true

错误 6:

# kubectl logs -f -n grafana redis-master-0 -c redis 
useradd: Permission denied.
useradd: cannot lock /etc/passwd; try again later.
chown: invalid user: 'redis'

解决:
这里暂时看不受影响

错误 7:

# kubectl logs -f -n grafana --tail=20 redis-slave-0 -c redis 
1:S 30 Nov 2020 13:51:27.769 * Connecting to MASTER redis-master-0.redis-headless.grafana.svc.cluster.local:6379
1:S 30 Nov 2020 13:51:27.776 # Unable to connect to MASTER: Invalid argument
1:S 30 Nov 2020 13:51:28.778 * Connecting to MASTER redis-master-0.redis-headless.grafana.svc.cluster.local:6379
1:S 30 Nov 2020 13:51:28.785 # Unable to connect to MASTER: Invalid argument
1:signal-handler (1606744289) Received SIGTERM scheduling shutdown...
1:S 30 Nov 2020 13:51:29.086 # User requested shutdown...
1:S 30 Nov 2020 13:51:29.086 * Calling fsync() on the AOF file.
1:S 30 Nov 2020 13:51:29.086 # Redis is now ready to exit, bye bye...

解决:
因为 master 挂掉了

错误 8:

# kubectl logs -f -n grafana redis-master-0 -c redis 
1:M 01 Dec 2020 02:39:33.588 # Connection with replica 10.41.182.170:6379 lost.
1:M 01 Dec 2020 02:39:33.590 # Connection with replica 10.41.176.75:6379 lost.
1:M 01 Dec 2020 02:39:50.092 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
1:M 01 Dec 2020 02:40:05.010 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
1:M 01 Dec 2020 02:40:20.053 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
1:M 01 Dec 2020 02:40:34.111 * Replica 10.41.176.75:6379 asks for synchronization
1:M 01 Dec 2020 02:40:34.111 * Full resync requested by replica 10.41.176.75:6379
1:M 01 Dec 2020 02:40:34.111 * Can't attach the replica to the current BGSAVE. Waiting for next BGSAVE for SYNC
1:M 01 Dec 2020 02:40:35.075 * Replica 10.41.182.170:6379 asks for synchronization
1:M 01 Dec 2020 02:40:35.075 * Full resync requested by replica 10.41.182.170:6379
1:M 01 Dec 2020 02:40:35.075 * Can't attach the replica to the current BGSAVE. Waiting for next BGSAVE for SYNC

解决:
Redis 提供两种相对有效的备份方法:

  1. RDB
    RDB 是在某个时间点将内存中的所有数据的快照保存到磁盘上,在数据恢复时,可以恢复备份时间以前的所有数据,但无法恢复备份时间点后面的数据。
    默认 redis 在磁盘上创建二进制格式的命名为 dump.rdb 的数据快照。可以通过配置文件配置每隔 N 秒且数据集上至少有 M 个变化时创建快照、是否对数据进行压缩、快照名称、存放快照的工作目录。

  2. AOF
    AOF 是以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的。
    优点是基本可以实现数据无丢失(缓存的数据有可能丢失),缺点是随着数据量的持续增加,AOF 文件也会越来越大。
    在保证数据安全的情况下,尽量避免因备份数据消耗过多的 Redis 资源,采用如下备份策略:
    Master 实例: 不采用任何备份机制。
    Slave 实例: 采用 AOF(严格数据要求时可同时开启 RDB),每天将 AOF 文件备份至备份服务器。
    为了最大限度减少主实例的资源干扰,将备份相关全部迁移至 Slave 端完成。同时这样也有缺点,当主实例挂掉后,应用服务切换至 Slave 端,此时的 Slave 端的负载将会很大。

利用 RDB 快照的持久化方式不是非常可靠,当运行 Redis 的计算机停止工作、意外掉电、意外杀掉了 Redis 进程那么最近写入 Redis 的数据将会丢。对于某些应用这或许不成问题,但对于持久化要求非常高的应用场景快照方式不是理想的选择。
利用 AOF 文件是一个替代方案,用以最大限度的持久化数据。同样,可以通过配置文件来开闭 AOF。

打开 AOF 持久化功能后,Redis 处理完每个事件后会调用 write(2) 将变化写入 kernel 的 buffer,如果此时 write(2) 被阻塞,Redis 就不能处理下一个事件。
Linux 规定执行 write(2) 时,如果对同一个文件正在执行 fdatasync(2) 将 kernel buffer 写入物理磁盘,或者有 system wide sync 在执行,write(2) 会被 Block 住,整个 Redis 被 Block 住。
如果系统 IO 繁忙,比如有别的应用在写盘,或者 Redis 自己在 AOF rewrite 或 RDB snapshot(虽然此时写入的是另一个临时文件,虽然各自都在连续写,但两个文件间的切换使得磁盘磁头的寻道时间加长),就可能导致 fdatasync(2) 迟迟未能完成从而 Block 住 write(2),Block 住整个 Redis。
为了更清晰的看到 fdatasync(2) 的执行时长,可以使用下面命令跟踪,但会影响系统性能:

strace -p (pid of redis server) -T -e -f trace=fdatasync

Redis 提供了一个自救的方式,当发现文件有在执行 fdatasync(2) 时,就先不调用 write(2),只存在 cache 里,免得被 Block。但如果已经超过两秒都还是这个样子,则会硬着头皮执行 write(2),即使 redis 会被 Block 住。
此时那句要命的 log 会打印:Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
之后用 redis-cli INFO 可以看到 aof_delayed_fsync 的值被加 1。

因此,对于 fsync 设为 everysec 时丢失数据的可能性的最严谨说法是:
如果有 fdatasync 在长时间的执行,此时 redis 意外关闭会造成文件里不多于两秒的数据丢失。
如果 fdatasync 运行正常,redis 意外关闭没有影响,只有当操作系统 crash 时才会造成少于 1 秒的数据丢失。

方法一:关闭 AOF
如果采用 redis 主从 + sentinel 方式的话,主节点挂了从节点会自己提升为主点,主节点恢复后全量同步一次数据就可以了,关系也不是太大

方法二:修改系统配置
原来是 AOF rewrite 时一直埋头的调用 write(2),由系统自己去触发 sync。默认配置 vm.dirty_background_ratio=10,也就是占用了 10% 的可用内存才会开始后台 flush
而我的服务器有 8G 内存,很明显一次 flush 太多数据会造成阻塞,所以最后果断设置了sysctl vm.dirty_bytes=33554432(32M) 问题解决

# sysctl -a | grep dirty_background_ratio
vm.dirty_background_ratio = 10

# sysctl -a | grep vm.dirty_bytes
vm.dirty_bytes = 0

# echo "vm.dirty_bytes=33554432" >> /etc/sysctl.conf
# sysctl -p

错误 9:

# kubectl logs -f -n grafana redis-master-0 -c redis --previous
1:M 01 Dec 2020 03:24:37.728 * Reading RDB preamble from AOF file...
1:M 01 Dec 2020 03:24:37.728 * Loading RDB produced by version 6.0.9
1:M 01 Dec 2020 03:24:37.728 * RDB age 3622 seconds
1:M 01 Dec 2020 03:24:37.728 * RDB memory usage when created 1024.94 Mb
1:M 01 Dec 2020 03:24:37.728 * RDB has an AOF tail
1:M 01 Dec 2020 03:24:44.534 * Reading the remaining AOF tail...

解决:
看着是启动的时候加载 AOF 文件到内存,然后被 liveness 杀掉了

# kubectl exec -it -n grafana redis-master-0 -- df -Th|grep /data
/dev/vdd       ext4      99G   80G   20G  81% /data

随着命令不断写入 AOF,文件会越来越大,为了解决这个问题,redis 引入了 AOF 重写机制压缩文件。文件能缩小的原因是:

  1. 旧文件中的无效命令不会保留,如 del,key1,sort
  2. 多条合并成一条,如 lplush list a 和 lplush list b 转换为 lplush a b,也可以合并重复项

AOF 重写可以手动触发和自动触发:

  1. 手动触发可以调用 bgrewriteaof
# redis-cli -p 6379 -h 127.0.0.1
127.0.0.1:6379> BGREWRITEAOF
  1. 根据如下两个参数自动触发

代表当前 AOF 文件空间和上次重写后 AOF 空间的比值。

auto-aof-rewrite-percentage 100

AOF 超过 16m 就开始收缩

auto-aof-rewrite-min-size 16mb

所以这里处理下,控制 AOF 文件大小:

configmap: |-
  appendonly yes
  save ""
  # 代表当前 AOF 文件空间和上次重写后 AOF 空间的比值
  auto-aof-rewrite-percentage 100
  # AOF 超过 16m 就开始收缩
  auto-aof-rewrite-min-size 16mb

注意:这里依旧没能解决问题,文件依旧很大,而且会造成大量的磁盘 IO,最终导致 redis 失去响应

# top
top - 12:05:03 up 19 days, 21:17,  1 user,  load average: 120.02, 85.39, 38.98
Tasks: 340 total,   2 running, 338 sleeping,   0 stopped,   0 zombie
%Cpu(s): 28.8 us,  6.3 sy,  0.0 ni, 59.4 id,  3.2 wa,  0.0 hi,  2.3 si,  0.0 st
KiB Mem : 65806668 total, 17644992 free, 44516804 used,  3644872 buff/cache
KiB Swap:        0 total,        0 free,        0 used. 20453980 avail Mem 

# iostat -x 1
...
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          32.59    0.06    8.53    6.87    0.00   51.94

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
vda               0.00    40.00   34.00   12.00   140.00   240.00    16.52     0.10    2.24    2.76    0.75   1.50   6.90
vdb               0.00     0.00    0.00    4.00     0.00     8.00     4.00     0.00    0.75    0.00    0.75   0.75   0.30
vdc               0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
vdd               0.00     0.00  918.00    0.00 117504.00     0.00   256.00     1.69    1.85    1.85    0.00   1.04  95.10
vde               0.00     0.00  918.00    0.00 116992.00     0.00   254.88     1.81    1.97    1.97    0.00   1.05  96.60

彻底解决:

configmap: |-
  # 关闭 aof,文件太大,IO 太高了
  appendonly no
  # 代表当前 AOF 文件空间和上次重写后 AOF 空间的比值
  auto-aof-rewrite-percentage 100
  # AOF 超过 64m 就开始收缩
  auto-aof-rewrite-min-size 8mb

  # RDB 磁盘消耗也不小啊,要彻底 RDB 功能需要注释默认的 3 行任务并添加 save ""
  # Save the DB on disk: 保存数据库到磁盘  
  #   save <秒> <更新>  
  #  
  #   如果指定的秒数和数据库写操作次数都满足了就将数据库保存。  
  #   900 秒(15 分钟)内至少 1 个 key 值改变(则进行数据库保存--持久化)
  #save 900 1
  #   300 秒(5 分钟)内至少 10 个 key 值改变(则进行数据库保存--持久化)
  #save 300 10
  #   60 秒(1 分钟)内至少 10000 个 key 值改变(则进行数据库保存--持久化)
  #save 60 10000
  save ""
  
  # 默认情况下,如果 RDB 快照持久化操作被激活并且持久化操作失败,Redis 则会停止接受更新操作。这样会让用户了解到数据没有被正确的存储到磁盘上
  # 如果后台存储(持久化)操作进程再次工作,Redis 会自动允许更新操作。  
  stop-writes-on-bgsave-error yes  
    
  # 是否在导出 .rdb 数据库文件的时候采用 LZF 压缩字符串和对象?压缩会产生较高的 CPU
  rdbcompression yes
    
  # 从版本 RDB版本 5 开始,一个 CRC64 的校验就被放在了文件末尾。 这会让格式更加耐攻击,但是当存储或者加载 rbd 文件的时候会有一个 10% 左右的性能下降
  # 没有校验的 RDB 文件会有一个 0 校验位,来告诉加载代码跳过校验检查 
  rdbchecksum yes
  
  dbfilename dump.rdb

之后 dump.rdb 文件一直稳定在 255M

# kubectl exec -it -n grafana redis-master-0 -c redis -- ls -lht /data
total 255M
-rw-r--r-- 1 1001 1001 255M Dec  1 05:38 dump.rdb
drwxrws--- 2 root 1001  16K Dec  1 05:38 lost+found

你可能感兴趣的:(Loki 日志系统分布式部署实践三 redis)