k8s基本介绍及核心技术简介(2)

3.6 Secret

3.6.1 Secret 存在意义

Secret 解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec 中。

Secret 可以以 Volume 或者环境变量的方式使用。

3.6.2 Secret 有三种类型

  • Service Account:用来访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod 的/run/secrets/kubernetes.io/serviceaccount 目录中

  • Opaque: base64 编码格式的 Secret,用来存储密码、密钥等

  • kubernetes.io/dockerconfigjson:用来存储私有 docker registry 的认证信息

3.6.3 Service Account

Service Account 用来访问 Kubernetes API,甶 Kubernetes 自动创建,并且会自动挂载到Pod 的/run/secrets/kubernetes.io/serviceaccount 目录中:

$ kubectl run nginx --image nginx
deployment "nginx" created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-3137573019-md1u2 1/1 Running 0 13s
$ kubectl exec nginx-3137573019-md1u2 ls
/run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token

3.6.4 Opaque Secret

1、创建:Opaque 类型的数据是一个 map 类型,要求 value 是 base64 编码格式:

$ echo -n "admin" | base64
YWRtaW4=
$ echo -n "1f2d1e2e67df" | base64
MWYyZDFlMmU2N2Rm:

2、secrets.yml

apiVersion: v1
kind: Secret
metadata:
	name: mysecret
type: Opaque
data:
	password: MWYyZDFlMmU2N2Rm
	username: YWRtaW4=

3、使用:将 Secret 挂载到 Volume 中

apiVersion: v1
kind: Pod
metadata:
	labels:
		name: seret-test
spec:
	volumes:
	- name: secrets
	secret:
		secretName: mysecret
	containers:
	-image: hub.atguigu.com/library/myapp:v1
		name: db
		volumeMounts:
		- name: secrets
			mountPath:"
readOnly: true

4、将 Secret 导出到环境变量中

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
	name: pod-deployment
spec:
	replicas: 2
	template:
		metadata:
			labels:
				app: pod-deployment
	spec:
		containers:
		- name: pod-1
			image:hub.atguigu.com/library/myapp:v1
			ports:
			-containerPort: 80
			env:
			-name: TEST_USER
				valueFrom:
					secretKeyRef:
						name: mysecret
key: username

3.6.5 kubernetes.io/dockerconfigjson

使用 Kuberctl 创建 docker registry 认证的 secret

$ kubectl create secret docker-registry myregistrykey --docker-
server=DOCKER_REGISTRY_SERVER -- docker-username=DOCKER_USER --docker-
password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL secret "myregistrykey"
created.

在创建 Pod 的时候,通过 imagePullSecrets 来引用刚创建的’myregistrykey

apiVersion: v1
kind: Pod
metadata:
	name: foo
spec:
	containers:
		- name: foo
  			image: roc/awangyang:v1
  	imagePullSecrets:
  		-name: myregistrykey

3.7 configMap

3.7.1 概述

ConfigMap 功能在 Kubernetes1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配 置信息。

ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制大对象。

3.7.2 创建

1、使用目录创建

$ ls docs/user-guide/configmap/kubectl/
game.properties
ui.properties
$ cat docs/user-guide/configmap/kubectl/game.properties
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
$ cat docs/user-guide/configmap/kubectl/ui.properties
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
$ kubectl create configmap game-config --from-file=docs/user-
guide/configmap/kubectl

-from-file 指定在目录下的所有文件都会被用在 ConfigMap 里面创建一个键值对,键的名字就是文件名,值就 是文件的内容。

2、使用文件创建

只要指定为一个文件就可以从单个文件中创建 ConfigMap

$ kubectl create configmap game-config-2
--from-file=docs/user- guide/configmap/kubectl/game.properties

$ kubectl get configmaps game-config-2 -o yaml

-from-file 这个参数可以使用多次,你可以使用两次分別指定上个实例中的那两个配置文件,效果就跟指定整个 目录是一样的。

3、使用字面值创建
使用文字值创建,利用-from-literal 参数传递配置信息,该参数可以使用多次。

$ kubectl create configmap special-config --from-literal=special.how=very --
from-literal=special.type=charm

$ kubectl get configmaps special-config -o yaml

3.7.3 Pod中使用

1、使用 ConfigMap 来替代环境变量

apiVersion: v1
kind: ConfigMap
metadata:
	name: special-config
	namespace: default
data:
	special.how: very
	special.type: charm


apiVersion: v1
kind: ConfigMap
metadata:
	name: env-config
namespace: default
data:
	log_level: INFO


apiVersion: v1
kind: Pod
metadata:
	name: dapi-test-pod
spec:
	containers:
		- name: test-container
			image: hub.atguigu.com/library/myapp:v1
			command: [ "/bin/sh", "-c", "env"]
		env:
			-name: SPECIAL_LEVEL_KEY
				valueFrom:
					configMapKeyRef:
						name: special-config
						key: special.how
			-name: SPECIAL_TYPE_KEY
				valueFrom:
					configMapKeyRef:
						name: special-config
						key: special.type
			envFrom:
				-configMapRef:
					name: env-config
restartPolicy: Never

2、使用 ConfigMap 设置命令行参数

apiVersion: vl
kind: ConfigMap
metadata:
	name: special-config
	namespace: default
data:
	special.how: very
	special.type: charm


apiVersion: v1
kind: Pod
metadata:
	name: dapi-test-pod
spec.
	containers:
		name: test-container
			image: hub.guahao.com/library/myapp.v1
			command: [ */bin/sh*, *-c", "echo $(SPECIAL LEVEL_KEY) $(SPECIAL_TYPE KEY)"]
		env:
			-name:SPECIALLLEVEL.KEY
				valueFrom:
					configMapKeyRef:
						name: special-config
						key: special.how
			-name: SPECIAL_TYPE_KEY
				valueFrom:
					configMapKeyRef:
						name: special-config
						key: special.type
	restartPolicy: Never

3、通过数据卷插件使用 ConfigMap

apiVersion: vl
kind: ConfigMap
metadata:
	name: special-config
	namespace: default
data:
	special.how: very
	special.type: charm

在数据卷里面使用这个 ConfigMap,有不同的选项。最基本的就是将文件填入数据卷,在这个文件中,键就是文 件名,键值就是文件内容

apiVersion: v1
kind: Pod
metadata:
	name: dapi-test-pod
spec.
	containers:
		- name: test-container
			image: hub.guahao.com/library/myapp.v1
			command: [ */bin/sh*, *-c", "cat /etc/config/special.how"]
			volumeMounts:
				- name:config-volume
					mountPath:/etc/config
	volums:
		- name: config-volume
			configMap:
				name: special-config
	restartPolicy: Never

4、ConfigMap 更新后滚动更新 Pod
更新 ConfigMap 目前并不会触发相关 Pod 的滚动更新,可以通过修改 pod annotations 的方式强制触发滚动更新。

$ kubectl patch deployment my-nginx --patch '{"spec": {"template": {"metadata":
{"annotations": {"version/config": "20190411" }}}}}'

这个例子里我们在.spec.template.metadata.annotations 中添加 version/config ,每次通过修改 version/config 来触发滚动更新更新 ConfigMap 后:

  • 使用该 ConfigMap 挂载的 Env 不会同步更新
  • 使用该 ConfigMap 挂载的 Volume 中的数据需要一段时间(实测大概 10 秒)才能同步更新

3.8 Namespace

3.8.1 概述

Namespace 在很多情况下用于实现多用户的资源隔离,通过将集群内部的资源对象分配到不同的 Namespace 中, 形成逻辑上的分组,便于不同的分组在共享使用整个集群的资源同时还能被分别管理。

Kubernetes 集群在启动后,会创建一个名为"default"的 Namespace,如果不特别指明 Namespace,则用户创建的 Pod,RC,Service 都将 被系统 创建到这个默认的名为 default 的 Namespace 中。

3.8.1 创建

apiVersion: v1 kind: Namespace metadata:
name: development

---------------------
apiVersion: v1 kind: Pod metadata:
name: busybox namespace: development
spec:
containers:
- image: busybox command:
- sleep
- -"3600"
name: busybox

3.8.2 查看

kubectl get pods --namespace=development

3.9 Service

3.9.1 概述

Service 是 Kubernetes 最核心概念,通过创建 Service,可以为一组具有相同功能的容器应用提供一个统一的入口地 址,并且将请求负载分发到后端的各个容器应用上。

3.9.2 定义

1、yaml 格式的 Service 定义文件

apiVersion: v1 kind: Service matadata:
name: string namespace: string labels:
-name: string annotations:
-name: string spec:
selector: [] type: string clusterIP: string
sessionAffinity: string ports:
-name: string protocol: string port: int targetPort: int nodePort: int
status: loadBalancer:
ingress:
ip: string hostname: string

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.9.3 基本用法

1、一般来说,对外提供服务的应用程序需要通过某种机制来实现,对于容器应用最简便的方式就是通过 TCP/IP 机制及 监听 IP 和端口号来实现。创建一个基本功能的 Service:

apiVersion: v1
kind: ReplicationController metadata:
name: mywebapp spec:
replicas: 2 template:
metadata:
name: mywebapp labels:
app: mywebapp spec:
containers:
-name: mywebapp image: tomcat ports:
-containerPort: 8080

2、也可以通过 kubectl get pods -l app=mywebapp -o yaml | grep podIP 来获取Pod 的 IP 地址和端口号来访问 Tomcat 服务,但是直接通过 Pod 的 IP 地址和端口访问应用服务是不可靠的,因为当 Pod 所在的 Node 发生故障时, Pod 将被 kubernetes 重新调度到另一台 Node,Pod 的地址会发生改变。我们可以通过配置文件来定义 Service,再通过kubectl create 来创建,这样可以通过 Service 地址来访问后端的 Pod。

apiVersion: v1 kind: Service metadata:
name: mywebAppService spec:
ports:
- port: 8081
targetPort: 8080 selector:
app: mywebapp

3.9.4 多端口Service

有时一个容器应用也可能需要提供多个端口的服务,那么在 Service 的定义中也可以相应地设置为将多个端口对应 到多个应用服务。

apiVersion: v1 kind: Service metadata:
name: mywebAppService spec:
ports:
- port: 8080
targetPort: 8080 name: web
- port: 8005
targetPort: 8005 name: management
selector:
app: mywebapp

3.9.5 外部服务 Service

在某些特殊环境中,应用系统需要将一个外部数据库作为后端服务进行连接,或将另一个集群或 Namespace 中的 服务作为服务的后端,这时可以通过创建一个无 Label Selector的 Service 来实现。

apiVersion: v1 kind: Service metadata:
name: my-service spec:
ports:
- protocol: TCP port: 80
targetPort: 80

--------------------------
apiVersion: v1
kind: Endpoints metadata:
name: my-service subsets:
- addresses:
- IP: 10.254.74.3
ports:
- port: 8080

3.10 探针

3.10.1 类型

k8s 中存在两种类型的探针:liveness probe 和 readiness probe。

1、liveness probe (存活探针)

用于判断容器是否存活,即 Pod 是否为 running 状态,如果 LivenessProbe 探针探测到容器不健康,则 kubelet 将 kill 掉容器,并根据容器的重启策略是否重启。如果一个容器不包含 LivenessProbe 探针,则 Kubelet 认为容器的 LivenessProbe 探针的返回值永远成功。

有时应用程序可能因为某些原因(后端服务故障等)导致暂时无法对外提供服务,但应用软件没有终止,导致 K8S 无法隔离有故障的 pod,调用者可能会访问到有故障的 pod,导致业务不稳定。K8S 提供 livenessProbe 来检测应用程序是否正常运行,并且对相应状况进行相应的补救措施。

2、readiness probe (就绪探针)
用于判断容器是否启动完成,即容器的 Ready 是否为 True,可以接收请求,如果ReadinessProbe 探测失败,则容器的 Ready 将为 False,控制器将此 Pod 的 Endpoint 从对应的 service 的 Endpoint 列表中移除,从此不再将任何请求调度此 Pod 上,直到下次探测成功。通过使用 Readiness 探针,Kubernetes 能够等待应用程序完全启动,然后才允许服务将流量发送到新副本。

比如使用 tomcat 的应用程序来说,并不是简单地说 tomcat 启动成功就可以对外提供服务
的,还需要等待 spring 容器初始化,数据库连接没连上等等。对于 spring boot 应用,默
认的 actuator 带有/health 接口,可以用来进行启动成功的判断。

在这里插入图片描述

3.10.2 探针探测方法

每类探针都支持三种探测方法:

  • exec:通过执行命令来检查服务是否正常,针对复杂检测或无 HTTP 接口的服务,命
    令返回值为 0 则表示容器健康。
  • httpGet:通过发送 http 请求检查服务是否正常,返回 200-399 状态码则表明容器健
    康。
  • tcpSocket:通过容器的 IP 和 Port 执行 TCP 检查,如果能够建立 TCP 连接,则表明
    容器健康。

3.10.3 探针探测的结果

1、Success:Container 通过了检查。

2、Failure:Container 未通过检查。

3、 Unknown:未能执行检查,因此不采取任何措施。

3.10.4 Pod 重启策略

1、Always: 总是重启

2、OnFailure: 如果失败就重启

3、Never: 永远不重启

3.10.5 示例

apiVersion: v1
kind: Pod
metadata:
	name: goproxy
	labels:
		app: goproxy
spec:
	containers:
	- name: goproxy
		image: k8s.gcr.io/goproxy:0.1
		ports:
		- containerPort: 8080
		readinessProbe:
			tcpSocket:
				port: 8080
			initialDelaySeconds: 5
			periodSeconds: 10
		livenessProbe:
			tcpSocket:
				port: 8080
			initialDelaySeconds: 15
			periodSeconds: 20

探针(Probe)有许多可选字段,可以用来更加精确的控制 Liveness 和 Readiness 两种探针的行为。这些参数包括:

  • initialDelaySeconds:容器启动后第一次执行探测是需要等待多少秒
  • periodSeconds:执行探测的频率。默认是 10 秒,最小 1 秒
  • timeoutSeconds:探测超时时间。默认 1 秒,最小 1 秒
  • successThreshold:探测失败后,最少连续探测成功多少次才被认定为成功,默认是 1;对于 liveness 必须是 1,最小值是 1
  • failureThreshold:探测成功后,最少连续探测失败多少次才被认定为失败,默认是 3;
    最小值是 1。

3.11 调度器

3.11.1 概述

一个容器平台的主要功能就是为容器分配运行时所需要的计算,存储和网络资源。容器调度系统负责选择在最合适的主机上启动容器,并且将它们关联起来。它必须能够自动的处理容器故障并且能够在更多的主机上自动启动更多的容器来应对更多的应用访问。

目前三大主流的容器平台 Swarm, Mesos 和 Kubernetes 具有不同的容器调度系统。

  • 1、Swarm 的特点是直接调度 Docker 容器,并且提供和标准 Docker API 一致的 API。

  • 2、Mesos 针对不同的运行框架采用相对独立的调度系统,其中 Marathon 框架提供了Docker容器的原生支持。

  • 3、Kubernetes 则采用了 Pod 和 Label 这样的概念把容器组合成一个个的互相存在依赖关系的逻辑单元。相关容器被组合成 Pod 后被共同部署和调度,形成服务(Service)。

这个是 Kubernetes 和 Swarm,Mesos 的主要区别。

相对来说,Kubernetes 采用这样的方式简化了集群范围内相关容器被共同调度管理的复杂性。换一种角度来看,Kubernetes 采用这种方式能够相对容易的支持更强大,更复杂的容器调度算法。

3.11.2 k8s 调度工作方式

Kubernetes 调度器作为集群的大脑,在如何提高集群的资源利用率、保证集群中服务的稳定运行中也会变得越来越重要。

Kubernetes 的资源分为两种属性。

1、可压缩资源(例如 CPU 循环,Disk I/O 带宽)都是可以被限制和被回收的,对于一个Pod 来说可以降低这些资源的使用量而不去杀掉 Pod。

2、不可压缩资源(例如内存、硬盘空间)一般来说不杀掉 Pod 就没法回收。未来
Kubernetes 会加入更多资源,如网络带宽,存储 IOPS 的支持。

3.11.3 k8s 调度器

1、kube-scheduler

kube-scheduler是 kubernetes 系统的核心组件之一,主要负责整个集群资源的调度功能,根据特定的调度算法和策略,将 Pod 调度到最优的工作节点上面去,从而更加合理、更加充分的利用集群的资源,这也是选择使用kubernetes 一个非常重要的理由。

2、调度流程

默认情况下,kube-scheduler 提供的默认调度器能够满足我们绝大多数的要求,之前接触的示例也基本上用的默认的策略,都可以保证我们的 Pod 可以被分配到资源充足的节点上运行。但是在实际的线上项目中,可能我们自己会比 kubernetes 更加了解我们自己的应用,比如我们希望一个 Pod 只能运行在特定的几个节点上,或者这几个节点只能用来运行特定类型的应用,这就需要我们的调度器能够可控。

kube-scheduler 是 kubernetes 的调度器,它的主要作用就是根据特定的调度算法和调度策略将 Pod 调度到合适的 Node 节点上去,是一个独立的二进制程序,启动之后会一直监听 API Server,获取到 PodSpec.NodeName 为空的 Pod,对每个 Pod 都会创建一个binding。

在这里插入图片描述

调度主要分为以下几个部分:

首先是预选过程,过滤掉不满足条件的节点,这个过程称为 Predicates;

然后是优选过程,对通过的节点按照优先级排序,称之为 Priorities;

最后从中选择优先级最高的节点,如果中间任何一步骤有错误,就直接返回错误。

Predicates 阶段首先遍历全部节点,过滤掉不满足条件的节点,属于强制性规则,这一阶段输出的所有满足要求的 Node 将被记录并作为第二阶段的输入,如果所有的节点都不满足条件,那么 Pod 将会一直处于 Pending 状态,直到有节点满足条件,在这期间调度器会不断的重试。

所以我们在部署应用的时候,如果发现有 Pod 一直处于 Pending 状态,那么就是没有满足调度条件的节点,这个时候可以去检查下节点资源是否可用。Priorities 阶段即再次对节点进行筛选,如果有多个节点都满足条件的话,那么系统会按照节点的优先级(priorites)大小对节点进行排序,最后选择优先级最高的节点来部署 Pod应用。

在这里插入图片描述
在这里插入图片描述

3.12 集群安全机制 RBAC

3.12.1 基本概念

RBAC(Role-Based Access Control,基于角色的访问控制)在 k8s v1.5 中引入,在 v1.6 版本时升级为 Beta 版本,并成为 kubeadm 安装方式下的默认选项,相对于其他访问控制方式,新的 RBAC 具有如下优势:

1、对集群中的资源和非资源权限均有完整的覆盖;

2、整个 RBAC 完全由几个 API 对象完成,同其他 API 对象一样,可以用 kubectl 或 API
进行操作;

3、可以在运行时进行调整,无需重启 API Server。

要使用 RBAC 授权模式,需要在 API Server 的启动参数中加上–authorization-mode=RBAC。

3.12.2 原理和用法

1、RBAC 的 的 API 资源对象说明

RBAC 引入了 4 个新的顶级资源对象:Role、ClusterRole、RoleBinding、ClusterRoleBinding。同其他 API 资源对象一样,用户可以使用 kubectl 或者 API 调用等方式操作这些资源对象。

(1)角色 (Role)

一个角色就是一组权限的集合,这里的权限都是许可形式的,不存在拒绝的规则。在一个命名空间中,可以用角色来定义一个角色,如果是集群级别的,就需要使用 ClusterRole了。角色只能对命名空间内的资源进行授权。

(2)集群角色 (ClusterRole)

集群角色除了具有和角色一致的命名空间内资源的管理能力,因其集群级别的范围,还可以用于以下特殊元素的授权。集群范围的资源,例如 Node;非资源型的路径,例如/healthz;包含全部命名空间的资源,例如 pods。

(3)角色绑定 (RoleBinding) 和集群角色绑定 (ClusterRoleBinding)

角色绑定或集群角色绑定用来把一个角色绑定到一个目标上,绑定目标可以是 User、Group 或者 Service Account。使用 RoleBinding 为某个命名空间授权,ClusterRoleBinding 为集群范围内授权。RoleBinding 可以引用 Role 进行授权。

2、对资源的引用方式

多数资源可以用其名称的字符串来表达,也就是 Endpoint 中的 URL 相对路径,例如 pods。然后,某些 Kubernetes API 包含下级资源,例如 Pod 的日志(logs)。Pod 日志的 Endpoint是 GET /api/v1/namespaces/{namespaces}/pods/{name}/log。

Pod 是一个命名空间内的资源,log 就是一个下级资源。要在一个 RBAC 角色中体现,则需要用斜线/来分割资源和下级资源。若想授权让某个主体同时能够读取 Pod 和 Pod log,则可以配置 resources 为一个数组。

资源还可以通过名字(ResourceName)进行引用。在指定 ResourceName 后,使用 get、delete、update、patch 动词的请求,就会被限制在这个资源实例范围内。

3、常见的角色(Role) 示例

(1)允许读取核心API 组中Pod 的资源

rules:
- apiGroups: [""]
	resources: ["pods"]
	verbs: ["get", "list", "watch"]

(2)允许读写 “extensions” 和 “apps” 两个 API 组中的deployment 资源

rules:
- apiGroups: ["extensions", "apps"]
	resources: ["deployments"]
	verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

(3) 允许读写pods 及读写 jobs

rules:
- apiGroups: [""]
	resources: ["pods"]
	verbs: ["get", "list", "watch"]
- apiGroups: ["batch", "extensions"]
	resources: ["jobs"]
	verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

(4) 允许读取一个名为my-config 的 的 ConfigMap( 必须绑定到一个RoleBinding 来限制到
一个namespace 下的 ConfigMap)

rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["my-config"]
  verbs: ["get"]

(5) 读取核心组的node 资源(Node 属于集群级别的资源,必须放在ClusterRole 中,并使用ClusterRoleBinding 进行绑定)

rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list", "watch"]

(6) 允许对非资源端点/healthz 及其所有子路径进行GET/POST 操作(必须使用ClusterRole 和 和 ClusterRoleBinding)

rules:
- nonResourceURLs: ["/healthz", "/healthz/*"]
  verbs: ["get", "post"]

4、常用的角色绑定

  • 用户名 Alice@example.com

    subjects:
    - kind: User
      name: "Alice@example.com"
      apiGroup: rbac.authorization.k8s.io
    
  • 组名 frontend-admins

    subjects:
    - kind: Group
      name: "frontend-admins"
      apiGroup: rbac.authorization.k8s.io
    
  • kube-system 命名空间中的默认Service Account

    subjects:
    - kind: ServiceAccount
      name: default
      namespace: kube-system
    
  • qa 命名空间中的所有 e Service Account

    subjects:
    - kind: Group
      name: system:serviceaccounts:qa
      apiGroup: rbac.authorization.k8s.io
    
  • 所有 e Service Account

    subjects:
    - kind: Group
      name: system:serviceaccounts
      apiGroup: rbac.authorization.k8s.io
    
  • 所有认证用户

    subjects:
    - kind: Group
      name: system:authentication
      apiGroup: rbac.authorization.k8s.io
    
  • 所有未认证用户

    subjects:
    - kind: Group
      name: system:unauthentication
      apiGroup: rbac.authorization.k8s.io
    
  • 全部用户

    subjects:
    - kind: Group
      name: system:authentication
      apiGroup: rbac.authorization.k8s.io
    - kind: Group
      name: system:unauthentication
      apiGroup: rbac.authorization.k8s.io
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值