文档

OSS存储读写分离最佳实践

更新时间:

OSS数据卷是使用ossfs文件进行挂载的FUSE文件系统,适合于读文件场景。OSS为共享存储,支持ReadOnlyMany和ReadWriteMany两种访问模式。ossfs适用于并发读场景,建议您配置PVC和PV的访问模式为ReadOnlyMany。本文介绍在读多写少场景下如何通过OSS SDK、ossutil工具等方式实现数据的读写分离。

前提条件

使用场景

OSS存储常见的使用场景包含只读和读写。对于读多写少的场景,建议您将OSS数据的读写操作进行分离,然后通过配置缓存参数优化数据读取速度,并通过SDK等方式写入数据。

只读

在大数据业务的推断过程、数据分析、数据查询等场景中使用时,为避免数据被误删除和误修改,建议您将OSS存储卷的访问模式配置为ReadOnlyMany。具体操作,请参见使用OSS静态存储卷

您还可以通过配置缓存参数优化数据的读取速度。

参数

说明

kernel_cache

开启后,通过内核缓存优化读性能。适用于不需要实时访问最新内容的场景。

缓存命中时,ossfs重复读取文件时,将通过内核缓冲区高速缓存处理,仅使用未被其他进程使用的可用内存。

parallel_count

以分片模式上传或下载大文件时,分片的并发数,默认值为20。

max_multireq

列举文件时,访问文件元信息的最大并发数。此处需大于等于parallel_count的值,默认值为20。

max_stat_cache_size

用于指定文件元数据的缓存空间可缓存多少个文件的元数据。单位为个,默认值为1000。如需禁止使用元数据缓存,可设置为0。

在不需要实时访问最新内容的场景下,当目录下文件比较多时,可以根据实例规格增加支持的缓存个数,加快ls的速度。

通过OSS控制台、SDK、ossutil工具等其他方式上传的文件及目录,在ossfs中默认权限为640。您可以根据实际业务需求,通过配置-o gid=xxx -o uid=xxx-o mask=022参数,避免OSS挂载的目录及子目录不可读的问题。更多信息,请参见OSS存储挂载权限问题。更多ossfs配置项,请参见ossfs/README-CN.md

读写

在读写场景中,您需要将OSS存储卷的访问模式配置为ReadWriteMany。通过ossfs进行写操作时,注意事项如下。

  • 并发写场景中,OSSFS无法保证数据写入的一致性。

  • 挂载状态下,登录应用Pod或宿主机,在挂载路径下删除或变更文件,都会直接删除或变更OSS Bucket中对应的源文件。您可以通过开启OSS Bucket的版本控制,避免误删除重要数据,请参见版本控制概述

在读多写少、尤其是读写路径分离的场景中,例如,在大数据业务的训练过程中,建议您将OSS数据的读写操作进行分离,即将OSS存储卷的访问模式配置为ReadOnlyMany,然后通过配置缓存参数优化数据读取速度,并通过SDK等方式写入数据。具体操作,请参见使用示例

使用示例

本文以手写图像识别训练应用为例,介绍如何实现OSS存储的读写分离。该示例为一个简单的深度学习模型训练,业务通过只读OSS存储卷从OSS的/data-dir目录中读取训练集,并通过OSS SDK将checkpoint写入OSS的/log-dir目录。

通过ossfs实现读写

  1. 参考以下模板部署手写图像识别训练应用。该应用使用简单的Python编写,并挂载使用OSS静态存储卷。关于OSS存储卷配置,请参见使用OSS静态存储卷

    以下示例中,应用将OSS Bucket的子路径/tf-train挂载至Pod的/mnt目录,在/tf-train/train/data目录中存放了MNIST手写图像训练集,供应用读取。目录如下图所示。image.png

    展开查看手写图像识别训练应用示例的YAML文件

    cat << EOF | kubectl apply -f -
    apiVersion: v1
    kind: Secret
    metadata:
      name: oss-secret
      namespace: default
    stringData:
      akId: "<your-accesskey-id>"
      akSecret: "<your-accesskey-secret>"
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: tf-train-pv
      labels:
        alicloud-pvname: tf-train-pv
    spec:
      capacity:
        storage: 10Gi
      accessModes:
        - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain
      csi:
        driver: ossplugin.csi.alibabacloud.com
        volumeHandle: tf-train-pv
        nodePublishSecretRef:
          name: oss-secret
          namespace: default
        volumeAttributes:
          bucket: "<a-bucket-name>"
          url: "oss-cn-beijing.aliyuncs.com"
          otherOpts: "-o max_stat_cache_size=0 -o allow_other"
          path: "/tf-train"
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: tf-train-pvc
    spec:
      accessModes:
      - ReadWriteMany
      resources:
        requests:
          storage: 10Gi
      selector:
        matchLabels:
          alicloud-pvname: tf-train-pv
    ---
    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        app: tfjob
      name: tf-mnist
      namespace: default
    spec:
      containers:
      - command:
        - sh
        - -c
        - python /app/main.py
        env:
        - name: NVIDIA_VISIBLE_DEVICES
          value: void
        - name: gpus
          value: "0"
        - name: workers
          value: "1"
        - name: TEST_TMPDIR
          value: "/mnt"
        image: registry.cn-beijing.aliyuncs.com/tool-sys/tf-train-demo:rw
        imagePullPolicy: Always
        name: tensorflow
        ports:
        - containerPort: 20000
          name: tfjob-port
          protocol: TCP
        volumeMounts:
          - name: train
            mountPath: "/mnt"
        workingDir: /root
      priority: 0
      restartPolicy: Never
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - name: train
        persistentVolumeClaim:
          claimName: tf-train-pvc
    EOF

    训练开始前,trainning_logs目录为空。在训练过程中,中间文件将写入Pod的/mnt/training_logs目录中,由ossfs上传至OSS Bucket的/tf-train/trainning_logs目录中。

  2. 验证数据正常读写。

    1. 执行以下命令,查看Pod的状态。

      kubectl get pod tf-mnist

      等待Pod状态从Running转换至Completed,约需要数分钟,预期输出为:

      NAME       READY   STATUS    RESTARTS   AGE
      tf-mnist   1/1     Completed   0          2m
    2. 执行以下命令,查看Pod运行日志。

      通过Pod运行日志查询数据加载所需的时间,该时间包含从OSS下载文件及TensorFlow加载的时间。

      kubectl logs pod tf-mnist | grep dataload

      预期输出:

      dataload cost time:  1.54191803932

      实际查询的时间与实例的性能和网络状态相关。

    3. 登录OSS管理控制台查看OSS Bucket的/tf-train/trainning_logs目录中已出现相关文件,表明数据可以正常从OSS中读写。image.png

通过读写分离优化ossfs数据读取速度

下文以手写图像识别训练应用和OSS SDK为例,介绍如何改造应用实现读写分离。

  1. 在容器环境中安装SDK,可在构建镜像时,增加以下内容。具体操作,请参见Python安装

    RUN pip install oss2
  2. 参考OSS的官方文档Python SDK demo修改源代码。

    以上述手写图像识别训练应用为例,源镜像的相关源代码如下。

    def train():
        ...
    	saver = tf.train.Saver(max_to_keep=0)
        
        for i in range(FLAGS.max_steps):
            if i % 10 == 0:  # Record summaries and test-set accuracy
                summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False))
                print('Accuracy at step %s: %s' % (i, acc))
                if i % 100 == 0:
                    print('Save checkpoint at step %s: %s' % (i, acc))
                    saver.save(sess, FLAGS.log_dir + '/model.ckpt', global_step=i)

    以上代码中,每进行100次迭代,会将中间文件(checkpoint)存入指定的log_dir目录,即Pod的/mnt/training_logs目录。由于Saver的max_to_keep参数为0,将维护所有的中间文件。如果迭代1000次,则存放10组checkpoint文件在OSS端。

    通过修改代码,实现通过OSS SDK上传中间文件,修改要求如下:

    1. 配置访问凭证,从环境变量中读取AccessKey和Bucket信息。具体操作,请参见Python配置访问凭证

    2. 为减少容器内存的使用,可将max_to_keep设置为1,即总是只保存最新一组训练中间文件。每次保存中间文件时,通过put_object_from_file函数上传至对应Bucket目录。

    说明

    在读写目录分离的场景中,使用SDK时,还可以通过异步读写进一步提升训练效率。

    import oss2
    from oss2.credentials import EnvironmentVariableCredentialsProvider
    
    auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
    url = os.getenv('URL','<default-url>')
    bucketname = os.getenv('BUCKET','<default-bucket-name>')
    bucket = oss2.Bucket(auth, url, bucket)
    
    ...
    def train():
      ...
      saver = tf.train.Saver(max_to_keep=1)
    
     for i in range(FLAGS.max_steps):
        if i % 10 == 0:  # Record summaries and test-set accuracy
          summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False))
          print('Accuracy at step %s: %s' % (i, acc))
          if i % 100 == 0:
            print('Save checkpoint at step %s: %s' % (i, acc))
            saver.save(sess, FLAGS.log_dir + '/model.ckpt', global_step=i)
            # FLAGS.log_dir = os.path.join(os.getenv('TEST_TMPDIR', '/mnt'),'training_logs')
            for path,_,file_list in os.walk(FLAGS.log_dir) :  
              for file_name in file_list:  
                bucket.put_object_from_file(os.path.join('tf-train/training_logs', file_name), os.path.join(path, file_name))

    修改后的容器镜像为registry.cn-beijing.aliyuncs.com/tool-sys/tf-train-demo:ro

  3. 修改部分应用模板,使其通过只读方式访问OSS。

    1. 将PV和PVC的accessModes均修改为ReadOnlyMany,Bucket的挂载路径可缩小至/tf-train/train/data

    2. otherOpts中增加-o kernel_cache -o max_stat_cache_size=10000 -oumask=022选项,使ossfs在读取数据时能使用内存高速缓冲区加速处理,并增加元数据支持的缓存个数(10000个元数据缓存大约占40M的内存,可根据实例规格及读取的数据量多少进行调整),以及通过umask使容器进程以非root用户运行时也有读权限。更多信息,请参见使用场景

    3. 在Pod模板中增加OSS_ACCESS_KEY_ID、OSS_ACCESS_KEY_SECRET环境变量,其值可从oss-secret中获取,与配置OSS存储卷中的信息保持一致。

    展开查看手写图像识别训练应用示例修改后的YAML文件

    cat << EOF | kubectl apply -f -
    apiVersion: v1
    kind: Secret
    metadata:
      name: oss-secret
      namespace: default
    stringData:
      akId: "<your-accesskey-id>"
      akSecret: "<your-accesskey-secret>"
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: tf-train-pv
      labels:
        alicloud-pvname: tf-train-pv
    spec:
      capacity:
        storage: 10Gi
      accessModes:
        - ReadOnlyMany
      persistentVolumeReclaimPolicy: Retain
      csi:
        driver: ossplugin.csi.alibabacloud.com
        volumeHandle: tf-train-pv
        nodePublishSecretRef:
          name: oss-secret
          namespace: default
        volumeAttributes:
          bucket: "cnfs-oss-csdr-test"
          url: "oss-cn-beijing.aliyuncs.com"
          otherOpts: "-o max_stat_cache_size=10000 -o kernel_cache -o umask=022"
          path: "/tf-train/train/data"
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: tf-train-pvc
    spec:
      accessModes:
      - ReadOnlyMany
      resources:
        requests:
          storage: 10Gi
      selector:
        matchLabels:
          alicloud-pvname: tf-train-pv
    ---
    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        app: tfjob
      name: tf-mnist
      namespace: default
    spec:
      containers:
      - command:
        - sh
        - -c
        - python /app/main.py
        env:
        - name: NVIDIA_VISIBLE_DEVICES
          value: void
        - name: gpus
          value: "0"
        - name: workers
          value: "1"
        - name: TEST_TMPDIR
          value: "/mnt"
        - name: OSS_ACCESS_KEY_ID      #与pv的aksk来源一致
          valueFrom:
            secretKeyRef:
              name: oss-secret
              key: akId
        - name: OSS_ACCESS_KEY_SECRET  #与pv的aksk来源一致
          valueFrom:
            secretKeyRef:
              name: oss-secret 
              key: akSecret
        - name: URL                    #若已经配置了default URL,可忽略
          value: "https://oss-cn-beijing.aliyuncs.com"
        - name: BUCKET                 #若已经配置了default BUCKET,可忽略
          value: "<bucket-name>"
        image: registry.cn-beijing.aliyuncs.com/tool-sys/tf-train-demo:ro
        imagePullPolicy: Always
        name: tensorflow
        ports:
        - containerPort: 20000
          name: tfjob-port
          protocol: TCP
        volumeMounts:
          - name: train
            mountPath: "/mnt/train/data"
        workingDir: /root
      priority: 0
      restartPolicy: Never
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - name: train
        persistentVolumeClaim:
          claimName: tf-train-pvc
    EOF
  4. 验证数据正常读写。

    1. 执行以下命令,查看Pod状态。

      kubectl get pod tf-mnist

      等待Pod状态从Running转换至Completed,约需要数分钟,预期输出为:

      NAME       READY   STATUS    RESTARTS   AGE
      tf-mnist   1/1     Completed   0          2m
    2. 执行以下命令,查看Pod运行日志。

      通过Pod运行日志查询数据加载所需的时间,该时间包含从OSS下载文件及TensorFlow加载的时间。

      kubectl logs pod tf-mnist | grep dataload

      预期输出:

      dataload cost time:  0.843528985977

      预期输出表明,在只读模式中合理利用缓存,可提升数据读取的速度。在大规模训练或其他持续加载数据的场景中,优化效果更加明显。

    3. 登录OSS管理控制台查看OSS Bucket的/tf-train/trainning_logs目录中已出现相关文件,表明数据可以正常从OSS中读写。image.png

阿里云官方OSS SDK参考代码

阿里云官方OSS SDK部分参考代码如下。更多支持语言PHP、Node.js、Browser.js、.NET、Android、iOS、Ruby,请参见SDK参考

编程语言

参考代码

JAVA

Java快速入门

Python

Python快速入门

GO

Go快速入门

C++

C++快速入门

C

C快速入门

实现OSS读写分离的其他工具

工具

相关文档

OSS管理控制台

控制台快速入门

OpenAPI

PutObject

ossutil命令行工具

cp(上传文件)

ossbrowser图形化管理工具

快速使用ossbrowser