目录

K3s Embedded Etcd on Tmpfs

文章简介:为了提高 etcd 在延迟非常高的磁盘上的稳定性,提出了两种解决方法

背景

etcd 官方文档有提到对磁盘写 io latency 比较敏感。为满足要求,已经将 etcd 存储 bind mount 到了系统盘中,尽可能的隔离数据盘的影响。参考 openshift-scale benchmark 磁盘脚本,生产环境已经在安装检查了磁盘的性能满足要求,但是几乎所有的私有云虚拟化平台提供的磁盘在后续业务负载提高、磁盘写入增加的时候,磁盘写 io lantency 异常增高,导致 etcd 异常主从切换,k3s controller manager 异常 leaderelection lost 而重启,跟着其上的 pod 也会因 NodeNotReady 而失联。为了解决这个问题,参考 蚂蚁集团万级规模 K8s 集群 etcd 高可用建设之路 的建议。

解决思路

  1. 考虑在磁盘性能特别差的环境中使用 -unsafe-no-fsync,但注意,这个参数会导致 etcd 有丢失数据的风险
  2. 将数据放到 tmpfs 中,为 etcd 提供稳定的存储,并使用定时同步到磁盘做持久化 + 定时 snapshot

方案 1: unsafe-no-fsync

如果 etcd 版本 >= 3.5,可以在 etcd config 中添加 unsafe-no-fsync=true。其实现在这里(https://github.com/etcd-io/etcd/pull/11946/files),sync 不真正持久化,但已经写入 os buf,在异常断电等场景会丢数据的可能,建议 snapshot 定时备份数据周期改小一点。

方案 2: etcd on tmpfs

首先 tmpfs 提供稳定的读写

1
2
3
mkdir -p /var/lib/rancher/k3s/server/db/
echo 'tmpfs /var/lib/rancher/k3s/server/db/ tmpfs size=4g 0 0' >> /etc/fstab
mount -a

tmpfs 需要定时同步到磁盘

1
*/5 * * * * /usr/local/bin/etcd-on-tmpfs backup

每次服务启动如果文件不存在需要同步到 tmpfs 中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env bash
set -o errtrace
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace

mkdir -p /usr/lib/systemd/system/k3s.service.d/

cat <<EOF > /usr/lib/systemd/system/k3s.service.d/cosmos-restore-etcd-pre-start.conf
[Service]
ExecStartPre=/usr/local/bin/etcd-on-tmpfs restore
EOF

cat <<EOF > /usr/lib/systemd/system/k3s.service.d/cosmos-backup-etcd-post-stop.conf 
[Service]
ExecStopPost=/usr/local/bin/etcd-on-tmpfs backup
EOF

systemctl daemon-reload

其中 /usr/local/bin/etcd-on-tmpfs 代码见:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/bin/bash

set -o errtrace
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace
shopt -s extglob

# etcd-tmpfs 管理脚本
# 功能:
# 1. 将 etcd 的数据目录使用 tmpfs
# 2. 开机和定时同步数据到 /opt/backup 中
# 3. 开机及数据目录无数据时将 /opt/backup 中的数据 copy 到数据目录中
# 4. 备份恢复命令使用 rsync

# 配置变量
ETCD_DATA_DIR="/var/lib/rancher/k3s/server/db"
BACKUP_DIR="/var/lib/rancher/k3s/etcdpersistent"

# 日志函数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# 检查是否以 root 权限运行
check_root() {
    if [ "$EUID" -ne 0 ]; then
        log "请以 root 权限运行此脚本"
        exit 1
    fi
}

# 备份 etcd 数据到 /opt/backup
backup_etcd() {
    log "开始备份 etcd 数据到 $BACKUP_DIR/etcd"
    
    # 创建备份目录
    mkdir -p "$BACKUP_DIR/etcd"
    
    # 如果 etcd 数据目录不存在或为空,则不执行备份
    if [ ! -d "$ETCD_DATA_DIR" ] || [ ! "$(ls -A $ETCD_DATA_DIR)" ]; then
        log "etcd 数据目录不存在或为空,跳过备份"
        return 0
    fi

    CLUSTER_NAME_PATH="$BACKUP_DIR/name"

    if [ ! -f "$CLUSTER_NAME_PATH" ];then
        info ${CLUSTER_NAME_PATH} 不存在,开始备份
        cp "/var/lib/rancher/k3s/server/db/etcd/name" "$CLUSTER_NAME_PATH"
        log ${CLUSTER_NAME_PATH} 备份完成
    else
        log $CLUSTER_NAME_PATH 存在
    fi

    if ! diff /var/lib/rancher/k3s/server/db/etcd/name ${CLUSTER_NAME_PATH}; then
        log "etcd/name 变化,禁止备份,请人工检查一下"
        return 1
    fi

    # 使用 rsync 同步数据
    if rsync -av --progress --delete "$ETCD_DATA_DIR/" "$BACKUP_DIR/etcd/"; then
        log "成功备份 etcd 数据到 $BACKUP_DIR/etcd"

        tail -n 10 $BACKUP_DIR/backup.log > $BACKUP_DIR/.backup.log || true
        date "+%Y-%m-%d %H:%M:%S %Z" >> $BACKUP_DIR/.backup.log
        mv $BACKUP_DIR/.backup.log $BACKUP_DIR/backup.log
        return 0
    else
        log "备份 etcd 数据失败"
        return 1
    fi
}

# 从 /opt/backup 恢复 etcd 数据
restore_etcd() {
    log "检查是否需要从 $BACKUP_DIR/etcd 恢复 etcd 数据"
    
    # 创建 etcd 数据目录
    mkdir -p "$ETCD_DATA_DIR"
    
    # 如果 etcd 数据目录不为空,则不执行恢复
    if [ "$(ls -A $ETCD_DATA_DIR)" ]; then
        log "etcd 数据目录不为空,跳过恢复"
        return 0
    fi
    
    # 检查备份目录是否存在且不为空
    if [ ! -d "$BACKUP_DIR/etcd" ] || [ ! "$(ls -A $BACKUP_DIR/etcd)" ]; then
        log "备份目录不存在或为空,跳过恢复"
        return 0
    fi

    CLUSTER_NAME_PATH="$BACKUP_DIR/name"

    if [ ! -f "$CLUSTER_NAME_PATH" ];then
        log 文件 ${CLUSTER_NAME_PATH} 不存在,不允许恢复, 不允许启动
        return 1
    fi

    log "从 $BACKUP_DIR/etcd 恢复 etcd 数据"
    
    # 使用 rsync 恢复数据
    if rsync -av --progress "$BACKUP_DIR/etcd/" "$ETCD_DATA_DIR/"; then
        log "成功从 $BACKUP_DIR/etcd 恢复 etcd 数据"
        return 0
    else
        log "从 $BACKUP_DIR/etcd 恢复 etcd 数据失败"
        return 1
    fi
}

# 显示帮助信息
show_help() {
    echo "用法: $0 [选项]"
    echo "选项:"
    echo "  backup    备份 etcd 数据到 $BACKUP_DIR/etcd"
    echo "  restore   从 $BACKUP_DIR/etcd 恢复 etcd 数据"
    echo "  sync      同步 etcd 数据到 $BACKUP_DIR/etcd"
    echo "  status    显示当前状态"
    echo "  help      显示此帮助信息"
}

# 显示当前状态
show_status() {
    set +o xtrace
    echo "=== etcd-tmpfs 状态 ==="
    echo "etcd 数据目录: $ETCD_DATA_DIR"
    echo "备份目录: $BACKUP_DIR/etcd"
    
    if mount | grep -q "$ETCD_DATA_DIR.*tmpfs"; then
        echo "tmpfs 状态: 已挂载"
        echo "tmpfs 使用情况:"
        df -h "$ETCD_DATA_DIR"
    else
        echo "tmpfs 状态: 未挂载"
    fi
    
    echo "etcd 数据目录内容:"
    ls -la "$ETCD_DATA_DIR" 2>/dev/null || echo "目录不存在或为空"
    
    echo "备份目录内容:"
    ls -la "$BACKUP_DIR/etcd" 2>/dev/null || echo "目录不存在或为空"
    set -o xtrace
}

# 主函数
main() {
    # 检查 root 权限
    check_root
    
    case "$1" in
        backup)
            backup_etcd
            ;;
        restore)
            restore_etcd
            ;;
        status)
            show_status
            ;;
        help|*)
            show_help
            ;;
    esac
}

# 执行主函数
main "$@"

定时 snapshot 备份

k3s 默认自动备份,无须调整,文档见这里

总结

综上,正常开关机或服务挂掉,有 systemd service 的 ExecStartPre 和 ExecStopPost 备份与还原;异常场景有 etcd 集群其他节点的数据 raft 自动同步及定时 snapshot 可以兜底,基本可以确保 etcd 的数据不丢失了。