使用存储的实践
本文以介绍如何在不同场景下选用合适的存储类型,并以实际的例子演示如何通过 TKEStack 部署和管理一个分布式存储服务,并以云原生的方式为容器化的应用提供高可用、高性能的存储服务。
简介
TKEStack 是腾讯开源的一款集易用性和扩展性于一身的企业级容器服务平台,帮助用户在私有云环境中敏捷、高效地构建和发布应用服务。TKEStack 本身不提供存储功能,但是可以通过集成云原生的存储应用,或者通过存储扩展组件的方式,对接用户的存储设施,扩展 TKEStack 平台的存储能力。
在本文中,通过 TKEStack 集成不同类型存储的介绍,帮助用户掌握云原生环境下存储的使用与管理,助力用户构建面向不同业务场景的容器云解决方案。
本文所介绍的实践方案基于社区开源的存储方案,以及部分云提供商的存储服务方案,TKEStack 不提供对存储服务的质量保证。用户请根据自身实际情况,选择合适的存储方案,或者联系 TKEStack 官方社区、论坛寻求帮助。
TKEStack 支持通过 CSI 存储插件的方式对接外部存储系统,详情请参考 TKEStack CSI Operator。
存储类型的选择
存储类型大致有三种:块存储、文件存储及对象存储
- 块存储:是以块为单位,块存储实际上是管理到数据块一级,相当于直接管理硬盘的数据块。高性能、低时延,满足随机读写,使用时需要格式化为指定的文件系统后才能访问。
- 文件存储:文件系统存储,文件系统是操作系统概念的一部份,支持 POSIX 的文件访问接口。优势是易管理、易共享,但由于采用上层协议, 因此开销大, 延时比块存储高。
- 对象存储:提供 Key-Value(简称 K/V)方式的 RESTful 数据读写接口,并且常以网络服务的形式提供数据的访问。优点是高可用性、全托管、易扩展。
上述几种存储类型各具特色,分别对应不同场景下的需求,例如:
- 块存储具备高性能的读写,提供原始块设备操作能力,非常适合作为一些数据库系统的底层存储。
- 文件系统有着与操作系统一致的 POSIX 文件访问接口,能够方便的在特定范围内共享文件空间,典型的场景是 AI 学习、模型训练场景下对训练数据,模型和结果的存储。
- 对象存储是近年来兴起的一种新的存储方式,适用于分布式云计算场景下的应用业务的海量,高并发的互联网产品场景。
更多存储系统的信息,请参考:wiki
块存储参考实践
本节将介绍如何通过 TKEStack 部署一套块存储系统,并演示如何在集群中使用该存储,在集群中部署 ElasticSearch 对外提供服务。
块存储系统有着较长的历史,基于传统 SAN 存储系统的方案已经非常成熟,但是 SAN 系统的价格较高,且可扩展性较差,难以满足大规模云计算系统下的使用需求。
但块存储本身具有的高带宽、低延迟,高吞吐率等优势,使它在云计算领域仍具有一席之地,典型的产品有 Ceph RBD,AWS EBS,腾讯云 CBS 等。
本节将以 Ceph RBD 为例,通过 Rook 在 TKEStack 容器平台中部署 Ceph RBD 块存储集群,对业务提供块存储服务。
Rook 是一个自管理的分布式存储编排系统,可以为 Kubernetes 提供便利的存储解决方案。
Rook 支持在 K8S 中部署,主要由 Operator 和 Cluster 两部分组成:
- Operator:Rook 的核心组件,自动启动存储集群,并监控存储守护进程,来确保存储集群的健康。
- Cluster:负责创建 CRD 对象,指定相关参数,包括 Ceph 镜像、元数据持久化位置、磁盘位置、dashboard 等等。
部署块存储系统
Rook 支持通过 helm 或 yaml 文件的方式进行部署,本文直接使用官方的 yaml 文件部署 Rook Ceph 存储集群。
登录 TKEStack 管理页面,进入集群管理页下,新建一个至少包括三台节点的集群
登录至该集群的任意节点下,下载 Rook 项目,通过官方例子部署 Rook 集群
git clone --single-branch --branch release-1.3 https://github.com/rook/rook.git cd rook/cluster/examples/kubernetes/ceph kubectl create -f common.yaml kubectl create -f operator.yaml kubectl create -f cluster.yaml
文件中有几个地方要注意:
- dataDirHostPath: 这个路径是会在宿主机上生成的,默认为 /var/lib/rook,保存的是 ceph 的相关的配置文件,再重新生成集群的时候要确保这个目录为空,否则 Ceph 监视器守护进程 MON 会无法启动
- useAllDevices: 使用节点上所有的设备,默认为 true,使用宿主机所有可用的磁盘
- useAllNodes:使用所有的 node 节点,默认为 true,使用用 k8s 集群内的所有 node 来搭建 Ceph
- network.hostNetwork: 使用宿主机的网络进行通讯,默认为 false,如果需要集群外挂载的场景可以开启这个选项
部署完毕后,检查 Rook 组件工作状态:
# kubectl get pod -n rook-ceph
NAME READY STATUS RESTARTS AGE
csi-rbdplugin-b52jx 3/3 Running 3 9d
csi-rbdplugin-jpmgv 3/3 Running 1 2d9h
csi-rbdplugin-provisioner-54cc7d5848-5fn8c 5/5 Running 0 2d8h
csi-rbdplugin-provisioner-54cc7d5848-w8dvk 5/5 Running 3 4d22h
csi-rbdplugin-z8dlc 3/3 Running 1 2d9h
rook-ceph-mgr-a-6775645c-7qg5t 1/1 Running 1 4d7h
rook-ceph-mon-a-98664df75-jm72n 1/1 Running 0 2d9h
rook-ceph-operator-676bcb686f-2kwmz 1/1 Running 0 9d
rook-ceph-osd-0-6949755785-gjpxw 1/1 Running 0 2d9h
rook-ceph-osd-1-647fdc4d84-6lvw2 1/1 Running 0 11d
rook-ceph-osd-2-c6c6db577-gtcpz 1/1 Running 0 2d8h
rook-ceph-osd-prepare-172.21.64.15-xngl7 0/1 Completed 0 2d8h
rook-ceph-osd-prepare-172.21.64.36-gv2kw 0/1 Completed 0 28d
rook-ceph-osd-prepare-172.21.64.8-bhffr 0/1 Completed 0 2d8h
rook-ceph-tools-864695994d-b7nb8 1/1 Running 0 2d7h
rook-discover-28fqr 1/1 Running 0 2d9h
rook-discover-m529m 1/1 Running 0 9d
rook-discover-t2jzc 1/1 Running 0 2d9h
至此一个完整的 Ceph RBD 集群就建立完毕,每台节点上都部署有 Ceph 对象存储守护进程 OSD,默认使用节点下的 /var/lib/rook 目录存储数据。
使用块存储部署 ElasticSearch
创建 Ceph pool,创建 StorageClass
# cat storageclass.yaml --- apiVersion: ceph.rook.io/v1 kind: CephBlockPool metadata: name: replicapool namespace: rook-ceph spec: failureDomain: host replicated: size: 1 # 池中数据的副本数 --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: global-storageclass provisioner: rook-ceph.rbd.csi.ceph.com parameters: # clusterID is the namespace where the rook cluster is running # If you change this namespace, also change the namespace below where the secret namespaces are defined clusterID: rook-ceph # Ceph pool into which the RBD image shall be created pool: replicapool # RBD image format. Defaults to "2". imageFormat: \"2\" # RBD image features. Available for imageFormat: "2". CSI RBD currently supports only layering feature. imageFeatures: layering # The secrets contain Ceph admin credentials. These are generated automatically by the operator # in the same namespace as the cluster. csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph # Specify the filesystem type of the volume. If not specified, csi-provisioner # will set default as ext4. csi.storage.k8s.io/fstype: ext4 # uncomment the following to use rbd-nbd as mounter on supported nodes #mounter: rbd-nbd reclaimPolicy: Delete
通过 StorageClass 动态创建 PVC,检查 PVC 能够正确的创建并绑定
# cat pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: rbd-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: global-storageclass # kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE rbd-pvc Bound pvc-f9703e0d-9887-4bf2-8e31-1c3103a6ce2f 1Gi RWO global-storageclass 2s
创建 ElasticSearch 应用,通过 StorageClass 动态申请块存储,使用 Ceph RBD 集群作为 ElasticSearch 的后端存储设备
# cat es.yaml apiVersion: v1 kind: Namespace metadata: name: efk --- apiVersion: apps/v1 kind: StatefulSet metadata: name: elasticsearch-master namespace: efk labels: app: elasticsearch-master spec: podManagementPolicy: Parallel serviceName: elasticsearch-master replicas: 3 selector: matchLabels: app: elasticsearch-master template: metadata: labels: app: elasticsearch-master spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 podAffinityTerm: topologyKey: kubernetes.io/hostname labelSelector: matchLabels: app: "elasticsearch-master" initContainers: # see https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html # and https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration-memory.html#mlockall - name: "sysctl" image: "busybox" imagePullPolicy: "Always" command: ["sysctl", "-w", "vm.max_map_count=262144"] securityContext: allowPrivilegeEscalation: true privileged: true containers: - name: elasticsearch env: - name: cluster.name value: elasticsearch-cluster - name: discovery.zen.ping.unicast.hosts value: elasticsearch-master - name: discovery.zen.minimum_master_nodes value: "2" - name: KUBERNETES_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace # node roles, default to all true # - name: NODE_MASTER # value: "true" # - name: NODE_DATA # value: "true" # - name: NODE_INGEST # value: "true" - name: PROCESSORS valueFrom: resourceFieldRef: resource: limits.cpu - name: ES_JAVA_OPTS value: "-Djava.net.preferIPv4Stack=true -Xmx1g -Xms1g" resources: readinessProbe: httpGet: path: /_cluster/health?local=true port: 9200 initialDelaySeconds: 5 image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.4 imagePullPolicy: IfNotPresent ports: - containerPort: 9300 name: transport - containerPort: 9200 name: http volumeMounts: - mountPath: /usr/share/elasticsearch/data name: elasticsearch-master volumes: terminationGracePeriodSeconds: 120 volumeClaimTemplates: - metadata: name: elasticsearch-master spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi storageClassName: global-storageclass volumeMode: Filesystem --- apiVersion: v1 kind: Service metadata: name: elasticsearch-master namespace: efk labels: app: elasticsearch-master spec: type: ClusterIP ports: - name: http port: 9200 protocol: TCP targetPort: 9200 - name: transport port: 9300 protocol: TCP targetPort: 9300 selector: app: elasticsearch-master
等待 ElasticSearch 实例建立完成,磁盘被正确的挂载
# kubectl get pod -n efk NAME READY STATUS RESTARTS AGE elasticsearch-master-0 1/1 Running 0 2d19h elasticsearch-master-1 1/1 Running 0 2d19h elasticsearch-master-2 1/1 Running 0 2d19h # kubectl get pvc -n efk NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE elasticsearch-master-elasticsearch-master-0 Bound pvc-405fc7b2-82eb-4eda-a6b7-e54d1713cbeb 20Gi RWO global-storageclass 41d elasticsearch-master-elasticsearch-master-1 Bound pvc-6e89f8d5-4702-4b2f-81c2-f9f89a249b0a 20Gi RWO global-storageclass 41d elasticsearch-master-elasticsearch-master-2 Bound pvc-16c9c1ab-4c77-463f-b5f0-fc643ced2fce 20Gi RWO global-storageclass 41d
块存储小结
按照本节指引,用户可以在 TKEStack 平台下创建出一个 Ceph RBD 块存储集群,并通过 StorageClass 方式动态的申请和使用块存储资源,并能够搭建出 ElasticSearch 应用对外提供服务。
由于块存储的特性,在 K8S 场景下仅支持 ReadWriteOnce 和 ReadOnlyMany 访问方式,并且这里的访问方式是节点级别的,例如 ReadOnlyMany, 只能被同一节点的多个 Pod 挂载,如果从多个节点挂载,系统会报 Multi-Attach 错误。
因此在 K8S 使用块存储场景上,基本上一个 Pod 挂载一个 PVC,典型的应用场景是 Redis,ElasticSearch,Mysql数据库等。
如果用户已存在块存储设备,TKEStack 支持通过 CSI 插件对接已有的存储设备,详情请参考 TKEStack CSI Operator。
文件存储参考实践
本节将介绍如何在 TKEStack 中部署一套文件存储系统,并演示如何在平台中使用该存储。
本节以 ChubaoFS (储宝文件系统)为例,通过在集群中部署和集成 ChubaoFS,向用户展示如何在 TKEStack 中使能文件存储功能。
ChubaoFS (储宝文件系统)是为大规模容器平台设计的分布式文件系统,详情请参考ChubaoFS 官方文档。
ChubaoFS 支持在 k8s 集群中部署,通过 Helm 的方式在集群中安装元数据子系统,数据子系统和资源管理节点等,对外提供文件存储服务。
部署文件存储系统
登录 TKEStack 管理页面,进入集群管理页下,新建一个至少包括五台节点的集群,并为该集群使能"Helm 应用管理"扩展组件
设置节点标签,ChubaoFS 将根据标签分配不同的组件到节点上运行
kubectl label node <nodename> chuabaofs-master=enabled kubectl label node <nodename> chuabaofs-metanode=enabled kubectl label node <nodename> chuabaofs-datanode=enabled
登录到该集群下的一台节点上,下载 ChubaoFS 应用的 chart 包,根据环境修改 values.yaml 文件中的参数(本文使用默认值)
# ls chubaofs Chart.yaml config README.md templates values.yaml
本地安装 Helm 客户端, 更多可查看 安装 Helm,使用 Helm 客户端安装 ChubaoFS ,等待安装完成
# helm install --name chubao ./chubaofs # ./helm status chubao
安装完成后,检查所有组件工作正常
# kubectl get pod -n chubaofs NAME READY STATUS RESTARTS AGE client-c5c5b99f6-qqf7h 1/1 Running 0 17h consul-6d67d5c55-jgw9z 1/1 Running 0 18h datanode-9vqvm 1/1 Running 0 18h datanode-bgffs 1/1 Running 0 18h datanode-dtckp 1/1 Running 0 18h datanode-jtrzj 1/1 Running 0 18h datanode-p5nmc 1/1 Running 0 18h grafana-7cc9db7489-st27v 1/1 Running 0 18h master-0 1/1 Running 0 18h master-1 1/1 Running 0 18h master-2 1/1 Running 0 17h metanode-ghrpm 1/1 Running 0 18h metanode-gn5kl 1/1 Running 0 18h metanode-wqzwp 1/1 Running 0 18h prometheus-77d5d6cb7f-xs748 1/1 Running 0 18h kubectl get svc -n chubaofs NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE consul-service NodePort 192.168.255.116 <none> 8500:30831/TCP 20h grafana-service ClusterIP 192.168.255.16 <none> 3000/TCP 20h master-service NodePort 192.168.255.104 <none> 8080:32102/TCP 20h prometheus-service ClusterIP 192.168.255.4 <none> 9090/TCP 20h
使用文件存储系统
参考 ChubaoFS CSI 文档,在想要使用文件存储的集群上部署 ChubaoFS CSI Driver 插件
# git clone https://github.com/chubaofs/chubaofs-csi.git # cd chubaofs-csi # kubectl apply -f deploy/csi-controller-deployment.yaml # kubectl apply -f deploy/csi-node-daemonset.yaml
创建 StorageClass,指定 master 和 consul 的访问地址
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: chubaofs-sc provisioner: csi.chubaofs.com reclaimPolicy: Delete parameters: masterAddr: "172.21.64.14:32102" # Master地址 owner: "csiuser" # cannot set profPort and exporterPort value, reason: a node may be run many cfs-client # profPort: "10094" # exporterPort: "9513" consulAddr: "172.21.64.14:30831" # 监控系统的地址 logLevel: "debug"
通过 StorageClass 创建 PVC,创建使用 PVC 的应用
# cat pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: chubaofs-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: chubaofs-sc # cat deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: cfs-csi-demo namespace: default spec: replicas: 1 selector: matchLabels: app: cfs-csi-demo-pod template: metadata: labels: app: cfs-csi-demo-pod spec: containers: - name: chubaofs-csi-demo image: nginx:1.17.9 imagePullPolicy: "IfNotPresent" ports: - containerPort: 80 name: "http-server" volumeMounts: - mountPath: "/usr/share/nginx/html" name: mypvc volumes: - name: mypvc persistentVolumeClaim: claimName: chubaofs-pvc
文件存储小结
通过上面的例子,用户可以创建一个 ChubaoFS 的文件系统集群,并通过 ChubaoFS CSI Driver 插件,将文件存储映射为 K8S 的资源(PVC,StorageClass)。
由于文件存储的支持多读多写的特性,使得用户在有共享存储需求的场景下,如 AI 计算、模型训练等,通过 TKEStack + ChubaoFS 的方案,快速构建出容器产品和解决方案。
如果用户已存在文件存储设备,TKEStack支持通过 CSI 插件对接已有的存储设备,详情请参考 TKEStack CSI Operator。
对象存储参考实践
本节介绍最后一个存储类型——对象存储, 对象存储的访问接口基本都是 RESTful API,用户可通过网络存储和查看数据,具备高扩展性、低成本、可靠和安全特性。
常见的对象存储有 Ceph 的 RADOS、OpenStack 的 Swift、AWS S3 等,并且各大主流的云提供商的都有提供对象存储服务,方便互联网用户快速地接入,实现了海量数据访问和管理。
本节将在一个公有云的环境下,申请云提供商提供的对象存储,通过标准的 S3 接口对接 TKEStack 的镜像仓库的服务,这样就可以将 TKEStack 平台下的镜像存储在对象存储中,方便扩展和管理。
申请对象存储
以腾讯云为例,登录控制台后进入对象存储产品中心,在存储桶列表页面下创建一个新的存储桶,创建成功后记录下访问域名,所属地域,访问 ID 和密钥等。
注:访问 ID 和密钥信息请参考腾讯云对象存储文档,更多关于对象存储的信息访问官网。
配置镜像仓库
TKEStack 提供镜像仓库功能,为用户提供容器镜像的上传,下载和管理功能,并且镜像仓库中保存平台所需的所有镜像,满足各种离线环境的需求。
如果按照默认方式安装配置,TKEStack 的镜像仓库默认使用 Global 集群下 Master 主机上的存储资源,该种方式占用了有限的主机资源,且不方便进行扩展和迁移,有必要对镜像仓库模块重新配置,使其对接对象存储,方便扩展。
登录 TKEStack 管理界面,进入 Global 集群配置管理,找到 tke 命名空间下的 tke-registry-api 配置(configmap),修改
storage
字段如下tke-registry-config.yaml: | apiVersion: registry.config.tkestack.io/v1 kind: RegistryConfiguration storage: # fileSystem: # rootDirectory: /storage s3: bucket: xxxxxxxxxxxxx region: ap-beijing accessKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx secretKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx secure: false regionEndpoint: https://xxxxxxxxxxxxxx.cos.ap-beijing.myqcloud.com
修改完成后,重启 tke 命名空间下的 tke-registry-api 的 pod,使配置生效,等待重启后 pod 恢复
此时镜像仓库已使用基于 S3 接口的对象存储,用户可以向该仓库推送或下载镜像,验证功能
注意:
- 由于更换了底层存储,重启后的镜像仓库中没有镜像,需要用户提前将原仓库中的所有镜像备份后,重新恢复至新的镜像仓库中
- 也可以在安装 TKEStack 平台时,为镜像仓库,以及监控存储配置对象存储服务,免去后期转移镜像和存储的操作,关于如何在安装时指定存储服务,详见高可用部署相关文章
对象存储小结
本节展示如果通过云提供商提供的对象存储服务,增强 TKEStack 的镜像仓库服务。在实际场景中,用户根据自身情况,选择合适的公有云上服务。
本文前面介绍的 Rook Ceph,ChubaoFS 等都支持对象存储服务,用户也可自行搭建本地的对象存储集群,详细指引参考对应产品的官网。
总结
综上所述,TKEStack 平台能够通过各种云原生及扩展组件的方式,对接和集成不同种类的存储服务,满足各类应用场景的需求。后续 TKEStack 还会继续增强在存储方面的功能,不断完善操作体验,使得容器平台存储功能具备易于上手,种类丰富,灵活扩展的能力。
参考链接
- https://github.com/tkestack/tke
- https://www.zhihu.com/question/21536660
- https://rook.io/
- https://github.com/chubaofs/chubaofs
- https://cloud.tencent.com/document/product/436/6222