Kubernetes入门 十五、高级调度、污点/容忍

定向调度

定向调度,指的是利用在 Pod 上声明的 nodeName 或 nodeSelector ,以此将 Pod 调度到期望的 Node 节点上。

注意:这里的调度是强制的,这就意味着即使要调度的目标 Node 不存在,也会向上面进行调度,只不过 Pod 运行失败而已。

nodeName(不建议)

nodeName 是节点选择约束的最简单方法,但是由于其自身限制,通常不使用它。 nodeName 是 PodSpec 的一个字段。 如果它不为空,调度器将忽略 Pod,并且给定节点上运行的 kubelet 进程尝试执行该 Pod。 因此,如果 nodeName 在 Pod 的 Spec 中指定了,则它优先于 nodeSelector 。

使用 nodeName 来选择节点的一些限制:

    • 如果指定的节点不存在,
    • 如果指定的节点没有资源来容纳 Pod,Pod 将会调度失败并且其原因将显示为, 比如 OutOfmemory 或 OutOfcpu。
    • 云环境中的节点名称并非总是可预测或稳定的。

示例如下:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  nodeName: k8s-node1 # 指定调度到k8s-node1节点上
  containers:
  - name: nginx
    image: nginx:1.20.2
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    ports:
    - containerPort:  80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
  restartPolicy: Always

nodeSelector

nodeSelector 是节点选择约束的最简单推荐形式。nodeSelector 是 PodSpec 的一个字段。 它包含键值对的映射。为了使 pod 可以在某个节点上运行,该节点的标签中 必须包含这里的每个键值对(它也可以具有其他标签)。 最常见的用法的是一对键值对。

除了自己 添加 的标签外,节点还预制了一组标准标签。 参见这些常用的标签,注解以及污点

    • kubernetes.io/hostname
    • failure-domain.beta.kubernetes.io/zone
    • failure-domain.beta.kubernetes.io/region
    • topology.kubernetes.io/zone
    • topology.kubernetes.io/region
    • beta.kubernetes.io/instance-type
    • node.kubernetes.io/instance-type
    • kubernetes.io/os
    • kubernetes.io/arch

注意:这些标签的值是特定于云供应商的,因此不能保证可靠。 例如,kubernetes.io/hostname 的值在某些环境中可能与节点名称相同, 但在其他环境中可能是一个不同的值。

示例:

打标签:

# 给 k8s-node2 打上标签
kubectl label node k8s-node2 nodeevn=pro

pod的yaml:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  nodeSelector:
    nodeevn: pro # 指定调度到 nodeevn = pro 标签的 Node 节点上
  containers:
  - name: nginx
    image: nginx:1.20.2
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    ports:
    - containerPort: 80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
  restartPolicy: Always

亲和性调度

虽然定向调度的两种方式,使用起来非常方便,但是也有一定的问题,那就是如果没有满足条件的 Node,那么 Pod 将不会被运行,即使在集群中还有可用的 Node 列表也不行,这就限制了它的使用场景。

基于上面的问题,Kubernetes 还提供了一种亲和性调度(Affinity)。它在 nodeSelector 的基础之上进行了扩展,可以通过配置的形式,实现优先选择满足条件的 Node 进行调度,如果没有,也可以调度到不满足条件的节点上,使得调度更加灵活。

Affinity 主要分为三类:

    • nodeAffinity(node亲和性):以 Node 为目标,解决 Pod可 以调度到那些 Node 的问题。
    • podAffinity(pod亲和性):以 Pod 为目标,解决 Pod 可以和那些已存在的 Pod 部署在同一个拓扑域中的问题。
    • podAntiAffinity(pod反亲和性):以 Pod 为目标,解决 Pod 不能和那些已经存在的 Pod 部署在同一拓扑域中的问题。

亲和性和反亲和性的使用场景的说明:

  • 亲和性:如果两个应用频繁交互,那么就有必要利用亲和性让两个应用尽可能的靠近,这样可以较少因网络通信而带来的性能损耗。
  • 反亲和性:当应用采用多副本部署的时候,那么就有必要利用反亲和性让各个应用实例打散分布在各个 Node 上,这样可以提高服务的高可用性。

nodeAffinity

nodeAffinity 概念上类似于 nodeSelector,它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点。 但是比nodeSelector更加强大。

支持硬性过滤 和 软性评分:

  • RequiredDuringSchedulingIgnoredDuringExecution:硬亲和力,即支持必须部署在指定的节点上,也支持必须不部署在指定的节点上。支持多条件之间的逻辑或运算。
  • PreferredDuringSchedulingIgnoredDuringExecution:软亲和力:尽量部署在满足条件的节点上,或尽量不要部署在被匹配的节点上。支持设置条件权重。

支持运算符:

  • In:部署在满足条件的节点上
  • NotIn:匹配不在条件中的节点,实现节点反亲和性
  • Exists:只要存在 key 名字就可以,不关心值是什么
  • DoesNotExist:匹配指定 key 名不存在的节点,实现节点反亲和性
  • Gt:value 为数值,且节点上的值小于指定的条件
  • Lt:value 为数值,且节点上的值大于指定条件

nodeAffinity 的可选配置项:

pod.spec.affinity.nodeAffinity
  requiredDuringSchedulingIgnoredDuringExecution  # Node节点必须满足指定的所有规则才可以,硬性过滤
    nodeSelectorTerms  # 节点选择列表
      matchFields   # 按节点字段列出的节点选择器要求列表  
      matchExpressions   # 按节点标签列出的节点选择器要求列表(推荐)
        key    # 键
        values # 值
        operator # 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
  preferredDuringSchedulingIgnoredDuringExecution # 优先调度到满足指定的规则的Node,软性评分 (倾向)   
    preference   # 一个节点选择器项,与相应的权重相关联
      matchFields # 按节点字段列出的节点选择器要求列表
      matchExpressions   # 按节点标签列出的节点选择器要求列表(推荐)
        key # 键
        values # 值
        operator # 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt  
    weight # 倾向权重,在范围1-100。

注意:

  • 如果我们修改或删除 Pod 调度到节点的标签,Pod 不会被删除;换言之,亲和调度只是在 Pod 调度期间有效。

  • 如果同时定义了 nodeSelector 和 nodeAffinity ,那么必须两个条件都满足,Pod 才能运行在指定的 Node 上。

  • 如果 nodeAffinity 指定了多个 nodeSelectorTerms ,那么只需要其中一个能够匹配成功即可。

  • 如果一个 nodeSelectorTerms 中有多个 matchExpressions ,则一个节点必须满足所有的才能匹配成功。

硬性过滤示例:

打标签

# 给 k8s-node1 和 k8s-nodes 打上标签
kubectl label node k8s-node1 disktype=ssd
kubectl label node k8s-node2 disktype=hdd

pod的yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.20.2
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    ports:
    - containerPort: 80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
  affinity: 
    nodeAffinity:
      # DuringScheduling(调度期间有效)IgnoredDuringExecution(执行期间忽略,执行期间:Pod 运行期间)
      requiredDuringSchedulingIgnoredDuringExecution: # 硬性过滤:Node 节点必须满足指定的所有规则才可以
        nodeSelectorTerms:
          - matchExpressions: # 所有 matchExpressions 满足条件才行
            - key: disktype  # 节点标签的key
              operator: In # key在values种的才满足体检
              values: 
                - ssd      
                - hdd
  restartPolicy: Always

软性评分示例:

打标签:

# 给 k8s-node1 和 k8s-nodes 打上标签
kubectl label node k8s-node1 disk=50 gpu=1000
kubectl label node k8s-node2 disk=30 gpu=5000

pod的yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.20.2
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    ports:
    - containerPort: 80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
  affinity: 
    nodeAffinity:
      # DuringScheduling(调度期间有效)IgnoredDuringExecution(执行期间忽略,执行期间:Pod 运行期间)
      preferredDuringSchedulingIgnoredDuringExecution: # 软性评分:优先调度到满足指定的规则的 Node
        - weight: 90 # 权重,权重高的优先调度
          preference: # 一个节点选择器项,与相应的权重相关联
            matchExpressions:
              - key: disk  # 标签key
                operator: Gt  # 等于
                values: 
                 - "40"
        - weight: 10 # 权重
          preference: # 一个节点选择器项,与相应的权重相关联
            matchExpressions:
              - key: gpu
                operator: Gt
                values: 
                 - "4000"        
  restartPolicy: Always

podAffinity 和 podAntiAffinity

podAffinity 主要实现以运行的 Pod 为参照,实现让新创建的 Pod 和参照的 Pod 在一个区域的功能。

podAntiAffinity 主要实现以运行的 Pod 为参照,让新创建的 Pod 和参照的 Pod 不在一个区域的功能。

PodAffinity 的可选配置项:

pod.spec.affinity.podAffinity
  requiredDuringSchedulingIgnoredDuringExecution  # 硬限制
    namespaces # 指定参照pod的namespace
    topologyKey # 指定调度作用域
    labelSelector # 标签选择器
      matchExpressions  # 按节点标签列出的节点选择器要求列表(推荐)
        key    # 键
        values # 值
        operator # 关系符 支持In, NotIn, Exists, DoesNotExist.
      matchLabels    # 指多个matchExpressions映射的内容  
  preferredDuringSchedulingIgnoredDuringExecution # 软限制    
    podAffinityTerm  # 选项
      namespaces
      topologyKey
      labelSelector
         matchExpressions 
            key    # 键  
            values # 值  
            operator
         matchLabels 
    weight # 倾向权重,在范围0-1

注意:topologyKey 用于指定调度的作用域,例如:

  • 如果指定为 kubernetes.io/hostname(可以通过 kubectl get node --show-labels 查看),那就是以 Node 节点为区分范围。

  • 如果指定为 kubernetes.io/os,则以 Node 节点的操作系统类型来区分。

示例:在一个两节点的集群中,部署一个使用 redis 的 WEB 应用程序,并期望 web-server 尽可能和 redis 在同一个节点上。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deploy
  namespace: default
  labels:
    app: redis-deploy
spec:
  selector:
    matchLabels:
      app: store
  replicas: 2
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity: # 亲和性配置
        podAntiAffinity: # Pod 反亲和性,符合以下指定条件不会被调度过去
          requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
            - labelSelector:
                matchExpressions: 
                  - key: app
                    operator: In
                    values:
                      - store
              topologyKey: kubernetes.io/hostname # 拓扑键,划分逻辑区域。 
              # node 节点以 kubernetes.io/hostname 为拓扑网络,如果 kubernetes.io/hostname 相同,就认为是同一个东西。
              # 亲和就是都放在这个逻辑区域,反亲和就是必须避免放在同一个逻辑区域。
      containers:
      - name: redis-server
        image: redis:5.0.14-alpine
        resources:
           limits:
             memory: 500Mi
             cpu: 1
           requests:
             memory: 250Mi
             cpu: 500m
        ports:
        - containerPort: 2375
          name: redis
        volumeMounts:
        - name: localtime
          mountPath: /etc/localtime
      volumes:
        - name: localtime
          hostPath:
            path: /usr/share/zoneinfo/Asia/Shanghai
      restartPolicy: Always
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name:  nginx-deploy
  namespace: default
  labels:
    app:  nginx-deploy
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 2
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity: # 亲和性配置
        podAffinity: # Pod 亲和性,符合以下指定条件会被调度过去
          requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
            - labelSelector:
                matchExpressions: 
                  - key: app
                    operator: In
                    values:
                      - store
              topologyKey: kubernetes.io/hostname # 拓扑键,划分逻辑区域。 
              # node 节点以 kubernetes.io/hostname 为拓扑网络,如果 kubernetes.io/hostname 相同,就认为是同一个东西。
              # 亲和就是都放在这个逻辑区域,反亲和就是必须避免放在同一个逻辑区域。
        podAntiAffinity: # Pod 反亲和性,符合以下指定条件不会被调度过去
          requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
            - labelSelector:
                matchExpressions: 
                  - key: app
                    operator: In
                    values:
                      - web-store
              topologyKey: kubernetes.io/hostname # 拓扑键,划分逻辑区域。 
              # node 节点以 kubernetes.io/hostname 为拓扑网络,如果 kubernetes.io/hostname 相同,就认为是同一个东西。
              # 亲和就是都放在这个逻辑区域,反亲和就是必须避免放在同一个逻辑区域。              
      containers:
      - name:  nginx
        image:  nginx:1.20.2
        resources:
          limits:
            cpu: 200m
            memory: 500Mi
          requests:
            cpu: 100m
            memory: 200Mi
        ports:
        - containerPort:  80
          name:  nginx
        volumeMounts:
        - name: localtime
          mountPath: /etc/localtime
      volumes:
        - name: localtime
          hostPath:
            path: /usr/share/zoneinfo/Asia/Shanghai
      restartPolicy: Always

上面yaml中定义了两个Deployment,来完成两个pod的部署。其中第一个是redis应用,它的标签是app: store,设置了一个反亲和性,标签也是app: store,这是为了让redis尽量部署在不同节点。第二个是一个web应用,它的标签是app: web-store,其中设置了一个反亲和性,标签是app: web-store,为了使web部署在不同的node,还有一个亲和性标签app: store,则是为了和redis尽量部署在同一节点。

容忍和污点

前面的调度方式都是站在 Pod 的角度上,通过在 Pod 上添加属性,来确定 Pod 是否要调度到指定的 Node 上,其实我们也可以站在 Node 的角度上,通过在 Node 上添加污点(Taint),来决定是否运行 Pod 调度过来。

Node 被设置了污点之后就和 Pod 之间存在了一种相斥的关系,进而拒绝 Pod 调度进来,甚至可以将已经存在的 Pod 驱逐出去。

容忍度(Toleration)是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的 Pod。

污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod, 是不会被该节点接受的。

应用场景:

  • 专用节点:如果想将某些节点专门分配给特定的一组用户使用,你可以给这些节点添加一个污点(即, kubectl taint nodes nodename dedicated=groupName:NoSchedule), 然后给这组用户的 Pod 添加一个相对应的容忍度 (通过编写一个自定义的准入控制器, 很容易就能做到)。 拥有上述容忍度的 Pod 就能够被调度到上述专用节点,同时也能够被调度到集群中的其它节点。 如果你希望这些 Pod 只能被调度到上述专用节点, 那么你还需要给这些专用节点另外添加一个和上述污点类似的 label(例如:dedicated=groupName), 同时还要在上述准入控制器中给 Pod 增加节点亲和性要求,要求上述 Pod 只能被调度到添加了 dedicated=groupName 标签的节点上。
  • 配备了特殊硬件的节点:在部分节点配备了特殊硬件(比如 GPU)的集群中, 我们希望不需要这类硬件的 Pod 不要被调度到这些特殊节点,以便为后继需要这类硬件的 Pod 保留资源。 要达到这个目的,可以先给配备了特殊硬件的节点添加污点 (例如 kubectl taint nodes nodename special=true:NoSchedulekubectl taint nodes nodename special=true:PreferNoSchedule), 然后给使用了这类特殊硬件的 Pod 添加一个相匹配的容忍度。 和专用节点的例子类似,添加这个容忍度的最简单的方法是使用自定义 准入控制器

污点

污点的格式为:

key=value:effect
# key 和 value 是污点的标签及对应的值
# effect 描述污点的作用

effect 支持如下的三个选项:

  • PreferNoSchedule:Kubernetes 将尽量避免把 Pod 调度到具有该污点的 Node 上,除非没有其他节点可以调度;换言之,尽量不要来,除非没办法。
  • NoSchedule:Kubernets 将不会把 Pod 调度到具有该污点的 Node 上,但是不会影响当前 Node 上已经存在的 Pod ;换言之,新的不要来,在这的就不要动。
  • NoExecute:Kubernets 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐;换言之,新的不要来,这这里的赶紧走。

注意:NoExecute 一般用于实际生产环境中的 Node 节点的上下线。

污点语法:

# 设置污点
kubectl taint node xxx key=value:effect

# 去除污点
kubectl taint node xxx key:effect-

# 去除所有污点
kubectl taint node xxx key-

# 查看污点
kubectl describe node xxx | grep -i taints

kubeadm 安装的集群上 k8s-master 自带有污点:node-role.kubernetes.io/mater:Noschedule。这是因为master节点用来管理集群,一般不用来直接部署pod。

污点演示(为了演示效果更为明显,暂时停止 k8s-node2 节点,现在只有k8s-node1 节点):

  • ① 为 k8s-node1 设置污点tag=xudaxian:PreferNoSchedule,然后创建 Pod1 (Pod1 可以运行)
  • ② 修改 k8s-node1 节点的污点为 tag=xudaxian:NoSchedule,然后创建Pod2(Pod1 可以正常运行,Pod2 失败)。
  • ③ 修改 k8s-node1 节点的污点为 tag=xudaxian:NoExecute,然后创建 Pod3(Pod1、Pod2、Pod3失败)。

容忍

上面介绍了污点的作用,我们可以在 Node上 添加污点用来拒绝 Pod 调度上来,但是如果就是想让一个 Pod 调度到一个有污点的 Node 上去,这时候应该怎么做?这就需要使用到容忍。

污点就是拒绝,容忍就是忽略,Node 通过污点拒绝 Pod 调度上去,Pod 通过容忍忽略拒绝。

容忍的详细配置:

kubectl explain pod.spec.tolerations
......
FIELDS:
  key       # 对应着要容忍的污点的键,空意味着匹配所有的键
  value     # 对应着要容忍的污点的值
  operator  # key-value的运算符,支持Equal和Exists(默认)
  effect    # 对应污点的effect,空意味着匹配所有影响
  tolerationSeconds   # 容忍时间, 当effect为NoExecute时生效,表示pod在Node上的停留时间

当满足如下条件的时候,Kubernetes 认为污点和容忍匹配:

  • 键(key)相同
  • 效果(effect)相同
  • 污点的 operator 为:
    1. Exists ,此时污点中不应该指定 value,只要键存在就匹配
    2. Equal,此时容忍的 value 应该和污点的 value 相同

特殊情况:

  • 如果不指定 operator ,默认为 Equal
  • 容忍中没有定义 key ,但是定义了 operator 为 Exists ,Kubernetes 则认为此容忍匹配所有的污点
  • 容忍中没有定义 effect,但是定义了 key,Kubernetes 认为此容忍匹配所有 effect

示例:

设置污点

kubectl taint node k8s-node1 tag=xudaxian:NoExecute

pod的yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-toleration
spec:
  containers: # 容器配置
    - name: nginx
      image: nginx:1.20.2
      imagePullPolicy: IfNotPresent
      ports:
        - name: nginx-port
          containerPort: 80
          protocol: TCP
  tolerations: # 容忍
    - key: "tag" # 要容忍的污点的key
      operator: Equal # 操作符
      value: "xudaxian" # 要容忍的污点的value
      effect: NoExecute # 添加容忍的规则,这里必须和标记的污点规则相同

可以给一个节点添加多个污点,也可以给一个 Pod 添加多个容忍度设置。 Kubernetes 处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历, 过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点。需要注意以下情况:

  • 如果未被忽略的污点中存在至少一个 effect 值为 NoSchedule 的污点, 则 Kubernetes 不会将 Pod 调度到该节点。
  • 如果未被忽略的污点中不存在 effect 值为 NoSchedule 的污点, 但是存在至少一个 effect 值为 PreferNoSchedule 的污点, 则 Kubernetes 会 尝试 不将 Pod 调度到该节点。
  • 如果未被忽略的污点中存在至少一个 effect 值为 NoExecute 的污点, 则 Kubernetes 不会将 Pod 调度到该节点(如果 Pod 还未在节点上运行), 并且会将 Pod 从该节点驱逐(如果 Pod 已经在节点上运行)。

例如,假设你给一个节点添加了如下污点:

kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule

假定某个 Pod 有两个容忍度:

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"

在这种情况下,上述 Pod 不会被调度到上述节点,因为其没有容忍度和第三个污点相匹配。 但是如果在给节点添加上述污点之前,该 Pod 已经在上述节点运行, 那么它还可以继续运行在该节点上,因为第三个污点是三个污点中唯一不能被这个 Pod 容忍的。

通常情况下,如果给一个节点添加了一个 effect 值为 NoExecute 的污点, 则任何不能忍受这个污点的 Pod 都会马上被驱逐,任何可以忍受这个污点的 Pod 都不会被驱逐。 但是,如果 Pod 存在一个 effect 值为 NoExecute 的容忍度指定了可选属性 tolerationSeconds 的值,则表示在给节点添加了上述污点之后, Pod 还能继续在节点上运行的时间。例如,

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  tolerationSeconds: 3600

这表示如果这个 Pod 正在运行,同时一个匹配的污点被添加到其所在的节点, 那么 Pod 还将继续在节点上运行 3600 秒,然后被驱逐。 如果在此之前上述污点被删除了,则 Pod 不会被驱逐。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ethan-running

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值