Kubernetes学习笔记

Kubernetes学习笔记

本文部分概念解释可能会使用AI进行总结

一些基本认知

  1. Kubernetes也叫K8s,是因为K和s中间有8个字母。

  2. K8s是一个基于容器技术的分布式架构方案,是容器云的平台型方案,各大云厂商都有基于K8s的容器服务,是新一代基于容器技术的PaaS平台的重要底层框架。利用K8s,可以方便的在各大云平台和本地服务器上实现迁移。

  3. K8s是一个完备的分布式系统支撑平台,具备完善的集群管理能力,包括多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和服务发现机制、内建的智能负载均衡器、强大的故障发现和自我修复能力、服务滚动升级和在线扩容能力、可扩展的资源自动调度机制,以及多粒度的资源配额管理能力。

  4. K8s提供了完善的管理工具,这些工具涵盖了开发,部署,测试,运维监控等各个环节。

重要概念

资源对象
  1. Node(节点):
    节点是Kubernetes中的工作机,可以是虚拟机或物理机,它负责运行容器应用。每个节点包括Kubelet(管理节点和与集群通信的代理)、容器运行时(如Docker或containerd),以及用于网络通信的代理。
  2. Pod(容器组):
    Pod是Kubernetes中的基本部署单元,也是可以创建和管理的最小可部署实体。一个Pod可以包含一个或多个容器,这些容器共享存储、网络和其他资源。
  3. Service(服务):
    Service是一个抽象层,它为运行在一个或多个Pod上的应用提供了一个统一的访问接口。这使得外部访问Pod提供的服务更加容易,且不受单个Pod生命周期的影响。
  4. Volume(卷):
    卷是一个可以被Pod中一个或多个容器访问的数据存储区域。它是一种抽象出来的存储资源,用于解决容器和Pod的数据持久化问题。
  5. Label(标签):
    标签是附加到资源如Pods、Services等上的键值对,用于组织、选择和执行资源上的操作。通过标签,用户可以非常灵活地管理群集中的资源组。
  6. Annotation(注解):
    注解也是键值对,用于存储额外的非标识信息,以帮助客户端如工具和库处理资源,但它们不用于标识和选择资源。
  7. Namespace(命名空间):
    命名空间是对一组资源和对象的逻辑分组,它可以帮助不同的项目、团队或客户在同一个物理集群中隔离运行。
  8. Deployment(部署):
    Deployment是对Pod和ReplicaSet(管理Pod副本的控制器)的声明式更新。它能够定义应用的生命周期,如更新、回滚等操作。
  9. HPA(Horizontal Pod Autoscaler):
    水平Pod自动扩缩器自动根据CPU使用率或其他选择的度量标准调整Pod的数量。它提高了应用的可扩展性和资源效率。
  10. PVC(Persistent Volume Claim,持久卷申领):
    PVC是用户对存储资源的请求。用户可以请求特定大小和访问模式的存储资源,而无需关心后端存储技术的具体细节。
集群类
  1. Master(包含kube-apiserver, kube-controller-manager, kube-scheduler,etcd服务)
  2. Node(kubelet,kube-proxy, docker)与Master一样,Node可以是一台物理主机,也可以是一台虚拟机。一台虚拟机只能部署一个Node。
应用类
  1. Service与Pod:
    Service是一个定义了如何访问一组Pod的规则的抽象。它允许通过一个固定的IP地址和端口访问后端的一个或多个Pod。Service通过选择器来选择它要代理流量的Pods。这样,即使后台Pod的数量或实例发生变化,Service提供的访问点仍然保持不变。
  2. Label 标签选择器:
    标签选择器用于选择一组通过标签(key-value对)标记的资源。在Kubernetes中,这通常用于Service选择它要路由流量的Pods,或者在Deployment中指定哪些Pods应该被纳入管理范围。通过标签选择器,用户可以非常灵活地管理群集中的资源。
  3. Pod与Deployment:
    Pod是运行容器的最小单元,而Deployment是管理Pod的生命周期的一种方式,如创建、更新和删除。Deployment使用模板定义Pod的期望状态,并确保任何时候都有指定数量的Pod副本在运行。如果Pod失败,Deployment将自动替换它,保证系统的可用性和稳定性。
  4. Service的ClusterIP地址:
    ClusterIP是Service的一种类型,它为Service提供了一个集群内部的虚拟IP,只能从集群内部访问。这是实现Pod之间通信的标准方法,例如,后端数据库服务和前端应用服务之间的通信。
  5. Service的外网访问:
    若要使Service能够从外部网络访问,可以使用类型为NodePort或LoadBalancer的Service。NodePort打开每个节点上的一个端口,使得外部请求可以通过任何节点的此端口访问Service。LoadBalancer类型使用云提供商的负载均衡器来分配外部流量到后端Pods。
  6. 有状态的应用集群:
    有状态应用(如数据库)需要持久化存储和唯一的网络标识。StatefulSets是Kubernetes中的一个API对象,用于管理有状态应用。它保证了Pod的部署和扩展顺序,并且可以保持Pod的身份不变,即使它们被重新调度到其他节点上。
  7. 批处理应用:
    对于需要执行一次性任务或批处理工作的应用,可以使用Kubernetes的Job资源。Job保证批处理任务可以运行到完成,如果任务失败,它可以重新启动容器直到任务成功完成。
  8. 应用的配置问题:
    应用配置通常通过ConfigMaps和Secrets来管理。ConfigMaps用于存储非机密的配置数据,如配置文件和环境变量。Secrets用于存储敏感信息,如密码和API密钥。这些资源可以被Pods在运行时访问,以便根据配置来调整其行为。
  9. 应用的运维问题:
    Kubernetes提供了多种监控和日志记录工具来帮助运维。例如,使用Metrics Server收集集群性能数据,使用Elasticsearch、Fluentd和Kibana(EFK)堆栈进行日志聚合和分析。此外,Prometheus和Grafana常用于监控Kubernetes集群的性能并提供实时可视化。
存储类
  1. emptyDir:
    emptyDir 是一个在Pod的生命周期内存在的临时目录。当一个Pod被分配到一个节点上时,会为该Pod创建一个emptyDir卷。emptyDir在Pod运行期间一直存在,无论容器崩溃与否。但是,当Pod从节点上删除时,emptyDir的内容也会被永久删除。emptyDir适合临时存储,如缓存数据和工作文件。
  2. hostPath:
    hostPath 卷用于将节点文件系统上的文件或目录挂载到你的Pod中。这允许Pod直接访问到节点的文件系统,可以用于运行需要访问特定系统文件的应用,或者从Kubernetes集群外部存储数据。然而,使用hostPath需要小心,因为它可以影响节点的稳定性,并且与Pod的可移植性和安全性相冲突。
  3. 公有云Volume:
    在公有云(如AWS, Azure, Google Cloud)环境中,Kubernetes支持使用云提供商的存储资源作为卷。例如,AWS上的EBS(Elastic Block Store)、Google Cloud的Persistent Disk和Azure的Disk Storage。这些卷类型支持持久存储,可以在Pod重启或迁移后保持数据不丢失。使用云卷可以利用云平台的高可用性和备份功能。
  4. 其他类型的Volume:
  • NFS(Network File System):允许你将一个NFS(网络文件系统)服务器挂载到你的Pod上。NFS卷可以由多个Pod同时读写。
    ConfigMap 和 Secret:用于在Pod中注入配置数据和敏感数据。ConfigMap 用于存储非敏感配置选项,而Secret用于存储密码、密钥等敏感信息。
  • PersistentVolume (PV) 和 PersistentVolumeClaim (PVC):用于设置Pods使用的持久化存储。PersistentVolume 是集群中的一个存储资源,而PersistentVolumeClaim 是用户对存储资源的请求。
  • CSI (Container Storage Interface):标准化的存储接口,使存储供应商可以开发一个插件,该插件能在多种容器编排系统中工作,包括Kubernetes。
安全类

Kubectl命令行

可以查看这位大佬的文章
kubectl命令大全

  1. 注意可以添加一些常用别名
  2. 自动补齐命令
yum -y install bash-completion
bash
source <(kubectl completion bash)
  1. 删除dev命名空间下所有处于Terminal状态的Pod(充分理解awk和xargs命令)
kubectl get pod -n dev | awk '{print $1}' | xargs -i kubectl delete pod {} --force --grace-period=0 -n dev

Pod

本节会介绍Pod的应用,配置,调度,升级和扩缩容,主要包括Pod和容器的使用,应用配置管理,pod的控制和调度管理,pod升级和回滚,以及pod的扩缩容机制。

Yaml文件

  1. 大小写敏感
  2. 使用缩进表示层级关系(缩进时不允许使用Tal键,只允许使用空格,缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  3. ”#” 表示注释,从这个字符一直到行尾,都会被解析器忽略
  4. 在Kubernetes中,只需要知道两种结构类型即可:Lists,Maps
  5. Deployment 管理的是replicaset-controller,RC会创建Pod。Pod自身会下载镜像并启动镜像。
  6. RC(replicaset-controller)主要功能:保持副本数量,扩容和缩容,滚动更新。
  7. 一般我们使用deployment而不是RC,因为Deployment 提供了更高级的功能,如声明式的更新(滚动更新和回滚)、暂停/恢复更新、状态检查等。
  8. 一个Pod里可以有多个容器,属于同一个Pod的多个容器之间互相访问只需要通过localhost就可以通信。
  9. 同一个Pod中的多个容器能共享Pod级别的存储卷Volume。
  10. ConfigMap生成容器的环境变量,设置容器启动命令的启动参数。
  11. ConfigMap 是一种用于存储配置信息的资源,允许你将非敏感数据(如配置文件、命令行参数等)以键值对的形式分离到容器应用之外,这样可以在不改动容器镜像的情况下调整应用的配置。
  12. ConfigMap必须在Pod之前创建,Pod才能引用。
  13. 一个Pod里可以有多个容器,属于同一个Pod的多个容器之间互相访问只需要通过localhost就可以通信。
  14. 同一个Pod中的多个容器能共享Pod级别的存储卷Volume。

副本的作用

  1. 高可用性:通过设置多个副本,即使在某些副本失败的情况下,应用程序也能继续运行。Kubernetes 的控制平面会监控集群中的 Pod,并在 Pod 数量少于预期时自动重启或创建新的 Pod。
  2. 负载均衡:副本允许在多个 Pod 实例之间分配流量,这有助于负载均衡和提高应用程序的处理能力。
  3. 滚动更新:在更新应用程序时,Kubernetes 可以控制更新过程中运行的不同版本的数量,从而实现零停机部署。例如,可以设置滚动更新策略,逐步替换旧版本的 Pod,直到所有副本都已更新到新版本。
  4. 弹性伸缩:基于当前负载,Kubernetes 可以自动调整 Pod 的数量。如果应用程序接收到的流量增加,可以增加副本数量;如果流量减少,可以减少副本数量。
  5. 资源管理:副本控制器(ReplicationController)、副本集(ReplicaSet)或部署(Deployment)等资源可以确保始终有指定数量的 Pod 运行。这些资源还提供了其他功能,如滚动更新和回滚。

创建Pod的YAML及其解释

apiVersion: v1
# 指定 Kubernetes API 的版本,这里使用的是 v1。

kind: Pod
# 定义要创建的资源类型,这里是 Pod。

metadata:
#metadata 字段用于存储关于 Kubernetes 资源的元数据。
  name: test-pod
  # Pod 的名称,在同一个命名空间中必须是唯一的。
  namespace: my-namespace  # 指定资源所属的命名空间

  labels:
    k8s-app: apache
    version: v1
    kubernetes.io/cluster-service: "true"
    # 标签是键值对,用于组织和选择对象的子集。

  annotations:
    example.com/my-annotation: "some-value"
    # annotations 用于存储额外的元数据,这些元数据用于存放一些不会用于标识和选择资源的信息。
    #注解可以用于工具、库和客户端API,
    #它们可以包含比 labels 更大的、非标识性的元信息,可以是任意的非结构化数据。
    #注解的常见用途包括:
    #1. 说明资源的修补或更改的信息
    #2. 工具的配置选项,如构建、部署工具
    #3. 对资源的描述,如使用该资源的人的电话信息等
    #4. 管理操作记录,如日志、监控、分析等

spec:
#spec 字段定义了 Kubernetes 对象的期望状态。这包括了资源需要运行的详细配置
  restartPolicy: Always
  # Pod 的重启策略。"Always"表示如果 Pod 崩溃,将自动重启。

  nodeSelector:
    zone: node1
    # 调度约束,指定 Pod 可以调度到哪些节点,基于节点的标签。

  containers:
  - name: test-pod
    # Pod 中容器的名称。
    image: 10.192.21.18:5000/test/chat:latest
    # 容器使用的镜像。
    imagePullPolicy: Never
    # 拉取镜像的策略。"Never" 表示假定镜像已经存在于本地。
    command: ['sh']
    # 覆盖镜像默认的 ENTRYPOINT 的命令。
    args: ["$(str)"]
    # 传递给命令的参数。
    env:
    - name: str
      value: "/etc/run.sh"
      # 容器中的环境变量。
    resources:
      requests:
        cpu: 0.1
        memory: 32Mi
        # 容器运行所需的最小资源。如果不满足,容器将不会启动。
      limits:
        cpu: 0.5
        memory: 1000Mi
        # 容器可以使用的最大资源。超过可能会导致容器重启或被杀死。
    ports:
    - containerPort: 80
      name: httpd
      protocol: TCP
      # 容器监听的网络端口。
    livenessProbe:
      httpGet:
        path: /
        port: 80
        scheme: HTTP
      # 用于检查容器健康的探针。如果失败,容器将被重启。
      initialDelaySeconds: 180 
      #这里时间如果过短,pod还没起来的话可能会导致pod反复重启
      timeoutSeconds: 5
      periodSeconds: 15
      # 活跃探针的初始延迟、超时和检查周期。
    lifecycle:
      postStart:
        exec:
          command:
            - 'sh'
            - 'yum upgrade -y'
        # 容器启动后执行的命令。
      preStop:
        exec:
          command: ['service httpd stop']
        # 容器停止前执行的命令。
    volumeMounts:
    - name: volume
      mountPath: /data
      readOnly: True
      # 指定挂载到容器的卷。
      
  volumes:
  - name: volume
    hostPath:
      path: /opt
      # 定义一个卷,它将主机的路径挂载到容器中。

创建Deployment的Yaml文件

apiVersion: apps/v1       # 指定 Kubernetes API 的版本,这里使用 apps/v1 是因为 Deployment 属于 apps 组
kind: Deployment          # 资源类型为 Deployment
metadata:
  name: example-deployment  # Deployment 的名称
  namespace: default      # 指定 Deployment 所属的命名空间,如果省略则默认为 default
  labels:
    app: myapp            # 自定义标签,用于标识相关的资源组
  annotations:
    example.com/info: "This is a sample deployment"  # 注解,用来存放额外的说明信息,不用于选择器
spec:
  replicas: 3             # 副本数量,指定希望运行的 Pod 副本数
  selector:
    matchLabels:
      app: myapp          # 选择器,指定这个 Deployment 管理的 Pod 必须拥有的标签
  strategy:
    type: RollingUpdate   # 更新策略为滚动更新
    rollingUpdate:
      maxSurge: 1         # 更新过程中最多可以超出的 Pod 数量
      maxUnavailable: 0   # 更新过程中最多有多少个 Pod 不可用
  template:               # 模板,定义 Pods 的规格
    metadata:
      labels:
        app: myapp        # Pod 模板的标签,被上面的选择器使用
    spec:
      containers:
      - name: my-container  # 容器的名称
        image: nginx:1.17   # 容器使用的镜像
        ports:
        - containerPort: 80 # 容器需要暴露的端口号
        resources:
          requests:
            cpu: "100m"     # 容器请求的 CPU 资源量
            memory: "200Mi" # 容器请求的内存资源量
          limits:
            cpu: "200m"     # 容器的 CPU 资源上限
            memory: "400Mi" # 容器的内存资源上限
            #CPU 资源的单位是核(cores)。m 代表毫核(milli-cores),即千分之一核。
        env:
        - name: ENV_VAR_NAME  # 环境变量的名称
          value: "value"      # 环境变量的值
        livenessProbe:
          httpGet:
            path: /health    # 健康检查路径
            port: 80         # 使用的端口
          initialDelaySeconds: 15  # 容器启动后延迟多久开始健康检查
          periodSeconds: 20        # 健康检查的间隔时间
        readinessProbe:
          httpGet:
            path: /ready     # 就绪检查路径
            port: 80         # 使用的端口
          initialDelaySeconds: 5   # 容器启动后延迟多久开始就绪检查
          periodSeconds: 10        # 就绪检查的间隔时间

创建Service的YAML及其解释

apiVersion: v1                      # Kubernetes API 版本,Service 属于 v1 版本
kind: Service                       # 资源类型是 Service
metadata:
  name: my-service                  # Service 的名称
  namespace: default                # Service 所在的命名空间
  labels:
    app: myapp                      # 标签,用于标识 Service
  annotations:
    description: "Sample service"   # 注解,用来提供有关 Service 的更多信息
spec:
  type: LoadBalancer                # Service 类型,LoadBalancer 为外部访问提供负载均衡
  selector:
    app: myapp                      # 选择器,用于选择哪些 Pods 将被此 Service 管理
  ports:
    - name: http                     # 端口配置的名称
      protocol: TCP                 # 通信协议,常用的是 TCP 或 UDP
      port: 80                      # Service 对外暴露的端口
      targetPort: 8080              # Pod 内部的端口,流量将被转发到此端口
      nodePort: 30000               # 当 type 为 NodePort 时使用,定义节点上的端口
  sessionAffinity: None             # 会话亲和性,控制是否将来自同一客户端的请求路由到同一 Pod
  externalTrafficPolicy: Cluster    # 定义如何路由外部流量到 Service,"Cluster" 或 "Local"
  loadBalancerIP: 192.168.0.1       # 指定负载均衡器的 IP,一般用于特定云服务提供商
  externalName: my-service.example.com  # 当 Service 类型为 ExternalName 时使用,定义外部服务的引用
  healthCheckNodePort: 30200        # 用于健康检查的节点端口,仅在某些类型的 Service 如 LoadBalancer 时使用
  publishNotReadyAddresses: true    # 是否允许流量到达未准备好的地址,通常用于在服务启动时的特殊情况

创建ConfigMap的Yaml文件

apiVersion: v1                # 指定API版本
kind: ConfigMap               # 资源类型是ConfigMap
metadata:                     
  name: example-configmap     # ConfigMap的名称
  namespace: default          # ConfigMap所在的命名空间,默认为"default"
data:                         
  key1: value1                # 数据项1,键名为key1,值为value1
  key2: value2                # 数据项2,键名为key2,值为value2
binaryData:
  key3: dmFsdWUz               # 二进制数据项,键名为key3,值为value3的base64编码

Pod调度

  1. 默认调度:
    调度器选择:Kubernetes的默认调度器会根据一系列调度算法(例如,预选和优选)来决定Pod应该被调度到哪个节点。
    预选阶段:这一阶段,调度器会过滤出那些资源不足以运行Pod的节点,例如CPU或内存不足。
    优选阶段:在符合条件的节点中,调度器会根据一系列的优先级规则,选择最优的节点。例如,可能会选择负载较低的节点,或是网络延迟较小的节点。
    亲和性与反亲和性调度:
  2. 节点亲和性(Node Affinity):允许你指定Pod应该被调度到满足特定规则的节点。例如,你可以指定Pod只能在具有特定标签的节点上运行。
    Pod亲和性与反亲和性(Pod Affinity/Anti-Affinity):允许你指定Pod之间的亲和性或反亲和性。亲和性可以用来将相关的Pod部署到靠近彼此的节点上,而反亲和性则用来确保某些Pod不会被调度到相同的节点。
  3. 污点和容忍度(Taints and Tolerations):
    污点:可以被添加到节点上,表示该节点可能不适合运行某些Pod。只有当Pod具有匹配的容忍度时,它才能被调度到带有特定污点的节点。
    容忍度:是设置在Pod上的,允许Pod忍受一个或多个污点,从而能在带有这些污点的节点上运行。
  4. 守护进程集(DaemonSets):
    特殊调度方式,用于需要在每个或某些特定节点上运行的服务。例如,日志收集器或监控代理。DaemonSet确保每个选定的节点上都运行一个Pod副本,而无需显式的调度指令。
  5. 优先级和抢占:
    Pod可以被赋予优先级,高优先级的Pod可以抢占低优先级的Pod,以确保重要的应用能够获得所需的资源。
  6. Job:批处理调度
    通过Job资源对象来定义并启动一个批处理任务。批处理任务通常并行(或者串行)启动多个计算进程去处理一批工作项,处理完成后,整个批处理任务结束。
  7. Cronjob:定时任务

Pod的升级和回滚

  1. 通过kubectl set image来为Deployment设置新的镜像名。
kubectl set image deployment/nginx-deployment nginx=nginx:1.2.2
  1. 也可以直接使用edit来编辑deployment的yaml文件修改镜像版本
kubectl edit deployment/nginx-deployment 
  1. 镜像名一旦发生更改,将触发系统完成deployment所有运行pod的滚动升级操作,可以用以下命令查看更新过程
kubectl rollout status deployment/nginx-deployment 
kubectl get pods
  1. 本质上是通过ReplicaSet来更新。更新时会先创建一个新的rs,新的rs有一个副本,然后旧的rs减一;一个pod副本创建好后,新的rs增加一,旧的rs减一,以此类推。
  2. Deployment的定义中,可以通过spec,strategy来指定pod更新策略,目前可以指定Recreate(更新时先杀掉正在运行的Pod,然后创建)和RollingUpdate(滚动更新)。

Service

Kubernetes(K8s)中的 Service 是一个抽象概念,它定义了一种访问应用程序容器的方法,并将此访问方式与容器的具体运行实例分离开来。简单来说,Service 允许通过一个固定的 IP 地址和端口来访问一组动态变化的 Pod 实例。这样即使后端 Pod 实例发生变化,前端服务的访问地址和方式仍然保持不变。

Service 的主要类型有:

  1. ClusterIP:这是默认的 Service 类型,它为 Service 在内部集群中分配一个内部 IP 地址,使得 Service 可以在集群内部被访问,但不能从集群外部访问。
  2. NodePort:这种类型的 Service 在 ClusterIP 基础上为 Service 在所有节点的一个指定端口上提供访问,从而可以从集群外部访问 Service。
  3. LoadBalancer:这种类型的 Service 使用云提供商的负载均衡器,可以从外部访问一个分配给 Service 的固定公共 IP 地址。
  4. ExternalName:通过返回一个任意的名称(通常是可以解析的外部域名),这种类型的 Service 允许 Service 作为集群内部的代理到一个外部服务。

Service的YAML文件

apiVersion: v1  # 指定 Kubernetes API 版本,v1 是大多数核心资源的 API 版本
kind: Service  # 指定资源类型为 Service
metadata:
  name: my-service  # Service 的名字
  labels:
    app: MyApp  # 标签可以用来组织、选择和分类资源
spec:
  type: LoadBalancer  # 指定 Service 类型,LoadBalancer 使服务通过外部负载均衡器可访问
  selector:
    app: MyApp  # 选择器,用于选择哪些 Pod 将被此 Service 的流量所访问
    tier: frontend  # 可以使用多个标签来更精细地控制选择的 Pod
  ports:
    - protocol: TCP  # 指定使用的协议,通常是 TCP 或 UDP
      port: 80  # Service 上的端口,外部通信到此端口
      targetPort: 9376  # Pod 上的端口,流量将被转发到此端口
      nodePort: 30007  # 当类型为 NodePort 时,这是集群外部可用来访问 Service 的端口
  sessionAffinity: ClientIP  # 会话亲和性设置为基于客户端 IP,确保来自同一客户端的请求被发送到同一 Pod
  externalTrafficPolicy: Local  # 控制是否保留外部流量的源 IP,设置为 Local 时只有本地节点上的 Pod 会处理流量
  healthCheckNodePort: 30234  # 当设置外部流量策略并使用 LoadBalancer 时,用于健康检查的节点端口
  loadBalancerIP: 192.0.2.1  # 指定负载均衡器的 IP 地址
  loadBalancerSourceRanges:
    - 192.0.2.0/24  # 限制哪些 IP 地址范围可以访问 LoadBalancer
  externalName: my.external.example.com  # 当 Service 类型为 ExternalName 时,指定服务请求应该被解析和转发到的外部域名

Sercive的负载均衡机制

kube-proxy代理
  1. userspace模式
  2. iptables模式
  3. ipvs模式

持续更新中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值