系统健康检查和快速恢复

Slack Docker Pulls

健康检查

当系统出现问题或要对系统做出任何改动时,都需要首先检查系统的整体健康状态。 本指南列出了用于判断系统是否健康的部分关键信息。

指标和仪表盘

不同配置和流量下,指标统计值和错误阈值可能有很大差异。 判断的基本思路是检查关键指标的天同比或周同比的情况,观察是否存在显著差异。

活跃性

对于 worker 而言,alluxio_data_access_bytes_count 指标将计算 worker 接收的读写请求。 在 Prometheus 中,我们可以通过查询 irate(alluxio_data_access_bytes_count[5m]) 来计算每秒请求数(RPS)。 RPS 应保持稳定。如果 RPS 快速增加,说明 worker 可能将面临容量不足的风险。

对于 FUSE 进程而言,alluxio_fuse_result 指标可以用来计算每个 fuse 操作的 RPS。 alluxio_fuse_concurrency 也可以反映 fuse 进程的负载情况。

UFS 数据流

alluxio_ufs_data_access 指标记录在 worker 上访问 UFS 的读/写数据流量,如果启用了 UFS 回退功能,也会记录 FUSE 进程的相关数据流量。

alluxio_ufs_error 指标记录每种 UFS 的 API 访问错误代码。如果该指标增加,说明访问 UFS 出现问题。 可使用error_code 标签来过滤掉可预期错误,如 “no such file(无此类文件)”。 在 S3 中,该错误代码是404。不同的 UFS 可能会有不同的错误代码。

缓存命中率

对于 worker 而言,alluxio_cached_data_read_bytes_totalalluxio_ufs_data_access_bytes_total 指标可以计算缓存命中率。 要计算每秒的缓存命中率,应在 Prometheus 中使用 irate 函数,然后使用 sum 来移除未使用的标签。

  • 单个 worker 的缓存命中率为:

    sum by (instance) (irate(alluxio_cached_data_read_bytes_total{job="worker"}[5m])) / (sum by (instance) (irate(alluxio_cached_data_read_bytes_total{job="worker"}[5m])) + sum by (instance) (irate(alluxio_ufs_data_access_bytes_total{job="worker"}[5m])))
    
  • 整体缓存命中率(已集成到仪表盘中):

    sum(irate(alluxio_cached_data_read_bytes_total{job="worker"}[5m])) / (sum(irate(alluxio_cached_data_read_bytes_total{job="worker"}[5m])) + sum(irate(alluxio_ufs_data_access_bytes_total{job="worker"}[5m])))
    

整体状态

Alluxio Process Readiness

# 检查 kubernetes 中 worker 的就绪性
# 确保 READY 的值为 100%。即使状态显示为 "Running(运行中)",也不一定表示 worker 是健康的。
$ kubectl get pod -l app.kubernetes.io/component=worker
NAME                              READY   STATUS    RESTARTS   AGE
alluxio-worker-59476bf8c5-lg4sc   1/1     Running   0          46h
alluxio-worker-59476bf8c5-vg6lc   1/1     Running   0          46h

# 检查 fuse 进程的就绪性
# selector 将同时选择 csi fuse 和 daemonSet fuse
$ kubectl get pod -l 'app.kubernetes.io/component in (fuse, csi-fuse)'
NAME                                   READY   STATUS    RESTARTS   AGE
alluxio-fuse-acee53e8f0a9-3gjbrdekk0   1/1     Running   0          57m

# 或使用以下一行命令来获取进程就绪的百分比
# 如果有多个 Alluxio 集群,请使用 `app.kubernetes.io/instance=alluxio` 来指定集群
$ kubectl get pod -l app.kubernetes.io/component=worker -o jsonpath='{range .items[*]}{.status.containerStatuses[0].ready}{"\n"}{end}' | awk 'BEGIN{t=0}{s+=1;if($1=="true")t+=1}END{print t,"ready /",s,"expected =",t/s*100,"%"}'
2 ready / 2 expected = 100 %

ETCD 就绪性

# 检查 etcd 集群的就绪性
# 使用 `app.kubernetes.io/instance=alluxio` 来选择与 Alluxio 集成的 etcd 集群
$ kubectl get pod -l 'app.kubernetes.io/component=etcd,app.kubernetes.io/instance=alluxio'
NAME             READY   STATUS    RESTARTS   AGE
alluxio-etcd-0   1/1     Running   0          46h
alluxio-etcd-1   1/1     Running   0          46h
alluxio-etcd-2   1/1     Running   0          46h

# 使用以下一行命令获取集群就绪的百分比
$ kubectl get pod -l 'app.kubernetes.io/component=etcd,app.kubernetes.io/instance=alluxio' -o jsonpath='{range .items[*]}{.status.containerStatuses[0].ready}{"\n"}{end}' | awk 'BEGIN{t=0}{s+=1;if($1=="true")t+=1}END{print t,"ready /",s,"expected =",t/s*100,"%"}'
3 ready / 3 expected = 100 %

# 检查 etcd 是否能够处理正常的 I/O 操作
# 输出为 Alluxio worker 的注册信息。请勿以这种方式更改或写入值
$ kubectl exec -it alluxio-etcd-0 -c etcd -- bash -c 'ETCDCTL_API=3 etcdctl get --keys-only --prefix /ServiceDiscovery'
/ServiceDiscovery/default-alluxio/worker-1b4bad64-f195-46a8-be5f-27825a8100a4

/ServiceDiscovery/default-alluxio/worker-49ca0d6f-7a0a-482f-bb17-5550bd602a02

UFS 就绪性

$ ./bin/alluxio exec ufsTest --path s3://your_bucket/test_path
Running test: createAtomicTest...
Passed the test! time: 5205ms
Running test: createEmptyTest...
Passed the test! time: 4076ms
Running test: createNoParentTest...
Passed the test! time: 0ms
Running test: createParentTest...
Passed the test! time: 4082ms
...
Running test: listStatusS3RootTest...
Passed the test! time: 543ms
Running test: createFileLessThanOnePartTest...
Passed the test! time: 3551ms
Running test: createAndAbortMultipartFileTest...
Passed the test! time: 6227ms
Tests completed with 0 failed.

# 该示例显示 client 将启动两个线程来向 UFS 写入并读取一个 512MB 的文件,并打印测试结果
$ ./bin/alluxio exec ufsIOTest --path s3://test_bucket/test_path --io-size 512m --threads 2
{
  "readSpeedStat" : {
    "mTotalDurationSeconds" : 483.992,
    "mTotalSizeBytes" : 1073741824,
    "mMaxSpeedMbps" : 2.0614405926641703,
    "mMinSpeedMbps" : 1.0578687251028942,
    "mAvgSpeedMbps" : 1.5596546588835323,
    "mClusterAvgSpeedMbps" : 2.1157374502057884,
    "mStdDev" : 0.7096324729606261,
    "className" : "alluxio.stress.worker.IOTaskSummary$SpeedStat"
  },
  "writeSpeedStat" : {
    "mTotalDurationSeconds" : 172.136,
    "mTotalSizeBytes" : 1073741824,
    "mMaxSpeedMbps" : 3.5236227246137433,
    "mMinSpeedMbps" : 2.974392340939722,
    "mAvgSpeedMbps" : 3.2490075327767327,
    "mClusterAvgSpeedMbps" : 5.948784681879444,
    "mStdDev" : 0.3883645287295896,
    "className" : "alluxio.stress.worker.IOTaskSummary$SpeedStat"
  },
  "errors" : [ ],
  "baseParameters" : {
    "mCluster" : false,
    "mClusterLimit" : 0,
    "mClusterStartDelay" : "10s",
    "mJavaOpts" : [ ],
    "mProfileAgent" : "",
    "mBenchTimeout" : "20m",
    "mId" : "local-task-0",
    "mIndex" : "local-task-0",
    "mDistributed" : false,
    "mStartMs" : -1,
    "mInProcess" : true,
    "mHelp" : false
  },
  "points" : [ {
    "mMode" : "WRITE",
    "mDurationSeconds" : 145.305,
    "mDataSizeBytes" : 536870912,
    "className" : "alluxio.stress.worker.IOTaskResult$Point"
  }, {
    "mMode" : "WRITE",
    "mDurationSeconds" : 172.136,
    "mDataSizeBytes" : 536870912,
    "className" : "alluxio.stress.worker.IOTaskResult$Point"
  }, {
    "mMode" : "READ",
    "mDurationSeconds" : 248.37,
    "mDataSizeBytes" : 536870912,
    "className" : "alluxio.stress.worker.IOTaskResult$Point"
  }, {
    "mMode" : "READ",
    "mDurationSeconds" : 483.992,
    "mDataSizeBytes" : 536870912,
    "className" : "alluxio.stress.worker.IOTaskResult$Point"
  } ],
  "parameters" : {
    "className" : "alluxio.stress.worker.UfsIOParameters",
    "mThreads" : 2,
    "mDataSize" : "512m",
    "mPath" : "s3://test_bucket/test_path",
    "mUseUfsConf" : false,
    "mConf" : { }
  },
  "className" : "alluxio.stress.worker.IOTaskSummary"
}

日志

Alluxio 进程

# 使用 `kubectl logs <pod-name>` 来获取单个 pod 的所有日志
# 在裸机上,日志位于`logs/<component>.log`,例如:`logs/worker.log`

# 使用 `grep` 命令来过滤特定的日志级别
# 当捕获到异常时,在 WARN/ERROR 日志之后会有附加信息,
# 使用 `-A` 打印更多行
$ kubectl logs alluxio-fuse-acee53e8f0a9-3gjbrdekk0 | grep -A 1 'WARN\|ERROR'
2024-07-04 17:29:53,499 ERROR HdfsUfsStatusIterator - Failed to list the path hdfs://localhost:9000/
java.net.ConnectException: Call From myhost/192.168.1.10 to localhost:9000 failed on connection exception: java.net.ConnectException: Connection refused; For more details see:  http://wiki.apache.org/hadoop/ConnectionRefused

# 使用 `kubectl logs -p` 获取之前失败容器的日志
$ kubectl logs -p alluxio-worker-59476bf8c5-lg4sc | grep -A 1 'WARN\|ERROR'
2024-07-05 14:22:28,290 [QuotaCoordinatorTimerThread] ERROR quota.QuotaCoordinator (QuotaCoordinator.java:lambda$new$0) - Failed to run quota janitor job
java.io.IOException: Failed to poll quota usage from worker WorkerInfo{id=7128818659502632567, identity=worker-7128818659502632567, address=WorkerNetAddress{host=sunbowen, containerHost=, rpcPort=64750, dataPort=64751, webPort=64753, domainSocketPath=, secureRpcPort=0, httpServerPort=0}, lastContactSec=283, state=LIVE, capacityBytes=1073741824, usedBytes=0, startTimeMs=1720160253072, capacityBytesOnTiers={MEM=1073741824}, usedBytesOnTiers={MEM=0}, version=3.x-7.0.0-SNAPSHOT, revision=fca83a4688187055d7abfd3a7d710b83a6b62ac6}

Kubernetes CSI 驱动程序

# 检查 csi 节点日志
# csi 节点将管理本地节点上的 fuse pod 和挂载点,因此我们需要检查与fuse pod和用户pod部署在同一节点上的csi节点
# 1. 获取 FUSE Pod /用户 Pod 的节点名称
$ kubectl get pod alluxio-fuse-acee53e8f0a9-3gjbrdekk0 -owide
NAME                                   READY   STATUS    RESTARTS   AGE   IP          NODE                         NOMINATED NODE   READINESS GATES
alluxio-fuse-acee53e8f0a9-3gjbrdekk0   1/1     Running   0          66m   10.0.5.97   ip-10-0-5-202.ec2.internal   <none>           <none>

# 2. 根据节点名称获取 CSI 节点 
$ kubectl get pod -n alluxio-operator -l app.kubernetes.io/component=csi-nodeplugin --field-selector spec.nodeName=ip-10-0-5-202.ec2.internal
NAME                           READY   STATUS    RESTARTS   AGE
alluxio-csi-nodeplugin-5vvdg   2/2     Running   0          47h

# 3. 从 csi 节点获取日志
$ kubectl logs alluxio-csi-nodeplugin-5vvdg -n alluxio-operator -c csi-nodeserver

# 或者可以使用以下一行命令获取 csi 节点日志
# 需要确保环境变量 `PODNS`和 `POD` 设置正确
$ PODNS=default POD=alluxio-fuse-acee53e8f0a9-3gjbrdekk0
$ kubectl logs -n alluxio-operator -c csi-nodeserver $(kubectl get pod -n alluxio-operator -l app.kubernetes.io/component=csi-nodeplugin --field-selector spec.nodeName=$(kubectl get pod -o jsonpath='{.spec.nodeName}' -n ${PODNS} ${POD}) -o jsonpath='{..metadata.name}')
...
{"level":"info","time":"2024-08-09T04:06:19.986Z","caller":"csi/driver.go:94","msg":"GRPC respond","response":{"capabilities":[{"Type":{"Rpc":{"type":1}}}]}}
{"level":"info","time":"2024-08-09T04:06:19.986Z","caller":"csi/driver.go:89","msg":"GRPC call","method":"/csi.v1.Node/NodeGetCapabilities","request":{}}

快速恢复

ETCD 故障

Alluxio 针对 ETCD 故障已经实现了自动应急处理。 在 ETCD 出现故障后,Alluxio 会在一段(一般这个周期为24小时)时间内容忍 ETCD 故障以保证 IO 的正常进行。 同时管理员应当在这段时间内解决ETCD 故障。

当 ETCD 出现故障时,一般可以通过重启 ETCD pod来解决。 在特殊情况下,如果 ETCD 节点无法通过K8s重启解决问题,可以参考如下步骤重建和恢复 ETCD 服务。

  1. 关闭集群:kubectl delete -f alluxio-cluster.yaml
  2. 删除原来的 ETCD PV 和PVC
  3. 对原来的 ETCD pv 目录进行备份,防止之后需要恢复数据
  4. 检查所有物理机上的 ETCD 文件夹并将里面的内容都删掉
  5. 创建三个 ETCD PV。如果 PV 是由管理员手动创建,则建议在 PV 当中增加nodeAffinity section 保证 PV 分别对应一个 ETCD 节点和一个物理机。
apiVersion: v1
kind: PersistentVolumeClaim
spec:
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - ip-10-0-4-212.ec2.internal
  1. 重启集群:kubectl create -f alluxio-cluster.yaml
  2. 如果 UnderFileSystem 资源中未定义 UFS 挂载点,则需要重新执行alluxio mount add命令以重新挂载 UFS。

Worker 故障

Alluxio 通过优化功能可以在保证 I/O 不中断的情况下处理 Worker 故障。 一般来说,少量 Worker 故障不会影响 Alluxio 的功能。

当 Worker 出现故障时,K8s 会通过重启 Worker pod 来解决,不会影响已缓存数据。

FUSE 故障

Fuse 容器如果崩溃,会由集群内部的机制自动重启并恢复。 容器内的挂载点也会在 Fuse 容器启动完毕后自动恢复。

如果遇到 Fuse 容器因为某些原因停止响应,希望恢复到正常状态,可以直接执行kubectl delete pod alluxio-fuse-xxx 命令删除Fuse容器,并等待自动恢复机制完成工作。

Coordinator 故障

Coordinator 负责处理负载作业和管理服务。 它将提交的作业的元数据持久化到元存储(metastore),并在崩溃时进行恢复。 如果持久化的数据出现损坏,未完成的作业将丢失,并需要重新提交。

当 coordinator 出现故障时,K8s 会自动重启 coordinator pod。