Skip to main content

生产环境部署建议

本章介绍在生产环境中使用 CSI 驱动的一系列最佳实践,以及注意事项。

Mount Pod 设置

Sidecar 模式推荐设置

目前 CSI 驱动不支持为 sidecar 模式的 mount 容器设置退出顺序,无法做到在应用容器退出以后,sidecar 才退出。这是由于 Kubernetes sidecar 自身便不支持退出顺序导致的,该特性在 v1.28 原生 sidecar 得到支持,因此如果你使用新版 Kubernetes 并且有相关需求,请在对应的 GitHub issue 下记录需求。

因此,在用户广泛采纳 Kubernetes v1.28,让 CSI 驱动有机会实现原生 sidecar 之前,我们建议用户通过设置 preStop 来满足延迟退出的需求:

mountPodPatch:
- terminationGracePeriodSeconds: 3600
lifecycle:
preStop:
exec:
command:
- sh
- -c
- |
sleep 30;

上方是最为简单的示范,sidecar(也就是 mount 容器)会等待 30 秒后才退出。如果你的应用监听了网络端口,也可以通过检测其监听端口来建立依赖关系,保证 sidecar 容器晚于业务容器退出。

mountPodPatch:
- terminationGracePeriodSeconds: 3600
lifecycle:
preStop:
exec:
command:
- sh
- -c
- |
set +e
# 根据实际情况修改
url=http://127.0.0.1:8000
while :
do
res=$(curl -s -w '%{exitcode}' $url)
# 仅当服务端口返回 Connection refused,才视为服务退出
if [[ "$res" == 7 ]]
then
exit 0
else
echo "$url is still open, wait..."
sleep 1
fi
done

监控 Mount Pod(社区版)

提示

本节介绍的监控相关实践仅适用于 JuiceFS 社区版,企业版客户端并不通过本地端口来暴露监控数据,而是提供中心化的抓取 API,详见企业版文档

默认设置下(未使用 hostNetwork),Mount Pod 通过 9567 端口提供监控 API(也可以通过在 mountOptions 中添加 metrics 选项来自定义端口号),端口名为 metrics,因此可以按如下方式配置 Prometheus 的监控配置。

Prometheus 收集监控指标

prometheus.yml 添加相应的抓取配置,来收集监控指标:

scrape_configs:
- job_name: 'juicefs'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_phase]
separator: ;
regex: (Failed|Succeeded)
replacement: $1
action: drop
- source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name, __meta_kubernetes_pod_labelpresent_app_kubernetes_io_name]
separator: ;
regex: (juicefs-mount);true
replacement: $1
action: keep
- source_labels: [__meta_kubernetes_pod_container_port_name]
separator: ;
regex: metrics # Mount Pod 监控 API 端口名
replacement: $1
action: keep
- separator: ;
regex: (.*)
target_label: endpoint
replacement: metrics
action: replace
- source_labels: [__address__]
separator: ;
regex: (.*)
modulus: 1
target_label: __tmp_hash
replacement: $1
action: hashmod
- source_labels: [__tmp_hash]
separator: ;
regex: "0"
replacement: $1
action: keep

上方的示范假定 Prometheus 服务运行在 Kubernetes 集群中,如果运行在集群外,除了确保正确设置安全组,允许 Prometheus 访问 Kubernetes 节点,还需要额外添加 api_servertls_config

scrape_configs:
- job_name: 'juicefs'
kubernetes_sd_configs:
# 详见 https://github.com/prometheus/prometheus/issues/4633
- api_server: <Kubernetes API Server>
role: pod
tls_config:
ca_file: <...>
cert_file: <...>
key_file: <...>
insecure_skip_verify: false
relabel_configs:
...

Prometheus Operator 收集监控指标

对于 Prometheus Operator,可以新增一个 PodMonitor 来收集监控指标:

apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: juicefs-mounts-monitor
labels:
name: juicefs-mounts-monitor
spec:
namespaceSelector:
matchNames:
# 设置成 CSI 驱动所在的 namespace,默认为 kube-system
- <namespace>
selector:
matchLabels:
app.kubernetes.io/name: juicefs-mount
podMetricsEndpoints:
- port: metrics # Mount Pod 监控 API 端口名
path: '/metrics'
scheme: 'http'
interval: '5s'

同时在 Prometheus 资源中设置上述的 PodMonitor

apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: prometheus
spec:
serviceAccountName: prometheus
podMonitorSelector:
matchLabels:
name: juicefs-mounts-monitor
resources:
requests:
memory: 400Mi
enableAdminAPI: false

在 Grafana 中进行数据可视化

按照上方步骤搭建好容器指标收集后,参考下方文档配置 Grafana 仪表盘:

在 EFK 中收集 Mount Pod 日志

CSI 驱动的问题排查,往往涉及到查看 Mount Pod 日志。如果实时查看 Mount Pod 日志无法满足你的需要,考虑搭建 EFK(Elasticsearch + Fluentd + Kibana),或者其他合适的容器日志收集系统,用来留存和检索 Pod 日志。以 EFK 为例:

  • Elasticsearch:负责对日志进行索引,并提供了一个完整的全文搜索引擎,可以方便用户从日志中检索需要的数据。安装方法参考官方文档
  • Fluentd:负责获取容器日志文件、过滤和转换日志数据,然后将数据传递到 Elasticsearch 集群。安装方法参考官方文档
  • Kibana:负责对日志进行可视化分析,包括日志搜索、处理以及绚丽的仪表板展示等。安装方法参考官方文档

Mount Pod 均包含固定的 app.kubernetes.io/name: juicefs-mount 标签。在 Fluentd 的配置文件中可以配置收集对应标签的日志:

<filter kubernetes.**>
@id filter_log
@type grep
<regexp>
key $.kubernetes.labels.app_kubernetes_io/name
pattern ^juicefs-mount$
</regexp>
</filter>

然后在 Fluentd 的配置文件中加上如下解析插件:

<filter kubernetes.**>
@id filter_parser
@type parser
key_name log
reserve_data true
remove_key_name_field true
<parse>
@type multi_format
<pattern>
format json
</pattern>
<pattern>
format none
</pattern>
</parse>
</filter>

CSI Controller 的高可用设置

CSI Driver 在 0.19.0 及以上版本支持并默认启用 CSI Controller 高可用模式,能够有效避免单点故障。默认为双副本,竞选间隔(Lease duration)为 15s,这意味着当 CSI Controller 服务节点出现意外后,至多需要 15s 来恢复服务。考虑到 CSI Controller 的异常并不会直接影响已有挂载点继续正常运作,正常情况下无需调整竞选间隔时间。

Helm

HA 已经在我们默认的 values.yaml 中启用:

controller:
leaderElection:
enabled: true # 开启 Leader 选举
leaseDuration: "15s" # Leader 的间隔,默认为 15s
replicas: 2 # 副本数,高可用模式下至少需要 2 副本

如果资源不足,或者集群压力较大导致选举超时,那么可以尝试禁用高可用:

values-mycluster.yaml
controller:
leaderElection:
enabled: false
replicas: 1

kubectl

用 kubectl 直接安装 CSI 驱动时,高可用相关的选项如下:

spec:
replicas: 2 # 副本数,高可用模式下至少需要 2 副本
template:
spec:
containers:
- name: juicefs-plugin
args:
- --leader-election # 开启 Leader 选举
- --leader-election-lease-duration=15s # Leader 的间隔,默认为 15s
...
- name: csi-provisioner
args:
- --enable-leader-election # 开启 Leader 选举
- --leader-election-lease-duration=15s # Leader 的间隔,默认为 15s
...

启用 Kubelet 认证鉴权

Kubelet 的认证鉴权分为很多种,默认的 AlwaysAllow 模式允许所有请求。但若 kubelet 关闭了匿名访问,会导致 CSI Node 获取 Pod 列表时报错(该报错本身已经修复,见后续描述):

kubelet_client.go:99] GetNodeRunningPods err: Unauthorized
reconciler.go:70] doReconcile GetNodeRunningPods: invalid character 'U' looking for beginning of value

面对这种情况,选择以下一种解决方法:

升级 CSI 驱动

升级 CSI 驱动至 v0.21.0 或更新版本,升级完毕后,当 CSI Node 遭遇一样的鉴权错误时,就不再直连 Kubelet,而是 watch APIServer 去获取信息,由于 watch list 机制在启动时会对 APIServer 进行一次 ListPod 请求(携带了 labelSelector,最大程度减少开销),在集群负载较大的情况下,会对 APIServer 造成额外的压力。因此如果你的 Kubernetes APIServer 负载已经很高,我们推荐配置 CSI Node 对 Kubelet 的认证(详见下一小节)。

需要注意,CSI 驱动需要配置 podInfoOnMount: true,上边提到的避免报错的特性才会真正生效。如果你采用 Helm 安装方式,则 podInfoOnMount 默认开启无需配置,该特性会随着升级自动启用。而如果你使用 kubectl 直接安装,你需要为 k8s.yaml 添加如下配置:

k8s.yaml
...
apiVersion: storage.k8s.io/v1
kind: CSIDriver
...
spec:
podInfoOnMount: true
...

这也是为什么在生产环境,我们推荐用 Helm 安装 CSI 驱动,避免手动维护的 k8s.yaml,在升级时带来额外的心智负担。

将 Kubelet 鉴权委派给 APIServer

下文中的配置方法均总结自官方文档

Kubelet 的配置,既可以直接放在命令行参数中,也可以书写在配置文件里(默认 /var/lib/kubelet/config.yaml),你可以使用类似下方的命令来确认 Kubelet 如何管理配置:

$ systemctl cat kubelet
# /lib/systemd/system/kubelet.service
...
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
...

注意上方示范中高亮行,这表明 Kubelet 将配置存放在 /var/lib/kubelet/config.yaml,这种情况下需要编辑配置该配置文件,启用 Webhook 认证(注意高亮行):

/var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
webhook:
cacheTTL: 0s
enabled: true
...
authorization:
mode: Webhook
...

但若 Kubelet 并未使用配置文件,而是将所有配置都直接追加在启动参数中,那么你需要追加 --authorization-mode=Webhook--authentication-token-webhook,来实现相同的效果。

大规模集群

本节语境中不对「大规模」作明确定义,如果你的集群节点数超过 100,或者 Pod 总数超 1000,或者前两个条件均未达到,但是 Kubernetes APIServer 的负载过高,都可以考虑本节中的推荐事项,排除潜在的性能问题。

  • 开启 ListPod 缓存:CSI 驱动需要获取 Pod 列表,如果 Pod 数量庞大,对 APIServer 和背后的 etcd 有性能冲击。此时可以通过 ENABLE_APISERVER_LIST_CACHE="true" 这个环境变量来启用缓存特性。你可以在 values.yaml 中通过环境变量声明:

    values-mycluster.yaml
    controller:
    envs:
    - name: ENABLE_APISERVER_LIST_CACHE
    value: "true"

    node:
    envs:
    - name: ENABLE_APISERVER_LIST_CACHE
    value: "true"
  • 同样是为了减轻 APIServer 访问压力,建议启用 Kubelet 认证鉴权

  • 如果 CSI 驱动造成的 APIServer 访问量太大,可以用 [KUBE_QPS|KUBE_BURST] 这两个环境变量来配置限速:

    values-mycluster.yaml
    # 默认值可以参考 https://pkg.go.dev/k8s.io/client-go/rest#Config
    controller:
    envs:
    - name: KUBE_QPS
    value: 3
    - name: KUBE_BURST
    value: 5

    node:
    envs:
    - name: KUBE_QPS
    value: 3
    - name: KUBE_BURST
    value: 5

客户端写缓存(不推荐)

就算脱离 Kubernetes,客户端写缓存(--writeback)也是需要谨慎使用的功能,他的作用是将客户端写入的文件数据存在本地盘,然后异步上传至对象存储。这带来不少使用体验和数据安全性的问题,在 JuiceFS 文档里都有着重介绍:

正常在宿主机上使用,便已经是具有风险的功能,因此我们不推荐在 CSI 驱动中开启 --writeback,避免因为容器生命周期短,造成数据还来不及上传,容器就销毁了,导致数据丢失。

在充分理解 --writeback 风险的前提下,如果你的场景必须使用该功能,那么请一定仔细阅读下列要点,保证集群正确配置,尽可能避免在 CSI 驱动中使用写缓存带来的额外风险:

  • 配置好缓存持久化,确保缓存目录不会随着容器销毁而丢失。具体配置方法阅读缓存设置
  • 选择下列方法之一(也可以都采纳),实现在应用容器退出的情况下,也保证 JuiceFS 客户端有足够的时间将数据上传完成:
    • 启用延迟删除 Mount Pod,即便应用 Pod 退出,Mount Pod 也会等待指定时间后,才由 CSI Node 销毁。合理设置延时,保证数据及时上传完成;

    • 自 v0.24 起,CSI 驱动支持定制 Mount Pod 的方方面面,因此可以修改 terminationGracePeriodSeconds,再配合 preStop 实现等待数据上传完成后,Mount Pod 才退出,示范如下:

      注意
      • 配置了 preStop 后,若写缓存一直未上传成功,Mount Pod 会一直等待 terminationGracePeriodSeconds 参数所设定的时间,长时间无法退出。这会影响某些操作的正常执行(如升级 Mount Pod),请充分测试并理解对应的风险;
      • 上述两种方案都不能完全保证所有写缓存数据都上传成功。
      values-mycluster.yaml
      globalConfig:
      mountPodPatch:
      - terminationGracePeriodSeconds: 600 # 请适当调整容器退出时的等待时间
      lifecycle:
      preStop:
      exec:
      command:
      - sh
      - -c
      - |
      set +e

      # 获取保存写缓存数据的目录
      staging_dir="$(cat ${MOUNT_POINT}/.config | grep 'CacheDir' | cut -d '"' -f 4)/rawstaging/"

      # 等待写缓存目录中的文件全部上传完毕再退出
      if [ -d "$staging_dir" ]; then
      while :
      do
      staging_files=$(find $staging_dir -type f | head -n 1)
      if [ -z "$staging_files" ]; then
      echo "all staging files uploaded"
      break
      else
      echo "waiting for staging files: $staging_files ..."
      sleep 3
      fi
      done
      fi

      umount -l ${MOUNT_POINT}
      rmdir ${MOUNT_POINT}
      exit 0