OSS存儲(chǔ)讀寫分離最佳實(shí)踐
OSS數(shù)據(jù)卷是使用ossfs文件進(jìn)行掛載的FUSE文件系統(tǒng),適合于讀文件場(chǎng)景。OSS為共享存儲(chǔ),支持ReadOnlyMany和ReadWriteMany兩種訪問模式。ossfs適用于并發(fā)讀場(chǎng)景,建議您配置PVC和PV的訪問模式為ReadOnlyMany。本文介紹在讀多寫少場(chǎng)景下如何通過OSS SDK、ossutil工具等方式實(shí)現(xiàn)數(shù)據(jù)的讀寫分離。
前提條件
- 說明
若Bucket和ECS實(shí)例位于相同地域,請(qǐng)選擇私網(wǎng)域名。
使用場(chǎng)景
OSS存儲(chǔ)常見的使用場(chǎng)景包含只讀和讀寫。對(duì)于讀多寫少的場(chǎng)景,建議您將OSS數(shù)據(jù)的讀寫操作進(jìn)行分離,然后通過配置緩存參數(shù)優(yōu)化數(shù)據(jù)讀取速度,并通過SDK等方式寫入數(shù)據(jù)。
只讀
在大數(shù)據(jù)業(yè)務(wù)的推斷過程、數(shù)據(jù)分析、數(shù)據(jù)查詢等場(chǎng)景中使用時(shí),為避免數(shù)據(jù)被誤刪除和誤修改,建議您將OSS存儲(chǔ)卷的訪問模式配置為ReadOnlyMany。具體操作,請(qǐng)參見使用OSS靜態(tài)存儲(chǔ)卷。
您還可以通過配置緩存參數(shù)優(yōu)化數(shù)據(jù)的讀取速度。
參數(shù) | 說明 |
kernel_cache | 開啟后,通過內(nèi)核緩存優(yōu)化讀性能。適用于不需要實(shí)時(shí)訪問最新內(nèi)容的場(chǎng)景。 緩存命中時(shí),ossfs重復(fù)讀取文件時(shí),將通過內(nèi)核緩沖區(qū)高速緩存處理,僅使用未被其他進(jìn)程使用的可用內(nèi)存。 |
parallel_count | 以分片模式上傳或下載大文件時(shí),分片的并發(fā)數(shù),默認(rèn)值為20。 |
max_multireq | 列舉文件時(shí),訪問文件元信息的最大并發(fā)數(shù)。此處需大于等于parallel_count的值,默認(rèn)值為20。 |
max_stat_cache_size | 用于指定文件元數(shù)據(jù)的緩存空間可緩存多少個(gè)文件的元數(shù)據(jù)。單位為個(gè),默認(rèn)值為1000。如需禁止使用元數(shù)據(jù)緩存,可設(shè)置為0。 在不需要實(shí)時(shí)訪問最新內(nèi)容的場(chǎng)景下,當(dāng)目錄下文件比較多時(shí),可以根據(jù)實(shí)例規(guī)格增加支持的緩存?zhèn)€數(shù),加快ls的速度。 |
通過OSS控制臺(tái)、SDK、ossutil工具等其他方式上傳的文件及目錄,在ossfs中默認(rèn)權(quán)限為640。您可以根據(jù)實(shí)際業(yè)務(wù)需求,通過配置-o gid=xxx -o uid=xxx
或-o mask=022
參數(shù),避免OSS掛載的目錄及子目錄不可讀的問題。更多信息,請(qǐng)參見OSS存儲(chǔ)掛載權(quán)限問題。更多ossfs配置項(xiàng),請(qǐng)參見ossfs/README-CN.md。
讀寫
在讀寫場(chǎng)景中,您需要將OSS存儲(chǔ)卷的訪問模式配置為ReadWriteMany。通過ossfs進(jìn)行寫操作時(shí),注意事項(xiàng)如下。
并發(fā)寫場(chǎng)景中,OSSFS無法保證數(shù)據(jù)寫入的一致性。
掛載狀態(tài)下,登錄應(yīng)用Pod或宿主機(jī),在掛載路徑下刪除或變更文件,都會(huì)直接刪除或變更OSS Bucket中對(duì)應(yīng)的源文件。您可以通過開啟OSS Bucket的版本控制,避免誤刪除重要數(shù)據(jù),請(qǐng)參見版本控制概述。
在讀多寫少、尤其是讀寫路徑分離的場(chǎng)景中,例如,在大數(shù)據(jù)業(yè)務(wù)的訓(xùn)練過程中,建議您將OSS數(shù)據(jù)的讀寫操作進(jìn)行分離,即將OSS存儲(chǔ)卷的訪問模式配置為ReadOnlyMany,然后通過配置緩存參數(shù)優(yōu)化數(shù)據(jù)讀取速度,并通過SDK等方式寫入數(shù)據(jù)。具體操作,請(qǐng)參見使用示例。
使用示例
本文以手寫圖像識(shí)別訓(xùn)練應(yīng)用為例,介紹如何實(shí)現(xiàn)OSS存儲(chǔ)的讀寫分離。該示例為一個(gè)簡(jiǎn)單的深度學(xué)習(xí)模型訓(xùn)練,業(yè)務(wù)通過只讀OSS存儲(chǔ)卷從OSS的/data-dir目錄中讀取訓(xùn)練集,并通過OSS SDK將checkpoint寫入OSS的/log-dir目錄。
通過ossfs實(shí)現(xiàn)讀寫
參考以下模板部署手寫圖像識(shí)別訓(xùn)練應(yīng)用。該應(yīng)用使用簡(jiǎn)單的Python編寫,并掛載使用OSS靜態(tài)存儲(chǔ)卷。關(guān)于OSS存儲(chǔ)卷配置,請(qǐng)參見使用OSS靜態(tài)存儲(chǔ)卷。
以下示例中,應(yīng)用將OSS Bucket的子路徑
/tf-train
掛載至Pod的/mnt
目錄,在/tf-train/train/data
目錄中存放了MNIST手寫圖像訓(xùn)練集,供應(yīng)用讀取。目錄如下圖所示。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
訓(xùn)練開始前,
trainning_logs
目錄為空。在訓(xùn)練過程中,中間文件將寫入Pod的/mnt/training_logs
目錄中,由ossfs上傳至OSS Bucket的/tf-train/trainning_logs
目錄中。驗(yàn)證數(shù)據(jù)正常讀寫。
執(zhí)行以下命令,查看Pod的狀態(tài)。
kubectl get pod tf-mnist
等待Pod狀態(tài)從Running轉(zhuǎn)換至Completed,約需要數(shù)分鐘,預(yù)期輸出為:
NAME READY STATUS RESTARTS AGE tf-mnist 1/1 Completed 0 2m
執(zhí)行以下命令,查看Pod運(yùn)行日志。
通過Pod運(yùn)行日志查詢數(shù)據(jù)加載所需的時(shí)間,該時(shí)間包含從OSS下載文件及TensorFlow加載的時(shí)間。
kubectl logs pod tf-mnist | grep dataload
預(yù)期輸出:
dataload cost time: 1.54191803932
實(shí)際查詢的時(shí)間與實(shí)例的性能和網(wǎng)絡(luò)狀態(tài)相關(guān)。
登錄OSS管理控制臺(tái)。查看OSS Bucket的
/tf-train/trainning_logs
目錄中已出現(xiàn)相關(guān)文件,表明數(shù)據(jù)可以正常從OSS中讀寫。
通過讀寫分離優(yōu)化ossfs數(shù)據(jù)讀取速度
下文以手寫圖像識(shí)別訓(xùn)練應(yīng)用和OSS SDK為例,介紹如何改造應(yīng)用實(shí)現(xiàn)讀寫分離。
在容器環(huán)境中安裝SDK,可在構(gòu)建鏡像時(shí),增加以下內(nèi)容。具體操作,請(qǐng)參見Python安裝。
RUN pip install oss2
參考OSS的官方文檔Python SDK demo修改源代碼。
以上述手寫圖像識(shí)別訓(xùn)練應(yīng)用為例,源鏡像的相關(guān)源代碼如下。
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)
以上代碼中,每進(jìn)行100次迭代,會(huì)將中間文件(checkpoint)存入指定的log_dir目錄,即Pod的
/mnt/training_logs
目錄。由于Saver的max_to_keep
參數(shù)為0,將維護(hù)所有的中間文件。如果迭代1000次,則存放10組checkpoint文件在OSS端。通過修改代碼,實(shí)現(xiàn)通過OSS SDK上傳中間文件,修改要求如下:
配置訪問憑證,從環(huán)境變量中讀取AccessKey和Bucket信息。具體操作,請(qǐng)參見Python配置訪問憑證。
為減少容器內(nèi)存的使用,可將
max_to_keep
設(shè)置為1,即總是只保存最新一組訓(xùn)練中間文件。每次保存中間文件時(shí),通過put_object_from_file函數(shù)上傳至對(duì)應(yīng)Bucket目錄。
說明在讀寫目錄分離的場(chǎng)景中,使用SDK時(shí),還可以通過異步讀寫進(jìn)一步提升訓(xùn)練效率。
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
。修改部分應(yīng)用模板,使其通過只讀方式訪問OSS。
將PV和PVC的
accessModes
均修改為ReadOnlyMany
,Bucket的掛載路徑可縮小至/tf-train/train/data
。在
otherOpts
中增加-o kernel_cache -o max_stat_cache_size=10000 -oumask=022
選項(xiàng),使ossfs在讀取數(shù)據(jù)時(shí)能使用內(nèi)存高速緩沖區(qū)加速處理,并增加元數(shù)據(jù)支持的緩存?zhèn)€數(shù)(10000個(gè)元數(shù)據(jù)緩存大約占40M的內(nèi)存,可根據(jù)實(shí)例規(guī)格及讀取的數(shù)據(jù)量多少進(jìn)行調(diào)整),以及通過umask使容器進(jìn)程以非root用戶運(yùn)行時(shí)也有讀權(quán)限。更多信息,請(qǐng)參見使用場(chǎng)景。在Pod模板中增加OSS_ACCESS_KEY_ID、OSS_ACCESS_KEY_SECRET環(huán)境變量,其值可從oss-secret中獲取,與配置OSS存儲(chǔ)卷中的信息保持一致。
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 #若已經(jīng)配置了default URL,可忽略 value: "https://oss-cn-beijing.aliyuncs.com" - name: BUCKET #若已經(jīng)配置了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
驗(yàn)證數(shù)據(jù)正常讀寫。
執(zhí)行以下命令,查看Pod狀態(tài)。
kubectl get pod tf-mnist
等待Pod狀態(tài)從Running轉(zhuǎn)換至Completed,約需要數(shù)分鐘,預(yù)期輸出為:
NAME READY STATUS RESTARTS AGE tf-mnist 1/1 Completed 0 2m
執(zhí)行以下命令,查看Pod運(yùn)行日志。
通過Pod運(yùn)行日志查詢數(shù)據(jù)加載所需的時(shí)間,該時(shí)間包含從OSS下載文件及TensorFlow加載的時(shí)間。
kubectl logs pod tf-mnist | grep dataload
預(yù)期輸出:
dataload cost time: 0.843528985977
預(yù)期輸出表明,在只讀模式中合理利用緩存,可提升數(shù)據(jù)讀取的速度。在大規(guī)模訓(xùn)練或其他持續(xù)加載數(shù)據(jù)的場(chǎng)景中,優(yōu)化效果更加明顯。
登錄OSS管理控制臺(tái)。查看OSS Bucket的
/tf-train/trainning_logs
目錄中已出現(xiàn)相關(guān)文件,表明數(shù)據(jù)可以正常從OSS中讀寫。
阿里云官方OSS SDK參考代碼
阿里云官方OSS SDK部分參考代碼如下。更多支持語言PHP、Node.js、Browser.js、.NET、Android、iOS、Ruby,請(qǐng)參見SDK參考。
編程語言 | 參考代碼 |
JAVA | |
Python | |
GO | |
C++ | |
C |
實(shí)現(xiàn)OSS讀寫分離的其他工具
工具 | 相關(guān)文檔 |
OpenAPI | |
ossutil命令行工具 | |
ossbrowser圖形化管理工具 |