8.我在B站学云原生之Kubernetes入门实践资源清单与Namespace名称空间介绍
全栈工程师修炼指南
2022年05月15日 17:34
收录于文集
共39篇

B站关注「网页链接​网页链接​

设为「特别关注」,每天带你玩转网络安全运维、应用开发、物联网IOT学习!

帅哥(靓仔)、美女,点个关注后续不迷路!

本章目录


  • 0x00 前言简述

  • 0x01 资源清单

    • apiVersion - 必须

    • kind - 必须

    • metadata - 必须

    • Annotation - 注解

    • spec - 必须

    • (1) 对象字段

  • 0x02 NameSpace - 名称空间

    • namespace 创建查看

    • namespace 配额


作者: WeiyiGeek

原文地址:https://blog.weiyigeek.top/2020/4-27-521.html


0x00 前言简述

前面简单的介绍了Kubernetes基础知识以及单节点和高可以用集群的搭建, 本章将从实操来介绍Kubernetes概念和术语以及控制器,便于各位读者进行学习;

0x01 资源清单

Q: 什么是资源?

答: K8s中所有的内容都抽象为资源在资源实例化()之后叫做对象;

Q: 什么是Kubernetes对象?

答:Kubernetes对象指的是Kubernetes系统的持久化实体,所有这些对象合起来代表了你集群的实际情况。创建一个k8s对象就是告诉Kubernetes,您需要的集群中的工作负载是什么(集群的目标状态), 因为一个Kubernetes对象代表着用户的一个意图(a record of intent),一旦您创建了一个Kubernetes对象,Kubernetes将持续工作以尽量实现此用户的意图。

常规的应用里我们把应用程序的数据存储在数据库中,Kubernetes将其数据以Kubernetes对象的形式通过  存储在 etcd 中; Kubernetes对象数据描述的信息:

  • 集群中运行了哪些容器化应用程序(以及在哪个节点上运行)

  • 集群中对应用程序可用的资源

  • 应用程序相关的策略定义,例如,重启策略、升级策略、容错策略

  • 其他Kubernetes管理应用程序时所需要的信息

如何进行Kubernetes对象的CURD呢?

答:自带的kubectl命令或者一些管理k8s的图形化界面工具比如Kuboard或者Kubedash;

PS: kubectl、kuboard 最终都通过调用 kubernetes API 来实现对 Kubernetes 对象的操作。您也可以直接在自己的程序中调用 Kubernetes API,此时您可能要有用到 Client Libraries

基础补充:

  • 1.名称空间级别资源 (Namespace Level): 仅在此名称空间下生效k8s的系统组件是默认放在kube-system名称空间下的,而kubectl get pod等价于kubectl get pod -n default,因此查看不到k8s的系统组件。

    • 工作负载型资源(workload):Pod、ReplicaSet(调度器控制器通过标签保证Pod的副本数以及创建Pod)、Deployment、StatefulSet(有状态服务的控制器)、DaemonSet(可以在每个节点都运行一个Pod的组件)、Job、CronJob(ReplicationController在v1.11版本被废弃)

    • 服务发现及负载均衡型资源(Service Discovery LoadBalance):Service、Ingress、...

    • 配置与存储型资源:Volume(存储卷)、CSI(容器存储接口可以扩展各种各样的第三方存储卷)

    • 特殊类型的存储卷:ConfigMap(当配置中心来使用的资源类型可以达到热更新)、Secret(保存敏感数据)、DownwardAPI(把外部环境中的信息输出给容器)

  • 2.集群级资源:不管在任何名称空间下定义,在其他的名称空间下都能看得到,在定义的时候无需指定名称空间

    • Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding

  • 3.元数据型资源:提供一个指标,不像是名称空间类型又不像集群级别,本质上更像是在两者之间,但是它有自己的特点,所以更应该作为一个单独的分类,例如HPA【通过cpu的利用率进行平滑扩展】就是一个很明显的元数据类型,通过指标进行操作。

    • HPA、PodTemplate、LimitRange(根据指标进行相对应的操作)

Q: 什么是资源清单?

答: 您可以将其理解为剧本,里面规定了每一步如何操作,Kubernets只需要按照要求去做即可; 在 K8s 中一般使用Yaml格式或者Json格式的文件来创建符合我们预期期望的Pod,该yaml文件我们称为资源清单;

Q: 如何编辑资源清单?

答: 在编写清单时候必须对Kubernetes定义Pod以及控制器(Controller)的常用字段有一定的了解, 如果忘记对象的资源清单字段可以通过explain命令查看相对应的控制清单编写字段;

资源清单编写帮助: 获取资源的apiVersion的版本信息(以pod为例),该命令同时输出属性设置帮助文档

代码块
Shell
自动换行
复制代码
kubectl explain pod
	# KIND:     Pod
	# VERSION:  v1
	# DESCRIPTION:
	# 		Pod is a collection of containers that can run on a host. This resource is
	# 		created by clients and scheduled onto hosts.

	# FIELDS: 
	# 	apiVersion   <string>
	# 		APIVersion defines the versioned schema of this representation of an
	# 		object. Servers should convert recognized schemas to the latest internal
	# 		value, and may reject unrecognized values. More info:
	# 		https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

	# 	kind <string>
	# 		Kind is a string value representing the REST resource this object
	# 		represents. Servers may infer this from the endpoint the client submits
	# 		requests to. Cannot be updated. In CamelCase. More info:
	# 		https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

	# 	metadata     <Object>
	# 		Standard object's metadata. More info:
	# 		https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

	# 	spec <Object>
	# 		Specification of the desired behavior of the pod. More info:
	# 		https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

	# 	status       <Object>
	# 		Most recently observed status of the pod. This data may not be up to date.
	# 		Populated by the system. Read-only. More info:
	# 		https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

kubectl explain pod.apiVersion #查看指定属性说明
	# KIND:     Pod
	# VERSION:  v1

	# FIELD:    apiVersion <string>

	# DESCRIPTION:
	# 		APIVersion defines the versioned schema of this representation of an
	# 		object. Servers should convert recognized schemas to the latest internal
	# 		value, and may reject unrecognized values. More info:
	# 		https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
复制成功

查看k8s中有对象可用那些资源的API版本(1.19.6)

代码块
Shell
自动换行
复制代码
$ kubectl api-versions

#常用
apiVersion: apps/v1
# API版本
apps/v1
v1
admissionregistration.k8s.io/v1
admissionregistration.k8s.io/v1beta1
apiextensions.k8s.io/v1
apiextensions.k8s.io/v1beta1
apiregistration.k8s.io/v1
apiregistration.k8s.io/v1beta1
authentication.k8s.io/v1
authentication.k8s.io/v1beta1
authorization.k8s.io/v1
authorization.k8s.io/v1beta1
autoscaling/v1
autoscaling/v2beta1
autoscaling/v2beta2
batch/v1
batch/v1beta1
certificates.k8s.io/v1beta1
coordination.k8s.io/v1
coordination.k8s.io/v1beta1
crd.projectcalico.org/v1
discovery.k8s.io/v1beta1
events.k8s.io/v1beta1
extensions/v1beta1
networking.k8s.io/v1
networking.k8s.io/v1beta1
node.k8s.io/v1beta1
policy/v1beta1
rbac.authorization.k8s.io/v1
rbac.authorization.k8s.io/v1beta1
scheduling.k8s.io/v1
scheduling.k8s.io/v1beta1
storage.k8s.io/v1
storage.k8s.io/v1beta1
复制成功

(1) 对象字段

描述: 每个K8s控制器对象都包含了两个重要的字段,即  和  字段, Kubernetes通过对应的控制器,不断地使实际状态趋向于您期望的目标状态。

  • spec 必须由您来提供,描述了您对该对象所期望的 目标状态

  • status 只能由 Kubernetes 系统来修改,描述了该对象在 Kubernetes 系统中的 实际状态

例如,一个 Kubernetes Deployment 对象可以代表一个应用程序在集群中的运行状态。当您创建 Deployment 对象时,您可以通过 Deployment 的 spec 字段指定需要运行应用程序副本数(replicas假设为3)。Kubernetes 从 Deployment 的 spec 中读取这些信息,并为您创建指定容器化应用程序的 3 个副本,再将实际的状态更新到 Deployment 的 status 字段。Kubernetes 系统将不断地比较 实际状态 staus 和 目标状态 spec 之间的差异,并根据差异做出对应的调整。 例如,如果任何一个副本运行失败了,Kubernetes 将启动一个新的副本,以替代失败的副本。

apiVersion - 必须

描述: 用来创建对象时所使用的Kubernetes API版本,可通过命令查询可用API版本;

代码块
Shell
自动换行
复制代码
kubectl api-versions
admissionregistration.k8s.io/v1
apiextensions.k8s.io/v1
apiregistration.k8s.io/v1
apps/v1
authentication.k8s.io/v1
authorization.k8s.io/v1
autoscaling/v1
autoscaling/v2
autoscaling/v2beta1
autoscaling/v2beta2
batch/v1
batch/v1beta1
certificates.k8s.io/v1
coordination.k8s.io/v1
crd.projectcalico.org/v1
discovery.k8s.io/v1
discovery.k8s.io/v1beta1
events.k8s.io/v1
events.k8s.io/v1beta1
flowcontrol.apiserver.k8s.io/v1beta1
flowcontrol.apiserver.k8s.io/v1beta2
networking.k8s.io/v1
node.k8s.io/v1
node.k8s.io/v1beta1
policy/v1
policy/v1beta1
rbac.authorization.k8s.io/v1
scheduling.k8s.io/v1
storage.k8s.io/v1
storage.k8s.io/v1beta1
v1
复制成功

kind - 必须

描述:被创建对象的类型常用的有

metadata - 必须

描述:用于唯一确定该对象的元数据,包括  和 ,如果 namespace 为空,则默认值为 default;

代码块
Shell
自动换行
复制代码
metadata:
  name: nginx-deployment   # 创建的资源对象名称
  namespace: nginx-app     # 设置指定的名称空间,默认为default
复制成功

Annotation - 注解

描述: 它是包含在元数据(metadata)对象中的,从Annotation字面意思理解其用途就是注解,或者有一些小伙伴看到过它,它类使用前面所提到的Label其也是采用Key/Value键值对进行表示;

代码块
Shell
自动换行
复制代码
metadata:
  annotation:
    key1: value1
    key2: value2
复制成功

Q: Annotation 与 Lables 不同之处?

答: Lable 具有更为严格的命令规则,主要用于定义资源对象的元数据(Metadata)并且被Label Seletor使用 Annotation 是用户任意定义的附加信息,便于外部工具查找;

Q: Annotation 记录的信息?

A: Build 信息、Release 信息、Docker Image 信息、Docker Registry地址 A: 开发环境信息、工具名称、版本号等 A: 团队开发信息、电话号码、负责人、以及网站等

spec - 必须

描述:您对该对象的期望状态但是需注意不同类型的 Kubernetes,其 spec 对象的格式不同(含有不同的内嵌字段),通过 API 手册 可以查看 Kubernetes 对象的字段和描述;

例如,假设您想了解 Pod 的 spec 定义,可以在 这里 找到,Deployment 的 spec 定义可以在 这里 找到;

资源清单常用的字段描述:

  • 1.必须存在的属性【创建资源清单的时候没有这些属性的存在它是不允许被执行的】

代码块
Shell
自动换行
复制代码
#参数名称 	字段类型 	说明
version 	String 	这里是指的是K8SAPI的版本,目前基本上是v1,可以用 `kubectl api-version` 命令查询
kind 	String 	    这里指的是yam文件定义的资源类型和角色,比如:Pod
metadata 	Object 	元数据对象,固定值就写metadata
metadata.name 	String 	元数据对象的名字,这里由我们编写,比如命名Pod的名字
metadata.namespace 	String 	元数据对象的命名空间,由我们自身定义,如果不定义的话则默认是default名称空间
Spec 	Object 	详细定义对象,固定值就写Spec
spec.containers[] 	List 	这里是Spec对象的容器列表定义,是个列表
spec.containers[].name 	String 	这里定义容器的名字
spec.containers[].image 	String 	这里定义要用到的镜像名称
复制成功

  • 2.主要属性【这些属性比较重要,如果不指定的话系统会自动补充默认值】

代码块
Shell
自动换行
复制代码
#参数名称 	字段类型 	说明
spec.containers[].name 	String 	这里定义容器的名字
spec.containers[].image 	String 	这里定义要用到的镜像名称
spec.containers[].imagePullPolicy 	String 	定义镜像拉取策略,有Always、Never、 IfNotPresent三个值可选(1)Always:意思是每次都尝试重新拉取镜像(2)Never:表示仅使用本地镜像(3)lfNotPresent:如果本地有镜像就使用本地镜像,没有就拉取在线镜像。上面三个值都没设置的话,默认是Always。
spec.containers[].command[] 	List 	指定容器启动命令,因为是数组可以指定多个,不指定则使用镜像打包时使用的启动命令。
spec.containers[].args[] 	List 	指定容器启动命令参数,因为是数组可以指定多个。
spec.containers[].workingDir 	String 	指定容器的工作目录,进入容器时默认所在的目录
spec.containers[].volumeMounts[] 	List 	指定容器内部的存储卷配置
spec.containers[].volumeMounts[].name 	String 	指定可以被容器挂载的存储卷的名称
spec.containers[].volumeMounts[].mountPath 	String 	指定可以被容器挂载的存储卷的路径
spec.containers[].volumeMounts[].readOnly 	String 	设置存储卷路经的读写模式,true或者false,默认为读写模式
spec.containers[].ports[] 	List 	指定容器需要用到的端口列表
spec.containers[].ports[].name 	String 	指定端口名称
spec.containers[].ports[].containerPort 	String 	指定容器需要监听的端口号
spec.containers[].ports[].hostPort 	String 	指定容器所在主机需要监听的端口号,默认跟上面containerPort相同,注意设置了hostPort同一台主机无法启动该容器的相同副本(因为主机的端口号不能相同,这样会冲突)
spec.containers[].ports[].protocol 	String 	指定端口协议,支持TCP和UDP,默认值为 TCP
spec.containers[].env[] 	List 	指定容器运行前需设置的环境变量列表
spec.containers[].env[].name 	String 	指定环境变量名称
spec.containers[].env[].value 	String 	指定环境变量值
spec.containers[].resources 	Object 	指定资源限制和资源请求的值(这里开始就是设置容器的资源上限)
spec.containers[].resources.limits 	Object 	指定设置容器运行时资源的运行上限
spec.containers[].resources.limits.cpu 	String 	指定CPU的限制,单位为core数,将用于docker run --cpu-shares参数这里前面文章 Pod资源限制有讲过)
spec.containers[].resources.limits.memory 	String 	指定MEM内存的限制,单位为MlB、GiB 
spec.containers[].resources.requests 	Object 	指定容器启动和调度时的限制设置
spec.containers[].resources.requests.cpu 	String 	CPU请求,单位为core数,容器启动时初始化可用数量
spec.containers[].resources.requests.memory 	String 	内存请求,单位为MIB、GiB,容器启动的初始化可用数量
复制成功

  • 3.额外的参数项

代码块
Shell
自动换行
复制代码
参数名称 	字段类型 	说明
spec.restartPolicy 	 String 	定义Pod的重启策略,可选值为Always、OnFailure、Never 默认值为Always。1.Always:Pod一旦终止运行,则无论容器是如何终止的,kubelet服务都将重启它。2.OnFailure:只有Pod以非零退出码终止时,kubelet才会重启该容器。如果容器正常结束(退出码为0),则kubelet将不会重启它。3.Never:Pod终止后,kubelet将退出码报告给Master,不会重启该Pod。
spec.nodeSelector 	Object 	定义Node的Label过滤标签,以key:value格式指定,选择node节点去运行
spec.imagePullSecrets 	Object 	定义pull镜像时使用secret名称,以name:secretkey格式指定
spec.hostNetwork 	Boolean 	定义是否使用主机网络模式,默认值为false。设置true表示使用宿主机网络,不使用docker网桥,同时设置了true将无法在同一台宿主机上启动第二个副本。
复制成功

资源清单格式:

代码块
Shell
自动换行
复制代码
apiVersion: group/apiversion #定义对象的版本模式; 如果没有给定group名称,那么默认为core可以使用kubectlapi-versions命令获取当前k8s版本上所有的apiversion版本信息(每个版本可能不同)
kind:     #资源类别: 该对象表示的REST资源
metadata: #资源元数据
 name:    #资源自定义名称
 namespace: #资源所属的名称空间
 lables:    #资源
 annotations: #注解:主要目的是方便用户阅读查找
spec:   #期望的状态(disired state)
status: #当前状态,本字段由Kubernetes自身维护,用户不能去定义
复制成功

示例:以下是创建Pod所定义清单文件内容

代码块
Shell
自动换行
复制代码
apiVersion: v1 
kind: Pod 
metadata:
 name: pod-demo 
 namespace: default 
 1abels:
  app:myapp 
spec:
 containers:
 - name: myapp-1 
   image: harbor.weiyigeek.top/1ibrary/myapp:v1    # 支持多个容器运行在同一个Pod之中
 - name: busybox-1 
   image: busybox:latest 
   command:
   - "/bin/sh"
   - "-c"
   - "sleep 3600"
复制成功

PS: 创建pod时如发生错误可以通过命令查看pod相关信息(注意如果有名称空间则需要加上); PS: 一个Pod内支持多个容器运行所以在定义资源清单的时候,可以在数组中指定多个运行的容器及其镜像;

0x02 NameSpace - 名称空间

描述: Namespace即名称空间,您可能在或者中听说它, 它也是K8s系统中非常重要的一个概念;

Tips : 由于创建多个集群会导致集群资源使用碎片化以及更多的维护成本,可以通过Kubernetes的NameSpace对不同工作组的需求隔离。

Q: namespace 有何作用?

A: 它可以通过不同的资源对象调度到不同的Namespace中, 从逻辑上形成不同项目、小组或用户组,便于不同的分组之间能够共享使用整个集群的资源同时还能分别管理; 简而言之: namespace 能够帮助不同的租户(多租户管理)共享一个k8s集群的资源,使得整个集群的配置非常灵、方便。

Q: namespace 命名规则?

A: 只能包括并且最大长度为63位;

Tips : 默认情况下k8s集群会创建一个默认的名称空间即Default, 如果采用资源对象未指定namespace时,则用户所创建的所有资源对象都将被分配到default的namespace之中;

namespace 创建查看

  • Step 1.名称空间创建的两种方式命令行、yaml配置文件

代码块
Shell
自动换行
复制代码
# 方式1
kubectl create namespace dev-ops
# namespace/dev-ops created

# 方式2
apiVersion: v1
kind: Namespace
metadata:
  name: dev-ops

kubecrl create -f namespace-create.yaml
# namespace/dev-ops created
复制成功

  • Step 2.查看名称空间

代码块
Shell
自动换行
复制代码
~$ kubectl get ns
  # NAME                   STATUS   AGE
  # default                Active   89d
  # dev-ops                Active   16s

~$ kubectl describe ns dev-ops
  # Name:         dev-ops
  # Labels:       <none>
  # Annotations:  <none>
  # Status:       Active

  # No resource quota.  # 后续讲解资源配额

  # No LimitRange resource.
复制成功

  • Step 3.删除名称空间(非常注意删除名称空间是非常注意是该namespace下存在pvc/deployment/rc等资源对象, 否则删除名称空间后其资源将被销毁)

代码块
Shell
自动换行
复制代码
kubectl delete namespace dev-ops # 慎用 -force,-f 选项;
kubectl delete -f namespace-create.yaml
复制成功

namespace 配额

描述: 默认情况下创建的namespace并不会对资源进行配额,如果需要对某一个Namespace配额则需要配合使用;

基础示例: ResourceQuota 配额管理

代码块
Shell
自动换行
复制代码
# (1) 创建名称空间
kubectl create namespace dev-ops

# (2) resource-quote-demo.yaml | 资源清单yaml文件
cat > K8s/Day1/resource-quote-demo.yaml <<'end'
apiVersion: v1
kind: ResourceQuota
metadata:
  name: resource-quote-demo
spec:
  hard:
    # 表示全部Container的内存requests总和不能超过1G,而内存Limits的总和不能超过2GiB (CPU 与之相同)
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "2"
    limits.memory: 2Gi
end

# (3) 创建ResourceQuota并为其指定名称空间
~/K8s/Day1$ kubectl create -f resource-quote-demo.yaml -n dev-ops
  # resourcequota/resource-quote-demo created

# (4) 获取名称空间的配额信息
~/K8s/Day1$ kubectl get resourcequota -n dev-ops  # -o yaml 可以查看到使用的情况
  # NAME                  AGE   REQUEST                                     LIMIT
  # resource-quote-demo   73s   requests.cpu: 0/1, requests.memory: 0/1Gi   limits.cpu: 0/2, limits.memory: 0/2Gi
~/K8s/Day1$ kubectl describe resourcequota -n dev-ops
  # Name:            resource-quote-demo
  # Namespace:       dev-ops
  # Resource         Used  Hard
  # --------         ----  ----
  # limits.cpu       0     2
  # limits.memory    0     2Gi
  # requests.cpu     0     1
  # requests.memory  0     1Gi

# (5) 如果在该名称空间中创建Pod并设置了resource,则 通过 -o yaml 可以查看到该名称空间使用的情况
~/K8s/Day1$ kubectl get resourcequota -n dev-ops -o yaml 
# status:
#   hard:
#     limits.cpu: "2"
#     limits.memory: 2Gi
#     requests.cpu: "1"
#     requests.memory: 1Gi
#   used:
#     limits.cpu: "1"
#     limits.memory: "1.5Gi"
#     requests.cpu: "800m"
#     requests.memory: "700Mi"
复制成功

Tips : 如果此时再创建一个Pod并设置的值为700Mi。则会显示错误由于请求700Mi已经超出剩余的剩余的Memoryquota的值,所以新创建Pod的请求将会被终止;

Tips : 非常建议每个Container设置CPU/Memory的request与limit值(涉及到Qos后文讲述)

Tips :我们不单单可以对Namespace做名称空间资源限制还可以通过其对应Container、Pod数量配额(副本数)、API对象的配额等;


原文地址:https://blog.weiyigeek.top/2020/4-27-521.html


本文至此完毕,更多技术文章,尽情期待下一章节!


已发布的相关历史文章(点击即可进入)

1.我在B站学云原生之Kubernetes基础入门学习概述系统架构及组件浅析​

2.我在B站学云原生之Kubernetes基础概念名词浅析与简单实践指南​

3.我在B站学云原生之Kubernetes入门实践CentOS系统上安装部署K8S单控制平面环境​

4.我在B站学云原生之Kubernetes入门实践CentOS系统上手动安装部署K8S集群环境​

网页链接​5.我在B站学云原生之Kubernetes入门实践Ubuntu系统上安装部署K8S单控制平面环境​

6.我在B站学云原生之Kubernetes入门实践Ubuntu系统上安装部署高可用的K8S集群环境​

7.我在B站学云原生之Kubernetes手把手教你使用二进制方式部署K8S集群v1.23.6实践(上)​

7.我在B站学云原生之Kubernetes手把手教你使用二进制方式部署K8S集群v1.23.6实践(下) ​


1.我在B站学云原生之快速拥抱下一代容器引擎Podman来替代Docker容器​

2.我在B站学云原生之快速拥抱下一代容器引擎Podman常用命令浅析与简单配置​

1.我在B站学云原生之Containerd容器运行时‎基础介绍与Ubuntu系统中安装配置实践​

2.我在B站学云原生之Containerd容器运行时实践尝试与客户端crictl工具介绍使用​


欢迎各位志同道合的朋友一起学习交流,如文章有误请在下方留下您宝贵的经验知识,个人邮箱地址【master#weiyigeek.top】或者 个人公众号【WeiyiGeek】联系我。


更多文章来源于【WeiyiGeek Blog - 为了能到远方,脚下的每一步都不能少】 个人博客。

个人主页: 【 https://weiyigeek.top 】

博客地址: 【 https://blog.weiyigeek.top 】

https://weiyigeek.top - Always keep a beginner's mind, don&#​39;t forget the beginner's mind

专栏书写不易,如果您觉得这个专栏还不错的,请给这篇专栏【点个赞、投个币、收个藏、关个注,转个发、留个言】,这将对我的肯定,谢谢!。

谢谢,各位帅哥、美女四连支持!!这就是我的动力!

 更多网络安全、系统运维、应用开发、全栈文章,尽在【个人博客 - https://blog.weiyigeek.top】站点,谢谢支持!