k8命令,pod的启动流程与资源文件书写,k8s集群调度

目录

一.常用命令及kubect介绍

1.kubect介绍

1)语法格式:

2)命名空间的概述

3)查看命名空间

4)查看命名空间中的资源对象

2.查用排错命令

1)查询资源文件

2)查看资源详细信息(经常用于排错)

3)查看容器的日志信息

3.kubectl 命令与示例

二.kubectl容器管理

1.Pod与控制器

1)Deployment 资源控制器

2)POD是什么

2.POD的概述

1)POD的启动过程

2)POD生命周期

3.POD的启动状态

4.POD特点

5.容器管理命令

1)进入一个正在运行的容器

6.其他资源控制器

三.资源文件的概述

1.资源对象文件的概述

2.YAMl语法概述

3.资源文件的管理命令

4.POD的资源文件书写格式概述

​ 5.deploy资源控制器文件的书写

6.标签和选择器的书写格式

三.集群的管理

1.集群扩容

2.集群更新与回滚

1)查看控制器规则

2)deployment.spec.strategy支持两种策略

3.集群调度

3)高级调度策略


一.常用命令及kubect介绍

1.kubect介绍

--Kubectl是用于控制Kubernetes集群的命令行工具

1)语法格式:

~]# kubectl [command] [TYPE] [NAME] [flagsJ
command: # 子命令,如create, get, describe, delete
type:    # 资源类型,可以表示为单数,复数或缩写形式
name:    # 资源的名称,如果省略,则显示所有资源信息
flags:   # 指定可选标志,或附加的参数
​
# 查看所有类型
~]# kubectl api-resources
​
## 示例
# 查询节点状态
~]# kubectl get node
# 查询主机信息
~]# kubectl get node node1 -o wide
# -o参数帮助
~]# kubectl get node node1 -o wide --help
....
[(-o|--output=)json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-as-json|jsonpath-file|custom-columns|custom-columns-file|wide]
.....
# -o 指定以yaml格式显示出来
~]# kubectl get node node1 -o yaml
# 查询deployment资源名称
~]# kubectl get deployment 
# 查询pod容器资源(默认命名空间default)
~]# kubectl get pods 
# 指定查看pods的名称空间
~]# kubectl get pods -n kube-system

2)命名空间的概述

k8s命名空间为对象名称提供了一个作用域,我们可以把资源放到不同的命名空间中,这样我们可以使用同名的资源名称,只要保证同一命名空间中的资源名称唯一即可

系统命名空间

--default默认的命名空间,不声明命名空间的POD都在这里

--kube-node-lease为高可用提供心跳监视的命名空间

--kube-public公共数据,所有用户都可以读取它

--kube-system 系统服务对象所使用的命名空间

3)查看命名空间

~]# kubectl get namespace

4)查看命名空间中的资源对象

~]# kubectl -n kube-system get pod

2.查用排错命令

1)查询资源文件

# 格式
~]# kubectl get [资源类型] [资源名称]
​
## 示例
# 查看所有pod容器
~]# kubectl get pods -A
# 查看指定容器名称空间内的所有容器
~]# kubectl get pods -n kube-system
NAME                             READY   STATUS    RESTARTS       AGE
coredns-557689b88f-g9pwl         1/1     Running   1 (5h1m ago)   2d22h
coredns-557689b88f-rj84r         1/1     Running   1 (5h1m ago)   2d22h
.......
# 查看指定容器名称空间内的指定容器
~]# kubectl get pods -n kube-system coredns-557689b88f-g9pwl
# 查看指定容器名称空间内的指定容器信息
~]# kubectl get pods -n kube-system coredns-557689b88f-g9pwl -o wide
# 查看指定容器名称空间内的指定容器详细信息以yaml格式显示
~]# kubectl get pods -n kube-system coredns-557689b88f-g9pwl -o yaml
# 查看节点信息并显示详细信息以yaml格式显示
~]# kubectl get nodes -o yaml

2)查看资源详细信息(经常用于排错)

# 格式
~]# kubectl describe [资源类型] [资源名称]
​
# 示例
~]# kubectl describe pods -n kube-system kube-flannel-ds-jxwf5

3)查看容器的日志信息

-查看console终端的输出信息

-为空是正常现象,表示没有日志输出

# 格式
~]# kubectl logs [名称空间] [容器名称]
​
# 示例
~]# kubectl logs -n kube-system kube-flannel-ds-jxwf5

排错流程,一般先get查看,然后再describe,最后再logs查看

3.kubectl 命令与示例

命令格式命令说明
kubectl run 资源名称 -参数 --image=镜像名称:标签创建资源对象,常用参数-i交互,-t终端
kubectl get 查询资源 可选参数 -o wide 显示主机信息常用查询的资源 node|deployment|pod
kubectl exec -it 容器id 执行的命令同 docker exec 指令,进入容器内
kubectl describe 资源类型 资源名称查询资源的详细信息
kubectl attach同 docker attach 指令,连接容器
kubectl logs 容器id查看容器控制台的标准输出
kubectl delete 资源类型 资源名称删除指定的资源
kubectl create|apply -f 资源文件执行指定的资源文件

命令示例

# 执行指定的资源文件
~]# kubectl apply -f kube-flannel.yml
# 删除指定资源文件(当flannel注册失败容器报pending时,可以用这种方法重启)
~]# kubectl delete -f kube-flannel.yml
# 查看
~]# kubectl get pods -A
NAMESPACE     NAME                             READY   STATUS    RESTARTS      AGE
default       testos                           1/1     Running   2 (31m ago)   125m
kube-system   coredns-557689b88f-g9pwl         1/1     Running   1 (8h ago)    3d1h
kube-system   coredns-557689b88f-rj84r         1/1     Running   1 (8h ago)    3d1h
kube-system   etcd-master                      1/1     Running   3 (8h ago)    3d1h
kube-system   kube-apiserver-master            1/1     Running   3 (8h ago)    3d1h
kube-system   kube-controller-manager-master   1/1     Running   3 (8h ago)    3d1h
kube-system   kube-proxy-flzrz                 1/1     Running   2 (8h ago)    3d1h
kube-system   kube-proxy-sv5n8                 1/1     Running   0             7h42m
kube-system   kube-proxy-tkctb                 1/1     Running   0             30h
kube-system   kube-scheduler-master            1/1     Running   4 (8h ago)    3d1h
​
## get 查询信息 
# 查看节点
~]# kubectl get nodes 
NAME STATUS ROLES AGE VERSION 
master Ready master 19h v1.17.6 
node-0001 Ready <none> 16h v1.17.6 
​
# 查看pod容器
~]# kubectl get pod
No resources found in default namespace.
# 注:这里是因为get pod不写指定名称空间,默认找default
​
# 查看所有名称空间
~]# kubectl get namespaces 
NAME                STATUS    AGE
default             Active    9h
kube-node-lease     Active    9h
kube-public         Active    9h
kube-system         Active    9h
​
# 指定名称空间查看pod容器
~]# kubectl -n kube-system get pod 
NAME READY STATUS RESTARTS AGE 
kube-flannel-ds-amd64-hf2jp 1/1 Running 0 41m 
kube-flannel-ds-amd64-rtl4l 1/1 Running 0 41m 
... ... 
​
# describe 查询详细信息 
~]# kubectl -n kube-system describe pod kube-flannel-ds-amd64-rtl4l 
Name: kube-proxy-4tbp6 
Namespace: kube-system 
... ... 
Events:
......
# 注:错误信息主要找Events
​
# 使用run启动容器 
~]# kubectl  run  testos -it --image=192.168.1.100:5000/myos:v1804 
# 注释: 命令 子命令 自己起的名字  交互式终端 指定镜像=镜像仓库地址/镜像名
If you don't see a command prompt, try pressing enter
/]# exit
~]# kubectl get pods
NAME     READY   STATUS    RESTARTS     AGE
testos   1/1     Running   1 (5s ago)   3m28s 
​
# 启动服务 
~]# kubectl run web-test --image=192.168.1.100:5000/myos:httpd 
​
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead. deployment.apps/web-test created 
​
# 访问节点 
~]# kubectl get pod -o wide #详细信息
NAME READY STATUS RESTARTS AGE IP 
testos-79778b4895-s8mxl 1/1 Running 1 6m33s 10.244.3.2 ... ... 
testweb--7bf98b9576-v566c 1/1 Running 0 4m24s 10.244.4.2 ... ... 
​
~]# curl http://10.244.4.2/info.php 
<pre> Array ( 
 [REMOTE_ADDR] => 10.244.0.0 
 [REQUEST_METHOD] => GET 
 [HTTP_USER_AGENT] => curl/7.29.0 
 [REQUEST_URI] => /info.php ) 
php_host: web-test-7bf98b9576-v566c 1229 
​
# 进入容器 
~]# kubectl exec -it testos-79778b4895-s8mxl -- /bin/bash 
/]#
~]# kubectl attach -it testos-79778b4895-s8mxl 
/]# 
​
# 查看终端日志 
~]# kubectl logs web-test-7bf98b9576-v566c 
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.244.4.2. Set the 'ServerName' directive globally to suppress this message 
​
# 删除资源控制器,直接删除POD会自动重建 
~]# kubectl delete pod testos-79778b4895-s8mxl 
pod "testos-79778b4895-s8mxl" deleted 
​
~]# kubectl delete deployments testos 
deployment.apps "testos" deleted

二.kubectl容器管理

1.Pod与控制器

1)Deployment 资源控制器

Deployment 为 RS 提供滚动更新

        -ReplicaSet资源控制器(RS)

        -ReplicaSet 创建管理POD

        -ReplicaSet可以扩容和缩容

        -POD最小的管理单元

        -POD负责启动和运行容器

控制器架构示例图

img

# 查看一级控制器
~]# kubectl get deployments.apps
NAME                      READY   STATUS    RESTARTS        AGE
testos   1/1     Running   1 (6m57s ago)   10m
​
# 查看二级控制器
~]# kubectl get replicasets.apps
NAME                      READY   STATUS    RESTARTS        AGE
testos-79778b4895   1/1     Running   1 (6m57s ago)   10m
​
# 查看三级控制器
~]# kubectl get pods
NAME                      READY   STATUS    RESTARTS        AGE
testos-79778b4895-s8mxl   1/1     Running   1 (6m57s ago)   10m
​

# 注:二级,三级等一下控制器都是自动生成的,而一级控制器是自己创建的(必须手定义)

2)POD是什么?

  • POD是Kubernetes中最小的管理元素

  • 一个pod 可以理解为多个linux命名空间的联合

同一个Pod共享进程(PID)

同一个Pod共享网络IP及权限(NETWORK)

同一个Pod共享IPC通信信号(IPC)

同一个Pod共享主机名(UTS)

  • 包涵1个或多个容器 例如:共享网络、共享存储等

理解POD对掌握kubernetes非常重要

Pod支持横向扩展和复制

2.POD的概述

1)POD的启动过程

用户创建pod--->>(kubectl )联系API server--->>(kubectl 的命令)记录在etcd数据库中--->>然后(api server)再去找Scheduler调度器,Scheduler再调用内部的算法去找适合运行容器的节点--->>(Scheduler)把结果返回aip server-->>api server再把结果记录到etcd数据库中,由于master默认不运行服务,所以api serve 回直接找node节点也就是(kubelete计算节点)--->> (kubelete)找docker创建容器--(docker)把容器状态返回kubelete--->>(kubelet)再把容器状态返回给api server--->>( api server)再把结果记录再etcd数据库中

注:此时数据库中记录了类似与(c1 node running),所以此时使用kubectl就能调用这些信息

Scheduler分配容器

第一步筛选

比如此时要运行一个容器需要(500m的内存,端口80),此时 Scheduler就会去找节点凡是内存没有500m的排除,端口80被占用的排除,

第二步优选

如果此时有多个节点符合筛选条件,则根据内部算法(打分机制)选择

2)POD生命周期

Pod对象自从其创建开始至其终止退出的时间范围称为其生命周期。在这段时间中,Pod会处于 多种不同的状态,并执行一些操作;其中,创建主容器(main container)为必需的操作,其他可选的操作还包括运行于初始化容器(init container)、容器启动后钩子 (postart hook)、容器的存活性探测(liveness probe) 就绪性探测 (readiness probe)以及容器终止前钩子 (pre stop hook) 等,这些操作是否执行则取决于Pod 的定义

pod的启动--->>初始化容器(init container)(默认没有条件,可以填加初始话条件)--->>随着容器启动的小程序,也叫启动前脚本(post start hook)(小程序可以执行一些特别的操作,比如指定容器为谁的从服务器,默认也是空的)--->> 容器关闭前脚本(pre stop hook)(默认也是空的)(比如删除容器时发现容器在启动之后产生了5个G的硬盘空间,此时想要删除,这时候就可以设置结束前执行脚本了)

此时pod还有两个探测器

  • 生存探测(liveness probe)

        如果容器失败了,就会重启

  • 就绪性探测 (readiness probe)

        如果失败不会重启容器,只会改为NoReady,没有就绪状态,这个时候就以排错了(默认为空)

## 生存探测器示例
~]# kubectl get pods
NAME     READY   STATUS    RESTARTS      AGE
testos   1/1     Running   1 (13m ago)   17m
~]# kubectl attach pods -n default -it testos
If you don't see a command prompt, try pressing enter.
# 在容器内查看进程
/]# echo $$
1
# 因为attach进入容器用的父进程,而容器的父进程是上帝进程,且容器退出的方式是杀掉父进程,也就是会干掉容器
/]# exit
​
# 就绪探测器发现容器没有启动就会直接显示该状态
~]# kubectl get pods
NAME     READY   STATUS      RESTARTS      AGE
testos   0/1     Completed   1 (90m ago)   93m
​
# 此时生存探测器发现容器被干掉了,就会重新生成容器
~]# kubectl get pods
NAME     READY   STATUS    RESTARTS     AGE
testos   1/1     Running   2 (4s ago)   93m

3.POD的启动状态

Pod phase(相位状态)

--Pod的status字段是一个PodStatus的对象,Pod对象总是应 该处于其生命进程中以下几个相位(phase)之-。

--Pending 容器创建过程中,但它尚未被调度完成(api server找Scheduler之前的状态就是Pending,如果是这种状态,则说明节点不服和调度)

--Running 所有容器都已经被kubelet创建完成(Scheduler到docker之间的状态)

--Succeeded 所有容器都已经成功终止了并不会被重启

--Failed Pod中的所有容器中至少有一个容器退出是非O状态

--Unknown 无法正常获取到Pod对象的状态信息

img

4.POD特点

--Pod的生命周期是短暂的,用后即焚的实体。 注意:重启Pod中的容器跟重启Pod不是一回事。Pod只提供容器的运行环境并保持容器的运行状态,重启容器不会造成Pod重启。(在k8s中没有停止的概念,只有创建与删除。create,delete)

--Pod不会自愈。如果Pod运行的Node故障,或者是调度器本身故障,这个Pod就会被删除。(但是如果有控制器就不不一样)

--控制器(Deployment/RC/RS)可以自动创建和管理多个Pod, 提供副本管理、滚动升级和集群级别的自愈能力。

5.容器管理命令

1)进入一个正在运行的容器

语法格式:

~]# kubectl exec -it 容器id --执行的命令
# 这样运行的退出后不会重启

2)删除资源

语法格式:

~]# kubectl delete 资源类型 资源名称
# 删除容器
~]# kubectl delete pods -n default testos-79778b4895-s8mxl 
pod "testos-79778b4895-s8mxl" deleted 
# 查看pod容器,发现还是会创建出来
~]# kubectl get pods
NAME     READY   STATUS    RESTARTS      AGE
testos-79778b4895-43er4   1/1     Running   2 (88m ago)   92m
​
# 此时就要删除该pod容器的控制器
~]# kubectl get deployments.apps
NAME     READY   STATUS    RESTARTS      AGE
testos   1/1     Running   2 (88m ago)   92m 
~]# kubectl delete deployments testos 
deployment.apps "testos" deleted
​
# 删除容器时有一个宽限期,为了保证数据的完整性,删除资源控制器时也有一个(terminating回收状态)宽限期

6.其他资源控制器

peployment

        Deployment /RC/RS 都是资源控制器

        Deployment为Pod和ReplicaSet提供了一个声明式定 义方法,用来替代以前的Replicationcontroller 来方便的管理应用。

典型的应用场景包括:

        --定义Deployment来创建Pod和ReplicaSet

        --滚动升级和回滚应用

        --扩容和缩容

        --暂停和继续Deployment

RC/RS/Deployment

        --kubernetes通过Replication Control1(简称RC) 管理POD,在RC中定义了如何启动POD,如何 运行,启动几副本等功能,如果我们创建文件, 在其中使用Yaml的语法格式描述了上面的信息, 这个文件就是我们的资源对象文件

        --ReplicaSet (简称RS)是RC的升级版

        --Deployment为Pods和RS提供描述性的更新方式

三.资源文件的概述

1.资源对象文件的概述

-kuberbetes通过RC/RS 管理POD,在RC中定义了如何启动POD,如何运行,启动几个副本等功能,如果我们创建的文件,在其中使用Yaml的语法描述了上面的信息,这个文件就是我们的资源对象文件

-资源文件可以创建,删除,管理资源对象

-资源文件有很多高级的复杂的功能靠简单的命令方式无法实现,这些都需要使用资源文件描述

2.YAMl语法概述

YAML 是什么?

YAML 是一种可读性高,以数据为中心的数据序列化格式。可以表达 对象(键值对)数组,标量; 这几种数据形式 能够被多种编程语言和脚本语言解析

YAML 语法与格式

基本语法

  • 以 k: v 的形式来表示键值对的关系,冒号后面必须有一个空格

  • 表示注释

  • 对大小写敏感

  • 通过缩进来表示层级关系,缩排中空格的数目不重要,只要相同阶层的元素左侧对齐就可以了

  • 缩进只能使用空格,不能使用 tab缩进键

  • 字符串可以不用双引号

格式

对象和键值对

通过 k: v 的方式表示对象或者键值对,冒号后必须要加一个空格:

Name: Astron
Sex: female
School: TJU

通过缩进来表示对象的多个属性:

People: 
Name: Astron
Sex: female
School: TJU

也可以写成

people: {name: Astron, sex: female}

数组

数组(或者列表)中的元素采用 - 表示,以 - 开头的行表示构成一个数组

# eg1:

- A
- B
- C
#eg2:

people: 
    - yyy
    - zzz
    - www

行内表示:

people: [yyy, zzz, www]

eg3: 对象数组

people: 
    - 
      name: yyy
      age: 18
    - 
      name: zzz
      age: 19

使用流式表示:

people: [{name: yyy, age: 18},{name: zzz, age: 19}]

标量

标量是最基本的不可再分的值,包括:

  • 整数

  • 浮点数

  • 字符串

  • 布尔值

  • Null

  • 时间

  • 日期

eg:

boolean:
   - true # 大小写都可以
   - false
​
float:
   - 3.14
   - 3.25e+5 # 科学计数法
​
int: 12
​
null: 
   nodeName: name
​
string: 123
​
date: 2020-01-01 # 格式为 yyyy-MM-dd
​
datetime: 2020-01-10T15:02:08+08:00 # 日期和时间使用T连接,+表示时区

引用

& 用于建立锚点,* 用于引用锚点,<< 表示合并到当前数据

eg1:

defaults: &defaults
adapter: ppp
host: qqq
​
development: 
database: mq
<<: *defaults

相当于:

defaults:
adapter: ppp
host: qqq
​
development: 
database: mq
adapter: ppp
host: qqq



eg2:
- &showell steve
- clark
- eve
- *showell

相当于:

- steve
- clark
- eve
- steve

3.资源文件的管理命令

--create 创建资源对象(没有密等性,一般不用)

--apply 生名更新资源对象

--delete 删除资源文件

# 更新资源对象,也可以用来创建
~]# kubectl apply -f mypod.yaml
# 创建资源对象,但是用create创建的资源不能用apply更新
~]# kubectl create -f mypod.yaml
# 删除资源文件
~]# kubectl delete -f mypod.yaml

4.POD的资源文件书写格式概述

---            # 资源的开始,当一个文件中有多个时,则说明一个文件有多个资源文件
kind: Pod      # 资源对象的类型,这里定义pod,默认键值对采用key(小驼峰) : value(大坨峰),资源所有对象查看(kubectl api-resources)
apiVersion: v1 # pod的版本,查看当前k8s支持的所有版本(kubectl api-versions),查看当前pod的版本,及基本概述(kubectl explain pods),详细的当输入前面这条命令的时候地下会有网址,不过建议访问国内的(https://kubernetes.io/) 
metadata:      # 元数据,(属性数据,定义该pod的详细信息,pod.metadata)
  name: mypod  # 定义pod的名字,且是唯一性
spec:          # 标志位,标志详细信息,前面定义了资源对象,所以这里就是pod的详细信息(pod.spec)
  containers:  # pod里的容器
  - name: mylinux  #相当于pod里面的容器(mypod.mylinux)
    image: 172.17.0.98:5000/myos:v1804   # 这个容器用的什么镜像
    stdin: true                           # 标准输出(相当与-i)
    tty: true                             # 终端(相当与-t)
# 注:stdin,tty不写默认是false
​
​
## 示例
# 创建容器
~]# kubectl apply -f mypod.yaml 
pod/mypod created
# 查看创建
~]# kubectl get pods 
NAME     READY   STATUS    RESTARTS      AGE
mypod    1/1     Running   0             5s
# 注意此时是没有控制器的,所以可以直接删除
~]# kubectl get deployments.apps 
No resources found in default namespace.
~]# kubectl get replicasets.apps 
No resources found in default namespace
# 直接删除
]# kubectl delete pods mypod
pod "mypod" deleted
~]# kubectl get replicasets.apps 
No resources found in default namespace.
~]# kubectl get pods 
NAME     READY   STATUS    RESTARTS      AGE

 5.deploy资源控制器文件的书写

img

# 由于创建不带控制器的pod非常的不安全,所以这里创建带控制器的pod
~]# vim myapache.yaml
---                     # 资源的开始
kind: Deployment        # 资源对象的类型,为Deployment控制器
apiVersion: apps/v1     # 控制器的版本,查看deployment的版本(kubectl explain deployment)
metadata:               # 元数据,(属性数据,定义该deployment控制器的详细信息,deploy.metadata) 
   name: myapache       # 定义deploy控制器的名字,且是唯一性 
   annotations:         # 附加信息,该字段保留,后续介绍
     kubernetes.io/change-cause: httpd.v1
spec:                   # 详细定义,定义了资源对象控制器的详细信息,(deploy.spec)
   selector:            # 定义了控制器的标签   
     matchLabels:           
       myapp: httpd     # 定义了标签为httpd 
   replicas: 1          # 定义pod副本数
   template:            # 定义pod的资源模板
     metadata:          # pod的元数据,(属性数据,定义该pod的详细信息,pod.metadata)
       labels:          # 定义pod的标签
          myapp: httpd  # 标签名 (由于创建了控制器所以不能给pod起名字,只能由控制器创建pod的名字,所以这里只定义了标签)
     spec:              # 定义pod的spec这里就是pod的详细信息(pod.spec)
       containers:      # pod里的容器
       - name: webcluster    # 定义于pod里面的容器名字(mypod.webcluster)
         image: 172.17.0.98:5000/myos:httpd  # 这个容器用的什么镜像
         stdin: false    # 标准输出(相当与-i),由于这边启动的是apache,不需要交互试的终端输出,所以这里同意设置flase,或者不写也是可以的
         tty: false      # 终端(相当与-t)
         ports:          # 由于启动的是服务所以要设置监听端口号
         - protocol: TCP # 监听的协议TCP 
           containerPort: 80  # 监听的端口是80
       restartPolicy: Always  # 定义策略,pod维护容器的策略(策略:Always;OnFailure;Never)默认Always,Always表示容器一但死亡,则会重新创建;OnFailure表示只有当容器退出时$?!=0时才重启,也就是启动失败才重启;Never表示不管哪种都不重启;
​
# 创建
~]# kubectl apply -f myapache.yaml 
deployment.apps/myapache created
# 查看pod
~]# kubectl get pods 
NAME                       READY   STATUS    RESTARTS      AGE
myapache-9d7557448-nq8rw   1/1     Running   0             17s
# 查看一级控制器
~]# kubectl get deployments.apps
myapache   1/1     1            1           3m49s
# 查看二级控制器
~]# kubectl get replicasets.apps 
NAME                 DESIRED   CURRENT   READY   AGE
myapache-9d7557448   1         1         1       4m
# 测试
~]# curl -ik http://10.244.2.4

6.标签和选择器的书写格式

k8s 的 标签 和 标签选择器 标签 可以附加在 kubernetes 任何资源对象之上的键值型 数据 ,常用于 标签选择器 的匹配度检查,从而完成资源筛选 资源 标签 当 Kubernetes 对系统的任何API对象如Pod和节点进行“分组”时,会对其添加Label(key=value形式的“键-值对”)用以精准地 选择 对应的API对象

lables是设置标签,matchLabels匹配标签

img

为了建立控制器和pod间的关联,因为pod的名字是唯一的,如果起多个容器的话,不可以这么操作,所以kubernetes 先给每个pod打上一个标签(Label),然后再给相应的位置定义标签选择器(Label Selector),引用这些标签管理,而且资源控制器会给pod自动分配名字;

三.集群的管理

一个资源控制器可以控制多个pod

1.集群扩容

  • replicas 决定了集群pod数量

创建一个单节点的web容器

  • 在集群运行的过程中我们可以动态调整集群pod的数量

在创建文件之初设置容器副本数

~]# cat myapache.yaml 
---
......
   replicas: 1   # 根据数字设置
 ......    

在线修改设置,及时生效

# 先查看要扩容的容器的资源控制器,只有修改资源控制器才有效,而pod是被创建出来的
~]# kubectl get deployment
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
myapache   1/1     1            1           26m
​
# 进入配置里修改
~]# kubectl edit deployment myapache
.......
replicas: 3    # 根据数字设置,这里设置3测试
......
​
# 查看
~]# kubectl get deployment myapache
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
myapache   3/3     3            3           31m
~]# kubectl get pods 
~]# kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS      AGE    IP           NODE              NOMINATED NODE   READINESS GATES
myapache-9d7557448-59fxm   1/1     Running   0             3m4s   10.244.1.4   vm-0-114-centos   <none>           <none>
myapache-9d7557448-64fhh   1/1     Running   0             109s   10.244.1.5   vm-0-114-centos   <none>           <none>
myapache-9d7557448-brsbc   1/1     Running   0             109s   10.244.2.4   vm-0-142-centos   <none>           <none>
testos                     1/1     Running   2 (23h ago)   24h    10.244.1.2   vm-0-114-centos   <none>           <none>

scale 命令

# 先查看要扩容的容器的资源控制器,只有修改资源控制器才有效,而pod是被创建出来的
~]# kubectl get deployment myapache
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
myapache   3/3     3            3           31m
​
## 用scale设置
# 缩减
~]# kubectl scale deployments.apps myapache --replicas=1
# 查看pod
~]# kubectl get deployment myapache 
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
myapache   1/1     1            1           35m
​
# 扩展
~]# kubectl scale deployments.apps myapache --replicas=3
# 查看pod
~]# kubectl get deployment myapache
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
myapache   3/3     3            3           31m
~]# kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS      AGE    IP           NODE              NOMINATED NODE   READINESS GATES
myapache-9d7557448-59fxm   1/1     Running   0             3m4s   10.244.1.4   vm-0-114-centos   <none>           <none>
myapache-9d7557448-64fhh   1/1     Running   0             109s   10.244.1.5   vm-0-114-centos   <none>           <none>
myapache-9d7557448-brsbc   1/1     Running   0             109s   10.244.2.4   vm-0-142-centos   <none>           <none>

2.集群更新与回滚

1)查看控制器规则

kubectl get 资源对象 资源名称 -o 格式

~]# kubectl get deployment myapache -o yaml
......
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10      # 保留最多10个历史版本
  selector:                     
    matchLabels:
      myapp: httpd
  strategy:                     # 更新策略 
    rollingUpdate:              # 滚动更新
      maxSurge: 25%             # 最多超过25%的副本数
      maxUnavailable: 25%       # 最多有25%的副本不可用
    type: RollingUpdate         # 更行方式(Recrete ,rollingUpdate)

2)deployment.spec.strategy支持两种策略

  • Recrete

重建式更新,就是删一个建一个(如果有一百个容器则会比较慢)

  • rollingUpdate

滚动更新,更新期间pod最多有几个(相当与多线程操作,这边删,那边创),滚动更新在创建的时候最多不能超过25%,最大有25%的副本数量在不可用(删除)

示例

## 更新;把apache升级成nginx
# 先查看原有配置及测试
~]# kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS      AGE   IP           NODE              NOMINATED NODE   READINESS GATES
myapache-9d7557448-brsbc   1/1     Running   0             31m   10.244.2.4   vm-0-142-centos   <none>           <none>
~]# curl -ik http://10.244.2.4
this is apache
# 在线修改
......
 kubernetes.io/change-cause: nginx.v1  # 版本名
.....
- image: 172.17.0.98:5000/myos:nginx   # 更新的镜像
.....
​
# 查看
]# kubectl get pods -o wide
NAME                        READY   STATUS             RESTARTS        AGE     IP           NODE              NOMINATED NODE   READINESS GATES
myapache-5886d7b69b-bl7l4   0/1     Running    6 (3m42s ago)   9m34s   10.244.1.6   vm-0-114-centos   <none>           <none>
~]# curl -ik http://10.244.2.4
this is nginx
​
## 回滚
# 查看历史记录
~]#  kubectl rollout history deployment myapache
deployment.apps/myapache 
REVISION  CHANGE-CAUSE
1         httpd.v1
2         nginx.v1
# 回滚
~]# kubectl rollout undo deployment myapache --to-revision=1
deployment.apps/myapache rolled back
~]# kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE     IP           NODE              NOMINATED NODE   READINESS GATES
myapache-9d7557448-8gv62   1/1     Running   0          9m59s   10.244.2.6   vm-0-142-centos   <none>           <none>
~]# curl -ik http://10.244.2.4
this is apache
​

3.集群调度

容器的创建是随机的,如何选择固定的宿主机

1)nodeName标签

容器创建是随机的,选择nodeName固宿主机

img

# 先查看原有的pod
~]# kubectl get pods -o wide 
NAME                       READY   STATUS    RESTARTS   AGE   IP           NODE              NOMINATED NODE   READINESS GATES
myapache-9d7557448-8gv62   1/1     Running   0          15h   10.244.2.6   node2   <none>           <none>
# 可以看出容器在node2上,那么此时修改yaml文件更新
~]#vim myapache.yaml 
---
kind: Deployment
apiVersion: apps/v1
metadata:
   name: myapache
   annotations:
     kubernetes.io/change-cause: httpd.v1
spec:
   selector:
     matchLabels:
        myapp: httpd 
   replicas: 1
   template:
     metadata:
       labels:
          myapp: httpd
     spec:
       nodeName: node1  # 在该配置文件中添加一行使其调度到node1
       containers:         
       - name: webcluster
         image: 172.17.0.98:5000/myos:httpd
         stdin: false
         tty: false
         ports:
         - protocol: TCP
           containerPort: 80
       restartPolicy: Always  
​
~]# kubectl apply -f myapache.yaml 
deployment.apps/myapache configured
​
~]# kubectl get pod -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE              NOMINATED NODE   READINESS GATES
myapache-58fdbddd8d-m78jv   1/1     Running   0          11s   10.244.1.8   node1   <none>           <none>

注:如果添加了选则标签,但无法使用该主机,POD将一直处于Pending状态

使用 nodeName 参数让容器运行在指定节点上

2)nodeSelector标签

如何选择一类宿主机?

需要提前为目标主机打上特定的标签(可以是多台)

在资源文件中根据标签选择宿主机(更加灵活)

img

node标签的管理

nodeSelector是节点选择约束的最简单推荐形式

  • 可以给节点打上标签,根据标签来选择需要的节点(标签名任意)

  • 查看标签

# 格式
~]# kubectl get node --show-labels
​
# 示例 
~]#  kubectl get node --show-labels
aster            Ready    control-plane,master   4d16h   v1.23.2   beta.kubernetes.io/arch=amd64,.......
node1   Ready    <none>                 2d22h   v1.23.2   beta.kubernetes.io/arch=amd64,......
node2   Ready    <none>                 47h     v1.23.2   beta.kubernetes.io/arch=amd64,......
  • 设置标签

# 格式
~]# kubectl label nodes <node-name> <label-key>=<label-value>
​
# 示例,设置磁盘类型为ssd
~]# kubectl label nodes node2 disktype=ssd
node/node2 labeled
~]#  kubectl get node --show-labels
NAME              STATUS   ROLES                  AGE     VERSION   LABELS
......
node2   Ready    <none>                 47h     v1.23.2   .....disktype=ssd........
  • 删除标签
# 格式
~]# kubectl label nodes <node-name>   <label-key>-
​
# 示例
~]# kubectl label nodes node2 disktype-
node/node2 unlabeled
~]#  kubectl get node --show-labels
NAME              STATUS   ROLES                  AGE     VERSION   LABELS
master            Ready    control-plane,master   4d16h   v1.23.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
node1   Ready    <none>                 2d22h   v1.23.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=vm-0-114-centos,kubernetes.io/os=linux
node2   Ready    <none>                 47h     v1.23.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=vm-0-142-centos,kubernetes.io/os=linux
​
  • 使用 标签 让容器运行在一些节点上

运用资源文件把容器跑在有标签的节点组上

# 先运行标签
~]# kubectl label nodes node2 disktype=ssd
node/node2 labeled
# 修改配置文件
]# cat myapache.yaml 
---
kind: Deployment
apiVersion: apps/v1
metadata:
   name: myapache
   annotations:
     kubernetes.io/change-cause: httpd.v1
spec:
   selector:
     matchLabels:
        myapp: httpd 
   replicas: 1
   template:
     metadata:
       labels:
          myapp: httpd
     spec:
       nodeSelector:      # 添加标签组
         disktype: ssd    # 绑定刚刚创建的标签
       containers:
       - name: webcluster
         image: 172.17.0.98:5000/myos:httpd
         stdin: false
         tty: false
         ports:
         - protocol: TCP
           containerPort: 80
       restartPolicy: Alwa
~]#  kubectl get node --show-labels
NAME              STATUS   ROLES                  AGE     VERSION   LABELS
........
node2   Ready    <none>                 47h     v1.23.2   disktype=ssd,.....
​
~]# kubectl get pod -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE              NOMINATED NODE   READINESS GATES
myapache-7d685d5d66-shd86   1/1     Running   0          42s   10.244.2.7   node2 <none>

3)高级调度策略

亲和与反亲和

  • 亲和可以理解成偏爱或喜好,同样反亲和可以理解成不喜欢

  • 在kubernetes 中亲和特性在pod. spec. affinity中设置

  • 从亲和的对象又可以分为(节点亲和)和(容器亲和)

  • 从亲和的策略又可以分为(硬亲和)和(软亲和)

手册地址:

将 Pod 分配给节点 | Kubernetes

pod节点亲和示例

亲和示例

## 非强制性亲和示例
# 先创建节点标签
~]# kubectl label nodes node2 app=myapp-apache
# 创建yaml文件
~]# vim web-example.yaml 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-example
spec:
  selector:
    matchLabels:
      app: myapp-web
  replicas: 3
  revisionHistoryLimit: 10
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: myapp-web
    spec:
      affinity:                                 
        podAffinity:                                         # pod的亲和设置
          preferredDuringSchedulingIgnoredDuringExecution:  # 亲和性,表示更倾向于部署在指定节点上,但不是必须的
          - weight: 1                # weight范围1-100。这个涉及调度器的优选打分过程,每个node的评分都会加上这个weight,最后bind最高的node
            podAffinityTerm:         #指定pod要调度在含有标签app=myapp-apache的节点上
              labelSelector:         # 连接的标签组 
                matchExpressions:   
                - key: app
                  operator: In
                  values:
                  - myapp-apache
              topologyKey: kubernetes.io/hostname  # 用topologyKey表示,具体值用node label表示,调度器需要考虑这些pods是否满足规则Y
      containers:       
      - name: nginx
        image: 172.17.0.98:5000/myos:httpd
        ports:
        - protocol: TCP
          containerPort: 80
      restartPolicy: Always
​
# 创建
~]# kubectl apply -fweb-example.yaml 
deployment.apps/web-example configured
# 查看调度
~]# kubectl get pods -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE              NOMINATED NODE   READINESS GATES
web-example-558fd5cd9b-gs7v4   1/1     Running   0          9s    10.244.1.9     node1   <none>           <none>
web-example-558fd5cd9b-jlx97   1/1     Running   0          6s    10.244.1.10   node2   <none>           <none>
web-example-558fd5cd9b-tl67p   1/1     Running   0          7s    10.244.2.17   node2   <none>           <none>
​
#  affinity.podAffinity第二个表示创建的3个pod部署时要满足倾向性亲和性,创建的3个pod更倾向于部署在Pod的标签为app=myapp-apache所在的节点上,并且所在的节点要含有kubernetes.io/hostname标签。如下所示,上设置,app=myapp-apache的pod在kus-node2节点上,新创建的3个pod调度时要满足倾向性亲和性,最终2个pod调度在了k8s-node2上,1个pod调度在了k8s-node1上,更倾向于调度在k8s-node2上

# 强制亲和性
~]# vim web-example.yaml 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-example
spec:
  selector:
    matchLabels:
      app: myapp-web
  replicas: 3
  revisionHistoryLimit: 10
  strategy:                  # 设置滚动升级
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: myapp-web
    spec:                         # pod设置
      nodeName: vm-0-142-centos   
      nodeSelector:               # 指定标签
        disktype: ssd 
      affinity:                   # 亲和设置
        podAffinity:              # pod的亲和设置
          requiredDuringSchedulingIgnoredDuringExecution:   #强制亲和性
          - labelSelector:        # 连接的标签组 
              matchExpressions:   #指定pod要调度在含有标签app=myapp-apache的节点上
              - key: app
                operator: In
                values:
                - myapp-apache
            topologyKey: kubernetes.io/hostname    # 必须也要满足此标签要求
      containers:
      - name: nginx
        image: 172.17.0.98:5000/myos:httpd
        ports:
        - protocol: TCP
          containerPort: 80
      restartPolicy: Always
# affinity.podAffinity第一个表示部署apache时会创建3个pod,并且这3个pod要满足强制亲和性,要调度在节点含有标签为app=myapp-apache的pod,并且,调度的节点要含有标签kubernetes.io/hostname。如上设置,app=myapp-apache的pod在kus-node2节点上,所以创建的3个pod全部调度在了k8s-node2节点上

反亲和示例

就是把podAffinity替换成podAntiAffinity

# 强制
~]# vim web-example.yaml 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-example
spec:
  selector:
    matchLabels:
      app: myapp-web
  replicas: 3
  revisionHistoryLimit: 10
  strategy:                  
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: myapp-web
    spec:                         
      nodeName: vm-0-142-centos   
      nodeSelector:               
        disktype: ssd 
      affinity:                   
        podAntiAffinity:              # pod的反亲和设置
          requiredDuringSchedulingIgnoredDuringExecution:   #
          - labelSelector:      
              matchExpressions:  
              - key: app
                operator: In
                values:
                - myapp-apache
            topologyKey: kubernetes.io/hostname    
      containers:
      - name: nginx
        image: 172.17.0.98:5000/myos:httpd
        ports:
        - protocol: TCP
          containerPort: 80
      restartPolicy: Always
# 非强制
~]# vim web-example.yaml 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-example
spec:
  selector:
    matchLabels:
      app: myapp-web
  replicas: 3
  revisionHistoryLimit: 10
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: myapp-web
    spec:
      affinity:                                 
        podAntiAffinity:            # pod的反亲和设置
          preferredDuringSchedulingIgnoredDuringExecution:  
          - weight: 1                
            podAffinityTerm:        
              labelSelector:         
                matchExpressions:   
                - key: app
                  operator: In
                  values:
                  - myapp-apache
              topologyKey: kubernetes.io/hostname  
      containers:       
      - name: nginx
        image: 172.17.0.98:5000/myos:httpd
        ports:
        - protocol: TCP
          containerPort: 80
      restartPolicy: Always

node节点亲和示例

## 反亲和性
~]# vim web-example.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-example
spec:
  selector:
    matchLabels:
      app: myapp-web
  replicas: 3
  revisionHistoryLimit: 10
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: myapp-web
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:  # 反亲和性,表示更倾向于部署在指定节点上,但不是必须的
          - weight: 1
            preference:
              matchExpressions:
              - key: cpu
                operator: In
                values:
                - high
      containers:
      - name: nginx
        image: 172.17.0.98:5000/myos:httpd
        ports:
        - protocol: TCP
          containerPort: 80
      restartPolicy: Always
​
# 创建容器
~]# kubectl apply -f web-example.yaml 
deployment.apps/web-example configured
# 查看
]# kubectl get pods -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE              NOMINATED NODE   READINESS GATES
web-example-64d897b884-f8gz5   1/1     Running   0          4s    10.244.2.31   node2   <none>           <none>
web-example-64d897b884-fqkj8   1/1     Running   0          5s    10.244.1.20   node1   <none>           <none>
web-example-64d897b884-pdsln   1/1     Running   0          7s    10.244.2.30   node2   <none>           <none>
​
#因为非强制所以都可以分配
​
## 强制反亲和性
​
~]# vim web-example.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-example
spec:
  selector:
    matchLabels:
      app: myapp-php
  replicas: 1
  template:
    metadata:
      labels:
        app: myapp-php
    spec:
      affinity:
          requiredDuringSchedulingIgnoredDuringExecution:  #强制反亲和性
            nodeSelectorTerms:
            - matchExpressions:
              - key: mem
                operator: In
                values:
                - high
                - mid
      containers:
      - name: php-fpm
        image: 192.168.1.100:5000/myos:php-fpm
        ports:
        - protocol: TCP
          containerPort: 9000
      restartPolicy: Always
# 查看pod的启动此时就可以看到处于pengding状态了
]# kubectl get pods -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE              NOMINATED NODE   READINESS GATES
10.244.2.30   node2   <none>           <none>
web-example-77647fb5fd-85g4m   0/1     Pending   0          3s    <none>        <none>            <none>           <none>
# 是因为几台节点上都没有标签所以不知道跳哪天导致的报错,这里给node1设置一个标签,就可以跳到node2上
~]# kubectl label node vm-0-114-centos  mem=high
]# kubectl get pods -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE              NOMINATED NODE   READINESS GATES
web-example-77647fb5fd-85g4m   1/1     Running   0          14m   10.244.1.21   node1   <none>           <none>
web-example-77647fb5fd-9bc9f   1/1     Running   0          41s   10.244.1.22   node1   <none>           <none>
web-example-77647fb5fd-9vf4k   1/1     Running   0          39s   10.244.1.23   node1   <none>           <none>  
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kubernetes 原理剖析与实战应用】 开篇 | 如何深入掌握 Kubernetes? 云原生基石:初识 Kubernetes 01 | 前世今生:Kubernetes 是如何火起来的? 02 | 高屋建瓴:Kubernetes 的架构为什么是这样的? 03 | 集群搭建:手把手教你玩转 Kubernetes 集群搭建 04 | 核心定义:Kubernetes 是如何搞定“不可变基础设施”的? 「关注公众号【云世】,免费获取全系列课程内容」 05 | K8s Pod:最小调度单元的使用进阶及实践 Kubernetes 进阶:部署高可用的业务 06 | 无状态应用:剖析 Kubernetes 业务副本及水平扩展底层原理 07 | 有状态应用:Kubernetes 如何通过 StatefulSet 支持有状态应用? 08 | 配置管理:Kubernetes 管理业务配置方式有哪些? 09 | 存储类型:如何挑选合适的存储插件? 10 | 存储管理:怎样对业务数据进行持久化存储? 11 | K8s Service:轻松搞定服务发现和负载均衡 12 | Helm Charts:如何在生产环境中释放部署生产力? 守护神:业务的日志与监控 「关注公众号【云世】,免费获取全系列课程内容」 13 | 服务守护进程:如何在 Kubernetes 中运行 DaemonSet 守护进程? 14 | 日志采集:如何在 Kubernetes 中做日志收集与管理? 15 | Prometheus:Kubernetes 怎样实现自动化服务监控告警? 16 | 迎战流量峰值:Kubernetes 怎样控制业务的资源水位? 17 | 案例实战:教你快速搭建 Kubernetes 监控平台 安全无忧:集群的安全性与稳定性 18 | 权限分析:Kubernetes 集群权限管理那些事儿 19 | 资源限制:如何保障你的 Kubernetes 集群资源不会被打爆 20 | 资源优化:Kubernetes 中有 GC(垃圾回收)吗? 21 | 优先级调度:你必须掌握的 Pod 抢占式资源调度 22 | 安全机制:Kubernetes 如何保障集群安全? 23 | 最后的防线:怎样对 Kubernetes 集群进行灾备和恢复? 「关注公众号【云世】,免费获取全系列课程内容」 加餐:问题答疑和优秀留言展示 知其所以然:底层核心原理及可扩展性 24 | 调度引擎:Kubernetes 如何高效调度 Pod? 25 | 稳定基石:带你剖析容器运行时以及 CRI 原理 26 | 网络插件:Kubernetes 搞定网络原来可以如此简单? 27 | K8s CRD:如何根据需求自定义你的 API? 28 | 面向 K8s 编程:如何通过 Operator 扩展 Kubernetes API? 特别放送 「关注公众号【云世】,免费获取全系列课程内容」 29 | Kubernetes 中也有定时任务吗? 30 | Kubectl 命令行工具使用秘笈 结束语 结束语 | Cloud Native is Eating the World:时代在召唤云原生 「关注公众号【云世】,免费获取全系列课程内容」

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值