云服务器内容精选
-
使用Volcano设置NUMA亲和性调度 以下为使用Volcano设置NUMA亲和性调度的示例。 示例一:在无状态工作负载中配置NUMA亲和性。 kind: Deployment apiVersion: apps/v1 metadata: name: numa-tset spec: replicas: 1 selector: matchLabels: app: numa-tset template: metadata: labels: app: numa-tset annotations: volcano.sh/numa-topology-policy: single-numa-node # set the topology policy spec: containers: - name: container-1 image: nginx:alpine resources: requests: cpu: 2 # 必须为整数,且需要与limits中一致 memory: 2048Mi limits: cpu: 2 # 必须为整数,且需要与requests中一致 memory: 2048Mi imagePullSecrets: - name: default-secret 示例二:创建一个Volcano Job,并使用NUMA亲和性。 apiVersion: batch.volcano.sh/v1alpha1 kind: Job metadata: name: vj-test spec: schedulerName: volcano minAvailable: 1 tasks: - replicas: 1 name: "test" topologyPolicy: best-effort # set the topology policy for task template: spec: containers: - image: alpine command: ["/bin/sh", "-c", "sleep 1000"] imagePullPolicy: IfNotPresent name: running resources: limits: cpu: 20 memory: "100Mi" restartPolicy: OnFailure NUMA调度分析。 假设NUMA节点情况如下: 工作节点 节点策略拓扑管理器策略 NUMA 节点 0 上的可分配 CPU NUMA 节点 1 上的可分配 CPU node-1 single-numa-node 16U 16U node-2 best-effort 16U 16U node-3 best-effort 20U 20U 则根据以上示例, 示例一中,Pod的CPU申请值为2U,设置拓扑策略为“single-numa-node”,因此会被调度到相同策略的node-1。 示例二中,Pod的CPU申请值为20U,设置拓扑策略为“best-effort”,它将被调度到node-3,因为node-3可以在单个NUMA节点上分配Pod的CPU请求,而node-2需要在两个NUMA节点上执行此操作。
-
Volcano开启NUMA亲和性调度 开启静态(static)CPU管理策略,具体请参考 开启CPU管理策略。 配置CPU拓扑策略。 登录CCE控制台,单击集群名称进入集群,在左侧选择“节点管理”,在右侧选择“节点池”页签,单击节点池名称后的“ 配置管理”。 将kubelet的拓扑管理策略(topology-manager-policy)的值修改为需要的CPU拓扑策略即可。 有效拓扑策略为“none”、“best-effort”、“restricted”、“single-numa-node”,具体策略对应的调度行为请参见Pod调度预测。 开启numa-aware插件功能和resource_exporter功能。 Volcano 1.7.1及以上版本 登录CCE控制台,单击集群名称进入集群,单击左侧导航栏的“插件中心”,在右侧找到Volcano,单击“编辑”,并在“参数配置”中设置Volcano调度器配置参数。 { "ca_cert": "", "default_scheduler_conf": { "actions": "allocate, backfill, preempt", "tiers": [ { "plugins": [ { "name": "priority" }, { "name": "gang" }, { "name": "conformance" } ] }, { "plugins": [ { "name": "drf" }, { "name": "predicates" }, { "name": "nodeorder" } ] }, { "plugins": [ { "name": "cce-gpu-topology-predicate" }, { "name": "cce-gpu-topology-priority" }, { "name": "cce-gpu" }, { // add this also enable resource_exporter "name": "numa-aware", // the weight of the NUMA Aware Plugin "arguments": { "weight": "10" } } ] }, { "plugins": [ { "name": "nodelocalvolume" }, { "name": "nodeemptydirvolume" }, { "name": "nodeCSIscheduling" }, { "name": "networkresource" } ] } ] }, "server_cert": "", "server_key": "" } Volcano 1.7.1以下版本 Volcano插件开启resource_exporter_enable参数,用于收集节点numa拓扑信息。 { "plugins": { "eas_service": { "availability_zone_id": "", "driver_id": "", "enable": "false", "endpoint": "", "flavor_id": "", "network_type": "", "network_virtual_subnet_id": "", "pool_id": "", "project_id": "", "secret_name": "eas-service-secret" } }, "resource_exporter_enable": "true" } 开启后可以查看当前节点的numa拓扑信息。 kubectl get numatopo NAME AGE node-1 4h8m node-2 4h8m node-3 4h8m 启用Volcano numa-aware算法插件。 kubectl edit cm -n kube-system volcano-scheduler-configmap kind: ConfigMap apiVersion: v1 metadata: name: volcano-scheduler-configmap namespace: kube-system data: default-scheduler.conf: |- actions: "allocate, backfill, preempt" tiers: - plugins: - name: priority - name: gang - name: conformance - plugins: - name: overcommit - name: drf - name: predicates - name: nodeorder - plugins: - name: cce-gpu-topology-predicate - name: cce-gpu-topology-priority - name: cce-gpu - plugins: - name: nodelocalvolume - name: nodeemptydirvolume - name: nodeCSIscheduling - name: networkresource arguments: NetworkType: vpc-router - name: numa-aware # add it to enable numa-aware plugin arguments: weight: 10 # the weight of the NUMA Aware Plugin
-
确认NUMA使用情况 您可以通过lscpu命令查看当前节点的CPU概况: # 查看当前节点的CPU概况 lscpu ... CPU(s): 32 NUMA node(s): 2 NUMA node0 CPU(s): 0-15 NUMA node1 CPU(s): 16-31 然后查看NUMA节点使用情况。 # 查看当前节点的CPU分配 cat /var/lib/kubelet/cpu_manager_state {"policyName":"static","defaultCpuSet":"0,10-15,25-31","entries":{"777870b5-c64f-42f5-9296-688b9dc212ba":{"container-1":"16-24"},"fb15e10a-b6a5-4aaa-8fcd-76c1aa64e6fd":{"container-1":"1-9"}},"checksum":318470969} 以上示例中表示,节点上运行了两个容器,一个占用了NUMA node0的1-9核,另一个占用了NUMA node1的16-24核。
-
Pod调度预测 当Pod设置了拓扑策略时,Volcano会根据Pod设置的拓扑策略预测匹配的节点列表。调度过程如下: 根据Pod设置的Volcano拓扑策略,筛选具有相同策略的节点。Volcano提供的拓扑策略与拓扑管理器相同。 在设置了相同策略的节点中,筛选CPU拓扑满足该策略要求的节点进行调度。 Volcano拓扑策略 节点调度行为 1.筛选具有相同策略的节点 2.节点的CPU拓扑满足该策略的要求 none 无筛选行为: none:可调度 best-effort:可调度 restricted:可调度 single-numa-node:可调度 - best-effort 筛选拓扑策略同样为“best-effort”的节点: none:不可调度 best-effort:可调度 restricted:不可调度 single-numa-node:不可调度 尽可能满足策略要求进行调度: 优先调度至单NUMA节点,如果单NUMA节点无法满足CPU申请值,允许调度至多个NUMA节点。 restricted 筛选拓扑策略同样为“restricted”的节点: none:不可调度 best-effort:不可调度 restricted:可调度 single-numa-node:不可调度 严格限制的调度策略: 单NUMA节点的CPU容量上限大于等于CPU的申请值时,仅允许调度至单NUMA节点。此时如果单NUMA节点剩余的CPU可使用量不足,则Pod无法调度。 单NUMA节点的CPU容量上限小于CPU的申请值时,可允许调度至多个NUMA节点。 single-numa-node 筛选拓扑策略同样为“single-numa-node”的节点: none:不可调度 best-effort:不可调度 restricted:不可调度 single-numa-node:可调度 仅允许调度至单NUMA节点。 假设单个节点CPU总量为32U,由2个NUMA节点提供资源,分配如下: 工作节点 节点拓扑策略 NUMA节点1上的CPU总量 NUMA节点2上的CPU总量 节点-1 best-effort 16 16 节点-2 restricted 16 16 节点-3 restricted 16 16 节点-4 single-numa-node 16 16 Pod设置拓扑策略后,调度情况如图1所示。 当Pod的CPU申请值为9U时,设置拓扑策略为“best-effort”,Volcano会匹配拓扑策略同样为“best-effort”的节点-1,且该策略允许调度至多个NUMA节点,因此9U的申请值会被分配到2个NUMA节点,该Pod可成功调度至节点-1。 当Pod的CPU申请值为9U时,设置拓扑策略为“restricted”,Volcano会匹配拓扑策略同样为“restricted”的节点-2/节点-3,且单NUMA节点CPU总量满足9U的申请值,但单NUMA节点剩余可用的CPU量无法满足,因此该Pod无法调度。 当Pod的CPU申请值为17U时,设置拓扑策略为“restricted”,Volcano会匹配拓扑策略同样为“restricted”的节点-2/节点-3,且单NUMA节点CPU总量无法满足17U的申请值,可允许分配到2个NUMA节点,该Pod可成功调度至节点-3。 当Pod的CPU申请值为17U时,设置拓扑策略为“single-numa-node”,Volcano会匹配拓扑策略同样为“single-numa-node”的节点,但由于单NUMA节点CPU总量均无法满足17U的申请值,因此该Pod无法调度。 图1 NUMA调度策略对比
-
背景信息 当节点运行许多CPU绑定的Pod时,工作负载可以迁移到不同的CPU核心,这取决于Pod是否被限制以及调度时哪些CPU核心可用。许多工作负载对此迁移不敏感,因此在没有任何干预的情况下工作正常。但是,在CPU缓存亲和性和调度延迟显著影响工作负载性能的工作负载中,如果CPU是从不同的NUMA节点分配的,会导致额外的延迟。因此kubelet允许使用拓扑管理器(Topology Manager)替代CPU管理策略来确定节点的分配。 CPU Manager和拓扑管理器都是kubelet组件,但有以下限制: K8s默认调度器不感知NUMA拓扑。因此,可能会调度到不满足NUMA拓扑要求的节点上,然后工作负载实例启动失败。这对于Tensorflow作业来说是不可接受的。如果节点上有任何工作进程或ps失败,则作业将失败。 管理器是节点级的,导致无法匹配整个集群中NUMA拓扑的最佳节点。 Volcano的目标是解决调度程序NUMA拓扑感知的限制,以便实现以下目标: 避免将Pod调度到NUMA拓扑不匹配的节点。 将Pod调度到NUMA拓扑的最佳节点。 更多资料请查看社区NUMA亲和性插件指导链接:https://github.com/volcano-sh/volcano/blob/master/docs/design/numa-aware.md
-
调度优先级 不管是什么拓扑策略,都是希望把Pod调度到当时最优的节点上,这里通过给每一个节点进行打分的机制来排序筛选最优节点。 原则:尽可能把Pod调度到需要跨NUMA节点最少的工作节点上。 打分公式如下: score = weight * (100 - 100 * numaNodeNum / maxNumaNodeNum) 参数说明: weight:NUMA Aware Plugin的权重。 numaNodeNum:表示工作节点上运行该Pod需要NUMA节点的个数。 maxNumaNodeNum:表示所有工作节点中该Pod的最大NUMA节点个数。 例如,假设有三个节点满足Pod的CPU拓扑策略,且NUMA Aware Plugin的权重设为10: Node A:由1个NUMA节点提供Pod所需的CPU资源,即numaNodeNum=1 Node B:由2个NUMA节点提供Pod所需的CPU资源,即numaNodeNum=2 Node C:由4个NUMA节点提供Pod所需的CPU资源,即numaNodeNum=4 则根据以上公式,maxNumaNodeNum=4 score(Node A) = 10 * (100 - 100 * 1 / 4) = 750 score(Node B) = 10 * (100 - 100 * 2 / 4) = 500 score(Node C) = 10 * (100 - 100 * 4 / 4) = 0 因此最优节点为Node A。
-
使用NPU 创建工作负载申请NPU资源,可按如下方法配置,指定显卡的数量。 kind: Deployment apiVersion: apps/v1 metadata: name: npu-test namespace: default spec: replicas: 1 selector: matchLabels: app: npu-test template: metadata: labels: app: npu-test spec: containers: - name: container-0 image: nginx:perl resources: limits: cpu: 250m huawei.com/ascend-310: '1' memory: 512Mi requests: cpu: 250m huawei.com/ascend-310: '1' memory: 512Mi imagePullSecrets: - name: default-secret 通过huawei.com/ascend-310指定申请NPU的数量。 使用huawei.com/ascend-310参数指定NPU数量时,requests和limits值需要保持一致。 指定huawei.com/ascend-310后,在调度时不会将负载调度到没有NPU的节点。如果缺乏NPU资源,会报类似“0/2 nodes are available: 2 Insufficient huawei.com/ascend-310.”的Kubernetes事件。 在CCE控制台使用NPU资源,只需在创建工作负载时,勾选NPU配额,并指定使用NPU芯片的数量。 图1 使用NPU
-
Binpack功能介绍 Binpack调度算法的目标是尽量把已有的节点填满(即尽量不往空白节点分配)。具体实现上,Binpack调度算法为满足调度条件的节点打分,节点的资源利用率越高得分越高。Binpack算法能够尽可能填满节点,将应用负载靠拢在部分节点,这非常有利于集群节点的自动扩缩容功能。 Binpack为调度器的多个调度插件之一,与其他插件共同为节点打分,用户可以自定义该插件整体权重和各资源维度打分权重,用以提高或降低Binpack在整体调度中的影响力。调度器在计算Binpack策略得分时,会考虑Pod请求的各种资源,如:CPU、Memory和GPU等扩展资源,并根据各种资源所配置的权重做平均。
-
Binpack算法原理 Binpack在对一个节点打分时,会根据Binpack插件自身权重和各资源设置的权重值综合打分。首先,对Pod请求资源中的每类资源依次打分,以CPU为例,CPU资源在待调度节点的得分信息如下: CPU.weight * (request + used) / allocatable 即CPU权重值越高,得分越高,节点资源使用量越满,得分越高。Memory、GPU等资源原理类似。其中: CPU.weight为用户设置的CPU权重 request为当前pod请求的CPU资源量 used为当前节点已经分配使用的CPU量 allocatable为当前节点CPU可用总量 通过Binpack策略的节点总得分如下: binpack.weight * (CPU.score + Memory.score + GPU.score) / (CPU.weight+ Memory.weight+ GPU.weight) * 100 即binpack插件的权重值越大,得分越高,某类资源的权重越大,该资源在打分时的占比越大。其中: binpack.weight为用户设置的装箱调度策略权重 CPU.score为CPU资源得分,CPU.weight为CPU权重 Memory.score为Memory资源得分,Memory.weight为Memory权重 GPU.score为GPU资源得分,GPU.weight为GPU权重 图1 Binpack策略示例
-
前提条件 创建GPU类型节点,具体请参见创建节点。 安装gpu-device-plugin(原gpu-beta)插件,安装时注意要选择节点上GPU对应的驱动,具体请参见CCE AI套件(NVIDIA GPU)。 gpu-device-plugin(原gpu-beta)插件会把驱动的目录挂载到/usr/local/nvidia/lib64,在容器中使用GPU资源需要将/usr/local/nvidia/lib64追加到LD_LIBRARY_PATH环境变量中。 通常可以通过如下三种方式追加。 制作镜像的Dockerfile中配置LD_LIBRARY_PATH。(推荐) ENV LD_LIBRARY_PATH /usr/local/nvidia/lib64:$LD_LIBRARY_PATH 镜像的启动命令中配置LD_LIBRARY_PATH。 /bin/bash -c "export LD_LIBRARY_PATH=/usr/local/nvidia/lib64:$LD_LIBRARY_PATH && ..." 创建工作负载时定义LD_LIBRARY_PATH环境变量(需确保容器内未配置该变量,不然会被覆盖)。 ... env: - name: LD_LIBRARY_PATH value: /usr/local/nvidia/lib64 ...
-
使用GPU 创建工作负载申请GPU资源,可按如下方法配置,指定显卡的数量。 apiVersion: apps/v1 kind: Deployment metadata: name: gpu-test namespace: default spec: replicas: 1 selector: matchLabels: app: gpu-test template: metadata: labels: app: gpu-test spec: containers: - image: nginx:perl name: container-0 resources: requests: cpu: 250m memory: 512Mi nvidia.com/gpu: 1 # 申请GPU的数量 limits: cpu: 250m memory: 512Mi nvidia.com/gpu: 1 # GPU数量的使用上限 imagePullSecrets: - name: default-secret 通过nvidia.com/gpu指定申请GPU的数量,支持申请设置为小于1的数量,比如nvidia.com/gpu: 0.5,这样可以多个Pod共享使用GPU。GPU数量小于1时,不支持跨GPU分配,如0.5 GPU只会分配到一张卡上。 使用nvidia.com/gpu参数指定GPU数量时,requests和limits值需要保持一致。 指定nvidia.com/gpu后,在调度时不会将负载调度到没有GPU的节点。如果缺乏GPU资源,会报类似如下的Kubernetes事件。 0/2 nodes are available: 2 Insufficient nvidia.com/gpu. 0/4 nodes are available: 1 InsufficientResourceOnSingleGPU, 3 Insufficient nvidia.com/gpu. 在CCE控制台使用GPU资源,只需在创建工作负载时,选择使用的GPU配额即可。 图1 使用GPU
-
优先级调度与抢占介绍 用户在集群中运行的业务丰富多样,包括核心业务、非核心业务,在线业务、离线业务等,根据业务的重要程度和SLA要求,可以对不同业务类型设置相应的高优先级。比如对核心业务和在线业务设置高优先级,可以保证该类业务优先获取集群资源。当集群资源被非核心业务占用,整体资源不足时,如果有新的核心业务提交部署请求,可以通过抢占的方式驱逐部分非核心业务,释放集群资源用于核心业务的调度运行。 CCE集群支持的优先级调度如表1所示。 表1 业务优先级保障调度 调度类型 说明 支持的调度器 基于优先级调度 调度器优先保障高优先级业务运行,但不会主动驱逐已运行的低优先级业务。基于优先级调度配置默认开启,不支持关闭。 kube-scheduler调度器/Volcano调度器 基于优先级抢占调度 当集群资源不足时,调度器主动驱逐低优先级业务,保障高优先级业务正常调度。 Volcano调度器
-
基于优先级抢占调度的亲和/反亲和示例 在Pod间亲和场景中,不推荐Pod与比其优先级低的Pod亲和。如果pending状态的Pod与节点上的一个或多个较低优先级Pod具有Pod间亲和性,对较低优先级的Pod发起抢占时,会无法满足Pod间亲和性规则,抢占规则和亲和性规则产生矛盾。 在这种情况下,调度程序无法保证pending状态的Pod可以被调度。详情请参见与低优先级Pod之间的Pod间亲和性。 在Pod间反亲和场景中,如果启用优先级抢占,当deploy1与比其优先级低的deploy2亲和,volcano-scheduler为保证业务自运维,将驱逐deploy3,并将deploy1调度到节点上。被驱逐的deploy3将会在新节点准备好后,调度到新节点上。 图2 与低优先级的Pod亲和场景 在Pod间反亲和场景中,volcano-scheduler为减少对其它业务的影响,将不驱逐deploy2和deploy3,而是在新节点准备好后,调度到新节点上。 图3 与低优先级Pod反亲和场景
-
基于优先级调度示例 如果集群中存在两个空闲节点,存在3个优先级的工作负载,分别为high-priority,med-priority,low-priority,首先运行high-priority占满集群资源,然后提交med-priority,low-priority的工作负载,由于集群资源全部被更高优先级工作负载占用,med-priority,low-priority的工作负载为pending状态,当high-priority工作负载结束,按照优先级调度原则,med-priority工作负载将优先调度。 通过priority.yaml创建3个优先级定义(PriorityClass),分别为:high-priority,med-priority,low-priority。 priority.yaml文件内容如下: apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 100 globalDefault: false description: "This priority class should be used for volcano job only." --- apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: med-priority value: 50 globalDefault: false description: "This priority class should be used for volcano job only." --- apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: low-priority value: 10 globalDefault: false description: "This priority class should be used for volcano job only." 创建PriorityClass: kubectl apply -f priority.yaml 查看优先级定义信息。 kubectl get PriorityClass 回显如下: NAME VALUE GLOBAL-DEFAULT AGE high-priority 100 false 97s low-priority 10 false 97s med-priority 50 false 97s system-cluster-critical 2000000000 false 6d6h system-node-critical 2000001000 false 6d6h 创建高优先级工作负载high-priority-job,占用集群的全部资源。 high-priority-job.yaml apiVersion: batch.volcano.sh/v1alpha1 kind: Job metadata: name: priority-high spec: schedulerName: volcano minAvailable: 4 priorityClassName: high-priority tasks: - replicas: 4 name: "test" template: spec: containers: - image: alpine command: ["/bin/sh", "-c", "sleep 1000"] imagePullPolicy: IfNotPresent name: running resources: requests: cpu: "1" restartPolicy: OnFailure 执行以下命令下发作业: kubectl apply -f high_priority_job.yaml 通过 kubectl get pod 查看Pod运行信息,如下: NAME READY STATUS RESTARTS AGE priority-high-test-0 1/1 Running 0 3s priority-high-test-1 1/1 Running 0 3s priority-high-test-2 1/1 Running 0 3s priority-high-test-3 1/1 Running 0 3s 此时,集群节点资源已全部被占用。 创建中优先级工作负载med-priority-job和低优先级工作负载low-priority-job。 med-priority-job.yaml apiVersion: batch.volcano.sh/v1alpha1 kind: Job metadata: name: priority-medium spec: schedulerName: volcano minAvailable: 4 priorityClassName: med-priority tasks: - replicas: 4 name: "test" template: spec: containers: - image: alpine command: ["/bin/sh", "-c", "sleep 1000"] imagePullPolicy: IfNotPresent name: running resources: requests: cpu: "1" restartPolicy: OnFailure low-priority-job.yaml apiVersion: batch.volcano.sh/v1alpha1 kind: Job metadata: name: priority-low spec: schedulerName: volcano minAvailable: 4 priorityClassName: low-priority tasks: - replicas: 4 name: "test" template: spec: containers: - image: alpine command: ["/bin/sh", "-c", "sleep 1000"] imagePullPolicy: IfNotPresent name: running resources: requests: cpu: "1" restartPolicy: OnFailure 执行以下命令下发作业: kubectl apply -f med_priority_job.yaml kubectl apply -f low_priority_job.yaml 通过 kubectl get pod 查看Pod运行信息,集群资源不足,Pod处于Pending状态,如下: NAME READY STATUS RESTARTS AGE priority-high-test-0 1/1 Running 0 3m29s priority-high-test-1 1/1 Running 0 3m29s priority-high-test-2 1/1 Running 0 3m29s priority-high-test-3 1/1 Running 0 3m29s priority-low-test-0 0/1 Pending 0 2m26s priority-low-test-1 0/1 Pending 0 2m26s priority-low-test-2 0/1 Pending 0 2m26s priority-low-test-3 0/1 Pending 0 2m26s priority-medium-test-0 0/1 Pending 0 2m36s priority-medium-test-1 0/1 Pending 0 2m36s priority-medium-test-2 0/1 Pending 0 2m36s priority-medium-test-3 0/1 Pending 0 2m36s 删除high_priority_job工作负载,释放集群资源,med_priority_job会被优先调度。 执行 kubectl delete -f high_priority_job.yaml 释放集群资源,查看Pod的调度信息,如下: NAME READY STATUS RESTARTS AGE priority-low-test-0 0/1 Pending 0 5m18s priority-low-test-1 0/1 Pending 0 5m18s priority-low-test-2 0/1 Pending 0 5m18s priority-low-test-3 0/1 Pending 0 5m18s priority-medium-test-0 1/1 Running 0 5m28s priority-medium-test-1 1/1 Running 0 5m28s priority-medium-test-2 1/1 Running 0 5m28s priority-medium-test-3 1/1 Running 0 5m28s
-
Volcano Scheduler Volcano Scheduler是负责Pod调度的组件,它由一系列action和plugin组成。action定义了调度各环节中需要执行的动作;plugin根据不同场景提供了action 中算法的具体实现细节。Volcano Scheduler具有高度的可扩展性,您可以根据需要实现自己的action和plugin。 图1 Volcano Scheduler工作流 Volcano Scheduler的工作流程如下: 客户端提交的Job被调度器识别到并缓存起来。 周期性开启会话,一个调度周期开始。 将没有被调度的Job发送到会话的待调度队列中。 遍历所有的待调度Job,按照定义的次序依次执行enqueue、allocate、preempt、reclaim、backfill等动作,为每个Job找到一个最合适的节点。将该Job 绑定到这个节点。action中执行的具体算法逻辑取决于注册的plugin中各函数的实现。 关闭本次会话。
更多精彩内容
CDN加速
GaussDB
文字转换成语音
免费的服务器
如何创建网站
域名网站购买
私有云桌面
云主机哪个好
域名怎么备案
手机云电脑
SSL证书申请
云点播服务器
免费OCR是什么
电脑云桌面
域名备案怎么弄
语音转文字
文字图片识别
云桌面是什么
网址安全检测
网站建设搭建
国外CDN加速
SSL免费证书申请
短信批量发送
图片OCR识别
云数据库MySQL
个人域名购买
录音转文字
扫描图片识别文字
OCR图片识别
行驶证识别
虚拟电话号码
电话呼叫中心软件
怎么制作一个网站
Email注册网站
华为VNC
图像文字识别
企业网站制作
个人网站搭建
华为云计算
免费租用云托管
云桌面云服务器
ocr文字识别免费版
HTTPS证书申请
图片文字识别转换
国外域名注册商
使用免费虚拟主机
云电脑主机多少钱
鲲鹏云手机
短信验证码平台
OCR图片文字识别
SSL证书是什么
申请企业邮箱步骤
免费的企业用邮箱
云免流搭建教程
域名价格