k8s之资源调度

一、pod的创建及使用

1.1 使用yaml创建

apiVersion: v1 # api 文档版本
kind: Pod  # 资源对象类型,也可以配置为像Deployment、StatefulSet这一类的对象
metadata: # Pod 相关的元数据,用于描述 Pod 的数据
  name: nginx-demo # Pod 的名称
  labels: # 定义 Pod 的标签
    type: app # 自定义 label 标签,名字为 type,值为 app
    test: 1.0.0 # 自定义 label 标签,描述 Pod 版本号
  namespace: 'default' # 命名空间的配置
spec: # 期望 Pod 按照这里面的描述进行创建
  containers: # 对于 Pod 中的容器描述
  - name: nginx # 容器的名称
    image: nginx:1.7.9 # 指定容器的镜像
    imagePullPolicy: IfNotPresent # 镜像拉取策略,指定如果本地有就用本地的,如果没有就拉取远程的
    command: # 指定容器启动时执行的命令
    - nginx
    - -g
    - 'daemon off;' # nginx -g 'daemon off;'
    workingDir: /usr/share/nginx/html # 定义容器启动后的工作目录
    ports:
    - name: http # 端口名称
      containerPort: 80 # 描述容器内要暴露什么端口
      protocol: TCP # 描述该端口是基于哪种协议通信的
    - env: # 环境变量
      name: JVM_OPTS # 环境变量名称
      value: '-Xms128m -Xmx128m' # 环境变量的值
    reousrces:
      requests: # 最少需要多少资源
        cpu: 100m # 限制 cpu 最少使用 0.1 个核心
        memory: 128Mi # 限制内存最少使用 128兆
      limits: # 最多可以用多少资源
        cpu: 200m # 限制 cpu 最多使用 0.2 个核心
        memory: 256Mi # 限制 最多使用 256兆
  restartPolicy: OnFailure # 重启策略,只有失败的情况才会重启

1.2 探针

1.2.1类型


1.startupProbe:当配置了 startupProbe 后,会先禁用其他探针,直到 startupProbe 成功后,其他探针才会继续。

作用:由于有时候不能准确预估应用一定是多长时间启动成功,因此配置另外两种方式不方便配置初始化时长来检测,而配置了 statupProbe 后,只有在应用启动成功了,才会执行另外两种探针,可以更加方便的结合使用另外两种探针使用。

startupProbe:
  httpGet:
    path: /api/startup
    port: 80



2.livenessProbe:用于探测容器中的应用是否运行,如果探测失败,kubelet 会根据配置的重启策略进行重启,若没有配置,默认就认为容器启动成功,不会执行重启策略。

livenessProbe:
  failureThreshold: 5
  httpGet:
    path: /health
    port: 8080
    scheme: HTTP
  initialDelaySeconds: 60
  periodSeconds: 10
  successThreshold: 1
  timeoutSeconds: 5


3.readinessProbe:用于探测容器内的程序是否健康,它的返回值如果返回 success,那么就认为该容器已经完全启动,并且该容器是可以接收外部流量的。

readinessProbe:
  failureThreshold: 3 # 错误次数
  httpGet:
    path: /ready
    port: 8181
    scheme: HTTP
  periodSeconds: 10 # 间隔时间
  successThreshold: 1
  timeoutSeconds: 1

1.2.2 探测方式

1.ExecAction:在容器内部执行一个命令,如果返回值为 0,则任务容器时健康的。

livenessProbe:
  exec:
    command:
      - cat
      - /health


2.TCPSocketAction:通过 tcp 连接监测容器内端口是否开放,如果开放则证明该容器健康

livenessProbe:
  tcpSocket:
    port: 80


3.HTTPGetAction:生产环境用的较多的方式,发送 HTTP 请求到容器内的应用程序,如果接口返回的状态码在 200~400 之间,则认为容器健康。

livenessProbe:
  failureThreshold: 5
  httpGet:
    path: /health
    port: 8080
    scheme: HTTP
    httpHeaders:
      - name: xxx
        value: xxx

1.2.3 探针参数配置

initialDelaySeconds: 60 # 初始化时间
timeoutSeconds: 2 # 超时时间
periodSeconds: 5 # 监测间隔时间
successThreshold: 1 # 检查 1 次成功就表示成功
failureThreshold: 2 # 监测失败 2 次就表示失败

1.2.4 三种探针在yaml中定义

    startupProbe: #应用启动探针配置
		  httpGet: #探测方式
        path: /index.html #探测路径
      #tcpSocket:
       #port: 80
      exec:
        command:
        - sh
        - -c
        - "sleep 4; echo 'success' > /inited"
      failureThreshold: 3 #失败多少次才算失败    
      periodSeconds: 10  #间隔时间
      successThreshold: 1 #多少次成功算成功
      timeoutSeconds: 5  #超时时间
    livenessProbe: #应用启动探针配置
      httpGet: #探测方式
        path: /start.html #探测路径
      #tcpSocket:
        port: 80
      failureThreshold: 3 #失败多少次才算失败    
      periodSeconds: 10  #间隔时间
      successThreshold: 1 #多少次成功算成功
      timeoutSeconds: 5  #超时时间
    readinessProbe: #应用启动探针配置
      httpGet: #探测方式
        path: /start.html #探测路径
        #tcpSocket:
        port: 80
      failureThreshold: 5 #失败多少次才算失败    
      periodSeconds: 10  #间隔时间
      successThreshold: 1 #多少次成功算成功
      timeoutSeconds: 3  #超时时间

1.3 生命周期

1.3.1 pod的退出流程

删除操作==> Endpoint 删除 pod 的 ip 地址==>Pod 变成 Terminating 状态=>执行 preStop 的指令

 terminationGracePeriodSeconds参数:

变为删除中的状态后,会给 pod 一个宽限期,让 pod 去执行一些清理或销毁操作。


配置参数:# 作用于 pod 中的所有容器
terminationGracePeriodSeconds: 30
containers:
  - xxx
1.3.2 PreStop 的应用

注册中心下线、数据清理、数据销毁

如果应用销毁操作耗时需要比较长,可以在 preStop 按照如下方式进行配置

preStop:
  exec:
    command:
      - sh
      - -c
      - 'sleep 20; kill pgrep java'

但是需要注意,由于 k8s 默认给 pod 的停止宽限时间为 30s,如果我们停止操作会超过 30s 时,不要光设置 sleep 50,还要将 terminationGracePeriodSeconds: 30 也更新成更长的时间,否则 k8s 最多只会在这个时间的基础上再宽限几秒,不会真正等待 50s



下方为yaml的配置:

    lifecycle: #生命周期的配置
      postStart: #生命周期启动阶段做的事情 不一定在容器的command之前运行
        exec:
          command:
          - sh
          - -c
          - "echo '<h1>per srop</h1>' > /opt/nginx/html/prestop.html"
      preStop:
        exec:
          command:
          - sh
          - -c
          - "sleep 50;echo 'sleep finished...' >> /opt/nginx/html/prestop.html"

二、资源调度

包括 label、Selector、Deployment、StatefulSet、DaemonSet、HPA 自动扩/缩容

2.1 Label 和 Selector

标签(Label): 

1.在各类资源的 metadata.labels 中进行配置

2.使用kubectl
2.1临时创建
kubectl label po <资源名称> app=hello

2.2修改已经存在的标签
kubectl label po <资源名称> app=hello2 --overwrite

2.3查看label
# selector 按照 label 单值查找节点
kubectl get po -A -l app=hello

# 查看所有节点的 labels
kubectl get po --show-labels
选择器(Selector)

配置文件:在各对象的配置 spec.selector 或其他可以写 selector 的属性中编写



kubectl: 

# 匹配单个值,查找 app=hello 的 pod
kubectl get po -A -l app=hello

# 匹配多个值
kubectl get po -A -l 'k8s-app in (metrics-server, kubernetes-dashboard)'
或 

# 查找 version!=1 and app=nginx 的 pod 信息
kubectl get po -l version!=1,app=nginx

# 不等值 + 语句
kubectl get po -A -l version!=1,'app in (busybox, nginx)'

2.2 Deployment

2.2.1 使用yaml创建

apiVersion: apps/v1 # deployment api 版本
kind: Deployment # 资源类型为 deployment
metadata: # 元信息
  labels: # 标签
    app: nginx-deploy # 具体的 key: value 配置形式
  name: nginx-deploy # deployment 的名字
  namespace: default # 所在的命名空间
spec:
  replicas: 1 # 期望副本数
  revisionHistoryLimit: 10 # 进行滚动更新后,保留的历史版本数
  selector: # 选择器,用于找到匹配的 RS
    matchLabels: # 按照标签匹配
      app: nginx-deploy # 匹配的标签key/value
  strategy: # 更新策略
    rollingUpdate: # 滚动更新配置
      maxSurge: 25% # 进行滚动更新时,更新的个数最多可以超过期望副本数的个数/比例
      maxUnavailable: 25% # 进行滚动更新时,最大不可用比例更新比例,表示在所有副本数中,最多可以有多少个不更新成功
    type: RollingUpdate # 更新类型,采用滚动更新
  template: # pod 模板
    metadata: # pod 的元信息
      labels: # pod 的标签
        app: nginx-deploy
    spec: # pod 期望信息
      containers: # pod 的容器
      - image: nginx:1.7.9 # 镜像
        imagePullPolicy: IfNotPresent # 拉取策略
        name: nginx # 容器名称
      restartPolicy: Always # 重启策略
      terminationGracePeriodSeconds: 30 # 删除操作最多宽限多长时间

2.2.2 创建

创建一个 deployment
kubectl create deploy nginx-deploy --image=nginx:1.7.9

或执行
kubectl create -f xxx.yaml --record
--record 会在 annotation 中记录当前命令创建或升级了资源,后续可以查看做过哪些变动操作。

查看部署信息
kubectl get deployments

查看 rs
kubectl get rs

查看 pod 以及展示标签,可以看到是关联的那个 rs
kubectl get pods --show-labels

2.2.3 滚动更新

假设当前有 5 个 nginx:1.7.9 版本,你想将版本更新为 1.9.1,当更新成功第三个以后,你马上又将期望更新的版本改为 1.9.2,那么此时会立马删除之前的三个,并且立马开启更新 1.9.2 的任务

只有修改了 deployment 配置文件中的 template 中的属性后,才会触发更新操作

修改 nginx 版本号
kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1

或者通过 kubectl edit deployment/nginx-deployment 进行修改

查看滚动更新的过程
kubectl rollout status deploy <deployment_name>

查看部署描述,最后展示发生的事件列表也可以看到滚动更新过程
kubectl describe deploy <deployment_name>

通过 kubectl get deployments 获取部署信息,UP-TO-DATE 表示已经有多少副本达到了配置中要求的数目

通过 kubectl get rs 可以看到增加了一个新的 rs

通过 kubectl get pods 可以看到所有 pod 关联的 rs 变成了新的


2.2.4 回滚

有时候你可能想回退一个Deployment,例如,当Deployment不稳定时,比如一直crash looping。

默认情况下,kubernetes会在系统中保存前两次的Deployment的rollout历史记录,以便你可以随时会退(你可以修改revision history limit来更改保存的revision数)。


案例:
更新 deployment 时参数不小心写错,如 nginx:1.9.1 写成了 nginx:1.91
kubectl set image deployment/nginx-deploy nginx=nginx:1.91

监控滚动升级状态,由于镜像名称错误,下载镜像失败,因此更新过程会卡住
kubectl rollout status deployments nginx-deploy

结束监听后,获取 rs 信息,我们可以看到新增的 rs 副本数是 2 个
kubectl get rs

通过 kubectl get pods 获取 pods 信息,我们可以看到关联到新的 rs 的 pod,状态处于 ImagePullBackOff 状态

为了修复这个问题,我们需要找到需要回退的 revision 进行回退
通过 kubectl rollout history deployment/nginx-deploy 可以获取 revison 的列表

通过 kubectl rollout history deployment/nginx-deploy --revision=2 可以查看详细信息

确认要回退的版本后,可以通过 kubectl rollout undo deployment/nginx-deploy 可以回退到上一个版本

也可以回退到指定的 revision
kubectl rollout undo deployment/nginx-deploy --to-revision=2

再次通过 kubectl get deployment 和 kubectl describe deployment 可以看到,我们的版本已经回退到对应的 revison 上了

可以通过设置 .spec.revisonHistoryLimit 来指定 deployment 保留多少 revison,如果设置为 0,则不允许 deployment 回退了。

2.2.5 扩容缩容

通过 kube scale 命令可以进行自动扩容/缩容
以及通过 kube edit 编辑 replcas 也可以实现扩容/缩容

扩容与缩容只是直接创建副本数,没有更新 pod template 因此不会创建新的 rs

2.2.6 暂停与恢复

由于每次对 pod template 中的信息发生修改后,都会触发更新 deployment 操作,那么此时如果频繁修改信息,就会产生多次更新,而实际上只需要执行最后一次更新即可,当出现此类情况时我们就可以暂停 deployment 的 rollout

通过 kubectl rollout pause deployment <name> 就可以实现暂停,直到你下次恢复后才会继续进行滚动更新

尝试对容器进行修改,然后查看是否发生更新操作了
kubectl set image deploy <name> nginx=nginx:1.17.9
kubectl get po 

通过以上操作可以看到实际并没有发生修改,此时我们再次进行修改一些属性,如限制 nginx 容器的最大cpu为 0.2 核,最大内存为 128M,最小内存为 64M,最小 cpu 为 0.1 核
kubectl set resources deploy <deploy_name> -c <container_name> --limits=cpu=200m,memory=128Mi --requests=cpu100m,memory=64Mi

通过格式化输出 kubectl get deploy <name> -oyaml,可以看到配置确实发生了修改,再通过 kubectl get po 可以看到 pod 没有被更新

那么此时我们再恢复 rollout,通过命令 kubectl rollout deploy <name>

恢复后,我们再次查看 rs 和 po 信息,我们可以看到就开始进行滚动更新操作了
kubectl get rs
kubectl get po

2.3 StatefulSet

2.3.1 使用yaml创建

---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
      annotations:
        volume.alpha.kubernetes.io/storage-class: anything
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

2.3.2 创建

kubectl create -f web.yaml

# 查看 service 和 statefulset => sts
kubectl get service nginx
kubectl get statefulset web

# 查看 PVC 信息
kubectl get pvc

# 查看创建的 pod,这些 pod 是有序的
kubectl get pods -l app=nginx

# 查看这些 pod 的 dns
# 运行一个 pod,基础镜像为 busybox 工具包,利用里面的 nslookup 可以看到 dns 信息
kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh
nslookup web-0.nginx

 2.3.4 扩容缩容

# 扩容
$ kubectl scale statefulset web --replicas=5

# 缩容
$ kubectl patch statefulset web -p '{"spec":{"replicas":3}}'

2.3.5 镜像更新

# 镜像更新(目前还不支持直接更新 image,需要 patch 来间接实现)

kubectl patch sts web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"nginx:1.9.1"}]'
2.3.5.1 RollingUpdate和OnDelete

RollingUpdate:

StatefulSet  也可以采用滚动更新策略,同样是修改 pod template 属性后会触发更新

但是由于 pod 是有序的,在 StatefulSet 中更新时是基于 pod 的顺序倒序更新的


OnDelete:只有在 pod 被删除时会进行更新操作
2.3.5.2 灰度发布
利用滚动更新中的 partition 属性,可以实现简易的灰度发布的效果
  updateStrategy:
    rollingUpdate:
      partition: 3    只会更新那些 序号 >= 3 的pod


例如我们有 5 个 pod,如果当前 partition 设置为 3
那么此时滚动更新时,只会更新那些 序号 >= 3 的 pod

利用该机制,我们可以通过控制 partition 的值
来决定只更新其中一部分 pod,确认没有问题后再主键增大更新的 pod 数量,最终实现全部 pod 更新

2.3.6 删除和级联删除

# 删除 StatefulSet 和 Headless Service

# 级联删除:删除 statefulset 时会同时删除 pods
kubectl delete statefulset web

# 非级联删除:删除 statefulset 时不会删除 pods,删除 sts 后,pods 就没人管了,此时再删除 pod 不会重建的
kubectl deelte sts web --cascade=false

# 删除 service
kubectl delete service nginx



删除PVC
# StatefulSet删除后PVC还会保留着,数据不再使用的话也需要删除
$ kubectl delete pvc www-web-0 www-web-1

2.4 DaemonSet

2.4.1 使用yaml创建

apiVersion: apps/v1  #创建daemon 资源
kind: DaemonSet
metadata:
  name: fluentd  #名字
spec:
  selector:
    matchLabels:
        app: logging
  template:
    metadata:
      labels:
        app: logging
        id: fluentd
      name: fluentd
    spec:
      containers:
      nodeSelector:  #指定节点运行
        kubernetes.io/hostname: node2  
      - name: fluentd-es
        image: agilestacks/fluentd-elasticsearch:v1.3.0
        env:  #环境变量配置
         - name: FLUENTD_ARGS #环境变量的key
           value: -qq  #环境变量的名字
        volumeMounts: 
         - name: containers  #数据卷的名字
           mountPath: /var/lib/docker/containers  #将数据卷挂载到容器内的那个目录
         - name: varlog
           mountPath: /varlog
      volumes:  #定义数据卷
         - hostPath:  #数据卷类型,主机路径的模式,与node共享目录
             path: /var/lib/docker/containers  #node的共享目录
           name: containers #定义的数据卷名称
         - hostPath:
             path: /var/log
           name: varlog

2.4.2 指定 Node 节点

DaemonSet 会忽略 Node 的 unschedulable 状态,有两种方式来指定 Pod 只运行在指定的 Node 节点上:

nodeSelector:只调度到匹配指定 label 的 Node 上
nodeAffinity:功能更丰富的 Node 选择器,比如支持集合操作
podAffinity:调度到满足条件的 Pod 所在的 Node 上
2.4.2.1 nodeSelector
先为 Node 打上标签
kubectl label nodes k8s-node1 svc_type=microsvc

然后再 daemonset 配置中设置 nodeSelector
spec:
  template:
    spec:
      nodeSelector:
        svc_type: microsvc
2.4.2.2  nodeAffinity
nodeAffinity 目前支持两种:requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution,分别代表必须满足条件和优选条件。

比如下面的例子代表调度到包含标签 wolfcode.cn/framework-name 并且值为 spring 或 springboot 的 Node 上,并且优选还带有标签 another-node-label-key=another-node-label-value 的Node。


apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: wolfcode.cn/framework-name
            operator: In
            values:
            - spring
            - springboot
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: pauseyyf/pause
2.4.2.3  podAffinity
podAffinity 基于 Pod 的标签来选择 Node,仅调度到满足条件Pod 所在的 Node 上,支持 podAffinity 和 podAntiAffinity。这个功能比较绕,以下面的例子为例:
如果一个 “Node 所在空间中包含至少一个带有 auth=oauth2 标签且运行中的 Pod”,那么可以调度到该 Node
不调度到 “包含至少一个带有 auth=jwt 标签且运行中 Pod”的 Node 上

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: auth
            operator: In
            values:
            - oauth2
        topologyKey: failure-domain.beta.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: auth
              operator: In
              values:
              - jwt
          topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: pauseyyf/pause

2.4.3 滚动更新

不建议使用 RollingUpdate,建议使用 OnDelete 模式,这样避免频繁更新 ds

2.5 HPA 自动扩/缩容

2.5.1 开启指标服务

# 下载 metrics-server 组件配置文件
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml -O metrics-server-components.yaml

# 修改镜像地址为国内的地址
sed -i 's/k8s.gcr.io\/metrics-server/registry.cn-hangzhou.aliyuncs.com\/google_containers/g' metrics-server-components.yaml

# 修改容器的 tls 配置,不验证 tls,在 containers 的 args 参数中增加 --kubelet-insecure-tls 参数

# 安装组件
kubectl apply -f metrics-server-components.yaml

# 查看 pod 状态
kubectl get pods --all-namespaces | grep metrics

2.5.2 cpu、内存指标监控

实现 cpu 或内存的监控,首先有个前提条件是该对象必须配置了 resources.requests.cpu 或 resources.requests.memory 才可以,可以配置当 cpu/memory 达到上述配置的百分比后进行扩容或缩容

创建一个 HPA:
先准备一个好一个有做资源限制的 deployment
执行命令 kubectl autoscale deploy nginx-deploy --cpu-percent=20 --min=2 --max=5
通过 kubectl get hpa 可以获取 HPA 信息

测试:找到对应服务的 service,编写循环测试脚本提升内存与 cpu 负载
while true; do wget -q -O- http://<ip:port> > /dev/null ; done

可以通过多台机器执行上述命令,增加负载,当超过负载后可以查看 pods 的扩容情况 kubectl get pods

查看 pods 资源使用情况
kubectl top pods

扩容测试完成后,再关闭循环执行的指令,让 cpu 占用率降下来,然后过 5 分钟后查看自动缩容情况

2.5.3 自定义 metrics

控制管理器开启–horizontal-pod-autoscaler-use-rest-clients
控制管理器的–apiserver指向API Server Aggregator
在API Server Aggregator中注册自定义的metrics API

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KubernetesK8s)是一种用于容器编排和管理的开源平台,它提供了多维资源调度算法来实现高效的资源管理和利用。 K8s使用多维资源调度算法来平衡集群中所有节点的资源负载,确保每个节点能够充分利用其可用的计算和存储资源。这些资源包括CPU、内存、存储和网络带宽等。K8s通过采集集群中每个节点的资源使用情况,并将其报告给调度策略,从而实现资源的智能分配。 在K8s中,多维资源调度算法主要涉及以下几个方面: 1. 资源分配:K8s通过分配节点上的资源来满足容器的需求。调度器会考虑所有容器资源需求,并将其分配到合适的节点上。该算法会根据容器资源请求和节点的可用资源进行匹配,从而避免资源的浪费和不平衡。 2. 负载均衡:K8s通过负载均衡算法将容器分散到不同的节点上,以避免资源瓶颈和单点故障。该算法会根据节点的负载情况和容器资源需求,将容器分配到最佳节点上,从而实现负载的均衡。 3. 弹性调度K8s具有弹性调度的能力,可以根据节点的可用资源容器的优先级,自动对容器进行调度。当集群容量不足或节点发生故障时,该算法可以自动将容器从一个节点迁移到另一个节点上,以确保容器的正常运行。 总之,K8s的多维资源调度算法是为了实现高效、均衡和可靠的资源管理。通过合理分配和调度容器资源需求,K8s可以最大化地利用集群的资源,并提供高可用性和可伸缩性的应用环境。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值