K8S学习笔记

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、k8s框架

一个k8s集群主要是由控制节点(master)和工作节点(node)构成,每个节点上都会安装不同的组件。
在这里插入图片描述
在这里插入图片描述

1.master组件

master是集群的控制平面,负责集群的管理。

  • Api server:资源操作的唯一入口,接受用户输入的命令、提供认证、授权、API注册和发现等机制。
  • ContollerManager:负责维护集群的状态,比如程序部署安排、故障检测、自动扩展、滚动更新等。
  • Scheduler:负责集群资源调度,按照调度策略将pod调度到相应的node节点上。
  • etcd:负责存储集群中各种资源对象的信息(持久化)。

2.node组件

node是集群的数据平面、负责为容器提供运行环境(干活)。

  • Kubelet:负责维护容器的生命周期,即通过控制docker来创建、更新、销毁容器。
  • Kube proxy:负责容器内部的服务发现和负载均衡。
  • docker:负责节点上容器的各种操作。

3.其他重要组件

  • CoreDNS:可以为集群中的SVC创建一个域名IP的对应关系解析。
  • Dashboard:给k8s集群提供一个B/S结构访问体系。
  • Ingress Controller:官方只能实现四层代理,Ingress可以实现七层代理。
  • Federation:提供一个可以跨集群中心多k8s统一管理功能。
  • Prometheus:提供k8s集群的监控能力。
  • ELK:提供k8s集群日志统一分析介入平台。

二、k8s框架的基本概念

  1. Master:集群控制节点,每个集群需要至少一个master节点负责集群的管控。
  2. Node:工作负载节点,由master分配容器到这些node工作节点上,然后node节点上的docker负责容器的运行。
  3. Pod:k8s的最小控制单元,容器都是运行在pod中的,一个pod中可以有一个或者多个容器,K8s通过控制pod进而控制容器,进而控制程序的。
  4. Controller:控制器,通过它来实现对pod的管理,比如启动pod、停止pod、伸缩pod的数量等。
  5. Service:pod对外服务的统一入口,可以维护同一类的多个pod。
  6. Label:标签,用于对pod进行分类,同一类的pod具有相同的标签。
  7. Namespace:命名空间,用来隔离pod的运行环境。

三、k8s集群环境搭建

1.环境初始化

  1. 检查操作系统版本(至少在7.5以上)
# cat /etc/redhat-release
  1. 关闭防火墙和禁止防火墙开机启动
# systemctl stop firewalld
# systemctl stop firewalld
  1. 设置主机名
# vim /etc/hostname
  1. 主机名解析
# vim /etc/hosts
  1. 时间同步
# yum install ntpdate -y
# ntpdate time.windows.com
  1. 关闭selinux
# sed -i 's/enforcing/disabled/' /etc/selinux/config
# reboot
  1. 关闭swap分区
# sed -ri 's/.*swap.*/#&/' /etc/fstab
# reboot
  1. 将桥接的IPv4流量传递到iptables的链
# cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
vm.swappiness = 0
EOF

# modprobe br_netfilter
# lsmod | grep br_netfilter
# sysctl --system
  1. 开启ipvs
# yum -y install ipset ipvsadm

# cat > /etc/sysconfig/modules/ipvs.modules <<EOF
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
EOF

# chmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep -e ip_vs -e nf_conntrack_ipv4
# lsmod | grep -e ipvs -e nf_conntrack_ipv4
# reboot

2.安装Docker、kubeadm、kubelet和kubectl

  1. 安装docker
# yum -y install wget
# wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
# yum install -y docker-ce docker-ce-cli containerd.io
# systemctl enable docker && systemctl start docker
# docker version
# sudo mkdir -p /etc/docker
# sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "exec-opts": ["native.cgroupdriver=systemd"],	
  "registry-mirrors": ["https://du3ia00u.mirror.aliyuncs.com"],	
  "live-restore": true,
  "log-driver":"json-file",
  "log-opts": {"max-size":"500m", "max-file":"3"},
  "storage-driver": "overlay2"
}
EOF

# sudo systemctl daemon-reload
# sudo systemctl restart docker
  1. 添加阿里云的yum软件源
# cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
  1. 安装kubeadm、kubelet和kubectl
# yum install -y kubelet-1.18.0 kubeadm-1.18.0 kubectl-1.18.0
# vim /etc/sysconfig/kubelet
#修改
KUBELET_EXTRA_ARGS="--cgroup-driver=systemd"
KUBE_PROXY_MODE="ipvs"
# systemctl enable kubelet

3.部署master

apiserver-advertise-address对应的IP为Master节点的IP

# kubeadm init \
  --apiserver-advertise-address=192.168.56.20 \
  --image-repository registry.aliyuncs.com/google_containers \
  --kubernetes-version v1.18.0 \
  --service-cidr=10.96.0.0/12 \
  --pod-network-cidr=10.244.0.0/16
# mkdir -p $HOME/.kube
# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# sudo chown $(id -u):$(id -g) $HOME/.kube/config

默认的token有效期为24小时,当过期之后,该token就不能用了,这时可以使用如下的命令创建token:

# kubeadm token create --print-join-command

4.部署node

# kubeadm join...

5.在master部署CNI网络插件(flannel)

# wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
# kubectl apply -f kube-flannel.yml
# kubectl get pod -n kube-system
# kubectl get node

6.测试

# kubectl create deployment nginx --image=nginx:1.14-alpine
# kubectl expose deployment nginx --port=80 --type=NodePort
# kubectl get pods,svc -o wide

四、yaml语言

  1. 语法
    ① 大小写敏感
    ② 使用缩进表示层级关系
    ③ 缩进不允许tab,只允许空格(低版本限制)
    ④ 缩进的空格数不重要,只要相同层级的元素对齐即可
    ⑤ #表示注释
  2. 支持的数据类型
    ① 纯量—单个、不可再分的值
    ② 对象—键值对的集合
    ③ 数组—一组按次序排列的值
  3. 注意事项
    ① 书写yaml冒号后面要加一个空格
    ② 如果需要将多段yaml配置放在一个文件中,中间要使用—分隔
    ③ 可通过yaml转json的网站判断yaml语法是否正确yaml转jison

五、k8s的资源管理

在这里插入图片描述

1.命令式对象管理(kubectl命令)

直接使用命令去操作k8s资源

  1. kubectl语法
# kubectl [command] [type] [name] [flags]
command:指定要对资源执行的操作,例:create、get、delete等,可通过kubectl --help查看
type:指定资源类型,例如:deployment、pod、service等,可通过kubectl api-resources查看
name:指定资源的名称,大小写敏感
flags:指定额外可选的参数
  • command操作类型
    在这里插入图片描述

  • type资源类型
    在这里插入图片描述

2.命令式对象配置

通过命令配置和配置文件去操作k8s资源

3.声明式对象配置

通过apply命令(只用于创建和更新)和配置文件去操作k8s资源。使用apply操作资源:

  • 若资源存在,就更新,相当于kubectl patch
  • 若资源不存在,就创建,相当于kubectl create

六、Namespace

  1. 主要作用:实现多套环境的资源隔离或多租户的资源隔离
  2. K8s在集群启动后,会默认创建几个ns
    ① default:所有未指定ns的对象都会被分配到此空间
    ② kube-node-lease:集群节点之间的心跳维护
    ③ kube-public:此空间下的资源可被所有人访问
    ④ kube-system:所有由k8s系统创建的资源都处于此空间

七、Pod

1.pod的结构

每个pod中都可以包含一个或者多个容器,这些容器分为两类:

  1. 用户程序所在的容器,数量可多可少
  2. Pause容器,这是每个pod都会有的一个根容器,可以以它为依据评估整个pod的健康状态,还可以在根容器上设置ip地址,其他的容器都以此pod ip实现pod内部的网络通信。(这是pod内部的通讯,pod之间的通讯采用虚拟二层网络技术来实现,当前搭建的环境用的是flannel)

2.pod的yaml文件

apiVersion: v1        #必选,版本号
kind: Pod             #必选,资源类型
metadata:             #必选,元数据
  name: string        #必选,pod名称
  namespace: string   #pod所属的命名空间,默认为“default”
  labels:
      - name: string
spec:                 #必选,pod中容器的详细定义
  containers:         #必选,pod中容器列表
 - name: string      #必选,容器名称
   image:string       #必选,容器的镜像名称
   imagePullPolicy: [Always|Never|IfNotPresent]  #获取镜像的策略
   command: [string]  #容器的启动命令列表,如不指定,使用打包时使用的启动命令
   args: [string]     #容器的启动命令参数列表
   workingDir: string #容器的工作目录
   volumeMounts:      #挂载到容器内部的存储卷配置
   - name: string     #引用pod定义共享存储卷的名称,需用volumes[]部分定义的卷名
     mountPath: string     #存储卷在容器内mount的绝对路径
     readOnly: boolean     #是否为只读模式
   ports:             #需要暴露的端口
   - name: string     #端口的名称
     containerPort: int    #容器需要监听的端口号
     hostPort: int         #容器所在主机需要监听的端口号,默认与container相同
     protocol: string      #端口协议,支持TCP和UDP,默认TCP
   env:               #容器运行前需要设置的环境变量列表
   - name: string     #环境变量名称
     value: string         #环境变量的值
   resources:         #资源限制和请求的设置
     limits:          #资源限制的设置
      cpu: string     #cpu的限制,单位为core数,将用于docker run --cpu-share参数
      memory: string  #内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
     requests:        #资源请求的设置
      cpu: string     #cpu请求,容器启动的初始可用数量
      memory: string  #内存请求,容器启动后的初始可用数量
   lifecycle:         #生命周期钩子
     postStart:       #容器启动后立即执行此钩子,如果执行失败,会根据策略进行重启
     preStop:         #容器终止前执行此钩子,无论结果如何,容器都会终止
   livenessProbe:     #多容器内个容器健康检查的设置,当探测无响应几次后将重启
   exec:                 #对pod容器内检查方式设置为exec方式
     command: [string]   #exec方式需要指定的命令或脚本
   httpGet:              #对pod内各容器健康检查方法设置为HttpGet,需要指定Path、port
     path: string 
     port: number
     host: string
     scheme: string
     HttpHeaders:
     - name: string
       Value: string
   tcpSocket:               #对pod内各容器健康检查方式设置为tcpSocket方式
     port: number
     initialDelaySeconds: 0  #容器启动完成后首次探测的时间,单位为秒
     timeoutSecond: 0        #对容器健康检查探测等待响应的超时时间,单位为秒,默认1s
     periodSeconds: 0       #对容器监控检查的定期探测时间设置,单位秒,默认10秒/次
     successThreshold: 0
     failureThreshold: 0
     securityContext:
       privileged: false
restartPolicy: [Always|Never|OnFailure]   #pod的重启策略
nodeName: <string>        #设置NodeSelector表示该pod调度到包含这个label的node上
imagePullSecrets:         #Pull镜像使用时使用的secret名称,以key:secretkey格式指定
 - name: string             
   hostNetwork: false        #是否使用主机网络模式,默认为false,如设置为true,表示使用宿主机网络
volumes:                  #在该pod上定义共享存储卷列表
 - name: string            #共享存储卷名称
   emptyDir: {}             #类型为emtyDir的存储卷,与pod同生命周期的一个临时目录,为空值
   hostPath: string         #类型为hostPath的存储卷,表示挂载pod所在宿主机的目录
     path: string           #pod所在宿主机的目录,将被用于同期中的mount目录
   secret:                  #类型为secret的存储卷,挂载集群与定义的secret对象到容器内部
     scretname: string
     items:
     - key: string
       path: string
configMap:               #类型为configMap的存储,挂载预定义的configMap对象到容器内部
       name: string
       items:
        - key: string
          path: string

(1)K8s资源的一级属性

在k8s中基本所有的资源的一级属性是一样的,主要包含5部分:

  • api Version ——版本,由k8s内部定义,版本号必须可以用kubectl api-version查询到
  • kind ——类型,由k8s内部定义,版本号必须可以用kubectl api-version查询到
  • metadata ——元数据,主要是资源标识和说明,常用的有name、namespace、labels等
  • spec ——描述,这是配置中最重要的一部分,里面是对各种资源配置的详细描述
  • satus ——状态信息,里面的内容不需要定义,由k8s自动生成

(2)spec的重要子属性

  • containers <[]Object>:容器列表,用于定义容器的详细信息
  • nodeName :根据nodeName的值将pod调度到指定的Node节点上
  • nodeSelector <map[]>:根据NodeSelector中定义的信息选择将该pod调度到包含这些label的Node上
  • hostNetwork :是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
  • volumes <[]Object>:存储卷,用于定义pod上面挂载的存储信息
  • restartPolicy :重启策略,表示pod在遇到故障的时候的处理策略

(3)pod.spec.containers属性详解

在这里插入图片描述

1.镜像拉取策略imagePullPolicy
K8s支持配置三种拉取策略:

  • Always:总是从远程仓库拉取镜像
  • IfNotPresent:本地有则用本地镜像,本地没有就从远程仓库拉取
  • Never:只使用本地镜像,本地没有就报错

默认值说明:
如果镜像tag为具体版本号,默认策略为IfNotPresent;
如果镜像tag为latest,默认策略为Always。

2.容器的启动命令 command、args
command已经可以完成启动命令和传递参数的功能,为啥还要提供一个args选项用于参数传递呢?
答:与docker有关。K8s中的command、args两项其实是实现覆盖Dockerfile中的ENTRYPOINT的功能。

  • 如果command和args均没写,那么用Dockerfile的配置。
  • 如果command写了,args没写,那么Dockerfile默认的配置会被忽略,执行输入的command。
  • 如果command没写,args写了,那么Dockerfile中配置的ENTRYPOINT的命令会被执行,使用的是当前args的参数。
  • 如果command和args都写了,那么Dockerfile的配置会被忽略,执行command并追加上args的参数。
  1. 容器的暴露端口port
    在这里插入图片描述
    需要访问容器中的程序需要使用的是:podIp:containerPort

  2. 容器中的资源配额 resources
    容器中的程序运行是要占用一定的资源的,如果不对某个容器的资源做限制,那么它就可能吃掉大量资源,导致其他容器无法运行。
    resource的两个子选项:

  • limits:用于限制运行时容器的最大占用资源,当容器占用资源超过limits时会被终止并重启
  • request:用于设置容器需要的最小资源,如果环境资源不够,容器将无法启动

3.pod的生命周期

在整个生命周期中,pod会出现5中状态(相位),分别如下:

  1. 挂起(pending):apiserver已经创建了pod资源对象,但它尚未被调度完成或仍处于下载镜像的过程
  2. 运行中(running):pod已经被调度到某节点,并且所有容器都已经被kubelet创建完成
  3. 成功(succeeded):pod中的所有容器都已经成功终止并且不会重启
  4. 失败(failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态
  5. 未知(unkonwn):apiserver无法正常获取到pod对象的状态信息,通常由网络通信失败所导致

(1)创建过程

Pod的创建过程:

  1. 用户通过kubectl或其他api客户端提交需要创建的pod信息给apiServer
  2. apiServer开始生成pod对象的信息,并将信息存入etcd,然后返回确认信息至客户端
  3. apiServer开始反映etcd中的pod对象的变化,其他组件使用watch机制来跟踪检查apiServer上的变动
  4. Scheduler发现有新的pod对象要创建,开始为pod分配主机并将结果信息更新至apiServer
  5. Node节点上的kubectl发现有pod调度过来,尝试嗲平庸docker启动容器,并将结果回送至apiServer
  6. apiServer将收到的pod状态信息存入etcd中

(2)运行初始化容器过程

  1. 初始化容器是在pod的主容器启动之前要运行的容器,主要做一些主容器的前置工作,它具有两大特征。
  • 初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么k8s需要重启它直至成功。
  • 初始化容器必须按照定义的顺序执行,当且仅当前一个成功后,后面的一个才能运行。
  1. 初始化容器的运用场景:
  • 提供主容器镜像中不具备的工具程序或自定义代码
  • 初始化容器要先于应用容器串行启动并运行完成,因此可以用于延后应用容器的启动直至依赖的条件得到满足

(3)运行主容器过程

钩子函数

钩子函数能够感知自身生命周期中的事件,并在相应的时刻到来的时候运行用户指定的程序代码。K8s在主容器启动之后和停止之间提供了两个钩子函数。

  • Post start:容器创建之后执行,如果失败了会重启容器
  • Pre stop:容器终止之前执行,执行完成之后容器将成功终止,在其完成之前会阻塞删除容器的操作

钩子处理器支持使用下面三种方式定义动作:exec命令、TCPSocket、HTTPGet

容器探测

容器探测用于检测容器中的应用实例是否正常工作,是保障业务可用性的一种传统机制。如果经过探测,实例的状态不符合预期,那么k8s就会把该问题实例“摘除”,不承担业务流量。K8s提供了两种探针来实现容器探测。

  • liveness probes:存活性探针,用于检测应用实例当前是否处于正常运行状态,如果不是,k8s会重启容器
  • readiness probes:就绪性探针,用于检测应用实例当前是否可以接受请求,如果不可以。K8s不会转发流量

livenessProbes决定是否重启容器,readinessProbes决定是否将请求转发给容器
两种探针支持使用下面三种探测方式:exec命令、TCPSocket、HTTPGet

容器重启策略

Pod的重启策略有3种:

  • Always:容器失效时,自动重启该容器,这是默认值
  • OnFailure:容器终止运行且退出码不为0时重启
  • Nerver:不论状态为何,都不重启该容器

重启策略适用于pod对象中的所有容器,首次需要重启的容器,将在其需要时立即冲去,随后再次需要重启的操作将由kubectl延迟一段时间后进行。

(4)停止过程

Pod的终止过程:

  1. 用户向apiServer发送删除pod对象的命令
  2. apiServer中的pod对象信息会随着时间的推移而更新,在宽限内(默认30s),pod被视为dead
  3. 将pod标记为terminating状态
  4. Kubectl在监控到pod对象转为termintaing状态的同时启动pod关闭过程
  5. 端点控制器监控到pod对象的关闭行为时将其从所有匹配到此端点的service资源的端点列表中移除
  6. 如果当前pod对象定义了prestop钩子处理器,则在其标记为terminating后即会以同步的方式启动执行
  7. Pod对象中的容器进程收到停止信号
  8. 宽限期结束后,若pod中还存在仍在运行的进程,那么pod对象会收到立即停止的信号
  9. Kubectl请求apiServer将此pod资源的宽限期设置为0从而完成删除操作,此时pod对于用户已经不可见

4.pod的调度

一个pod在Node节点上运行,是由Scheduler组件采用相应的算法算出来的,这个过程不受人工控制。在实际应用中,想要 控制pod到达某些节点,需要了解k8s的调度规则。

(1)自动调度

运行在哪个节点上完全由Scheduler经过一系列的算法计算得出

(2)定向调度

定向调度,指的是利用在pod上声明nodeName或者nodeSelector,以此将pod调度到期望的node节点上。这里的调度是强制的,这就意味着即使要调度的目标node不存在,也会向上面调度,只不过pod运行失效而已。

  • NodeName——用于强制约束将pod调度到指定Name的Node节点上。这种方式其实是直接跳过Scheduler的调度逻辑,直接将pod调度到指定名称的节点。
  • NodeSelector——用于将pod调度到添加了指定标签的node节点上。它是通过k8s的label-selector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后将pod调度到目标节点,该匹配规则是强制约束。

(3)亲和度调度

亲和度调度在NodeSelector的基础上进行了扩展,可以通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足的节点上,使调度更灵活。

  1. NodeAffinity——Node亲和度,以node为目标,解决pod可以调度到哪些node的问题
    在这里插入图片描述
    在这里插入图片描述
    NodeAffinity规则设置的注意事项:
  • 如果同时定义了NodeSelector和NodeAffinity,那么必须两个条件都得到满足,Pod才能运行在指定的Node
  • 如果NodeAffinity指定了多个NodeSelectorTerms,那么只需要其中一个能够匹配成功即可
  • 如果NodeSelectorTerms中有多个matchExpression,则一个节点必须满足所有的才能匹配成功
  • 如果一个pod所在的Node在Pod运行期间其标签发生了改变,不再符合该pod的节点亲和性需求,则系统将忽略此变化
  1. PodAffinity——Pod亲和度,以pod为目标,解决pod可以和哪些已经存在的pod部署在同一个拓扑域中的问题
    在这里插入图片描述
    在这里插入图片描述

  2. PodAntiAffinity——Pod反亲和性,以Pod为目标,解决pod不能和哪些已存在的pod部署在同一个拓扑域中的问题

亲和性和反亲和性的应用场景:

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

(4)污点调度

  1. 污点调度
    前面的调度方式都是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否要调度到指定的Node上。其实也可以站在Node的角度上,通过在Node上添加污点属性,来确定是否允许Pod调度过来。Node被设置上污点之后就和Pod之间存在了一种相斥关系,进而拒绝Pod调度进来,甚至可以将已经存在的Pod驱逐出去。 污点的格式为:key=value:effect,key和value是污点的标签,effect描述污点的作用,支持以下三种选项:
  • PreferNoSchedule:k8s将尽量避免将pod调度到具有该污点的Node上,除非没有其他节点可调度
  • NoSchedule:k8s不会把pod调度到具有该污点的Node上,但不会影响当前Node上已经存在的Pod
  • NoExecute:k8s不会将Pod调度到具有该污点的 Node上,同时也会将Node上已经存在的Pod驱离
  1. 容忍调度
    有了污点,就可以在node上添加污点用于拒绝pod调度上来,但是如果就是想将一个pod调度到一个具有污点的node上,这时候就要用到容忍。污点就是拒绝,容忍就是忽略。Node通过污点拒绝pod调度上去,Pod通过容忍忽略拒绝。
    在这里插入图片描述

八、Label

Label是k8s系统中的一个重要概念。它的作用就是在资源上添加标识,用来对它们进行区分和选择。

  1. Label的特点
  • 一个label会以key/value键值对的形式附加到各种对象上,如Node、Pod、Service等;
  • 一个资源对象可以定义任意数量的label,同一个label也可以被添加到任意数量的资源对象上去;
  • Label通常在资源对象定义时确定,当然也可以在对象创建后动态添加或者删除。
  1. Label Selector
    Label用于给某个资源对象定义标识,而label service用于查询和筛选拥有某些标签的资源对象
  • 基于等式的label service
    name = slave:选择所有label中包含key=”name”且vlaue=”slave”的对象
    env != production:选择所有label中包含key=”env”且vlaue不等于”production”的对象
  • 基于集合的label service
    name in(master,slave):选择所有label中包含key=”name”且vlaue=”master”或”slave”的对象
    name not in (frontend):选择所有label中包含key=”name”且vlaue不等于”frontend”的对象

标签的选择条件可以使用多个,此时将多个label selector进行组合,使用逗号进行分隔即可。
例:
name=slave,env!=production
name not in (frontend),env!=production

  1. 命令方式
  • 为资源打标签——kubectl label pod nginx-pod version=1.0 -n dev
  • 为资源更新标签——kubectl label pod nginx-pod version=2.0 -n dev --overwrite
  • 查看标签——kubectl get pod nginx-pod -n dev --show-labels
  • 筛选标签——kubectl get pods -l “version=2.0” -n dev --show-labels
  • 删除标签——kubectl label pod nginx-pod version- -n dev

九、Pod控制器

在k8s中,按照pod的创建方式可将其分为两类:
自主式pod:k8s直接创建出来的pod,这种pod删除之后就没有了,也不会重建
控制器创建的pod:通过控制器创建的pod,这种pod删除了之后还会自动重建

Pod控制器是管理pod的中间层,使用了pod控制器后,我们只需要告诉pod控制器想要多少个什么样的pod就可以了,它就会创建出满足条件的pod并确保每一个pod处于用户期望的状态,如果pod在允许中出现故障,控制器会基于指定策略重启动或重建pod。

1、ReplicationController

比较原始的pod控制器,已废弃

2、ReplicaSet

保证指定数量的pod运行,并支持pod数量变更,镜像版本变更
资源清单文件

apiVersion: apps/v1 # 版本号
kind: ReplicaSet # 类型       
metadata: # 元数据
  name: # rs名称 
  namespace: # 所属命名空间 
  labels: #标签
    controller: rs
spec: # 详情描述
  replicas: 3 # 副本数量
  selector: # 选择器,通过它指定该控制器管理哪些pod
    matchLabels:      # Labels匹配规则
      app: nginx-pod
    matchExpressions: # Expressions匹配规则
      - {key: app, operator: In, values: [nginx-pod]}
  template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80

3、Deployment

通过控制ReplicaSet来控制pod,并支持滚动升级、版本回退
资源清单文件

apiVersion: apps/v1 # 版本号
kind: Deployment # 类型       
metadata: # 元数据
  name: # rs名称 
  namespace: # 所属命名空间 
  labels: #标签
    controller: deploy
spec: # 详情描述
  replicas: 3 # 副本数量
  revisionHistoryLimit: 3 # 保留历史版本
  paused: false # 暂停部署,默认是false
  progressDeadlineSeconds: 600 # 部署超时时间(s),默认是600
  strategy: # 策略
    type: RollingUpdate # 滚动更新策略
    rollingUpdate: # 滚动更新
      maxSurge: 30% # 最大额外可以存在的副本数,可以为百分比,也可以为整数
      maxUnavailable: 30% # 最大不可用状态的 Pod 的最大值,可以为百分比,也可以为整数
  selector: # 选择器,通过它指定该控制器管理哪些pod
    matchLabels:      # Labels匹配规则
      app: nginx-pod
    matchExpressions: # Expressions匹配规则
      - {key: app, operator: In, values: [nginx-pod]}
  template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80
  1. 扩缩容
  2. 镜像更新
strategy:指定新的Pod替换旧的Pod的策略, 支持两个属性:
  type:指定策略类型,支持两种策略
    Recreate:在创建出新的Pod之前会先杀掉所有已存在的Pod
    RollingUpdate:滚动更新,就是杀死一部分,就启动一部分,在更新过程中,存在两个版本Pod
  rollingUpdate:当type为RollingUpdate时生效,用于为RollingUpdate设置参数,支持两个属性:
    maxUnavailable:用来指定在升级过程中不可用Pod的最大数量,默认为25%。
    maxSurge: 用来指定在升级过程中可以超过期望的Pod的最大数量,默认为25%。
  1. 版本回退
    kubectl rollout:版本升级相关功能,支持下面的选项
  • status:显示当前升级状态
  • history:显示升级记录
  • pause:暂停版本升级功能
  • resume:继续已经暂停的版本升级过程
  • restart:重启版本升级过程
  • undo:回滚到上一级版本(可以使用–to-revision回滚到指定版本)
  1. 金丝雀发布
    Deploy支持更新过程中的控制,如“暂停pause”或“继续resume”更新操作。比如有一批新的pod资源创建完成后立即暂停更新过程,此时,仅存在一部分新版本的应用,主题部分还是旧的版本。然后,再筛选一小部分的用户请求路由到新版本的pod应用,继续观察能否稳定地按期望的方式运行。确定没有问题之后再继续完成余下的pod资源滚动更新,否则立即回滚更新操作,这就是所谓的金丝雀发布。

4、Horizontal Pod Autoscaler

可以根据集群负载自动调整pod的数量,实现削峰填谷。

上面的控制器都是手工执行kubectl scale命令来实现pod的扩容。但不切合实际情况,为了实现pod数量的自动调整,所以产生了HPA控制器。HPA可以获取每个pod利用率,然后和HPA中定义的指标进行对比,同时计算出需要伸缩的具体值,最后实现pod的数量的调整。(HPA在Deploy的基础上多加了一层)

  1. 安装metrics-server
    metrics-server可以用来手机集群中的资源使用情况
# yum remove git
# yum install git -y
# git clone -b v0.3.6 https://github.com/kubernetes-incubator/metrics-server
# cd /root/metrics-server/deploy/1.8+/
# vim metrics-server-deployment.yaml
添加如下选项
hostNetwork: true
image: registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server-amd64:v0.3.6
args:
- --kubelet-insecure-tls
- --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP

# g.kubectl apply -f ./
如果成功的话会自动在kube-system下面创建一个pod,其实它就是以pod形式运行的一个软件
# kubectl get pod -n kube-system
利用此软件查看资源使用情况,需要等一段时间。
# kubectl top node
# kubectl top pod -n 命名空间
  1. 准备deployment和service(为了方便不再使用yaml文件,而是使用命令行工具)
创建deployment
# kubectl run nginx --image=nginx:1.17.1 --requests=cpu=100m -n dev
创建service(暴露端口)
# kubectl expose deployment nginx --type=NodePort --port=80 -n dev
查看
# kubectl get deployment,pod,svc -n dev
  1. 部署HPA(使用yaml文件)
    资源清单文件
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: dev
spec:
  strategy: # 策略
    type: RollingUpdate # 滚动更新策略
  replicas: 1
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        resources: # 资源配额
          limits:  # 限制资源(上限)
            cpu: "1" # CPU限制,单位是core数
          requests: # 请求资源(下限)
            cpu: "100m"  # CPU限制,单位是core数

5、DaemonSet

在集群中的指定Node上运行一个副本,一般用于守护进程类的任务。

DaemonSet控制器可以保证集群中的每一台(或指定)节点上都运行一个副本,一般适用于日志收集、节点监控等场景,也就是说,如果一个pod提供的功能是节点级别的(每个节点都需要且只需要一个),那么这类pod就适合使用DaemonSet控制器创建。

DaemonSet控制器的特点:

  • 每当向集群中添加一个节点时,指定的pod副本也将添加到该节点上
  • 当节点从集群中移除时,pod也被垃圾回收了

资源清单文件

apiVersion: apps/v1 # 版本号
kind: DaemonSet # 类型       
metadata: # 元数据
  name: # rs名称 
  namespace: # 所属命名空间 
  labels: # 标签
    controller: daemonset
spec: # 详情描述
  revisionHistoryLimit: 3 # 保留历史版本
  updateStrategy: # 更新策略
    type: RollingUpdate # 滚动更新策略
    rollingUpdate: # 滚动更新
      maxUnavailable: 1 # 最大不可用状态的Pod的最大值,可以为百分比,也可以为整数
  selector: # 选择器,通过它指定该控制器管理哪些pod
    matchLabels: # Labels匹配规则
      app: nginx-pod
    matchExpressions: # Expressions匹配规则
      - {key: app, operator: In, values: [nginx-pod]}
  template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80

6、Job

它创建出来的pod只要完成任务就立即退出,用于执行一次性任务。

Job控制器主要用于负责批量处理(一次要处理指定数量任务)短暂的一次性(每个任务仅运行一次就结束)任务。Job的特点如下:

  • 当Job创建的pod执行成功结束时,Job将记录成功结束的pod数量
  • 当成功结束的pod达到指定的数量时,Job将完成执行

资源清单文件:

apiVersion: batch/v1 # 版本号
kind: Job # 类型       
metadata: # 元数据
  name: # rs名称 
  namespace: # 所属命名空间 
  labels: # 标签
    controller: job
spec: # 详情描述
  completions: 1 # 指定job需要成功运行Pods的次数。默认值:1
  parallelism: 1 # 指定job在任一时刻应该并发运行Pods的数量。默认值:1
  activeDeadlineSeconds: 30 # 指定job可运行的时间期限,超过时间还未结束,系统将会尝试进行终止
  backoffLimit: 6 # 指定job失败后进行重试的次数。默认是6
  manualSelector: true # 是否可以使用selector选择器选择pod,默认是false
  selector: # 选择器,通过它指定该控制器管理哪些pod
    matchLabels: # Labels匹配规则
      app: counter-pod
    matchExpressions: # Expressions匹配规则
      - {key: app, operator: In, values: [counter-pod]}
  template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
    metadata:
      labels:
        app: counter-pod
    spec:
      restartPolicy: Never # 重启策略只能设置为Never或者OnFailure
      containers:
      - name: counter
        image: busybox:1.30
        command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 2;done"]

关于重启策略设置的说明:

  • 如果指定为OnFailure,则Job会在 pod出现故障时重启容器,而不是创建pod,failed次数不变。
  • 如果指定为Never,则Job会在pod出现故障时创建新的pod,并且故障pod不会消失,也不会重启,failed次数+1。
  • 如果指定为Always的话,就意味着一直重启,意味着Job任务会重复取执行,所以不能取Always。

7、Cronjob

它创建的pod会周期性的执行,用于执行周期性任务。

CronJob控制器以Job控制器资源为其管控对象,并借助它管理pod资源对象。Job控制器定义的作业任务在其控制器资源创建后便会立即执行,但CronJob可以以类似于Linux操作系统的周期性任务作业计划的方式控制其运行时间点及重复运行的方式。也就是说,CronJob可以在特定的时间点反复的去运行Job任务。

资源清单文件

apiVersion: batch/v1beta1 # 版本号
kind: CronJob # 类型       
metadata: # 元数据
  name: # rs名称 
  namespace: # 所属命名空间 
  labels: # 标签
    controller: cronjob
spec: # 详情描述
  schedule: # cron格式的作业调度运行时间点,用于控制任务在什么时间执行
  concurrencyPolicy: # 并发执行策略,用于定义前一次作业运行尚未完成时是否以及如何运行后一次的作业
  failedJobHistoryLimit: # 为失败的任务执行保留的历史记录数,默认为1
  successfulJobHistoryLimit: # 为成功的任务执行保留的历史记录数,默认为3
  startingDeadlineSeconds: # 启动作业错误的超时时长
  jobTemplate: # job控制器模板,用于为cronjob控制器生成job对象;下面其实就是job的定义
    metadata:
    spec:
      completions: 1
      parallelism: 1
      activeDeadlineSeconds: 30
      backoffLimit: 6
      manualSelector: true
      selector:
        matchLabels:
          app: counter-pod
        matchExpressions: 规则
          - {key: app, operator: In, values: [counter-pod]}
      template:
        metadata:
          labels:
            app: counter-pod
        spec:
          restartPolicy: Never 
          containers:
          - name: counter
            image: busybox:1.30
            command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 20;done"]

需要重点解释的几个选项:

schedule: cron表达式,用于指定任务的执行时间

    */1    *      *    *     *
    <分钟> <小时> <日> <月份> <星期>

    分钟值从0到59
    小时值从0到23
    日值从1到31
    月值从1到12
    星期值从0到6,0代表星期日
    多个时间可以用逗号隔开,范围可以用连字符给出,*可以作为通配符,/表示每...

concurrencyPolicy:

  • Allow:允许Jobs并发运行(默认)
  • Forbid:禁止并发运行,如果上一次运行尚未完成,则跳过下一次运行
  • Replace:替换,取消当前正在运行的作业并用新作业替换它

8、StatefulSet

管理有状态应用

十、Service

在k8s中,pod是应用程序的载体,可以通过pod的ip来访问应用程序,但是pod的ip地址不是固定的,也意味着不方便直接采用pod的ip对服务进行访问。为了解决这个问题,k8s提供了service资源,service会对提供同一个服务的多个pod进行聚合,并且提供一个统一的入口地址,通过访问service的入口地址就讷讷感访问到后面的pod服务。

Service在很多情况下只是一个概念,真正起作用的其实是kube-proxy服务进程,每个node节点上都运行着一个kube-proxy服务进程。当创建service的时候会通过api-server向etcd写入创建的service的信息,而kube-proxy会基于监听的机制发现这种service的变动,然后它会将最新的service信息转换成对应的访问规则。

Kube-proxy支持三种工作模式:

  1. usersapce模式
    usersapce模式下,kube-proxy会为每一个service创建一个监听端口,发向Cluster IP的请求被Iptables规则重定向到kube-proxy监听的端口上,kube-proxy根据LB算法选择一个提供服务的Pod并和其建立链接,以将请求转发到Pod上。在该模式下,kube-proxy充当了一个四层负责均衡器的角色,由于kube-proxy运行在usersapce中,在进行转发处理时会增加内核和用户空间之间的数据拷贝,虽然比较稳定,但是效率较低。
    在这里插入图片描述

  2. iptables模式
    iptables模式下,kube-proxy为service后端的每个pod创建对应的iptables规则,直接将发向Cluster IP的请求重定向到一个Pod IP。在该模式下kube-proxy不承担四层负责均衡器的角色,只负责创建iptables规则。该模式的优点是较usersapce模式效率更高,但不能提供灵活的LB策略,当后端Pod不可用时也无法进行重试。
    在这里插入图片描述

  3. ipvs模式(使用这个)
    ipvs模式和iptables模式类似,kube-proxy监控Pod的变化并创建想要的ipvs规则。Ipvs相对iptables转发效率更高。除此之外,ipvs支持更多的LB算法。
    在这里插入图片描述
    Ipvs模式必须安装ipvs内核模块,否则会降级为iptables

开启ipvs
# kubectl edit cm kube-proxy -n kube-system修改里面的mode:”ipvs”
# kubectl delete pod -l k8s-app=kube-proxy -n kube-system
# ipvsadm -Ln

1、Service的资源清单文件

kind: Service #类型为service
apiVersion: v1 #service API版本, service.apiVersion
metadata: #定义service元数据,service.metadata
  labels: #自定义标签,service.metadata.labels
    app: wgs-nginx #定义service标签的内容
  name: wgs-nginx-service #定义service的名称,此名称会被DNS解析
  namespace: wgs #该service隶属于的namespaces名称,即把service创建到哪个namespace里面
spec: #定义service的详细信息,service.spec
  type: NodePort #service的类型,定义服务的访问方式,默认为ClusterIP, service.spec.type
  ports: #定义访问端口, service.spec.ports
  - name: http #定义一个端口名称
    port: 81 #service 80端口
    protocol: TCP #协议类型
    targetPort: 80 #目标pod的端口
    nodePort: 30001 #node节点暴露的端口
  - name: https #SSL 端口
    port: 1443 #service 443端口
    protocol: TCP #端口协议
    targetPort: 443 #目标pod端口
    nodePort: 30043 #node节点暴露的SSL端口
  selector: #service的标签选择器,定义要访问的目标pod
    app: wgs-nginx-selector #将流量路到选择的pod上,须等于Deployment.spec.selector.matchLabels

service类型如下:

  • ClusterIP:默认值,它是k8s系统自动分配的虚拟ip,只能在集群内部访问
  • NodePort:将service通过指定的node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务
  • LoadBalancer:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境支持
  • ExternalName:把集群外部的服务引入集群内部,直接使用

2、ClusterIP类型的service

apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: 10.97.97.97 # service的ip地址,如果不写,默认会生成一个
  type: ClusterIP
  ports:
  - port: 80  # Service端口       
    targetPort: 80 # pod端口
  1. Endppoint
    创建了ClusterIP类型的service后,会产生一个Endppoint属性。Endppoint是k8s中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址,它是根据service配置文件中的selector描述产生的。一个service由一组pod组成,这些pod通过Endppoint暴露出来。Endppoint是实现实际服务的端点集合。换句话说,service和pod之间的联系是通过Endppoint实现的。

  2. 负载分发策略
    对service的访问被分发发到了后端的pod上去,目前k8s提供了两种负载分发策略:

  • 如果不定义,默认使用kube-proxy的策略,比如随机、轮询
  • 基于客户端地址的会话保持模式,即来自同一个客户端发起的所有请求都会被转发到固定的一个pod上。此模式可以在sepc中添加sessionAffinity: ClientIp选项

3、HeadLiness类型的service

在某些场景中,开发人员可能不想要使用service提供的负载均衡功能,而希望自己来控制负载均衡策略。针对这种情况,k8s提供了HeadLiness Service,这类Service不会分配Cluster IP,即不会给service分配ip地址,如果想要访问service,只能通过service的域名进行查询。

apiVersion: v1
kind: Service
metadata:
  name: service-headliness
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: None # 将clusterIP设置为None,即可创建headliness Service
  type: ClusterIP
  ports:
  - port: 80    
    targetPort: 80

4、NodePort类型的service

在之前的样例中,创建的service的ip地址只有在集群内部才能够访问,如果希望将service暴露给集群外部使用,那么就要使用到NodePort类型的service。NodePort的工作原理其实就是将service的端口映射到Node的一个端口上,然后就可以通过NodeIP: NodePort来访问service了。

apiVersion: v1
kind: Service
metadata:
  name: service-nodeport
  namespace: dev
spec:
  selector:
    app: nginx-pod
  type: NodePort # service类型
  ports:
  - port: 80
    nodePort: 30002 # 指定绑定的node的端口(默认的取值范围是:30000-32767), 如果不指定,会默认分配
    targetPort: 80

5、LoadBalabcer类型的service

LoadBalabcer和NodePort是相似的,目的都是向外暴露一个端口,区别在于LoadBalabcer会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境的支持,外部服务发送到这个设备上的请求会被设备负载之后转发到集群中。

6、ExternalName类型的service

ExternalName类型的service用于引入集群外部的服务,它通过ExternalName属性指定外部一个服务的地址,然后在集群内部访问此service就可以访问到外部的服务了。

apiVersion: v1
kind: Service
metadata:
  name: service-externalname
  namespace: dev
spec:
  type: ExternalName # service类型
  externalName: www.baidu.com  #改成ip地址也可以

十一、Ingress

Service对集群外部暴露服务的主要方式有两种:NodePort和LoadBalancer,但是这两种方式都有一定的缺点:第一,NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,缺点就愈发明显;第二,LB方式的缺点是每个service都需要一个LB,浪费且麻烦,并且还需要k8s之外的设备支持。

基于这种现状,k8s提供了Ingress资源对象,Ingress只需要一个NodePort或者一个LB就可以满足暴露多个Service的需求。工作机制如图:
在这里插入图片描述
实际上,Ingress相当于一个7层的负载均衡器,是k8s对反向代理的一个抽象,它的工作原理类似于Ingress,可以理解成在Ingress里面建立诸多映射规则,Ingress Controller通过监听这些配置规则并转化成Nginx的反向代理配置,然后对外部提供服务。

  • Ingress:k8s中的一个对象,作用是定义请求如何转发到service的规则
  • Ingress Controller:具体实现反向代理及负载均衡的程序,对Ingress定义的规则进行解析,根据配置的规则来实现请求转发,实现的方法有很多,比如Nginx、Contour、Haproxy等

1、Ingress的工作原理

以nginx为例子
在这里插入图片描述

  1. 用户编写Ingress规则,说明哪个域名对应k8s集群中的哪个service
  2. Ingress控制器动态感知Ingress服务规则的变化,然后生成一段对应的Nginx反向代理配置
  3. Ingress控制器会将生成的Nginx配置写入一个运行着的Nginx服务中,并动态更新
  4. 到此,其实真正在工作的就是一个Nginx了,内部配置了用户定义的请求转发规则

2、搭建Ingress环境

  1. 编写ingress.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
 
---
# Source: ingress-nginx/templates/controller-serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx
  namespace: ingress-nginx
automountServiceAccountToken: true
---
# Source: ingress-nginx/templates/controller-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
---
# Source: ingress-nginx/templates/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
  name: ingress-nginx
rules:
  - apiGroups:
      - ''
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ''
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ''
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io   # k8s 1.14+
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ''
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - extensions
      - networking.k8s.io   # k8s 1.14+
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - networking.k8s.io   # k8s 1.14+
    resources:
      - ingressclasses
    verbs:
      - get
      - list
      - watch
---
# Source: ingress-nginx/templates/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
  name: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ingress-nginx
subjects:
  - kind: ServiceAccount
    name: ingress-nginx
    namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx
  namespace: ingress-nginx
rules:
  - apiGroups:
      - ''
    resources:
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ''
    resources:
      - configmaps
      - pods
      - secrets
      - endpoints
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ''
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io   # k8s 1.14+
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io   # k8s 1.14+
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - networking.k8s.io   # k8s 1.14+
    resources:
      - ingressclasses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ''
    resources:
      - configmaps
    resourceNames:
      - ingress-controller-leader-nginx
    verbs:
      - get
      - update
  - apiGroups:
      - ''
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ''
    resources:
      - events
    verbs:
      - create
      - patch
---
# Source: ingress-nginx/templates/controller-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx
  namespace: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ingress-nginx
subjects:
  - kind: ServiceAccount
    name: ingress-nginx
    namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-service-webhook.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx-controller-admission
  namespace: ingress-nginx
spec:
  type: ClusterIP
  ports:
    - name: https-webhook
      port: 443
      targetPort: webhook
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/component: controller
---
# Source: ingress-nginx/templates/controller-service.yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: http
    - name: https
      port: 443
      protocol: TCP
      targetPort: https
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/component: controller
---
# Source: ingress-nginx/templates/controller-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/instance: ingress-nginx
      app.kubernetes.io/component: controller
  revisionHistoryLimit: 10
  minReadySeconds: 0
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/component: controller
    spec:
      dnsPolicy: ClusterFirst
      containers:
        - name: controller
          image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/ingress-nginx-controller:v0.46.0
          imagePullPolicy: IfNotPresent
          lifecycle:
            preStop:
              exec:
                command:
                  - /wait-shutdown
          args:
            - /nginx-ingress-controller
            - --election-id=ingress-controller-leader
            - --ingress-class=nginx
            - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
            - --validating-webhook=:8443
            - --validating-webhook-certificate=/usr/local/certificates/cert
            - --validating-webhook-key=/usr/local/certificates/key
          securityContext:
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            runAsUser: 101
            allowPrivilegeEscalation: true
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: LD_PRELOAD
              value: /usr/local/lib/libmimalloc.so
          livenessProbe:
            failureThreshold: 5
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
            - name: https
              containerPort: 443
              protocol: TCP
            - name: webhook
              containerPort: 8443
              protocol: TCP
          volumeMounts:
            - name: webhook-cert
              mountPath: /usr/local/certificates/
              readOnly: true
          resources:
            requests:
              cpu: 100m
              memory: 90Mi
      nodeSelector:
        kubernetes.io/os: linux
      serviceAccountName: ingress-nginx
      terminationGracePeriodSeconds: 300
      volumes:
        - name: webhook-cert
          secret:
            secretName: ingress-nginx-admission
---
# Source: ingress-nginx/templates/admission-webhooks/validating-webhook.yaml
# before changing this value, check the required kubernetes version
# https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#prerequisites
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: admission-webhook
  name: ingress-nginx-admission
webhooks:
  - name: validate.nginx.ingress.kubernetes.io
    matchPolicy: Equivalent
    rules:
      - apiGroups:
          - networking.k8s.io
        apiVersions:
          - v1beta1
        operations:
          - CREATE
          - UPDATE
        resources:
          - ingresses
    failurePolicy: Fail
    sideEffects: None
    admissionReviewVersions:
      - v1
      - v1beta1
    clientConfig:
      service:
        namespace: ingress-nginx
        name: ingress-nginx-controller-admission
        path: /networking/v1beta1/ingresses
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ingress-nginx-admission
  annotations:
    helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: admission-webhook
  namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: ingress-nginx-admission
  annotations:
    helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: admission-webhook
rules:
  - apiGroups:
      - admissionregistration.k8s.io
    resources:
      - validatingwebhookconfigurations
    verbs:
      - get
      - update
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: ingress-nginx-admission
  annotations:
    helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: admission-webhook
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ingress-nginx-admission
subjects:
  - kind: ServiceAccount
    name: ingress-nginx-admission
    namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ingress-nginx-admission
  annotations:
    helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: admission-webhook
  namespace: ingress-nginx
rules:
  - apiGroups:
      - ''
    resources:
      - secrets
    verbs:
      - get
      - create
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ingress-nginx-admission
  annotations:
    helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: admission-webhook
  namespace: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ingress-nginx-admission
subjects:
  - kind: ServiceAccount
    name: ingress-nginx-admission
    namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: ingress-nginx-admission-create
  annotations:
    helm.sh/hook: pre-install,pre-upgrade
    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: admission-webhook
  namespace: ingress-nginx
spec:
  template:
    metadata:
      name: ingress-nginx-admission-create
      labels:
        helm.sh/chart: ingress-nginx-3.33.0
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/version: 0.47.0
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/component: admission-webhook
    spec:
      containers:
        - name: create
          image: docker.io/jettech/kube-webhook-certgen:v1.5.1
          imagePullPolicy: IfNotPresent
          args:
            - create
            - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc
            - --namespace=$(POD_NAMESPACE)
            - --secret-name=ingress-nginx-admission
          env:
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
      restartPolicy: OnFailure
      serviceAccountName: ingress-nginx-admission
      securityContext:
        runAsNonRoot: true
        runAsUser: 2000
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: ingress-nginx-admission-patch
  annotations:
    helm.sh/hook: post-install,post-upgrade
    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
  labels:
    helm.sh/chart: ingress-nginx-3.33.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.47.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: admission-webhook
  namespace: ingress-nginx
spec:
  template:
    metadata:
      name: ingress-nginx-admission-patch
      labels:
        helm.sh/chart: ingress-nginx-3.33.0
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/version: 0.47.0
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/component: admission-webhook
    spec:
      containers:
        - name: patch
          image: docker.io/jettech/kube-webhook-certgen:v1.5.1
          imagePullPolicy: IfNotPresent
          args:
            - patch
            - --webhook-name=ingress-nginx-admission
            - --namespace=$(POD_NAMESPACE)
            - --patch-mutating=false
            - --secret-name=ingress-nginx-admission
            - --patch-failure-policy=Fail
          env:
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
      restartPolicy: OnFailure
      serviceAccountName: ingress-nginx-admission
      securityContext:
        runAsNonRoot: true
        runAsUser: 2000
  1. 执行yaml文件以及检查安装结果
# kubectl apply -f ingress.yaml
# kubectl get pod,svc -n ingress-nginx

十二、数据存储

为了持久化保存容器的数据,k8s引入了Volume的概念。Volume是Pod中能够被多个容器访问的共享目录,它被定义在Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下,k8s通过Volume实现同一个Pod中不同容器之间的数据共享以及数据的持久化存储。Volume的生命周期不与Pod中单个容器的生命周期相关,当容器终止或重启时,Volume中的数据也不会丢失。

1、简单存储

(1)EmptyDir

最基础的Volume类型,一个EmptyDir就是主机上的一个空目录。EmptyDir是在Pod被分配到Node时创建的,它的初始目录为空,并且无须指定宿主机上对应的目录文件,因为k8s会自动分配一个目录,当Pod被销毁时,EmptyDir中的数据也会被永久删除。EmptyDir的用途如下:

  • 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留
  • 在一个Pod中,一个容器需要从另一个容器中获取数据的目录(多容器共享目录)

(2)HostPath

如果想简单的将数据持久化到主机中,就可以选择HostPath。HostPath就是将Node主机中的一个实际目录挂载到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据依旧可以存在于Node主机上。

(3)NFS

HostPath可以解决数据持久化的问题,但是一旦Node节点故障了,Pod如果转移到了别的节点,又会出现问题了,此时需要准备单独的网络存储系统,比如常用的NFS、CIFS。
NFS是一个网络文件存储系统,可以搭建一台NFS服务台,然后将Pod中的存储直接连接到NFS系统上,这样的话,无论Pod在节点上怎么转移,只要Node跟NFS的对接没问题,数据就可以成功访问。

用master节点做nfs服务器的步骤:
在master上安装nfs服务:
# yum install nfs-utils -y
准备一个共享目录: 
# mkdir /root/data/nfs -pv
将共享目录以读写权限暴露给网段中的所有主机:
# vim /etc/exports写入如下内容:
/root/data/nfs  192.168.109.0/24(rw,no_root_squash)
启动nfs服务:
# systemctl start nfs
在每个node节点上都安装nfs,这样的目的是为了node节点可以驱动nfs设备:
# yum install nfs-utils -y
编写pod配置文件
运行pod观察结果

2、高级存储

(1)PV

PV(Persistent Volume)是持久化卷的意思,是对底层的共享存储的一种抽象。一般情况下PV由k8s管理员进行创建和配置,它与底层具体的共享存储技术有关,并通过插件完成与共享存储的对接。是存储资源的抽象。

apiVersion: v1
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv2
spec:
  nfs: # 存储类型,与底层真正存储对应
  capacity: # 存储能力,目前只支持存储空间的设置
    storage: 2Gi
  accessModes: #访问模式
  storageClassName: #存储类别
  persistentVolumeReclaimPolicy: #回收策略
  1. 存储类型
    底层实际存储的类型,k8s支持多种存储类型,每种存储类型的配置都有所差异
  2. 存储能力
    目前只支持存储空间的设置(storage=1Gi),不过未来可能会加入IOPS、吞吐量等指标的配置
  3. 访问模式
    用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
  • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
  • ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
  • ReadWriteMany(RWX):读写权限,可以被多个节点挂载

注意:底层不同的存储类型可能支持的访问模式不同

  1. 回收策略
    当PV不再被使用了之后,对其处理方式有以下三种策略:
  • Retain(保留):保留数据,需要管理员手动清理数据
  • Recycle(回收):清除PV中的数据,效果相当于执行rm -rf /thevolume/*
  • Delete(删除):与PV相连的后端存储完成volume的删除操作,这常见于云服务商的存储服务

注意:底层不同的存储类型可能支持的回收策略不同

  1. 存储类别
    PV可以通过storageClassName参数指定一个存储类别
  • 具有特定类别的PV只能与请求了该类别的PVC进行绑定
  • 未设定类别的PV则只能与不请求任何类别的PVC进行绑定
  1. 状态
    一个PV的生命周期中,可能会处于4个不同的阶段:
  • Available(可用):表示可用状态,还未被任何PVC绑定
  • Bound(已绑定):表示PV已经被PVC绑定
  • Released(已释放):表示PVC被删除,但是资源还未被集群重新声明
  • Failed(失败):表示该PV的自动回收失败

(2)PVC

PVC(Persistent Volume Claim)是持久化卷声明的意思,是用户对于存储需求的一种声明。话句话说,PVC其实就是用户向k8s系统发出的一种资源需求申请。是PV资源的申请,用来声明对存储空间、访问模式、存储类别需求信息。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc		# 资源名
  namespace: test	# 所属命名空间
spec:		# 以下是对PV资源的需求信息
  accessModes: 	# 访问模式
  selector: 	# 采用标签对PV选择
  storageClassName: 	# 存储类别
  resources: 	#请求空间
    requests:
      storage: 5Gi
  1. 访问模式
    用于描述用户应用对存储资源的访问权限
  2. 选择条件
    通过Label Selector的设置,可使PVC对于系统中已存在的PV进行筛选
  3. 存储类别
    PVC在定义时可以设定需要的后端存储的类别,只有设置了该class的pv才能被系统选出
  4. 资源请求
    描述对存储资源的请求

(3)PV和PVC的生命周期

在这里插入图片描述

  1. 资源供应:管理员手动创建底层存储和PV
  2. 资源绑定:用户创建PVC,K8s负责根据PVC的声明去寻找PV,并绑定

在用户定义好PVC之后,系统将根据PVC对存储资源的请求在已存在的PV中选择一个满足条件的。
一旦找到,就将该PV与用户定义的PVC进行绑定,用户的应用就可以使用这个PVC了;如果找不到,PVC则会无限期处于Pending状态,直到等到系统管理员创建了一个符合其要求的PV。
PV一旦绑定到某个PVC上,就会被这个PVC独占,不能再与其他PVC进行绑定了。

  1. 资源使用:用户可以在pod中像volume一样使用pvc

Pod使用volume的定义,将PVC挂载到容器内的某个路径进行使用

  1. 资源释放:用户删除PVC来释放PV

当存储资源使用完毕后,用户可以删除PVC,与该PVC绑定的PV将会被标记为“已释放”,但还不能立刻与其他PVC进行绑定。通过之前PVC写入的数据可能还被留在存储设备上,只有在清除之后该PV才能再次使用。

  1. 资源回收:k8s根据PVC设置的回收策略进行资源的回收

对于PV,管理员可以设定回收策略,用于设置与之前绑定的PVC释放资源之后如何处理遗留数据的问题。只有PV的存储空间完成回收,才能供新的PVC绑定和使用

3、配置存储

(1)ConfigMap

特殊存储卷,用来存储配置信息的

apiVersion: v1
kind: ConfigMap
metadata:
  name: configmap
  namespace: dev
data:
  info: |
    username:admin
    password:123456

(2)Secret

与ConfigMap类似,主要用于存储敏感信息,例如密码、密钥、证书等

apiVersion: v1
kind: Secret
metadata:
  name: secret
  namespace: dev
type: Opaque
data:
  username: YWRtaW4=
  password: MTIzNDU2
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值