Kubernetes—基础


Kubernetes - 基础

1. Kubernetes介绍

1.1 应用部署方式演变

在部署应用程序的方式上,主要经历了三个时代:

  • 传统部署:互联网早期,会直接将应用程序部署在物理机上

    优点:简单,不需要其它技术的参与

    缺点:不能为应用程序定义资源使用边界,很难合理地分配计算资源,而且程序之间容易产生影响

  • 虚拟化部署:可以在一台物理机上运行多个虚拟机,每个虚拟机都是独立的一个环境

    优点:程序环境不会相互产生影响,提供了一定程度的安全性

    缺点:增加了操作系统,浪费了部分资源

  • 容器化部署:与虚拟化类似,但是共享了操作系统

    优点:

    • 可以保证每个容器拥有自己的文件系统、CPU、内存、进程空间等

    • 运行应用程序所需要的资源都被容器包装,并和底层基础架构解耦

    • 容器化的应用程序可以跨云服务商、跨Linux操作系统发行版进行部署

容器化部署方式给带来很多的便利,但是也会出现一些问题,比如说:

  • 一个容器故障停机了,怎么样让另外一个容器立刻启动去替补停机的容器
  • 当并发访问量变大的时候,怎么样做到横向扩展容器数量

这些容器管理的问题统称为容器编排问题,为了解决这些容器编排问题,就产生了一些容器编排的软件:

  • Swarm:Docker自己的容器编排工具
  • Mesos:Apache的一个资源统一管控的工具,需要和Marathon结合使用
  • Kubernetes:Google开源的的容器编排工具

编排软件排行榜

1.2 kubernetes简介

kubernetes,是一个全新的基于容器技术的分布式架构领先方案,是谷歌严格保密十几年的秘密武器—-Borg系统的一个开源版本,于2014年9月发布第一个版本,2015年7月发布第一个正式版本。

kubernetes的本质是一组服务器集群,它可以在集群的每个节点上运行特定的程序,来对节点中的容器进行管理。目的是实现资源管理的自动化,主要提供了如下的主要功能:

  • 自我修复:一旦某一个容器崩溃,能够在1秒中左右迅速启动新的容器
  • 弹性伸缩:可以根据需要,自动对集群中正在运行的容器数量进行调整
  • 服务发现:服务可以通过自动发现的形式找到它所依赖的服务
  • 负载均衡:如果一个服务起动了多个容器,能够自动实现请求的负载均衡
  • 版本回退:如果发现新发布的程序版本有问题,可以立即回退到原来的版本
  • 存储编排:可以根据容器自身的需求自动创建存储卷

1.3 kubernetes组件

[!tip]

参考官方文档:https://kubernetes.io/zh-cn/docs/concepts/architecture/

一个kubernetes集群主要是由**控制节点(master)工作节点(node)**构成,每个节点上都会安装不同的组件。

**master:集群的控制平面,负责集群的决策 ( 管理 )**,其中常用的控制平面组件如下:

  1. kube-apiserver:Kubernetes 的 API 服务器,作为所有资源操作的中心点,负责处理 RESTful 请求,将其分发给其他组件,并更新 etcd 中的状态。
  2. etcd:一个分布式键值存储,用于保存 Kubernetes 集群的所有状态信息,包括集群的配置信息和调度的状态。
  3. kube-scheduler(负责计算):负责根据资源需求、策略约束、数据位置等因素将未调度的 Pod 分配到适当的工作节点(Node)。
  4. kube-controller-manager(负责安排):负责管理控制器,如节点控制器、复制控制器、端点控制器等。这些控制器不断检查 Kubernetes 的状态,并通过 API 服务器与集群进行交互,确保实际状态符合期望状态。
  5. cloud-controller-manager:如果 Kubernetes 部署在云平台上,这个组件负责与底层云服务交互,例如处理节点生命周期、路由、负载均衡器等。

**node:集群的数据平面,负责为容器提供运行环境 ( 干活 )**,其中常用的节点组件如下:

  1. kubelet:运行在每个节点上,负责管理 Pod 和其中的容器。它接收来自 API 服务器的指令,并确保相应的容器在节点上正常运行。负责维护容器的生命周期,即通过控制docker,来创建、更新、销毁容器。
  2. kube-proxy:Kubernetes 的网络代理,负责维护节点上的网络规则,处理 Pod 间的通信和负载均衡。负责提供集群内部的服务发现和负载均衡。
  3. Container Runtime:容器运行时环境,如 Docker、containerd、CRI-O 等,负责拉取镜像、启动容器以及管理容器的生命周期。
  4. cAdvisor:运行在每个节点上,负责收集、聚合、处理和导出容器的资源使用情况和性能数据。

控制节点与工作节点示意图

下面,以部署一个nginx服务来说明kubernetes系统各个组件调用关系:

  1. 首先要明确,一旦kubernetes环境启动之后,masternode都会将自身的信息存储到etcd数据库中。

  2. 一个nginx服务的安装请求会首先被发送到master节点的apiServer组件。

  3. apiServer组件会调用scheduler组件来决定到底应该把这个服务安装到哪个node节点上,在此时,它会从etcd中读取各个node节点的信息,然后按照一定的算法进行选择,并将结果告知apiServer

  4. apiServer调用controller-manager去调度Node节点安装nginx服务

  5. kubelet接收到指令后,会通知docker,然后由docker来启动一个nginxpod,这样一个nginx服务就运行了,如果需要访问nginx,就需要通过kube-proxy来对pod产生访问的代理

    [!tip]

    podkubernetes最小操作单元,容器必须跑在pod中。

这样,外界用户就可以访问集群中的nginx服务了。

除了控制平面和节点组件以外, 还有一些附加的常用组件:

  1. DNS:Kubernetes 内部的 DNS 服务(如 CoreDNS),为集群内的服务和 Pod 提供 DNS 解析功能。
  2. Ingress Controller:用于管理外部访问服务的流量,通过定义 Ingress 资源来配置负载均衡、SSL 终止等。
  3. Dashboard:提供一个基于 Web 的用户界面,允许用户查看和管理集群中的资源。
  4. Helm:Kubernetes 的包管理工具,用于简化应用程序的部署和管理。

1.4 kubernetes概念

核心概念:

  1. 容器(Container): 容器是一个轻量级、独立的可执行软件包,它包含应用程序及其所有依赖项。Kubernetes 管理和编排这些容器,使它们能够在不同的环境中运行。
  2. 节点(Node): 节点是 Kubernetes 集群中的一台工作机器,可以是虚拟机或物理机。每个节点上运行着多个容器,并且都包含一个 kubelet、kube-proxy 和容器运行时。
  3. Pod: Pod 是 Kubernetes 中最小的可部署单元,通常表示单个容器或多个紧密耦合的容器的集合。Pod 共享网络命名空间和存储卷,一个Pod中可以有1个或者多个容器
  4. 服务(Service): 服务是 Kubernetes 中的一种抽象,定义了一组 Pod 及其访问策略。它为这些 Pod 提供一个固定的 IP 和 DNS 名称,即使 Pod 动态变化,服务的 IP 地址也保持不变。服务可以用于负载均衡。他是Pod对外服务的统一入口,下面维护着同一类的多个pod。
  5. 命名空间(Namespace): 命名空间用于将 Kubernetes 集群中的资源划分成逻辑组,以便不同团队或项目能够在同一个集群中工作而不互相干扰。用来隔离Pod的运行环境。
  6. 控制器(Controller): 控制器负责维护 Kubernetes 集群的期望状态,例如部署、复制控制器等。它们不断监控集群的实际状态,并采取行动将其调整到期望状态。通过它来实现对Pod的管理,比如启动Pod、停止Pod、伸缩Pod的数量等等。
  7. 调度器(Scheduler): 调度器负责将新创建的 Pod 分配到集群中的合适节点上。它根据资源需求、策略、数据位置等因素进行决策。
  8. 配置和密钥管理(ConfigMap 和 Secret): ConfigMap 用于存储配置信息,例如环境变量、配置文件等。Secret 则用于存储敏感数据,如密码、令牌等。这些都可以被 Pod 安全地引用。
  9. 持久卷(Persistent Volume, PV)和持久卷声明(Persistent Volume Claim, PVC): PV 是集群中的存储资源,独立于 Pod 的生命周期。PVC 是用户对 PV 的请求,它定义了所需的存储大小、访问模式等。Kubernetes 使用 PVC 来自动绑定合适的 PV。
  10. Ingress: Ingress 资源用于定义外部访问集群中服务的规则,例如 HTTP/HTTPS 路由、负载均衡和 SSL 终止等。Ingress Controller 实现这些规则并管理流量。
  11. 标签(Lable):Label是 Kubernetes 中的一种键值对,用于标记 Kubernetes 对象(如 Pod、节点等)。它们为对象添加了元数据,可以用来组织、选择和过滤这些对象。
  12. 主节点(Master):集群控制节点,每个集群需要至少一个master节点负责集群的管控

2. kubernetes集群环境搭建

2.1 环境规划

2.1.1 集群类型

kubernetes集群大体上分为两类:一主多从多组多从

  • 一主多从:一台Master节点和多台Node节点,搭建简单,但是有单机故瞳风险,适合用于测试环境
  • 多主多从:多台Master节点和多台Node节点,搭建麻烦,安全性高,适合用于生产环境

[!tip]

为了方便测试,这里我们采用一主一从即可。

2.1.2 安装方式

kubernets有多种部署方式,目前主流的方式有kubeadm、minikube、二进制包:

  • minikube:一个用于快速搭建单节点kubernetes的工具
  • kubeadm:一个用于快速搭建kubernetes集群的工具
  • 二进制包:从官网下载每个组件的二进制包,依次去安装,此方式对于理解kubernetes组件更加有效

[!note]

Kubeadm 降低部署门槛,但屏蔽了很多细节,遇到问题很难排查。如果想更容易可控,推荐使用二进制包部署Kubernetes 集群,虽然手动部署麻烦点,期间可以学习很多工作原理,也利于后期维护。

这里我为了方便,就使用kubeadm来搭建集群。

2.2 Linux系统初始化

[!important]

以下步骤所有节点均要操作。

2.2.1 检查操作系统的版本

kubernetes集群搭建要求CentOS版本要在7.5或者以上:

cat /etc/redhat-release

可见我这里是7.9的版本。

并且为了后面观看方便,我将主机名称也一并更改了:

hostnamectl set-hostname 主机名称

2.2.2 主机名解析

为了后续方便集群各个节点之间的直接调用,在这里配置一下主机名解析,这样后面就不用写具体的IP了,直接写主机名称就可以了。

[!tip]

企业中推荐使用内部的DNS服务器。

 vim /etc/hosts
192.168.5.45 k8s-master
192.168.5.16 k8s-node

2.2.3 时间同步

kubernetes要求集群中的节点时间必须精确一致,这里直接使用chronyd服务从网络同步时间。

[!tip]

企业中建议配置内部的时间同步服务器。

#启动chronyd服务
systemctl start chronyd
#设置chronyd服务开机自启
systemctl enable chronyd
#chronyd服务启动稍等几秒钟,就可以使用date命令验证时间了
date

2.2.4 禁用Iptables和firewall服务

kubernetes和docker在运行中会产生大量的Iptables规则,为了不让系统规则跟它们混淆,直接关闭系统的规则

#关闭防火墙
systemctl stop firewalld
#关闭防火墙开机自启
systemctl disable firewalld
#关闭Iptables
systemctl stop iptables
#关闭Iptables开机自启
systemctl disable iptables

2.2.5 禁用SELinux

selinux是linux系统下的一个安全服务,如果不关闭它,在安装集群中会产生各种各样的奇葩问题。

#先临时关闭selinux
setenforce 0
#编辑文件/etc/selinux/config 改为:SELINUX=disabled 来永久关闭
vim /etc/selinux/config

[!tip]

改为永久方式之后,需要重启才会生效,不过这里我们先不重启,因为我们做了临时关闭的。

2.2.6 禁用swap分区

swap分区指的是虚拟内存分区,它的作用是在物理内存使用完之后,将磁盘空间虚拟成内存来使用。

启用swap设备会对系统的性能产生非常负面的影响,因此kubernetes要求每个节点都要禁用swap设备

但是如果因为某些原因确实不能关闭swp分区,就需要在集群安装过程中通过明确的参数进行配置说明

[!tip]

这里最好还是禁用,不然你需要自己去查看配置了。

#编辑分区文件 /etc/fstab,注释掉swap分区一行
vim /etc/fstab

[!tip]

这里配置修改之后,需要重启次啊会生效。

2.6.7 修改linux内核参数

修改Linux的内核参数,添加网桥过滤和地址转发功能:

vim /etc/sysctl.d/kubernetes.conf

之后添加如下内容:

net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1

之后重新加载配置:

sysctl -p

加载网桥过滤模块:

modprobe br_netfilter

查看网桥过滤模块是否加载成功:

lsmod | grep br_netfilter

[!tip]

在安装和运行 Kubernetes 集群时,需要启用网桥过滤和地址转发功能,以确保集群中的网络功能能够正常工作。以下是原因:

  1. 网桥过滤(Bridge Filtering)
  • 原因:Kubernetes 使用网桥(Bridge)来连接不同的容器网络,并实现容器之间的通信。在 Linux 中,网桥过滤可以控制哪些数据包可以通过网桥进行转发。

  • 作用:为了确保 Kubernetes 正常工作,必须确保网络数据包不会被不必要的过滤规则阻挡。因此,需要配置 Linux 内核参数,允许通过网桥的流量能够被正常转发,而不会因为过滤规则而被丢弃。

    在 Kubernetes 中,常见的配置是将 net.bridge.bridge-nf-call-iptables 设置为 1,以便在网桥上的流量能够被 iptables 处理,这对于网络策略和安全性配置至关重要。

  1. 地址转发(IP Forwarding)
  • 原因:Kubernetes 中的 Pod 需要能够跨节点进行通信,而这通常需要通过路由和转发来实现。Linux 主机必须能够在不同的网络接口之间转发数据包,以便允许来自 Pod 的流量通过主机的网络堆栈进行路由。

  • 作用:启用地址转发可以使节点能够充当路由器,将数据包从一个网络接口转发到另一个接口,从而允许不同节点上的 Pod 相互通信。这对 Kubernetes 网络插件(如 Flannel、Calico 等)至关重要,这些插件依赖于内核的转发功能来实现节点间网络流量的路由。

    常见配置是启用 net.ipv4.ip_forward 参数,以确保 Linux 主机可以跨网络接口转发 IP 数据包。

2.6.8 配置ipvs功能

在kubernetes中service有两种代理模型,一种是基于iptables的,一种是基于ipvs

两者比较的话,ipvs的性能明显要高一些,但是如果要使用它,需要手动载入ipvs模块。

  1. 安装ipset和ipsvadm模块

    yum install -y ipset ipvsadm
    
  2. 添加需要加载的模块写入脚本文件

    cat <<EOF> /etc/sysconfig/modules/ipvs.modules
    #!bin/bash
    modprobe -- ip_vs
    modprobe -- ip_vs_rr
    modprobe -- ip_vs_wrr
    modprobe -- ip_vs_sh
    modprobe -- nf_conntrack_ipv4
    EOF
    
  3. 为脚本添加可执行权限

    chmod +x /etc/sysconfig/modules/ipvs.modules
    
  4. 执行脚本文件

    /bin/bash /etc/sysconfig/modules/ipvs.modules
    
  5. 查看模块是否加载成功

    lsmod | grep -e ip_vs -e nf_conntrack_ipv4
    

2.6.9 重启服务器

上述2.2.1~2.2.8所有步骤完成之后,重启服务器:

reboot

2.3 集群所需组件安装

[!important]

以下小节:2.3.1、2.3.2;不管是master还是node,都需要执行。

2.3.1 安装Docker

[!tip]

Docker安装可以去看我往期的Docker专栏,这里不做过多介绍:https://blog.cqwulyj.cn/posts/d3033fb2.html

2.3.2 安装Kubernetes组件

安装之前,请仔细阅读如下官方文章

  1. 安装 kubeadm、kubelet 和 kubectl:https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
  2. 版本偏差策略:https://kubernetes.io/zh-cn/releases/version-skew-policy/
  3. 在 Linux 系统中安装并设置 kubectl:https://kubernetes.io/zh-cn/docs/tasks/tools/install-kubectl-linux/

之后开始安装:

安装 kubeadm、kubelet 和 kubectl

# 此操作会覆盖 /etc/yum.repos.d/kubernetes.repo 中现存的所有配置
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.31/rpm
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.31/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF

[!tip]

这里我用的是阿里的镜像源:

yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
systemctl enable kubelet

现在kubelet、kubeadm、kubectl都已经安装好了,现在我们来配置cgroup驱动程序:

官网如下

官网也说了要配置cgroup。

#编辑 /etc/sysconfig/kubelet,添加如下配置
vim /etc/sysconfig/kubelet
KUBELET_CGROUP_ARGS="--cgroup-driver=systemd"
KUBE_PROXY_MODE="ipvs"

2.3.3 集群初始化

[!important]

集群初始化只需要主节点执行即可。

初始化集群之前,我们先来看看kubeadm需要用到的镜像以及版本都有哪些:

kubeadm config images list

在初始化之前,建议修改一下沙箱环境:

containerd config default > /etc/containerd/config.toml
sed -i 's/registry.k8s.io/registry.aliyuncs.com\/google_containers/' /etc/containerd/config.toml
systemctl daemon-reload
systemctl restart containerd

之后进行集群初始化:

kubeadm init \
--apiserver-advertise-address 192.168.254.142 \
--image-repository registry.aliyuncs.com/google_containers \
--kubernetes-version v1.31.0 \
--service-cidr 10.96.0.0/12 \
--pod-network-cidr 10.244.0.0/16

命令解读:

  1. kubeadm init:这是 kubeadm 的一个命令,用于初始化一个 Kubernetes 控制平面节点。它会启动 Kubernetes 集群的核心组件,如 API 服务器、控制器管理器和调度器。
  2. --apiserver-advertise-address:指定 Kubernetes API 服务器的外部可访问地址,即向集群中的其他节点通告的地址。其他节点将使用这个地址与 API 服务器进行通信。192.168.5.45 是这个控制平面节点的 IP 地址。也就是master节点的IP。
  3. --image-repository:指定使用的镜像仓库地址。由于国内访问 gcr.iok8s.gcr.io 的官方 Kubernetes 镜像仓库可能受到限制,因此使用阿里云的镜像仓库 registry.aliyuncs.com/google_containers 来拉取 Kubernetes 所需的镜像。
  4. --kubernetes-version:指定要安装的 Kubernetes 版本。在这个命令中,指定的版本是 v1.31.0kubeadm 将使用这个版本的二进制文件来初始化集群。
  5. --service-cidr:指定 Kubernetes 集群中 Service 虚拟 IP 的子网范围。这个范围内的 IP 地址将用于集群中创建的 Service 对象的虚拟 IP。10.96.0.0/12 是 Kubernetes 的默认值。
  6. --pod-network-cidr :指定 Pod 网络的 CIDR(Classless Inter-Domain Routing,无类别域间路由)范围。每个 Pod 将从这个范围中分配一个 IP 地址。在这个例子中,使用 10.244.0.0/16,这是 Flannel 等常见 CNI 插件的默认值。

[!note]

所以上述命令只建议你更改:--apiserver-advertise-address写你自己的master节点的IP,--kubernetes-version选择你自己喜欢的版本;这两个参数。

执行成功

由于我本来就是root用户来执行的,所以我执行如下命令即可:

 export KUBECONFIG=/etc/kubernetes/admin.conf

[!note]

上述执行的范围只是session范围,只在当前终端会话中有效,所以为了永久有效,我们加到系统的环境变量中去

vim /etc/profile

如果不执行如上命令,会报错:

所以,一定要在mater节点上执行如上命令,否则kubectl很多命令都无法使用。

之后来到node节点处,我这里的加入节点命令:

kubeadm join 192.168.5.45:6443 --token fidivm.gm4igx2yxxsl0xvl \
        --discovery-token-ca-cert-hash sha256:211e6ff857f4f76f0694fd5bc7ce33c99eb6b1f502029f1d5c522d2baceaf4e9

[!tip]

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

kubeadm token create --print-join-command

或者:

# 生成一个永不过期的token
kubeadm token create --ttl 0 --print-join-command

[!note]

不管是对于在集群初始化,还是在node节点加入master节点的时候,凡是出现如下类型的问题:

均可以运行如下命令解决:

containerd config default > /etc/containerd/config.toml
sed -i 's/registry.k8s.io/registry.aliyuncs.com\/google_containers/' /etc/containerd/config.toml
systemctl daemon-reload
systemctl restart containerd

简单来说就是更改containerd的默认沙箱镜像后,它开始工作;原文地址:https://discuss.kubernetes.io/t/kubeadm-init-error-api-server-is-not-running/25367/2。

执行成功如下:

node节点执行成功

在**控制面板节点(master)**执行如下命令查看当前的节点信息:

kubectl get nodes

可见他们的状态都是NotReady,这是因为我们还没有安装网络插件。

kubernetes支持多种网络插件,比如flannelcalicocanal等等,任选一种便用即可,本次选择flannel

[!tip]

实测,现在flannel有大坑。本人花费2天时间,试了各种方法,查阅了各种资料才解决😭

参考文章:https://blog.csdn.net/weixin_43205308/article/details/140874236

首先你需要先去网址:https://github.com/flannel-io/flannel/blob/master/Documentation/kube-flannel.yml。

在这个网址中将yml文件下载下来并且保存到你自己期望的路径下。

之后打开这个yml文件,查看所需要的镜像:

flannel镜像1

flannel镜像2

在24年中国禁止使用Docker以前,你可以直接运行如下命令完成flannel的安装:

kubectl apply -f kube-flannel.yml

执行结果

[!important]

对于命令kubectl apply -f kube-flannel.yml只需要在master节点上执行即可,也就是说,你的文件kube-flannel.yml也是要在master节点上的。

但是当你在24年中国禁用Docker以后,你这里就会遇到镜像拉取失败,就算你开启了Docker镜像加速,能够使用Docker下载国外的镜像,你也是不能够安装成功的

原因是在1.24之后的版本之后kubelet 彻底移除了dockershim,改为默认使用Containerd,并且默认使用k8s.io的命名空间。

这句话的意思就是,就算你能够将镜像下载到本地,并且修改了kube-flannel.yml文件中的镜像为本地的名称,你也无法直接使用这一镜像。

[!note]

把镜像下载到本地,然后修改kube-flannel.yml文件中的镜像为本地的名称,这种方法我试过,确实不可以😭,各位不要再固执了。

但是上诉描述确实给我提供了一种思路,那么我们是否可以把我们的镜像导入Containerd中呢?这样不久能够使用本地镜像了嘛?

这里我提供我的实现方式

  1. 根据kube-flannel.yml中镜像的版本,先把镜像下载到本地:

    [!caution]

    master和node节点都要操作。

    docker pull flannel/flannel:v0.25.6
    docker pull flannel/flannel-cni-plugin:v1.5.1-flannel2
    

    下载成功

  2. 由于现在kubelet默认使用Containerd,所以我们将镜像导入Containerd就好:

    [!caution]

    master和node节点都要操作。

    #1. 保存镜像为tar文件
    docker save flannel/flannel-cni-plugin:v1.5.1-flannel2 -o flannel-cni-plugin.tar
    docker save flannel/flannel:v0.25.6 -o flannel-v0.25.6.tar
    
    #2.导入 tar 文件到 containerd 的 k8s.io命名空间中
    ctr -n k8s.io images import flannel-cni-plugin.tar
    ctr -n k8s.io images import flannel-v0.25.6.tar
    
    #3. 导入成功之后,你可以验证导入的镜像
    ctr -n k8s.io images list
    
  3. 之后修改kube-flannel.yml文件为只是用本地镜像:

    [!caution]

    只操作master节点即可。

    imagePullPolicy: Never
    

    也就是在每一个image下面添加:imagePullPolicy: Never即可。

在上诉步骤都完成之后,执行kubectl apply -f kube-flannel.yml即可。

接下来你可以使用命令来查看flannel是否安装成功:

kubectl get nodes -A

或者使用:

kubectl get pods -A

如果都为Ready或者Running,那么就安装成功啦🎉🎉🎉

[!tip]

下面操作依旧只在master节点执行即可,插件使用的是DaemonSet的控制器,它会在每个节点上都运行。

2.4 测试kubernetes 集群

默认国内已无法直接从hub.docker上拉取镜像,不管是docker还是k8s的runtime(containerd)

所以在测试之前,由于我们的k8s使用的是Containerd,为了防止后面拉取镜像超时,所以我们来改一下配置。

Containerd配置镜像加速:

vim /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
            endpoint=[
                "https://dockerproxy.com",
                "https://mirror.baidubce.com",
                "https://ccr.ccs.tencentyun.com",
                "https://docker.m.daocloud.io",
                "https://docker.nju.edu.cn",
                "https://docker.mirrors.ustc.edu.cn",
                "https://registry-1.docker.io",
                "https://hbv0b596.mirror.aliyuncs.com"
            ]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]
            endpoint=[
                "https://dockerproxy.com",
                "https://mirror.baidubce.com",
                "https://ccr.ccs.tencentyun.com",
                "https://docker.m.daocloud.io",
                "https://docker.nju.edu.cn",
                "https://docker.mirrors.ustc.edu.cn",
                "https://hbv0b596.mirror.aliyuncs.com",
                "https://k8s.m.daocloud.io",
                "https://docker.mirrors.ustc.edu.cn",
                "https://hub-mirror.c.163.com"
            ]

配置之后重载服务:

systemctl restart containerd

[!important]

修改加速源这一步master和node节点都需要做。否则就会出现如下问题:

kubectl get pod

kubectl describe pod pod的名称

部署nginx 测试:

# 部署nginx
kubectl create deployment nginx --image=nginx

命令解释:

  • kubectl:Kubernetes 的命令行工具,用于与 Kubernetes 集群进行交互。
  • create deployment:表示创建一个新的部署资源。
  • nginx:这是部署的名称。在这个例子中,部署将被命名为 nginx
  • --image=nginx:这个部分指定了容器所使用的镜像。在这个例子中,容器使用的是官方的 nginx 镜像,默认拉取自 Docker Hub 的 nginx 镜像仓库。

执行此命令后,Kubernetes 会创建一个包含 Nginx 容器的部署,并确保所需数量的副本(默认是 1)在集群中运行。

#暴露端口
kubectl expose deployment nginx --port=80 --type=NodePort

命令解释:

  • kubectl:Kubernetes 的命令行工具,用于与 Kubernetes 集群进行交互。

  • expose deployment:表示将指定的部署暴露为一个服务(Service)。通过这个服务,其他应用或用户可以访问这个部署中的应用。

  • nginx:这是要暴露的部署的名称。在这里,指定了 nginx 部署。

  • --port=80:指定服务的端口号。这里的 80 是服务的端口,表示访问这个服务时使用的端口。

  • --type=NodePort:指定服务的类型。NodePort 类型的服务会在每个节点上开放一个特定的端口(30000-32767 之间的端口),并将流量转发到服务的 port 上。在这种情况下,可以通过节点的 IP 地址和指定的端口号来访问 Nginx 服务。

执行这条命令后,Kubernetes 会创建一个类型为 NodePort 的服务,允许用户通过节点的 IP 地址和端口 80 来访问 Nginx 部署。NodePort 服务会将流量路由到相应的 Nginx Pod 上。

#查看pod的运行状态
kubectl get pod

#查看service的运行状态
kubectl get service

之后在浏览器上通过IP+端口的形式访问:

node节点访问

master节点访问

[!tip]

可见将nginx部署好了之后,可以master和node节点都能够访问了。

3. 资源管理

3.1 资源管理介绍

在kubernetes中,所有的内容都抽象为资源,用户需要通过操作资源来管理kubernetes。

kubernetes的本质上就是一个集群系统,用户可以在集群中部署各种服务,所谓的部署服务,其实就是在kubernetes集群中运行一个个的容器,并将指定的程序跑在容器中。

kubernetes的最小管理单元是pod而不是容器,所以只能将容器放在Pod中,而kubernetes一般也不会直接管理Pod,而是通过Pod控制器来管理Pod的。

Pod可以提供服务之后,就要考虑如何访问Pod中服务,kubernetes提供了Service资源实现这个功能。

当然,如果Pod中程序的数据需要持久化,kubernetes还提供了各种存储系统。

学习kubernetes的核心,就是学习如何对集群上的Pod、Pod控制器、Service、存储等各种资源进行操作

3.2 资源管理方式

  • 命令式命令行管理:直接使用命令去操作kubernetes资源

    与命令式对象管理类似,但更关注使用 kubectl 提供的高级命令来直接管理集群资源。例如,使用 kubectl run 来启动一个 Pod,或者 kubectl expose 来暴露服务。这种方式适合快速测试或临时操作。

    kubectl run nginx-pod --image=nginx:1.17.1 --port=80
    
  • 命令式对象配置:通过命令配置和配置文件去操作kubernetes资源

    通过命令直接创建、更新或删除 Kubernetes 资源对象。常用的命令包括 kubectl create, kubectl apply, kubectl delete 等。命令式管理方式直观且操作简单,但不适合进行大规模或复杂的集群管理。

    kubectl create/patch -f nginx-pod.yaml
    
  • 声明式对象配置:通过apply命令和配置文件去操作kubernetes资源

    通过编写 YAML 或 JSON 格式的配置文件,声明所需的资源状态,然后使用 kubectl apply -f 命令来创建或更新这些资源。Kubernetes 会自动确保集群的实际状态与声明的状态一致。这种方式适合长期维护和版本控制资源配置。

    kubectl apply -f nginx-pod.yaml
    
管理方式 操作对象 适用环境 优点 缺点
命令式命令行管理 单个资源,通过命令行直接操作 快速测试、临时操作、调试 简单快捷,适合临时性任务或快速验证配置 操作不可重复性,难以追踪和审计,容易导致不一致性
命令式对象配置 YAML/JSON 配置文件,通过命令行应用 小规模集群管理,简单配置管理 直观易操作,适合对特定资源进行快速创建或修改 难以管理复杂或大规模的配置,难以版本控制,易出错
声明式对象配置 YAML/JSON 配置文件,通过命令行应用 长期维护、大规模集群管理,版本控制,CI/CD 环境 自动化确保集群状态与声明一致,易于版本控制和回滚 配置较为复杂,学习曲线较陡峭,初次设置耗时较长

3.2.1 命令式命令行管理

kubectl命令

kubectl是kubernetes集群的命令行工具,通过它能够对集群本身进行管理,并能够在集群上进行容器化应用的安装部署。kubectl命令的语法如下:

kubectl [command] [type] [name] [flags]

comand:指定要对资源执行的操作,例如create、get、delete

type:指定资源类型,比如deployment、pod、service

name:指定资源的名称,名称大小写敏感

flags:指定额外的可选参数

例如:

  • 查看所有pod

    kubectl get pod 
    
  • 查看某个pod

    kubectl get pod pod_name
    
  • 查看某个pod,以yaml格式展示结果

    kubectl get pod pod_name -o yaml
    

3.2.1.1 资源类型(Type)

kubernetes中所有的内容都抽象为资源,可以通过下面的命令进行查看:

kubectl api-resources

经常使用的资源有下面这些:

资源分类 资源名称 缩写 资源作用
集群级别资源 nodes no 集群组成部分
集群级别资源 namespaces ns 隔离Pod
pod资源 pods po 装载容器
pod资源控制器 replicationcontrollers rc 控制pod资源
pod资源控制器 replicasets rs 控制pod资源
pod资源控制器 deployments deploy 控制pod资源
pod资源控制器 daemonsets ds 控制pod资源
pod资源控制器 jobs 控制pod资源
pod资源控制器 cronjobs cj 控制pod资源
pod资源控制器 horizontalpodautoscalers hpa 控制pod资源
pod资源控制器 statefulsets sts 控制pod资源
服务发现资源 services svc 统一pod对外接口
服务发现资源 ingress ing 统一pod对外接口
存储资源 volumeattachments 存储
存储资源 persistentvolumes pv 存储
存储资源 persistentvolumeclaims pvc 存储
配置资源 configmaps cm 配置
配置资源 secrets 配置

3.2.1.2 操作(command)

kubernetes允许对资源进行多种操作,可以通过–help查看详细的操作命令

kubectl --help

经常使用的操作有下面这些:

命令分类 命令 翻译 命令作用
基本命令 create 创建 创建一个资源
基本命令 edit 编辑 编辑一个资源
基本命令 get 获取 获取一个资源
基本命令 patch 更新 更新一个资源
基本命令 delete 删除 删除一个资源
基本命令 explain 解释 展示资源文档
运行和调试 run 运行 在集群中运行一个指定的镜像
运行和调试 expose 暴露 暴露资源为Service
运行和调试 describe 描述 显示资源内部信息
运行和调试 logs 日志输出容器在 pod 中的日志 输出容器在 pod 中的日志
运行和调试 attach 缠绕进入运行中的容器 进入运行中的容器
运行和调试 exec 执行容器中的一个命令 执行容器中的一个命令
运行和调试 cp 复制 在Pod内外复制文件
运行和调试 rollout 首次展示 管理资源的发布
运行和调试 scale 规模 扩(缩)容Pod的数量
运行和调试 autoscale 自动调整 自动调整Pod的数量
高级命令 apply rc 通过文件对资源进行配置
高级命令 label 标签 更新资源上的标签
其他命令 cluster-info 集群信息 显示集群信息
其他命令 version 版本 显示当前Server和Client的版本

下面以一个namespace / pod的创建和删除简单演示下命令的使用:

  • 创建一个namespace

    kubectl create namespace namespace名称
    

    这里你也可以用简写,例如:

    kubectl create ns namespace名称
    
  • 获取namespace

    kubectl get ns
    

  • 在此namespace下创建并运行一个nginx的Pod

    kubectl run pod的名称 --image=nginx:latest -n namespace名称
    

    这里其实可以不用写版本,不写的化默认就是latest。

  • 查看新创建的pod

    kubectl get pod -n namespace名称
    

  • 显示Pod资源内部信息

    kubectl describe pod pod名称 -n namespace名称
    

  • 删除Pod

    kubectl delete pod pod名称 -n namespace名称
    

  • 删除指定的namespace

    kubectl delete ns namespace名称
    

[!tip]

如果你的操作后面没有跟上namespace名称,那么默认就是default名称空间。

3.2.2 命令式对象配置

命令式对象配置就是使用命令配合配置文件一起来操作kubernetes资源。

  1. 创建一个nginxpod.yaml:

    vim nginxpod.yaml
    

    内容如下:

    apiVersion: v1
    kind: Namespace #类型是namespace类型
    metadata:
      name: dev #namespace的名称为dev
    
    --- #用来分隔多个资源定义的标记,这样,Kubernetes 在处理该文件时会分别解析和处理每个资源
    
    apiVersion: v1
    kind: Pod #类型是Pod
    metadata:
      name: nginxpod #Pod的名称为nginxpod
      namespace: dev #所使用的namespace为dev
    spec: #描述
      containers:
      - name: nginx-containers #容器的名称
        image: nginx:latest #镜像的名称以及版本
    
  2. 执行create命令,创建资源并且查看:

    kubectl create -f nginxpod.yaml
    

    或者使用如下命令查看:

    kubectl get -f nginxpod.yaml
    

  3. 执行delete命令,删除资源:

    kubectl delete -f nginxpod.yaml
    

[!tip]

总结:命令式对象配置的方式操作资源,可以简单的认为:命令 + yaml配置文件(里面是命令需要的各种参数)

3.2.3 声明式对象配置

声明式对象配置跟命令式对象配置很相似,但是它只有一个命令apply。

继续使用3.2.2小节的文件:

kubectl apply -f nginxpod.yaml

其实声明式对象配置就是使用apply描述一个资源最终的状态(在yaml中定义状态)

使用apply操作资源:

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

[!note]

  1. 扩展:kubectl可以在node节点上运行吗 ?

    kubectl的运行是需要进行配置的,它的配置文件是$HOME/.kube,如果想要在node节点运行此命令,需要将master上的.kube文件复制到node节点上,即在master节点上执行下面操作:

    scp  -r  HOME/.kube   node1: HOME/
    
  2. 拓展:三种方式应该怎么用 ?

    • 创建/更新资源:使用声明式对象配置 kubectl apply -f XXX.yaml

    • 删除资源:使用命令式对象配置 kubectl delete -f XXX.yaml

    • 查询资源:使用命令式命令行管理 kubectl get(describe) 资源名称

4. 实战入门

本章节将介绍如何在kubernetes集群中部署一个nginx服务,并且能够对其进行访问。

4.1 Namespace

[!tip]

如果你了解Nacos的话,这里其实很好理解。

Namespacekubernetes系统中的一种非常重要资源,它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离

默认情况下,kubernetes集群中的所有的Pod都是可以相互访问的。但是在实际中,可能不想让两个Pod之间进行互相的访问,那此时就可以将两个Pod划分到不同的Namespace下。kubernetes通过将集群内部的资源分配到不同的Namespace中,可以形成逻辑上的”组”,以方便不同的组的资源进行隔离使用和管理。

可以通过kubernetes的授权机制,将不同的namespace交给不同租户进行管理,这样就实现了多租户的资源隔离。此时还能结合kubernetes的资源配额机制,限定不同租户能占用的资源,例如CPU使用量、内存使用量等等,来实现租户可用资源的管理。

kubernetes在集群启动之后,会默认创建几个namespace:

kubectl get ns

下面来看Namespace资源的具体操作:

相关的命令:

  • 查看所有的命名空间:

    kubectl get ns
    
  • 查看指定的命名空间:

    kubectl get ns 命名空间名称
    
  • 指定输出格式:

    kubectl get ns 命名空间名称  -o 格式参数(比如json、yaml)
    
  • 查看命名空间详情:

    kubectl describe ns 命名空间名称
    
  • 创建命名空间:

    kubectl create ns 命名空间名称
    
  • 删除命名空间:

     kubectl delete ns 命名空间名称
    

以上都是命令行的方式,现在看看配置文件的方式。

首先准备一个yaml文件,例如:ns-dev.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: dev

然后就可以执行对应的创建和删除命令了:

  • 创建:kubectl create -f ns-dev.yaml

  • 删除:kubectl delete -f ns-dev.yaml

4.2 Pod

Pod是kubernetes集群进行管理的最小单元,程序要运行必须部署在容器中,而容器必须存在于Pod中。

Pod可以认为是容器的封装,一个Pod中可以存在一个或者多个容器。

kubernetes在集群启动之后,集群中的各个组件也都是以Pod方式运行的。可以通过下面命令查看:

kubectl get pod -n kube-system

常用的一些Pod相关的命令:

kubernetes没有提供单独运行Pod的命令,都是通过Pod控制器来实现的

  • 创建并运行

    kubectl run Pod名称 --image=镜像名称:镜像版本 --port=端口 --namespace namespace名称 
    

    创建之前,必须要有这个容器

    例如:

    kubectl run nginx --image=nginx:latest --port=80 --namespace dev 
    

  • 查看pod信息

    kubectl get pods -n namespace名称
    

    例如:

    kubectl get pods -n dev
    

    当然你也可以查看详细的信息,使用describe参数即可,例如:

    kubectl describe pod nginx -n dev
    

  • 访问Pod

    获取Pod的IP:

    kubectl get pods -n namespace名称 -o wide
    

    -o wide:这个选项指定输出的格式。-o wide 表示输出将包含更详细的信息。除了标准的Pod名称、状态、就绪状态、重启次数等,-o wide 还会显示额外的信息,例如:Pod所运行的节点(Node)名称、Pod的IP地址、容器镜像的名称。

    例如:

    kubectl get pods -n dev -o wide
    

    访问Pod:

    curl http://10.244.1.8:80
    

  • 删除指定Pod

    kubectl delete pod nginx -n namespace名称
    

    例如:

    kubectl delete pod nginx -n dev
    

    此时,显示删除Pod成功,但是再查询,发现又新产生了一个 ,这是因为当前Pod是由Pod控制器创建的,控制器会监控Pod状况,一旦发现Pod死亡,会立即重建,此时要想删除Pod,必须删除Pod控制器。

    先来查询一下当前namespace下的Pod控制器:

    delete deploy nginx -n namespace名称
    

    所以,以后要想他重新创建,直接删除控制器即可。


以上都是命令行的方式,现在看看配置文件的方式。

首先创建一个pod-nginx.yaml,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: dev
spec:
  containers:
  - image: nginx:latest
    name: pod
    ports:
    - name: nginx-port
      containerPort: 80
      protocol: TCP

然后就可以执行对应的创建和删除命令了:

  • 创建:kubectl create -f pod-nginx.yaml

  • 删除:kubectl delete -f pod-nginx.yaml

4.3 Label

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

Label的特点:

  • 一个Label会以key/value键值对的形式附加到各种对象上,如NodePodService等等
  • 一个资源对象可以定义任意数量的Label ,同一个Label也可以被添加到任意数量的资源对象上去
  • Label通常在资源对象定义时确定,当然也可以在对象创建后动态添加或者删除

可以通过Label实现资源的多维度分组,以便灵活、方便地进行资源分配、调度、配置、部署等管理工作。

[!note]

一些常用的Label 示例如下:

  • 版本标签:”version”:”release”, “version”:”stable”……
  • 环境标签:”environment”:”dev”,”environment”:”test”,”environment”:”pro”
  • 架构标签:”tier”:”frontend”,”tier”:”backend”

标签定义完毕之后,还要考虑到标签的选择,这就要使用到Label Selector,即:Label用于给某个资源对象定义标识,Label Selector用于查询和筛选拥有某些标签的资源对象

当前有两种Label Selector

  • 基于等式的Label Selector等式选择器

    name = slave: 选择所有包含Label中key=”name”且value=”slave”的对象

    env != production: 选择所有包括Label中的key=”env”且value不等于”production”的对象

  • 基于集合的Label Selector集合选择器

    name in (master, slave): 选择所有包含Label中的key=”name”且value=”master”或”slave”的对象

    name not in (frontend): 选择所有包含Label中的key=”name”且value不等于”frontend”的对象

标签的选择条件可以使用多个,此时将多个Label Selector进行组合,使用逗号”,“进行分隔即可。例如:

name=slave,env!=production

name not in (frontend),env!=production

常用的命令如下:

  • 添加标签

    kubectl label pod pod名称 -n 命名空间名称 标签key=标签value 
    

    例如:

    kubectl label pod nginx version=latest -n dev
    

  • 查看标签

    使用--show-labels参数来查看标签

    kubectl get pod pod名称 -n 命名空间名称 --show-labels
    

    例如:

    kubectl get pod nginx -n dev --show-labels
    

  • 更新标签

    使用--overwrite参数来更新标签

    kubectl label pod pod名称 -n 命名空间名称 app=mynewapp --overwrite
    

    例如:

    kubectl label pod nginx version=1.0 -n dev --overwrite
    

  • 删除标签

    使用标签key后面跟上一个减号(-)来表示删除标签

    kubectl label pod pod名称 -n 命名空间名称 标签key-
    

    例如:

    kubectl label pod nginx -n dev run- 
    

  • 筛选标签

    使用参数-l来筛选,通过标签来筛选出Pod

    kubectl get pod -l -n 命名空间名称 标签key=标签value 
    

    例如:

    kubectl get pod -n dev -l version=1.0  --show-labels
    


以上都是命令行的方式,现在看看配置文件的方式,配置文件的方式很简单,就是直接再定义的时候就出标签的定义即可。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: dev
  labels:
    version: "3.0" 
    env: "test"
spec:
  containers:
  - image: nginx:latest
    name: pod
    ports:
    - name: nginx-port
      containerPort: 80
      protocol: TCP

然后就可以执行对应的更新命令了:kubectl apply -f pod-nginx.yaml

4.4 Deployment

在kubernetes中,Pod是最小的控制单元,但是kubernetes很少直接控制Pod,一般都是通过Pod控制器来完成的。Pod控制器用于pod的管理,确保pod资源符合预期的状态,当pod的资源出现故障时,会尝试进行重启或重建pod。这就是我们之前发现为什么删除Pod是始终删不掉的,你删掉一个,他就给你新建一个。

Deployment 是Kubernetes中的一种资源对象,允许用户以声明的方式管理应用程序的副本。它确保指定数量的Pod副本在集群中运行,并且能够自动处理Pod的创建、更新和删除。

在kubernetes中Pod控制器的种类有很多,本章节只介绍一种:Deployment

Deployment的主要功能:

  • 声明式管理:用户可以通过定义所需的状态(如Pod数量、使用的容器镜像等),Kubernetes会自动将实际状态调整到期望状态。
  • 滚动更新:支持在不停止服务的情况下逐步更新应用程序。用户可以控制更新的速度、策略和条件。
  • 回滚:如果更新出现问题,可以方便地将Deployment回滚到之前的版本。
  • 扩缩容:可以随时调整Pod的副本数,以应对流量变化。

Deployment的基本组成部分:

  • spec:定义Deployment的所需状态,包括Pod的模板、副本数和更新策略。
    • replicas:指定所需的Pod副本数量。
    • selector:定义如何选择Pod,通常使用标签选择器。
    • template:定义Pod的模板,包括容器的配置(镜像、端口、环境变量等)。

常用的命令如下:

  • 创建Deployment

    kubectl create deployment 名称  [参数] 
    

    例如:

    kubectl create deploy nginx --image=nginx:latest --port=80 --replicas=3 -n dev
    

    其中:

    • –image:指定pod的镜像

    • –port:指定端口

    • –replicas:指定创建pod数量

    • –namespace:指定namespace

    查看创建的pod:

  • 查看Deployment信息

    kubectl get deploy -n 命名空间名称
    

    例如:

    kubectl get deploy -n dev
    

    其中UP-TO-DATE表示成功升级的副本数量,AVAILABLE表示可用副本的数量

    如果要查看Deployment信息的详细信息,还是加个参数即可:describe

    kubectl describe deploy -n dev
    

  • 删除Deployment

    kubectl delete deploy Deployment名称 -n 命名空间名称
    

    例如:

    kubectl delete deploy nginx -n dev
    


以上都是命令行的方式,现在看看配置文件的方式,首先创建一个deploy-nginx.yaml,内容如下:

apiVersion: apps/v1
kind: Deployment #类型选择Deployment
metadata:
  name: nginx #Deployment名称
  namespace: dev #所在的命名空间
spec:
  replicas: 3 #副本的数量
  selector:
    matchLabels:
      run: nginx #标签 run=nginx
  template: #这里就是Pod模板,下面都是Pod信息
    metadata:
      labels:
        run: nginx
    spec:
      containers:
      - image: nginx:latest
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP

然后就可以执行对应的创建和删除命令了:

  • 创建:kubectl create -f deploy-nginx.yaml

  • 删除:kubectl delete -f deploy-nginx.yaml

4.5 Service

通过上小节,我们已经能够利用Deployment来创建一组Pod来提供具有高可用性的服务。

虽然每个Pod都会分配一个单独的Pod IP,然而却存在如下两问题:

  • Pod IP 会随着Pod的重建产生变化
  • Pod IP 仅仅是集群内可见的虚拟IP,外部无法访问

这样对于访问这个服务带来了难度。因此,kubernetes设计了Service来解决这个问题。

Service可以看作是一组同类Pod对外的访问接口。借助Service,应用可以方便地实现服务发现和负载均衡。

Service 是Kubernetes中的一种资源对象,旨在提供持久的网络服务,通常用于将流量路由到一组动态变化的Pod。Service定义了一种规则,用于将网络流量转发到匹配的Pod。

Service的主要功能:

  • 负载均衡:Service可以在后端Pod之间分配流量,提供基本的负载均衡。
  • 服务发现:Kubernetes集群中的其他组件可以通过Service名称来访问Pod,而不必直接使用Pod的IP地址。
  • 稳定的访问入口:即使Pod被删除或重建,Service仍然可以通过固定的DNS名称访问。

Service的类型:

Kubernetes提供了多种类型的Service,每种类型适用于不同的场景:

  • ClusterIP(默认类型):为Service分配一个内部IP地址,其他Pod可以通过该IP访问Service。它只在集群内部可用

    apiVersion: v1
    kind: Service
    metadata:
      name: myapp-service
    spec:
      type: ClusterIP #指定类型
      ports:
        - port: 80
          targetPort: 8080
      selector:
        app: myapp
    
  • NodePort:在每个节点上分配一个特定端口,外部用户可以通过节点IP和NodePort访问Service。适用于简单的外部访问场景

    apiVersion: v1
    kind: Service
    metadata:
      name: myapp-service
    spec:
      type: NodePort
      ports:
        - port: 80
          targetPort: 8080
          nodePort: 30007
      selector:
        app: myapp
    
  • LoadBalancer:在云提供商的环境中,创建一个外部负载均衡器,并将流量路由到Service。这种类型适用于需要外部访问的应用

    apiVersion: v1
    kind: Service
    metadata:
      name: myapp-service
    spec:
      type: LoadBalancer
      ports:
        - port: 80
          targetPort: 8080
      selector:
        app: myapp
    
  • ExternalName:通过CNAME记录将Service映射到外部服务,使得Kubernetes内部可以访问外部的DNS名称。

    apiVersion: v1
    kind: Service
    metadata:
      name: myapp-service
    spec:
      type: ExternalName
      externalName: example.com
    

Service的工作原理:

  • 选择器(Selector):Service通过标签选择器将流量路由到符合条件的Pod。Service会不断监测与其相关的Pod,并根据Pod的状态更新流量转发规则。
  • 虚拟IP(ClusterIP):Service会分配一个虚拟IP地址,所有流量都通过该IP地址访问后端Pod。
  • DNS:Kubernetes会为每个Service自动创建一个DNS条目,允许其他Pod通过Service名称进行访问。

常用命令:

  • 查看service:

    kubectl get services
    

    当然也可以简写:

    kubectl get svc
    
  • 删除Service:

    kubectl delete svc service名称
    

示例操作一:创建集群内部可访问的Service

  1. 暴露Service:

    kubectl expose deploy nginx --name=svc-nginx1 --type=ClusterIP --port=80 --target-port=80 -n dev
    

    前提是首先你要有名为nginx的deploy。

    参数解读:

    • --name:指定Service的名称,一般我们加上前缀svc,svc就是Service的缩写。
    • --type:指定Service的类型,这里我选择的是ClusterIP,他只可在集群内部访问,适合内部服务之间的通信
    • --port:这是是Service暴露的端口,这意味着其他Pod可以通过此端口访问Service。
    • --target-port:指定Service将流量转发到后端Pod的 80 端口。这个端口是Pod中的容器监听的端口。

  2. 查看Service

    kubectl get svc svc-nginx1 -n dev -o wide
    

    这里产生了一个CLUSTER-IP,这就是Service的IP,在Service的生命周期中,这个地址是不会变动的,可以通过这个IP访问当前Service对应的Pod。

    curl 10.107.231.235:80
    

示例操作二:创建集群外部也可访问的Service

上面创建的Service的type类型为ClusterIP,这个ip地址只用集群内部可访问,如果需要创建外部也可以访问的Service,需要修改type为NodePort

kubectl expose deploy nginx --name=svc-nginx2 --type=NodePort --port=80 --target-port=80 -n dev
kubectl get svc  svc-nginx2  -n dev -o wide

此时查看,会发现出现了NodePort类型的Service,而且有一对Port(80:30987/TCP)

接下来就可以通过集群外的主机访问节点IP:30987访问服务了,例如在的电脑主机上通过浏览器访问下面的地址:

4.6 小节

至此,已经掌握了Namespace、Pod、Deployment、Service资源的基本操作,有了这些操作,就可以在kubernetes集群中实现一个服务的简单部署和访问了,但是如果想要更好的使用kubernetes,就需要深入学习这几种资源的细节和原理。

5. Pod详解

5.1 Pod介绍

5.1.1 Pod结构

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

  • 用户程序所在的容器,数量可多可少,称为用户容器。

  • Pause容器,这是每个Pod都会有的一个根容器,它的作用有两个:

    1. 可以以它为依据,评估整个Pod的健康状态

    2. 可以在根容器上设置IP地址,其它容器都通过此IP(Pod IP),以实现Pod内部的网路通信

    这里是Pod内部的通讯,Pod的之间的通讯采用虚拟二层网络技术来实现,我们当前环境用的是Flannel

5.1.2 Pod定义

下面是Pod的资源清单(yaml文件):

apiVersion: v1     #必选,版本号,例如v1
kind: Pod         #必选,资源类型,例如 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的绝对路径,应少于512字符
      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-shares参数
        memory: string  #内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
      requests: #资源请求的设置
        cpu: string    #Cpu请求,容器启动的初始可用数量
        memory: string #内存请求,容器启动的初始可用数量
    lifecycle: #生命周期钩子
        postStart: #容器启动后立即执行此钩子,如果执行失败,会根据重启策略进行重启
        preStop: #容器终止前执行此钩子,无论结果如何,容器都会终止
    livenessProbe:  #对Pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器
      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       #容器启动完成后首次探测的时间,单位为秒
       timeoutSeconds: 0          #对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
       periodSeconds: 0           #对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
       successThreshold: 0
       failureThreshold: 0
       securityContext:
         privileged: false
  restartPolicy: [Always | Never | OnFailure]  #Pod的重启策略
  nodeName: <string> #设置NodeName表示将该Pod调度到指定到名称的node节点上
  nodeSelector: obeject #设置NodeSelector表示将该Pod调度到包含这个label的node上
  imagePullSecrets: #Pull镜像时使用的secret名称,以key:secretkey格式指定
  - name: string
  hostNetwork: false   #是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
  volumes:   #在该pod上定义共享存储卷列表
  - name: string    #共享存储卷名称 (volumes类型有很多种)
    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

上面的yaml文件我只是列出来了常用的参数,不用死记硬背,你可以通过命令来查看:

kubectl explain 资源类型

上面这些展示的都是一级属性,如果你想要查看某个一级属性的子属性(只有那些为非String类型的属性才有子属性),可以通过.的形式来完成。

例如我想要查看Pod资源下的spec属性的子属性:

kubectl explain pod.spec

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

一级属性 描述
apiVersion 指定资源对象使用的 Kubernetes API 版本。决定了资源对象所使用的 Kubernetes API 的版本及其行为。
版本号可以用 kubectl api-versions 查询到。
kind 指定资源对象的类型,例如 PodServiceDeploymentConfigMap 等。
可以使用kubectl api-resources命令查询到。
metadata 包含资源对象的元数据,包括名称、命名空间、标签、注解等。
spec 定义资源对象的具体规格和期望状态,内容根据不同的资源类型而不同。这是配置中最重要的一部分
status 描述资源对象的当前状态,通常由 Kubernetes 系统自动生成和管理,不需要自己维护。

在上面的属性中,spec是接下来研究的重点,继续看下它的常见子属性:

[!tip]

这里我只展示Pod的spec常见的子属性,其他资源类型的spec常见的子属性留在后面讲。

子属性 描述
containers 定义 Pod 内的容器列表,每个容器都需要配置镜像、名称等,用于定义容器的详细信息。
volumes 定义 Pod 内可供容器使用的存储卷,卷可以挂载到多个容器中,用于定义Pod上面挂在的存储信息。
restartPolicy 定义 Pod 的重启策略,可选值为 AlwaysOnFailureNever
nodeSelector 将 Pod 调度到具有特定标签的节点上。
affinity 定义 Pod 的亲和性规则,用于影响调度决策,包括节点亲和性和 Pod 亲和性。
tolerations 定义 Pod 可以容忍的节点污点(taints),允许 Pod 调度到有特定污点的节点上。
serviceAccountName 指定 Pod 使用的 ServiceAccount,以便访问 Kubernetes API。
imagePullSecrets 提供私有镜像仓库的凭证,使 Pod 能够拉取私有镜像。
hostNetwork 指定是否将 Pod 的网络命名空间与主机共享,若为 true,则 Pod 将使用主机的网络。
一般我们都设置为false,防止端口冲突。
dnsPolicy 定义 Pod 的 DNS 策略,如 ClusterFirst(默认)或 Default

5.2 Pod配置

本小节主要来研究pod.spec.containers属性,这也是pod配置中最为关键的一项配置:

kubectl explain pod.spec.containers
KIND:       Pod
VERSION:    v1
FIELD: containers <[]Container> # 数组,代表可以有多个容器
DESCRIPTION: #描述省略
    
FIELDS:
  args	<[]string> #容器启动时传递的参数,通常用于覆盖镜像中的默认命令行参数
  
  command	<[]string> #覆盖容器镜像中定义的默认启动命令。
  
  env	<[]EnvVar> #定义容器的环境变量,每个环境变量都是一个键值对。
  
  envFrom	<[]EnvFromSource> #从 ConfigMap 或 Secret 中导入环境变量。
  
  image <string> #容器使用的镜像名称(包含标签),例如 nginx:1.14.2。
  
  imagePullPolicy	<string> #镜像拉取策略,值可以是 Always、IfNotPresent 或 Never。
  
  lifecycle	<Lifecycle> #定义容器的生命周期钩子,允许在容器启动或终止时执行自定义操作。
  
  livenessProbe <Probe> #用于检测容器是否活着的探针,如果探测失败,Kubernetes 将重启该容器。
  
  name  <string> -required- #容器的名称,在同一 Pod 内必须唯一
  
  ports	<[]ContainerPort> #容器公开的端口列表,用于其他容器或服务访问。
  
  readinessProbe	<Probe> #用于检测容器是否准备好接受流量的探针,决定是否将流量路由到该容器。
  
  resizePolicy	<[]ContainerResizePolicy> #定义容器资源的调整策略,用于支持资源调整操作(Kubernetes1.27及以上版本本)。
  
  resources	<ResourceRequirements> #定义容器的资源需求和限制,如 CPU 和内存。
  
  restartPolicy <string> #容器的重启策略,常见于 Pod 级别的定义。
  
  securityContext	<SecurityContext> #容器的安全上下文,定义容器的权限和访问控制,如运行用户、权限等。
  
  startupProbe	<Probe> #用于检测容器启动时是否处于健康状态的探针,通常用于启动时间较长的应用程序。
  
  stdin <boolean> #指定容器是否应分配标准输入流。
  
  stdinOnce	<boolean> #指定容器是否只在第一次打开标准输入时提供输入流。
  
  terminationMessagePath	string> #容器终止时记录信息的文件路径,通常用于诊断容器退出原因。
  
  terminationMessagePolicy	<string> #指定容器终止时记录信息的策略,值可以是 File 或 FallbackToLogsOnError。
  
  tty	<boolean> #指定是否为容器分配一个 TTY(终端仿真器),通常用于交互式会话。
  
  volumeDevices <[]VolumeDevice> #将块设备直接挂载到容器内,常用于特殊的存储需求。
  
  volumeMounts  <[]VolumeMount> # 将卷挂载到容器内的文件系统路径,供容器使用持久化存储或共享数据。
  
  workingDir	<string> #容器内的工作目录,如果未设置,则使用镜像的默认工作目录。

在下面小节中,我们将挑选几个常用的containers属性来讲解。

5.2.1 基本配置

创建pod-base.yaml文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-base
  namespace: dev
  labels:
    user: heima
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  - name: busybox
    image: busybox:1.30

上面定义了一个比较简单Pod的配置,里面有两个容器:

  • nginx:用1.17.1版本的nginx镜像创建,(nginx是一个轻量级web容器)
  • busybox:用1.30版本的busybox镜像创建,(busybox是一个小巧的linux命令集合)

查看Pod情况可以看见:

  • READY 1/2:表示当前Pod中有2个容器,其中1个准备就绪,1个未就绪
  • RESTARTS:重启次数,因为有1个容器故障了,Pod一直在重启试图恢复它

我们通过describe参数来查看这个Pod内部的一个详情:

kubectl describe pod pod-base -n dev

这是因为busybox是一个小巧的linux命令集合,他只是一个命令集合,并不是一种程序,busybox 容器内运行的命令在执行完后立即退出,容器就会终止并导致 Kubernetes 反复尝试重新启动它。所以后面我们回来解决容器立即退出的问题(参考5.2.3启动命令小节)。

5.2.2 镜像拉取策略(imagePullPolicy)

镜像拉取策略(Image Pull Policy)决定了 Kubernetes 在启动容器时如何处理镜像的拉取操作。主要有以下三种策略:

  1. Always:每次启动容器时,Kubernetes 都会尝试从镜像仓库拉取最新版本的镜像,无论该镜像是否已经存在于节点上。适用于于需要确保始终使用镜像仓库中最新镜像的场景,尤其是在开发环境中频繁更新镜像时。

  2. IfNotPresent:如果节点上已经存在指定的镜像,Kubernetes 就不会再从镜像仓库拉取;如果镜像不存在,才会从仓库拉取镜像。适用于镜像更新频率不高或希望减少不必要的网络流量的场景,通常是生产环境中的常用策略。

  3. Never: Kubernetes 永远不会从镜像仓库拉取镜像,只会使用节点上已有的镜像。如果节点上没有该镜像,容器将启动失败。适用于需要确保使用本地镜像,或在特殊场景下避免自动拉取镜像的情况。

如果未显式指定镜像拉取策略:镜像版本带有lasted标签的镜像 默认策略为 Always。其他标签或不是lasted的镜像默认策略为 IfNotPresent

5.2.3 启动命令(command)

在前面的案例中,一直有一个问题没有解决,就是的busybox容器一直没有成功运行,那么到底是什么原因导致这个容器的故障呢?

原来busybox并不是一个程序,而是类似于一个工具类的集合,kubernetes集群启动管理后,它会自动关闭。解决方法就是让其一直在运行,这就用到了command配置。

修改之前的pod-base.yaml文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-base
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done;"]

修改了文件,运行:

kubectl apply -f pod-base.yaml

执行报错:

他的意思就是Pod只允许这些字段的更新spec.containers[*].imagespec.initContainers[*].imagespec.activeDeadlineSecondsspec.tolerations等,所以我们只需要修改一下pod的名称,让他重新启动一个Pod,而不是对原Pod做更新:

apiVersion: v1
kind: Pod
metadata:
  name: pod-commond #修改这里的名称就会启动一个新的Pod
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done;"]

可见新建了一个Pod,而且很快就是Running状态了。

command,用于在pod中的容器初始化完毕之后运行一个命令。

稍微解释下上面命令的意思:

"/bin/sh","-c", 使用sh执行命令

touch /tmp/hello.txt; 创建一个/tmp/hello.txt 文件

while true;do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done; 每隔3秒向文件中写入当前时间

[!note]

这里补充一个命令:

kubectl exec  pod名称 -n 命名空间 -it -c 容器名称 -- /bin/sh

如果你学习过docker,那么你是对这个命令应该特别亲切的😁。

使用这个命令就可以进入某个容器的内部,然后进行相关操作了,比如,可以查看txt文件的内容:

kubectl exec pod-command -n dev -it -c busybox -- /bin/sh
tail -f /tmp/hello.txt

可见是OK的。

特别说明:

通过上面发现command已经可以完成启动命令和传递参数的功能,为什么这里还要提供一个args选项,用于传递参数呢?这其实跟docker有点关系,kubernetes中的commandargs两项其实是实现覆盖DockerfileENTRYPOINT的功能。

  1. 如果command和args均没有写,那么用Dockerfile的配置。
  2. 如果command写了,但args没有写,那么Dockerfile默认的配置会被忽略,执行输入的command
  3. 如果command没写,但args写了,那么Dockerfile中配置的ENTRYPOINT的命令会被执行,使用当前args的参数
  4. 如果command和args都写了,那么Dockerfile的配置被忽略,执行command并追加上args参数。

5.2.4 环境变量(env)

env 是 Kubernetes Pod 中的一个配置项,用于在容器内定义环境变量。通过 env,你可以将一些配置信息或依赖注入到容器内的运行环境中,从而影响应用程序的行为。

env 字段的结构:env 是一个数组,其中每个元素都是一个 EnvVar 对象。每个 EnvVar 对象表示一个环境变量,并包含以下两个主要字段:

  • name:环境变量的名称(key),这是一个必需字段。
  • value:环境变量的值(value),这是一个可选字段。如果不指定 value,可以使用 valueFrom 动态获取值。

env 的使用场景:

  1. 静态环境变量

    如果你有一些固定的配置(如数据库连接字符串、服务端点等),可以直接在 env 中指定。示例如下:

    env:
    - name: DATABASE_HOST
      value: "db.example.com"
    - name: DATABASE_PORT
      value: "5432"
    
  2. 动态环境变量(从配置文件或密钥管理中获取)

    使用 valueFrom 字段可以从 Kubernetes 的 ConfigMapSecretPod 的字段引用(如 Pod 名称、命名空间等)中获取动态值。

    常见的 valueFrom子字段包括:

    • configMapKeyRef:从 ConfigMap 中获取值。
    • secretKeyRef:从 Secret 中获取值。
    • fieldRef:引用 Pod 本身的字段值。
    • resourceFieldRef:引用容器的资源请求或限制值(如 CPU 或内存)。

    示例如下:

    env:
    - name: DATABASE_USER
      valueFrom:
        secretKeyRef:
          name: db-secrets
          key: username
    - name: DATABASE_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secrets
          key: password
    - name: POD_NAME
      valueFrom:
        fieldRef:
          fieldPath: metadata.name
    - name: NODE_NAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName
    

env 示例及解释:

containers:
- name: my-app
  image: my-app:latest
  env:
  - name: ENVIRONMENT
    value: "production"
  - name: LOG_LEVEL
    value: "debug"
  - name: CONFIG_FILE
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: config.yaml
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: db-secret
        key: password
  - name: POD_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.name

详细解释:

  1. ENVIRONMENTLOG_LEVEL:这些是静态环境变量,直接将字符串值 "production""debug" 赋给 ENVIRONMENTLOG_LEVEL 环境变量。
  2. CONFIG_FILE:从名为 app-configConfigMap 中获取 config.yaml 的值,并将其设置为 CONFIG_FILE 环境变量的值。
  3. DB_PASSWORD:从名为 db-secretSecret 中获取 password 键的值,并将其设置为 DB_PASSWORD 环境变量。Secret 是 Kubernetes 中用于存储敏感信息的对象,如密码、OAuth 令牌等。
  4. POD_NAME:使用 fieldRef 从 Pod 的元数据中获取当前 Pod 的名称,并将其设置为 POD_NAME 环境变量。这对于在日志记录或调试时识别具体的 Pod 非常有用。

其他高级用法:

  • 多容器共享环境变量:你可以在 Pod 中的多个容器之间共享某些环境变量,确保它们使用相同的配置或依赖。
  • 条件变量配置:可以根据不同的部署环境(如开发、测试、生产)配置不同的环境变量集合,以适应不同的运行需求。

下面我再来演示一下,创建pod-env.yaml文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-env
  namespace: dev
spec:
  containers:
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","while true;do /bin/echo $(date +%T);sleep 60; done;"]
    env: # 设置环境变量列表
    - name: "username"
      value: "admin"
    - name: "password"
      value: "123456"

env,环境变量,用于在pod中的容器设置环境变量。

5.2.5 端口设置(ports)

ports 是 Kubernetes Pod.spec.container 中的一个配置项,用于定义容器中应用暴露的端口。通过 ports 配置,Kubernetes 知道哪些端口需要对外暴露,以便与外部服务进行通信。

可以使用如下语句查看下ports支持的子选项:

kubectl explain pod.spec.containers.ports

ports 字段的结构:ports 是一个数组,其中每个元素都是一个 ContainerPort 对象。每个 ContainerPort 对象表示容器内应用暴露的一个端口,并包含以下主要字段:

  1. containerPort(必填):

    描述:容器内应用暴露的端口号,即容器内监听的端口。

    类型:整数。

    示例:containerPort: 8080

  2. name(可选):

    描述:端口的名称,用于识别端口。特别在有多个端口时可以通过名称区分不同的端口。

    类型:字符串。

    示例:name: http

  3. protocol(可选):

    描述:指定端口的协议类型。支持 TCPUDP,默认值为 TCP

    类型:字符串。

    示例:protocol: TCP

  4. hostPort(可选):

    描述:将容器的 containerPort 映射到宿主机的某个端口上。如果不指定,hostPort 默认为 containerPort

    类型:整数。

    示例:hostPort: 80

    [!caution]

    多个容器在同一节点上使用相同的 hostPort 可能会导致端口冲突。

  5. hostIP(可选):

    描述:指定将容器的 hostPort 绑定到宿主机的哪个 IP 地址上。如果未指定,端口将绑定到宿主机的所有接口上。

    类型:字符串。

    示例:hostIP: 192.168.1.1


高级用法:

  1. 使用 hostPort 暴露端口

    如果你希望将容器内的端口直接映射到宿主机的端口,可以使用 hostPort

    例如暴露 80端口到宿主机的 8080端口:

    containers:
    - name: my-web-server
      image: nginx:1.17.1
      ports:
      - containerPort: 80
        hostPort: 8080
    
  2. 多端口配置

    如果容器内有多个服务或应用,每个服务需要不同的端口,可以配置多个 ContainerPort:

    containers:
    - name: my-app
      image: my-app:latest
      ports:
      - containerPort: 80
        name: http
      - containerPort: 443
        name: https
      - containerPort: 8080
        name: api
    
  3. **指定 hostIP**:

    如果宿主机有多个网络接口或 IP 地址,并且希望将 hostPort绑定到特定的 IP 地址,可以使用 hostIP:

    containers:
    - name: my-web-server
      image: nginx:1.17.1
      ports:
      - containerPort: 80
        hostPort: 8080
        hostIP: 192.168.1.100
    

[!note]

重要注意事项:

  • 端口冲突:在同一个节点上运行多个使用相同 hostPort 的 Pod 可能导致端口冲突。为避免这种情况,建议尽量避免使用 hostPort,除非确有必要。
  • 安全性:映射到宿主机上的端口直接暴露在外部网络中,因此需要注意网络安全问题,如限制访问源 IP、使用防火墙规则等。

5.2.6 资源配额(resources)

resources 是 Kubernetes Pod.spec.container 中的一个配置项,用于定义容器的计算资源需求和限制。通过 resources 配置,您可以确保容器在运行时有足够的 CPU 和内存资源,同时避免某个容器过度使用资源而影响其他容器的运行。

resources 包含两个子字段:

  1. requests:容器请求的资源量,这是容器启动时需要的最小资源量。Kubernetes 会根据这个值在集群中选择一个适合运行该容器的节点。如果环境资源不够,容器将无法启动。
  2. limits:容器能够使用的最大资源量。Kubernetes 会根据这个值限制容器的资源使用量,防止其超出该范围。当容器占用资源超过limits时会被终止,并进行重启。

resources.requestsresources.limits 具体说明:

  • requests

    描述:容器期望分配的资源量,确保容器有足够的资源启动和运行。

    类型:cpu(CPU 核心数)和 memory(内存量)。

    作用:决定容器调度到哪个节点上。节点必须有足够的可用资源来满足 requests 才能运行该容器。

  • limits

    描述:容器能够使用的最大资源量。

    类型:cpumemory

    作用:限制容器的资源使用,防止容器消耗过多的资源。超出 limits 的部分会被 Kubernetes 系统限制或杀掉容器。


resources 示例及解释

假设您有一个应用,它通常使用 0.5 个 CPU 核心和 256Mi 的内存,但在高负载时可能需要最多 1 个 CPU 核心和 512Mi 的内存。可以这样配置:

containers:
- name: my-app
  image: my-app:latest
  resources:
    requests:
      memory: "256Mi"
      cpu: "0.5"
    limits:
      memory: "512Mi"
      cpu: "1"

高级用法:

  1. 资源分配策略

    您可以通过 requestslimits 的组合,控制容器的优先级和行为。一般情况下,requests 较小、limits 较大,可以让容器在节点上更容易调度,同时在高负载时获得更多资源。

  2. 计算资源单位

    CPU:以 CPU 核心数为单位,可以使用小数或毫核(如 500m 表示 0.5 个核心)。

    内存:以字节为单位,可以使用 KiMiGi 等单位(例如 256Mi 表示 256 Mebibytes)。

  3. 资源配额(Resource Quotas)

    在命名空间级别,可以通过资源配额来限制整个命名空间中的资源使用。结合 requestslimits 可以确保集群中的所有 Pod 都能公平地使用资源。

  4. 自动伸缩(Horizontal Pod Autoscaler, HPA)

    基于 requestslimits 的配置,可以设置 HPA 根据 CPU 或内存使用率自动调整 Pod 的副本数量,从而实现应用的弹性伸缩。

接下来,编写一个测试案例,创建pod-resources.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-resources
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    resources: # 资源配额
      limits:  # 限制资源(上限)
        cpu: "2" # CPU限制,单位是core数
        memory: "10Gi" # 内存限制
      requests: # 请求资源(下限)
        cpu: "1"  # CPU限制,单位是core数
        memory: "10Mi"  # 内存限制

接下来允许Pod并且查看发现pod运行情况:

可见运行是正常的,现在我们修改pod,修改resources.requests.memory的值为10Gi,删除之前的Pod并且重新启动:

此时查看详情:

5.3 Pod生命周期

我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期,它主要包含下面的过程:

  1. pod创建过程:Pod 从被 API Server 接受并调度到某个节点上,开始进入生命周期。

  2. 运行初始化容器(init container)过程:如果定义了 Init 容器,首先运行这些容器。Init 容器通常用于执行一些初始化任务,例如配置环境或检查依赖关系,只有所有 Init 容器成功运行后,Pod 才会继续进入下一阶段。

  3. 运行主容器(main container):主容器是 Pod 的核心部分,负责执行应用的主要功能。

    • 容器启动后钩子(PostStart Hook):在主容器启动后立即执行,用于处理启动后的初始化任务。
    • 容器终止前钩子(PreStop Hook):在容器终止前执行,用于执行一些清理工作或保存状态。
    • 存活性探测(Liveness Probe):定期检查容器是否存活,用于判断是否需要重启容器。
    • 就绪性探测(Readiness Probe):定期检查容器是否准备好接收流量,未通过探测的容器将被从服务的负载均衡中移除。
  4. pod终止过程:当 Pod 被删除或因其他原因需要终止时,会首先发送一个终止信号给容器,触发 PreStop 钩子和宽限期内的清理工作。容器结束后,Pod 进入终止状态并最终被删除。

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

  • 挂起(Pending)Pod已被 API Server 接受,但尚未在节点上创建一个或多个容器。这通常包括调度到某个节点上、拉取容器镜像等步骤。此时Pod尚未开始运行,正在等待资源或调度决策。
  • 运行中(Running)Pod已成功分配到节点上,并且所有指定的容器都已经创建并运行。此时至少有一个容器正在运行,或者正在启动或重新启动中。此时Pod中所有容器至少有一个在运行。也有可能处于初始化阶段(如果有 Init Containers)。
  • 成功(Succeeded)Pod内的所有容器都已经成功终止,且不会重启。通常在容器执行完任务且退出时进入此状态。此时所有容器已正常退出,且 restartPolicy 被设置为 NeverOnFailure
  • 失败(Failed)Pod内的所有容器已经终止并至少有一个容器是以失败状态退出的(即退出码不为 0)。容器因错误退出,或 Pod无法继续运行(例如超出重启策略的限制)
  • 未知(Unknown)Pod的状态无法确定,通常是由于与节点通信失败造成的。此时系统无法获取到Pod的状态信息。

5.3.1 创建和终止

5.3.1.1 Pod 的创建过程

Pod 的创建过程包括从 Pod 定义到其成功运行的所有步骤,具体如下:

  1. API Server 接收请求:用户通过 kubectl 或其他工具向 Kubernetes API Server 提交 Pod创建请求。API Server 解析并验证请求中的 Pod定义。
  2. 保存到 etcd:通过验证后,Pod的定义被保存到 Kubernetes的分布式数据库 etcd 中。etcd 负责持久化存储集群的状态。
  3. Scheduler 调度Kubernetes调度器(Scheduler)从 etcd 中获取待调度的 Pod列表,选择一个合适的节点(Node)来运行该 Pod。调度器会考虑节点的资源状况(如 CPU、内存)、Pod 的资源请求(requests)、节点的污点和容忍度等因素进行调度。
  4. 分配节点Scheduler Pod分配一个节点,并将该信息更新回 etcd。此时 Pod 的状态从 Pending 转变为 Scheduled
  5. Kubelet 创建容器:节点上的 Kubelet 进程监听到分配给自己的Pod信息,开始执行创建容器的步骤。如果Pod定义了 Init 容器(Init Containers),Kubelet会首先运行这些容器,等待它们成功完成后再启动主容器。
  6. 拉取镜像Kubelet根据 Pod的定义,从指定的容器镜像仓库拉取所需的容器镜像。如果镜像已经存在于节点上,Kubelet会跳过此步骤。
  7. 创建并启动容器Kubelet使用容器运行时(如 DockerContainerd)来创建并启动容器。Kubelet还会设置必要的网络配置和存储卷挂载。
  8. 执行生命周期钩子:如果定义了 postStart 钩子,Kubelet会在容器启动后立即执行该钩子。
  9. Pod 状态更新:当所有容器启动并运行后,Pod的状态更新为 Running,并且此信息会被更新到 etcd 中。

5.3.1.2 Pod 的中止过程

Pod 的终止过程是指 Pod 从被标记为删除到完全终止的整个过程,具体步骤如下:

  1. 发起删除请求:用户通过 kubectl delete pod 或者系统控制器(如 Deployment 的滚动更新)发出删除 Pod 的请求。
  2. Graceful Termination(优雅终止):在接收到删除请求后,API Server会将 Pod标记为 Terminating 状态,并更新 etcd。此时,Kubernetes会等待 Pod 中的所有容器优雅地终止。默认宽限期是 30 秒,可通过 terminationGracePeriodSeconds 进行调整。
  3. 发送 SIGTERM 信号Kubelet向每个容器发送 SIGTERM 信号,通知它们进行清理操作。如果容器定义了 preStop 钩子,则会在发送 SIGTERM 信号之前执行这个钩子。
  4. 宽限期等待:容器有一个宽限期(Grace Period)来处理清理工作、保存数据、释放资源等操作。Kubelet会在宽限期内等待容器退出。
  5. 强制终止(SIGKILL):如果容器在宽限期内未能正常退出,Kubelet会发送 SIGKILL 信号强制终止容器,确保 Pod的终止过程不会无限制地拖延。
  6. 解除资源分配Kubelet解除 Pod使用的资源(如挂载的存储卷、分配的网络资源等),并从节点中移除 Pod的容器和相关配置。
  7. Pod 删除:当所有容器终止后,API Server 会从 etcd 中删除该 Pod 的记录,最终 Pod 状态从 Terminating 变为 Deleted
  8. 清理完成:在 Pod 记录从 etcd 中删除后,Pod 生命周期结束,相关资源(如服务负载均衡器中的条目)也会相应清理。

5.3.2 初始化容器

Pod 的初始化容器(Init Containers)是 Kubernetes 中的一种特殊类型的容器,它们在主容器(Main Containers)启动之前运行,用于执行一些初始化任务。初始化容器的过程对于确保主容器能够在适当的条件下启动和运行至关重要。下面是详细的初始化容器过程的说明:

初始化容器与主容器类似,都是在 Pod 中运行的容器,但它们有以下几个特点:

  • 先于主容器运行:Init 容器必须全部成功运行后,Pod 才会启动主容器。

  • 串行运行:如果有多个 Init 容器,它们会按顺序依次运行,只有前一个 Init 容器成功退出后,下一个才会启动。

  • 重新启动:如果 Init 容器失败(退出码不为 0),Kubernetes 会根据 Pod 的重启策略继续重启它,直到成功为止。

使用场景:

  1. 等待依赖服务:假设一个应用需要等待数据库服务启动并准备就绪。在这种情况下,您可以使用 Init 容器来定期检查数据库服务的可用性,并在主容器启动之前确保其已就绪。

    apiVersion: v1
    kind: Pod
    metadata:
      name: myapp-pod
    spec:
      initContainers: #定义初始化容器
      - name: init-myservice
        image: busybox
        command: ['sh', '-c', 'until nc -z mydb 3306; do echo waiting for mydb; sleep 2; done;']
      containers:
      - name: myapp-container
        image: myapp:latest
        ports:
        - containerPort: 8080
    
  2. 文件下载和配置初始化:如果您的应用需要某个配置文件,您可以使用 Init 容器下载这个文件并将其放置在共享卷中,主容器启动时便可以直接使用这些配置。

    apiVersion: v1
    kind: Pod
    metadata:
      name: myapp-pod
    spec:
      initContainers: #定义初始化容器
      - name: init-download-config
        image: busybox
        command: ['sh', '-c', 'wget -O /config/myapp.conf http://example.com/myapp.conf']
        volumeMounts:
        - name: config-volume
          mountPath: /config
      containers:
      - name: myapp-container
        image: myapp:latest
        volumeMounts:
        - name: config-volume
          mountPath: /etc/myapp
      volumes:
      - name: config-volume
        emptyDir: {}
    

5.3.3 钩子函数

Pod 的钩子函数(Hook)是 Kubernetes 提供的一种机制,允许在容器生命周期的关键时刻执行自定义的脚本或命令。Kubernetes 主要提供了两种钩子函数:PostStartPreStop。它们可以帮助您在容器启动后或终止前执行一些特定的操作,确保应用程序以预期的方式运行。

  1. PostStart 钩子:PostStart钩子是在容器启动后立即执行的钩子函数。它通常用于执行启动后需要完成的一些初始化任务或配置操作。

    工作流程:

    • 容器启动后,PostStart 钩子被触发。
    • Kubernetes 会在容器的主进程启动后,立即执行指定的 PostStart 脚本或命令。
    • PostStart 钩子执行完毕后,容器会继续其正常的运行。

    示例场景:

    • 初始化配置:例如,容器启动后,可以使用 PostStart 钩子向应用注入某些配置文件。
    • 启动日志收集服务:容器启动后,可以使用 PostStart 钩子启动一个附加的日志收集进程。

    yaml示例:

    apiVersion: v1
    kind: Pod
    metadata:
      name: example-pod
    spec:
      containers:
      - name: my-container
        image: busybox
        lifecycle:
          postStart: #PostStart钩子
            exec:
              command: ["sh", "-c", "echo 'Container has started!' >> /var/log/mylog.txt"]
    
  2. PreStop 钩子PreStop 钩子是在容器终止之前执行的钩子函数。它用于在容器终止前进行一些清理操作,确保应用程序可以安全地关闭。

    工作流程:

    • 当 Kubernetes 确定容器需要终止(例如,Pod 被删除,或由于滚动更新而替换),会触发 PreStop 钩子。
    • PreStop 钩子会在发送 SIGTERM 信号之前执行。
    • PreStop 钩子执行完毕后,Kubernetes 会等待容器在 terminationGracePeriodSeconds(默认 30 秒)内完成关闭。

    示例场景:

    • 保存状态:在容器终止前保存应用的当前状态,避免数据丢失。
    • 通知依赖服务:在容器关闭前,向其他依赖的服务发送通知,表明该容器即将终止。

    yaml示例:

    apiVersion: v1
    kind: Pod
    metadata:
      name: example-pod
    spec:
      containers:
      - name: my-container
        image: busybox
        lifecycle:
          preStop: #PreStop 钩子
            exec:
              command: ["sh", "-c", "echo 'Container is stopping!' >> /var/log/mylog.txt"]
    

钩子的实现方式有3种:

  • **Exec **:通过 exec 方式,钩子函数在容器内执行一次指定的命令。命令会在容器的命名空间中运行,允许访问容器内的文件系统、网络和环境变量。

    使用场景:执行脚本、配置文件的生成、日志记录等。

    示例:

    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo Hello, Kubernetes!"]
      preStop:
        exec:
          command: ["/bin/sh", "-c", "echo Goodbye, Kubernetes!"]
    
  • **TCPSocket **:通过 tcpSocket 方式,钩子函数向容器内的某个 TCP 端口发起连接请求。适用于只需要简单地测试端口是否可达的场景。

    使用场景:检查某个服务是否已经在指定端口上启动或准备好接收连接。

    示例:

    lifecycle:
      postStart:
        tcpSocket:
          port: 8080
      preStop:
        tcpSocket:
          port: 8080
    
  • **HTTPGet **:通过 httpGet 方式,钩子函数向容器内的某个 HTTP 端点发起 GET 请求。适用于需要与容器内的应用交互的场景。

    示例:

    lifecycle:
        postStart:
            httpGet:
            path: / #URI地址
          port: 80 #端口号
          host: 192.168.5.3 #主机地址
          scheme: HTTP #支持的协议,http或者https
    

比较和选择:

  • exec:最灵活,适用于执行复杂操作,但可能会影响容器启动和终止的时间。实际中,**这种方式用的最多 **。
  • httpGet:适用于与应用程序内的 HTTP 端点进行交互,可以触发应用的特定行为。
  • tcpSocket:轻量级,适用于检查服务的可用性,但只能进行端口可达性检查。

接下来,以exec方式为例,演示下钩子函数的使用,创建pod-hook-exec.yaml文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-hook-exec
  namespace: dev
spec:
  containers:
  - name: main-container
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    lifecycle:
      postStart: 
        exec: # 在容器启动的时候执行一个命令,修改掉nginx的默认首页内容
          command: ["/bin/sh", "-c", "echo postStart... > /usr/share/nginx/html/index.html"]
      preStop:
        exec: # 在容器停止之前停止nginx服务
          command: ["/usr/sbin/nginx","-s","quit"]

5.3.4 容器探测

在 Kubernetes 中,容器探测(Probes)是一种机制,用于定期检查容器的健康状态和就绪状态,从而确保应用程序的可靠性和稳定性。容器探测可以帮助 Kubernetes 知道容器是否可以开始处理请求、是否正在运行或是否需要重启。Kubernetes 提供了三种主要类型的探测:存活性探测(Liveness Probe)就绪性探测(Readiness Probe)启动探测(Startup Probe)

5.3.4.1 存活性探测(Liveness Probe)

存活性探测用于检查容器是否仍然存活。容器如果卡住或进入不可恢复的状态(如死锁),Kubernetes 会通过这个探测来检测,并尝试重新启动容器,以保证应用的持续可用性。

使用场景:当应用程序有可能进入不响应状态时,需要存活性探测来重新启动它。

配置示例:

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 3
  periodSeconds: 10
  • httpGet: 使用 HTTP GET 方法请求指定的路径,如果返回 2xx 或 3xx 状态码,则认为存活。
  • initialDelaySeconds: 指定探测在容器启动后延迟的时间。
  • periodSeconds: 指定探测之间的时间间隔。

5.3.4.2 就绪性探测(Readiness Probe)

就绪性探测**用于检查容器是否已经准备好接收请求 **。就绪性探测决定了 Pod 中的容器是否能被加入到服务的负载均衡器中。只有通过就绪性探测的容器才会接收外部流量。

使用场景:应用程序需要较长的初始化时间或依赖于其他服务时,可以使用就绪性探测,确保容器在完全准备好之前不接收流量。

配置示例

readinessProbe:
  exec:
    command: ["cat", "/tmp/healthy"]
  initialDelaySeconds: 5
  periodSeconds: 10
  • exec: 在容器中执行指定的命令,如果返回码为 0,则认为就绪。
  • initialDelaySeconds: 指定探测在容器启动后延迟的时间。
  • periodSeconds: 指定探测之间的时间间隔。

5.3.4.3 启动探测(Startup Probe)

启动探测用于检测容器的启动状态。与存活性探测不同,启动探测用于确定应用程序是否已正确启动。如果配置了启动探测,Kubernetes 将在启动探测通过之前,不会运行存活性探测。

使用场景:当应用程序启动时间较长,但一旦启动后非常稳定时,使用启动探测可以避免在应用未完全启动前触发存活性探测。

配置示例:

startupProbe:
  tcpSocket:
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 30
  • tcpSocket: 尝试连接指定端口,如果连接成功,则认为启动完成。
  • initialDelaySeconds: 指定探测在容器启动后延迟的时间。
  • periodSeconds: 指定探测之间的时间间隔。
  • failureThreshold: 失败次数超过此阈值后,容器将被杀死并重启。

5.3.4.4 探测机制的实现方式

每种探测类型都可以通过以下几种方式来实现:

  1. **exec**:在容器内执行命令并检查其退出状态。退出状态码为 0 表示成功,其他状态码表示失败。
  2. **httpGet**:对容器内的 HTTP 端点发起 GET 请求,2xx 和 3xx 的响应码表示成功。
  3. **tcpSocket**:尝试连接容器内指定的 TCP 端口,如果连接成功表示成功。

[!tip]

这三种方式和之前提到钩子函数的三种实现方式是一模一样的。


下面以liveness probes为例,做几个演示,因为其他几种探针都是一样的,只是名称不一样:

方式一:Exec:创建pod-liveness-exec.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-exec
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      exec:
        command: ["/bin/cat","/tmp/hello.txt"] # 执行一个查看文件的命令

我们知道,这肯定是没有hello.txt文件的。

创建pod,观察效果:

kubectl apply -f pod-liveness-exec.yaml
kubectl describe pods pod-liveness-exec -n dev

观察上面的信息就会发现nginx容器启动之后就进行了健康检查,检查失败之后,容器被kill掉,然后尝试进行重启(这是重启策略的作用,后面讲解),稍等一会之后,再观察pod信息,就可以看到RESTARTS不再是0,而是一直增长:

当然接下来,可以修改命令:

这里去查看一下tmp文件夹,这个是肯定存在的。所以能够通过存活性探测:

#删除之前的Pod
kubectl delete -f pod-liveness-exec.yaml
#重建Pod
kubectl apply -f pod-liveness-exec.yaml

之后查看运行情况可见正常:


方式二:TCPSocket:创建pod-liveness-tcpsocket.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-tcpsocket
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      tcpSocket:
        port: 8080 # 尝试访问8080端口

是访问不了8080端口的,因为容器的端口是80,所以创建pod,观察效果:

kubectl apply -f pod-liveness-tcpsocket.yaml
kubectl describe pods pod-liveness-tcpsocket -n dev

观察上面的信息,发现尝试访问8080端口,但是失败了,稍等一会之后,再观察pod信息,就可以看到RESTARTS不再是0,而是一直增长:

当然接下来,可以修改成一个可以访问的端口,比如80,再试,结果就正常了……这里就不在演示了。


方式三:HTTPGet:创建pod-liveness-httpget.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:  # 其实就是访问http://127.0.0.1:80/hello  
        scheme: HTTP #支持的协议,http或者https
        port: 80 #端口号
        path: /hello #URI地址

我们容器中目前肯定是没有/hello资源的,创建pod,观察效果

kubectl apply -f pod-liveness-httpget.yaml
kubectl describe pod pod-liveness-httpget -n dev

观察上面信息,尝试访问路径,但是未找到,出现404错误,稍等一会之后,再观察pod信息,就可以看到RESTARTS不再是0,而是一直增长:

当然接下来,可以修改成一个可以访问的路径path,比如/,再试,结果就正常了……

5.3.4.5 探测的配置参数

  • initialDelaySeconds: 探测在容器启动后第一次执行的延迟时间,默认 0。
  • periodSeconds: 探测之间的时间间隔,默认 10 秒。
  • timeoutSeconds: 探测的超时时间,默认 1 秒。
  • successThreshold: 探测成功次数达到此阈值后,探测被视为成功,默认 1。
  • failureThreshold: 探测失败次数达到此阈值后,认为探测失败,可能会触发容器重启,默认 3。

5.3.4.6 探测的作用与注意事项

  • 稳定性:通过探测可以确保只有健康的容器接收请求,从而提高应用的稳定性。
  • 自动恢复:当探测失败时,Kubernetes 可以自动重启容器,实现应用的自愈能力。
  • 延迟问题:不合理的探测配置可能导致容器频繁重启,尤其是初始延迟和超时时间的设置需要结合应用实际启动时间来配置。

5.3.5 重启策略

在上一节中,一旦容器探测出现了问题,kubernetes就会对容器所在的Pod进行重启,其实这是由pod的重启策略决定的,pod的重启策略有 3 种,分别如下:

  • Always :容器失效时,自动重启该容器,这也是默认值。适用于长时间运行的服务(例如 Web 服务、数据库服务等),需要保证服务持续可用。

    示例:

    spec:
      restartPolicy: Always
    
  • OnFailure: 容器终止运行且退出码不为0时重启。适用于完成特定任务的容器(如批处理任务、数据迁移任务等),希望在任务失败时重试,但在成功完成后不再重启。

    示例:

    spec:
      restartPolicy: OnFailure
    
  • Never: 无论容器以何种退出状态退出,Kubernetes 都不会自动重启该容器。适用于一次性任务或希望容器在任务完成后直接退出的场景,如初始化脚本或数据迁移任务,不需要重启的情况下使用。

    示例:

    spec:
      restartPolicy: Never
    

重启策略的应用场景总结:

  • 服务类应用:服务类应用通常设置为 Always,确保在任何情况下服务都能自动重启,保持持续运行。

  • 批处理作业:对于需要多次重试的批处理作业,OnFailure 是一个不错的选择,它能确保任务在失败时得到重试,但成功后不再执行。

  • 一次性任务:对于一次性任务或生命周期很短的容器,Never 是合适的选择,因为这些任务完成后无需再次启动。

重启策略的配置与containers平级:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
    - name: my-container
      image: my-image
  restartPolicy: OnFailure

重启策略适用于pod对象中的所有容器,首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长以此为10s、20s、40s、80s、160s和300s,300s是最大延迟时长。

示例:创建pod-restartpolicy.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-restartpolicy
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:
        scheme: HTTP
        port: 80
        path: /hello
  restartPolicy: Never # 设置重启策略为Never

运行Pod测试:

kubectl apply -f pod-restartpolicy.yaml
kubectl describe pods pod-restartpolicy  -n dev

多等一会,再观察pod的重启次数,发现一直是0,并未重启 :

[!caution]

  • Pod 控制器的影响:在 Pod 由某些控制器(如 Deployment、StatefulSet、DaemonSet 等)管理时,即使 restartPolicy 设置为 NeverOnFailure,控制器也会根据其自身的策略来管理 Pod 的重启。因为**控制器的行为优先于 Pod 的重启策略。 **
  • 默认行为:如果未明确指定 restartPolicy,则默认使用 Always 策略,这意味着容器将在任何情况下自动重启。
  • Job 和 CronJob:在 Job 和 CronJob 控制器中,restartPolicy 通常会设置为 OnFailureNever,因为它们的目的是执行特定任务,并在任务完成后终止。

5.4 Pod调度

在默认情况下,一个Pod在哪个Node节点上运行,是由Scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的。但是在实际使用中,这并不满足的需求,因为很多情况下,我们想控制某些Pod到达某些节点上,那么应该怎么做呢?这就要求了解kubernetes对Pod的调度规则,kubernetes提供了四大类调度方式:

  • 自动调度:运行在哪个节点上完全由Scheduler经过一系列的算法计算得出,这是Kubernetes默认的调度方式
  • 定向调度
    • NodeName:直接在 Pod 的 spec.nodeName 字段中指定节点名称,将 Pod 绑定到该节点上。
    • NodeSelector:用户可以通过 nodeSelector 字段指定 Pod 应该调度到带有特定标签的节点上。
  • 亲和性调度
    • NodeAffinity:节点亲和性,定义 Pod 对节点的亲和性策略,要求 Pod 调度到符合某些标签条件的节点上。节点亲和性分为硬亲和性(必须满足)和软亲和性(尽量满足)。
    • PodAffinity:Pod亲和性,控制 Pod 与其他 Pod 的调度关系,比如要求 Pod 和特定的其他 Pod 调度到同一个节点。
    • PodAntiAffinity:Pod反亲和性,控制 Pod 与其他 Pod 的调度关系,比如要求 Pod 和特定的其他 Pod 调度到不同节点。
  • 污点(容忍)调度
    • Taints:在节点上设置污点,表示不希望某些 Pod 被调度到这个节点。污点包括三个要素:键(key)、值(value)、效果(effect)。
    • Toleration:在 Pod 上设置容忍度,使其能够容忍节点的污点,从而允许 Pod 调度到带有这些污点的节点上。

5.4.1 定向调度

定向调度,指的是利用在Pod上声明NodeName或者NodeSelector,以此将Pod调度到期望的Node节点上。

[!caution]

这里的调度是强制的,这就意味着即使要调度的目标Node不存在,也会向上面进行调度,只不过Pod运行失败而已。

5.4.1.1 节点名称调度(NodeName)

NodeName用于强制约束将Pod调度到指定的Name的Node节点上。这种方式,其实是直接跳过Scheduler的调度逻辑,直接将Pod调度到指定名称的节点。

优点:

  • 简单直接:明确指定了 Pod 的运行节点,不需要额外的匹配条件。
  • 完全控制:适用于需要精确控制 Pod 调度的场景。

缺点:

  • 不灵活:如果指定的节点不可用,Pod 将无法调度,可能会卡住等待资源。
  • 依赖手动管理:节点名可能会随集群的动态变化而变化,这种方式不够灵活。

接下来,实验一下:

首先看看当前集群中的节点有哪些:

 kubectl get node

之后创建一个pod-nodename.yaml文件

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: k8s-node # 指定调度到k8s-node节点上

[!caution]

这里的nodeName必须为当前集群中的node名称: kubectl get node

执行结果:

接下来,删除Pod,修改nodeName的值为一个不存在的节点,比如test-node(并没有test-node节点):

5.4.1.2 节点选择器调度(NodeSelector)

NodeSelector用于将pod调度到添加了指定标签的node节点上。它是通过kubernetes的label-selector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后将pod调度到目标节点,该匹配规则是强制约束。

优点:

  • 灵活性:通过标签选择,允许多个节点匹配,从而提供更多的调度可能性。
  • 可扩展性:当多个节点满足条件时,调度器可以选择最合适的节点,提高调度的弹性。

缺点:

  • 只支持简单的匹配:NodeSelector 只支持“与”(AND)逻辑的标签匹配,不支持“或”(OR)逻辑或其他更复杂的匹配条件。
  • 不能动态调整:标签设置后不会自动根据集群状态调整,可能需要手动管理标签。

接下来,实验一下:

首先分别为k8s-node节点添加标签

kubectl label nodes k8s-node nodeenv=test

之后创建一个pod-nodeselector.yaml文件,并使用它创建Pod

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeselector
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeSelector: 
    nodeenv: test # 指定调度到具有nodeenv=test标签的节点上

执行结果并查看:

接下来,删除pod,修改nodeSelector的值为nodeenv: pro(不存在打有此标签的节点):

可见调度失败,现在我们来查看详细信息看看:

可见有两个节点可用(k8s-master、k8s-node),1个节点不匹配。

5.4.2 亲和性调度

上一节,介绍了两种定向调度的方式,使用起来非常方便,但是也有一定的问题,那就是如果没有满足条件的Node,那么Pod将不会被运行,即使在集群中还有可用Node列表也不行,这就限制了它的使用场景。

基于上面的问题,kubernetes还提供了一种亲和性调度(Affinity)。它在NodeSelector的基础之上的进行了扩展,可以通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足条件的节点上,使调度更加灵活。

Affinity主要分为三类:

  • NodeAffinity(节点亲和性): 以Node为目标,解决Pod可以调度到哪些Node的问题
  • **PodAffinity(Pod亲和性) **: 以Pod为目标,解决Pod可以和哪些已存在的Pod部署在同一个拓扑域中的问题
  • **PodAntiAffinity(Pod反亲和性) **: 以Pod为目标,解决Pod不能和哪些已存在Pod部署在同一个拓扑域中的问题

[!note]

关于亲和性(反亲和性)使用场景的说明:

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

节点亲和性又分为硬亲和性软亲和性两种类型:

  • 硬亲和性(requiredDuringSchedulingIgnoredDuringExecution):要求调度器必须将 Pod 调度到符合条件的节点上。如果没有节点符合条件,Pod 将不会被调度。
  • 软亲和性(preferredDuringSchedulingIgnoredDuringExecution):调度器会尽量将 Pod 调度到符合条件的节点上,但如果没有符合条件的节点,Pod 仍然可以调度到其他节点上。

5.4.2.1 NodeAffinity

首先来看一下NodeAffinity的可配置项:

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
  labels:
    app: frontend  # Pod 的标签,用于选择亲和性或反亲和性匹配

spec:
  affinity:  # 亲和性调度策略配置
    # 节点亲和性配置
    nodeAffinity:
      # 强制性节点亲和性,Node节点必须满足指定的所有规则才可以,相当于硬限制
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms: #节点选择列表
        - matchExpressions: #按节点标签列出的节点选择器要求列表(推荐)
          - key: disktype #匹配节点上的标签键
            operator: In #匹配操作符,可以是 In, NotIn, Exists, DoesNotExist, Gt, Lt等
            values:
            - ssd  #匹配的标签值,节点必须具有该值

      # 首选的节点亲和性,优先调度到符合条件的节点上,但非强制性(倾向)
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1 #权重,值越高优先级越高,在范围1-100。
        preference: #一个节点选择器项,与相应的权重相关联
          matchExpressions: #按节点标签列出的节点选择器要求列表(推荐)
          - key: region #匹配节点上的标签键
            operator: In #匹配操作符,可以是 In, NotIn, Exists, DoesNotExist, Gt, Lt等
            values:
            - us-west  #匹配的标签值,节点必须具有该值

关系符的使用说明:

- matchExpressions:
  - key: nodeenv #匹配存在标签的key为nodeenv的节点
    operator: Exists
  - key: nodeenv #匹配标签的key为nodeenv,且value是"xxx"或"yyy"的节点
    operator: In
    values: ["xxx","yyy"]
  - key: nodeenv #匹配标签的key为nodeenv,且value大于"xxx"的节点
    operator: Gt
    values: "xxx"

接下来首先演示一下requiredDuringSchedulingIgnoredDuringExecution ,

创建pod-nodeaffinity-required.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    nodeAffinity: #设置node亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
        nodeSelectorTerms:
        - matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]

查看执行结果:

我们来看看我们当前集群中节点上都有哪些标签:

可见只有k8s-node节点上有标签:nodeenv=test,并没有nodeenv=xxx或者nodeenv=yyy,所以没被调度到节点上也是合理的,现在我们来看看详情呢:

接下来我们删除Pod,修改文件,将values: ["xxx","yyy"]------> ["test","yyy"],之后再次启动查看:


接下来再演示一下preferredDuringSchedulingIgnoredDuringExecution ,

创建pod-nodeaffinity-preferred.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-preferred
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    nodeAffinity: #设置node亲和性
      preferredDuringSchedulingIgnoredDuringExecution: # 软限制
      - weight: 1
        preference:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签(当前环境没有)
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]

执行结果:

[!caution]

NodeAffinity规则设置的注意事项:

  1. 如果同时定义了nodeSelectornodeAffinity,那么必须两个条件都得到满足,Pod才能运行在指定的Node上。
  2. 如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可
  3. 如果一个nodeSelectorTerms中有多个matchExpressions ,则一个节点必须满足所有的才能匹配成功
  4. 如果一个Pod所在的NodePod运行期间其标签发生了改变,不再符合该Pod的节点亲和性需求,则系统将忽略此变化

5.4.2.2 PodAffinity

PodAffinity主要实现以运行的Pod为参照,实现让新创建的Pod跟参照pod在一个区域的功能。

首先来看一下PodAffinity的可配置项:

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
  labels:
    app: frontend  # Pod 的标签,用于选择亲和性或反亲和性匹配

spec:
  affinity:  # 亲和性调度策略配置
    # Pod 亲和性配置
    podAffinity:
      # 强制性 Pod 亲和性,Pod 必须调度到与符合条件的 Pod 位于同一拓扑域上
      requiredDuringSchedulingIgnoredDuringExecution:
        labelSelector: #标签选择器
          matchExpressions:
          - key: app #匹配目标 Pod 的标签键
            operator: In #匹配操作符
            values:
            - frontend #目标 Pod 必须具有 app=frontend 标签
        topologyKey: "kubernetes.io/hostname"  #拓扑域键,定义亲和性生效的范围,如节点(hostname),指定调度作用域

      # 首选的 Pod 亲和性,优先调度到与符合条件的 Pod 位于同一拓扑域上
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1  #权重,值越高优先级越高
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app #匹配目标 Pod 的标签键
              operator: In #匹配操作符
              values:
              - frontend #目标 Pod 必须具有 app=frontend 标签
          topologyKey: "kubernetes.io/hostname"  #拓扑域键,定义亲和性生效的范围,如节点(hostname),指定调度作用域

[!note]

topologyKey用于指定调度时作用域,例如:

  • 如果指定为kubernetes.io/hostname,那就是以Node节点为区分范围。
  • 如果指定为beta.kubernetes.io/os,则以Node节点的操作系统类型来区分。

接下来,演示下requiredDuringSchedulingIgnoredDuringExecution

首先创建一个参照Pod,pod-podaffinity-target.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-target
  namespace: dev
  labels:
    podenv: test #设置标签
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: k8s-node # 将目标pod名确指定到k8s-node上

执行结果:

创建pod-podaffinity-required.yaml,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    podAffinity: #设置pod亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
          - key: podenv
            operator: In
            values: ["xxx","yyy"]
        topologyKey: kubernetes.io/hostname

上面配置表达的意思是:新Pod必须要与拥有标签nodeenv=xxx或者nodeenv=yyy的pod在同一Node上,显然现在没有这样pod,接下来,运行测试一下:

可见是运行不起来的,接下来修改 values: ["xxx","yyy"]----->values:["test","yyy"],之后再查看一下:

关于PodAffinitypreferredDuringSchedulingIgnoredDuringExecution,这里不再演示。

5.4.2.3 PodAntiAffinity

PodAntiAffinity主要实现以运行的Pod为参照,让新创建的Pod跟参照pod不在一个区域中的功能。

它的配置方式和选项跟PodAffinty是一样的,这里不再做详细解释,直接做一个测试案例。

继续使用上个案例中目标pod,创建pod-podantiaffinity-required.yaml,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-podantiaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    podAntiAffinity: #设置pod亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配nodeenv的值在["test"]中的标签
          - key: nodeenv
            operator: In
            values: ["test"]
        topologyKey: kubernetes.io/hostname

上面配置表达的意思是:新Pod必须要与拥有标签nodeenv=test的pod不在同一Node上,自行测试即可。

5.4.3 污点和容忍

5.4.3.1 污点(Taints)

污点是添加到节点上的标签,用于标记该节点有某种不可调度的属性。通过设置污点,Kubernetes 可以防止不符合条件的 Pod 调度到这个节点上。污点由三个部分组成:

  • Key:污点的键,通常用来表示污点的类别或原因。

  • Value:污点的值,进一步说明污点的具体情况(可选)。

  • Effect:污点的效果,决定了该污点的影响,主要有三种类型:

    • NoSchedule: 禁止 Pod 调度到该节点上,除非 Pod 具备相应的容忍。

    • PreferNoSchedule: 尽量避免 Pod 调度到该节点,但并不是强制的。

    • NoExecute: 已经运行在节点上的 Pod 会被驱逐(除非它们容忍该污点),并且新的 Pod 也无法调度到该节点。


前面的调度方式都是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否要调度到指定的Node上,其实我们也可以站在Node的角度上,通过在Node上添加污点属性,来决定是否允许Pod调度过来。

Node被设置上污点之后就和Pod之间存在了一种相斥的关系,进而拒绝Pod调度进来,甚至可以将已经存在的Pod驱逐出去。

污点的格式为:key=value:effect, key和value是污点的标签,effect描述污点的作用,支持如下三个选项:

  • PreferNoSchedule:kubernetes将尽量避免把Pod调度到具有该污点的Node上,除非没有其他节点可调度。
  • NoSchedule:kubernetes将不会把Pod调度到具有该污点的Node上,但不会影响当前Node上已存在的Pod。
  • NoExecute:kubernetes将不会把Pod调度到具有该污点的Node上,同时也会将Node上已存在的Pod驱离。

图解说明:

使用kubectl设置和去除污点的命令示例如下:

  • 设置污点:

    kubectl taint node 节点名称 key=value:effect
    
  • 去除污点

    kubectl taint nodes 节点名称 key:effect-
    
  • 去除所有污点

    kubectl taint nodes 节点名称 key-
    

接下来,演示下污点的效果:

  1. 准备节点node1(为了演示效果更加明显,暂时停止node2节点)

  2. 为node1节点设置一个污点: tag=test:PreferNoSchedule;然后创建pod1( pod1 可以 )

  3. 修改为node1节点设置一个污点: tag=test:NoSchedule;然后创建pod2( pod1 正常 pod2 失败 )

  4. 修改为node1节点设置一个污点: tag=test:NoExecute;然后创建pod3 ( 3个pod都失败 )

  5. 为k8s-node设置污点(PreferNoSchedule)

    kubectl taint nodes k8s-node tag=test:PreferNoSchedule
    

  6. 创建Pod1

    kubectl run taint1 --image=nginx:1.17.1 -n dev
    

    查看信息:

     get pods taint1 -n dev -o wide
    

    可见运行成功。

    [!note]

    可见当把节点k8s-node污点类型为PreferNoSchedule的时候,kubernetes将尽量避免把Pod调度到具有该污点的Node上,除非没有其他节点可调度,由于我目前除了master节点以外,就只有一个node节点(k8s-node),所以,这里它会调度到k8s-node节点上,至于为什么不调度到k8s-master节点上,这里我们本小结的最后会讲到

  7. 接下来我们更改k8s-node节点的污点类型,改为NoSchedule

    k8s不会将Pod调度到具有该污点的Node上,但是已经存在的Pod不受影响:

    可见已经存在的Pod确实不受影响:

  8. 接下来我们更改k8s-node节点的污点类型,改为NoExecute

    kubernetes将不会把Pod调度到具有该污点的Node上,同时也会将Node上已存在的Pod驱离:

[!tip]

为什么再集群中,Pod始终不会分配到master节点上呢?这是因为使用kubeadm搭建的集群,默认就会给master节点添加一个污点标记,所以pod就不会调度到master节点上。

5.4.3.2 容忍(Toleration)

上面介绍了污点的作用,我们可以在node上添加污点用于拒绝pod调度上来,但是如果就是想将一个pod调度到一个有污点的node上去,这时候应该怎么做呢?这就要使用到容忍

容忍是定义在 Pod 上的属性,允许 Pod 调度到具有特定污点的节点上。容忍不会强制调度 Pod 到某个节点,而是使得带有该容忍的 Pod 可以被调度到带有匹配污点的节点。

容忍也由三部分组成:

  • Key:容忍的键,必须与污点的 Key 匹配。
  • Operator:容忍的操作符,支持以下操作:
    • Exists: 容忍任意带有该 Key 的污点。
    • Equal: 容忍带有该 Key 且值相等的污点。
  • Value:容忍的值,必须与污点的值匹配(仅在 Equal 操作符下使用)。
  • Effect:容忍的效果,必须与污点的效果一致。
  • TolerationSeconds:指定 Pod 可以容忍污点的时间,超时后 Pod 会被驱逐(仅对 NoExecute 生效)。

示例:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  tolerations:
  - key: "key"
    operator: "Equal"
    value: "value"
    effect: "NoSchedule"

容忍以及污点示意图:

污点就是拒绝,容忍就是忽略,Node通过污点拒绝pod调度上去,Pod通过容忍忽略拒绝

下面先通过一个案例看下效果:

  1. 上一小节,已经在k8s-node节点上打上了NoExecute的污点,此时pod是调度不上去的
  2. 本小节,可以通过给pod添加容忍,然后将其调度上去

创建pod-toleration.yaml,内容如下

apiVersion: v1
kind: Pod
metadata:
  name: pod-toleration
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  tolerations:      # 添加容忍
  - key: "tag"        # 要容忍的污点的key
    operator: "Equal" # 操作符
    value: "test"    # 容忍的污点的value
    effect: "NoExecute"   # 添加容忍的规则,这里必须和标记的污点规则相同

观察之前和之后的数据执行测试即可。

6. Pod控制器详解

6.1 Pod控制器介绍

在 Kubernetes 中,Pod 控制器(Pod Controller)是一种 Kubernetes 资源,它负责管理和协调一组 Pod 的生命周期。Pod 控制器确保指定数量的 Pod 始终在集群中运行,即使 Pod 出现故障、节点宕机或其他问题,控制器也会自动创建或删除 Pod 以维持期望的状态。

Pod是kubernetes的最小管理单元,在kubernetes中,按照pod的创建方式可以将其分为两类:

  • 自主式Pod:kubernetes直接创建出来的Pod,这种pod删除后就没有了,也不会重建。
  • 控制器创建的Pod:kubernetes通过控制器创建的pod,这种pod删除了之后还会自动重建。

在kubernetes中,有很多类型的pod控制器,每种都有自己的适合的场景,常见的有下面这些:

  • ReplicationController:比较原始的pod控制器,已经被废弃,由ReplicaSet替代
  • ReplicaSet:保证副本数量一直维持在期望值,并支持pod数量扩缩容,镜像版本升级。
  • Deployment:通过控制ReplicaSet来控制Pod,并支持滚动升级、回退版本
  • Horizontal Pod Autoscaler:可以根据集群负载自动水平调整Pod的数量,实现削峰填谷。
  • DaemonSet:在集群中的指定Node上运行且仅运行一个副本,一般用于守护进程类的任务(如日志收集、监控代理等)。
  • Job:它创建出来的pod只要完成任务就立即退出,不需要重启或重建,用于执行一次性任务。
  • Cronjob:它创建的Pod负责周期性任务控制,不需要持续后台运行。
  • StatefulSet:管理有状态应用,如数据库或其他需要持久化数据的应用。StatefulSet保证Pod的启动顺序、唯一性和持久化存储。

6.2 ReplicaSet(RS)

ReplicaSet的主要作用是保证一定数量的pod正常运行,它会持续监听这些Pod的运行状态,一旦Pod发生故障,就会重启或重建。同时它还支持对Pod数量的扩缩容和镜像版本的升降级

ReplicaSet的资源清单文件:

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

在这里面,需要新了解的配置项就是spec下面几个选项:

  • replicas:指定副本数量,其实就是当前rs创建出来的pod的数量,默认为1。

  • selector:选择器,它的作用是建立pod控制器和pod之间的关联关系,采用的Label Selector机制,在pod模板上定义label,在控制器上定义选择器,就可以表明当前控制器能管理哪些pod了。

  • template:模板,就是当前控制器创建pod所使用的模板板,里面其实就是前一章学过的pod的定义。

下面我们简单来演示一下:

6.2.1 创建ReplicaSet

创建pc-replicaset.yaml文件,内容如下:

apiVersion: apps/v1
kind: ReplicaSet   
metadata:
  name: pc-replicaset
  namespace: dev
spec:
  replicas: 3
  selector: 
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1

创建ReplicaSet控制器:

kubectl create -f pc-replicaset.yaml

查看ReplicaSet控制器

kubectl get rs pc-replicaset -n dev -o wide

其中:DESIRED表示期望副本数量;CURRENT表示当前副本数量;READY表示已经准备好提供服务的副本数量。

查看当前控制器创建出来的pod:

这里发现控制器创建出来的pod的名称是在控制器名称后面拼接了-xxxxx随机码。

6.2.2 扩缩容

再上一小节我们知道,我们创建了rs控制器,并且给到的副本是3,现在我们修改rs的副本数量,改为6。

更改rs副本数量(扩缩容)有两种方式:

  1. 编辑对应的yaml文件,直接改

    kubectl edit rs 控制器名称 -n 命名空间名称
    

    通过edit参数来编辑对应的yaml文件,编辑的过程其实就是使用vim/vi命令一样编辑即可。

    之后查看结果:

  2. 使用命令直接修改

    kubectl scale rs 控制器名称 --replicas=想要的副本数量 -n 命名空间名称
    

    这里假如我给2个副本:

可见两种方式都是OK的。

6.2.3 镜像升级

同上小结一样,也是可以通过编辑或者命令行的方式直接修改

  1. 编辑文件方式

    kubectl edit rs 控制器名称 -n 命名空间名称
    

    编辑rs的容器镜像,之前使用的是nginx:1.17.1,现在我要使用nginx:1.17.2

  2. 使用命令方式

    kubectl set image rs 控制器名称 容器=镜像版本 -n namespace -n 命名空间名称
    

6.2.4 删除ReplicaSet

使用kubectl delete命令会删除此RS以及它管理的Pod,在kubernetes删除RS前,会将RS的replicasclear调整为0,等待所有的Pod被删除后,在执行RS对象的删除。

如果希望仅仅删除RS对象(保留Pod),可以使用kubectl delete命令时添加--cascade=false选项(不推荐)。

也可以使用yaml直接删除(推荐):

kubectl delete -f yaml文件

6.3 Deployment(Deploy)

为了更好的解决服务编排的问题,kubernetes在V1.2版本开始,引入了Deployment控制器。值得一提的是,这种控制器并不直接管理Pod,而是通过管理ReplicaSet来间接管理Pod,即:Deployment管理ReplicaSet,ReplicaSet管理Pod。所以DeploymentReplicaSet功能更加强大。

Deployment主要功能有下面几个:

  • 支持ReplicaSet的所有功能。
  • 支持发布的停止、继续。
  • 支持滚动升级和回滚版本。

Deployment的资源清单文件:

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 # 滚动更新策略,Recreate(重建策略)
    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

6.3.1 创建deployment

创建pc-deployment.yaml,内容如下:

apiVersion: apps/v1
kind: Deployment      
metadata:
  name: pc-deployment
  namespace: dev
spec: 
  replicas: 3 #指定副本数量为3
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1

创建deployment:

kubectl create -f yaml文件 --record=true

[!note]

这里有一个参数:--record=true;表示用于记录执行的命令历史,并将其保存到资源的注释中(annotation)。加上这个参数可以方便我们后续对追踪资源的变化和了解特定操作的历史非常有用。比如能够直观地了解每个历史版本是通过哪个命令产生的,从而为回滚操作提供参考。

查看deployment:

kubectl get deploy yaml文件 -n 名称空间

执行结果:

可见在查看deploy的时候,有3个字段:

  • READY:表示当前有多少个 Pod 处于就绪状态(Ready)并且可以接收流量。格式通常是 X/Y,其中 X 是已就绪的 Pod 数量,Y 是期望的 Pod 副本数。
  • UP-TO-DATE:表示有多少个 Pod 已经使用了最新的配置(如镜像版本、资源请求等)。这个字段反映了 Deployment 中有多少 Pod 与当前的 Deployment 规范一致。
  • AVAILABLE:表示有多少个 Pod 处于可用状态,即这些 Pod 既已就绪(Ready),又满足 minReadySeconds 的要求(如果有配置)。

所以针对上诉的查询结果:

  • READY3/3 表示有 3 个 Pod 处于就绪状态,且与期望的 3 个 Pod 数量一致。

  • UP-TO-DATE3 表示有 3 个 Pod 已更新到最新配置。

  • AVAILABLE3 表示这 3 个 Pod 都已准备好处理请求并被标记为可用。

6.3.2 扩缩容

现在我们变更副本数量,参考之前6.2小结RS的扩缩容,这里也是如出一辙的:

  1. 使用命令式编辑

    kubectl scale deploy deploy名称 --replicas=副本数量  -n 名称空间
    

  2. 使用编辑文件方式

    kubectl edit deploy deploy名称 -n 名称空间
    

    修改spec:replicas: 3即可。

6.3.3 镜像更新

deployment支持两种更新策略:重建更新和**滚动更新(默认)**,可以通过strategy指定策略类型,支持两个属性:

strategy: # 指定新的Pod替换旧的Pod的策略, 支持两个属性:
type: #指定策略类型,支持两种策略
Recreate: #在创建出新的Pod之前会先杀掉所有已存在的Pod
RollingUpdate: #滚动更新,就是杀死一部分,就启动一部分,在更新过程中,存在两个版本Pod
rollingUpdate: #当type为RollingUpdate时生效,用于为RollingUpdate设置参数,支持两个属性:
    maxUnavailable: #用来指定在升级过程中不可用Pod的最大数量,默认为25%。
    maxSurge: #用来指定在升级过程中可以超过期望的Pod的最大数量,默认为25%。

6.3.3.1 重建更新

  1. 编辑pc-deployment.yaml,在spec节点下添加更新策略

    spec:
      strategy: # 策略
        type: Recreate # 重建更新
    

  2. 创建deploy进行验证

    现在为了方便观察,我开两个窗口,一个窗口用作滚动监听

    kubectl get pods -n dev -w
    

    [!tip]

    -w选项就是用来做滚动监听的。

    现在我变更镜像,让他触发重建更新操作,并且观察另外一个窗口:

    kubectl set image deployment deployment名称 容器=镜像版本 -n 名称空间
    

6.3.3.2 滚动更新

  1. 编辑pc-deployment.yaml,在spec节点下添加更新策略

    spec:
      strategy: # 策略
        type: RollingUpdate # 滚动更新策略
        rollingUpdate:
          maxSurge: 25% 
          maxUnavailable: 25%
    

    之后更新一下,否则更改的文件不会生效

    kubectl apply -f yaml文件
    

  2. 创建deploy进行验证

    可见确实是滚动更新的,并没有一次性全部关闭重建。

滚动更新的过程:

镜像更新中rs的变化:查看rs,发现原来的rs的依旧存在,只是pod数量变为了0,而后又新产生了一个rs,pod数量为3,其实这就是deployment能够进行版本回退的奥妙所在,后面会详细解释

6.3.3.3 版本回退

deployment支持版本升级过程中的暂停、继续功能以及版本回退等诸多功能,下面具体来看。

kubectl rollout: 版本升级相关功能,支持下面的选项:

  • status:显示当前升级状态
  • history:显示升级历史记录
  • pause:暂停版本升级过程
  • resume:继续已经暂停的版本升级过程
  • restart:重启版本升级过程
  • undo:回滚到上一级版本(可以使用–to-revision回滚到指定版本)

例如:

  • 查看当前升级版本的状态

    kubectl rollout status deploy deploy名称 -n 名称空间
    

    可见是升级成功的。

  • 查看升级历史记录

    kubectl rollout history deploy deploy名称 -n 名称空间
    

    可以发现有四次版本记录,说明完成过三次升级

  • 版本回退

    使用参数--to-revision=回退版本,如果省略这个选项,就是回退到上个版本

    kubectl rollout undo deployment deploy名称 --to-revision=回退版本 -n 名称空间
    

    [!tip]

    其实deployment之所以可是实现版本的回滚,就是通过记录下历史rs来实现的,一旦想回滚到哪个版本,只需要将当前版本pod数量降为0,然后将回滚版本的pod提升为目标数量就可以了。

6.3.3.4 金丝雀发布

Deployment控制器支持控制更新过程中的控制,如“暂停(pause)”或“继续(resume)”更新操作。

比如有一批新的Pod资源创建完成后立即暂停更新过程,此时,仅存在一部分新版本的应用,主体部分还是旧的版本。然后,再筛选一小部分的用户请求路由到新版本的Pod应用,继续观察能否稳定地按期望的方式运行。确定没问题之后再继续完成余下的Pod资源滚动更新,否则立即回滚更新操作。这就是所谓的金丝雀发布

例如现在我们来测试一下:

更新deployment的版本,并配置暂停deployment,然后观察更新的状态

kubectl set image deploy pc-deployment nginx=nginx:1.17.4 -n dev && kubectl rollout pause deployment pc-deployment  -n dev
kubectl rollout status deploy pc-deployment -n dev

监控更新的过程,可以看到已经新增了一个资源,但是并未按照预期的状态去删除一个旧的资源,就是因为使用了pause暂停命令:

确保更新的pod没问题了,继续更新:

kubectl rollout resume deploy pc-deployment -n dev

查看最后的更新情况:

6.3.3.5 删除Deployment

删除deployment,其下的rs和pod也将被删除:

kubectl delete -f yaml文件

6.4 Horizontal Pod Autoscaler(HPA)

在前面的课程中,我们已经可以实现通过手工执行kubectl scale命令实现Pod扩容或缩容,但是这显然不符合Kubernetes的定位目标:自动化、智能化。 Kubernetes期望可以实现通过监测Pod的使用情况,实现pod数量的自动调整,于是就产生了Horizontal Pod Autoscaler(HPA)这种控制器。

HPA可以获取每个Pod利用率,然后和HPA中定义的指标进行对比,同时计算出需要伸缩的具体值,最后实现Pod的数量的调整。其实HPA与之前的Deployment一样,也属于一种Kubernetes资源对象,它通过追踪分析RC控制的所有目标Pod的负载变化情况,来确定是否需要针对性地调整目标Pod的副本数,这是HPA的实现原理。

接下来,我们来做一个实验

要想实现HPA对Pod自动化调整,必须安装一个服务:metrics-server

6.4.1 安装metrics-server

  1. 安装metrics-server

    安装参考文档:K8S部署Metrics-Server服务_error from server (notfound): pods “dashboard-metr-CSDN博客

    metrics-server可以用来收集集群中的资源使用情况,获取metrics-server,注意使用的版本:

    wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.7.2/components.yaml
    

    当把文件下载下来了之后,需要编辑一下:

    • 修改镜像地址:

      registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server:v0.7.2
      

      采用国内的阿里镜像

    • 在args 里加上这个参数:--kubelet-insecure-tls,不验证kubelet提供的https证书,用于跳过 TLS 证书验证

    • 在args 里加上这个参数:--kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP

  2. 之后安装官方文档安装即可:

  3. 测试是否安装成功

    可见安装成功

6.4.2 准备deployment和servie

创建pc-hpa-pod.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数

创建service:

kubectl expose deployment nginx --type=NodePort --port=80 -n dev

查看信息:

kubectl get deployment,pod,svc -n dev

6.4.3 部署HPA

创建pc-hpa.yaml文件,内容如下:

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: pc-hpa
  namespace: dev
spec:
  minReplicas: 1  #最小pod数量
  maxReplicas: 10 #最大pod数量
  targetCPUUtilizationPercentage: 3 # CPU使用率指标
  scaleTargetRef:   # 指定要控制的nginx信息
    apiVersion: apps/v1
    kind: Deployment
    name: nginx

这里给了一个最小Pod和最大Pod的数量,让HPA控制器在空值Pod的时候要保持在这个范围。

之后外面定义了CPU使用率指标,因为HPA在空值Pod数量的时候,就是通过计算响应的指标来决定扩缩容的,这里设置3表示3%的意思,只要CPU的使用率大于了3%,就会触发扩容。

执行结果并查看:

6.4.4 测试

使用压测工具对service地址节点IP:服务端口进行压测,然后通过控制台查看hpa和pod的变化

我这里的nginx服务访问的端口是31420。

在开始压测之前,我们多开几个窗口,方便观察:

#窗口1执行
kubectl get hpa -n dev -w

#窗口2执行
kubectl get deployment -n dev -w

#窗口3执行
kubectl get pods -n dev -w

之后我这里使用Post来迭代发送1W个请求,之后观察各个窗口变化:

可见确实是在扩容,现在我们停止迭代,看看它有没有减少:

可见长时间恢复正常的话,它才会删除多余的Pod:

[!tip]

他这个检测是需要等一段时间的,如果它发现CPU长时间超过规定的指标,他才会去扩容,同理,如果发现CPU长时间小于指标,他才会缩容。

6.5 DaemonSet(DS)

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

DaemonSet控制器的特点:

  • 当一个新的节点加入集群,DaemonSet自动在该节点上调度一个新的 Pod。
  • 当节点从集群中移除时,DaemonSet会自动清理该节点上的 Pod。
  • 当Pod被删除或者意外中止时,DaemonSet会自动重新调度和恢复被删除的 Pod。

下面先来看下DaemonSet的资源清单文件

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

下面我们来做个示例,创建pc-daemonset.yaml,内容如下:

apiVersion: apps/v1
kind: DaemonSet      
metadata:
  name: pc-daemonset
  namespace: dev
spec: 
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1

由于我这里只有一个node节点,演示效果可能不明显,如果有多个node节点,你这里查看ds,它的DESIREDCURRENTREADYUP-TO-DATEAVAILABLE属性就会有多个。

  • DESIRED:期望在集群中运行的 Pod 数量。这是 DaemonSet 根据节点数量和调度策略计算出来的期望值。通常,DESIRED 表示应该在每个目标节点(可能是所有节点,或某些符合条件的节点)上运行一个 Pod。
  • CURRENT:当前实际在集群中运行的 Pod 数量。这是 DaemonSet 当前已经在目标节点上创建并正在运行的 Pod 数量。这个值应该和 DESIRED 相等。如果不相等,说明有些 Pod 还未创建,或者节点上 Pod 还未完全运行。
  • READY:当前处于就绪状态的 Pod 数量。就绪的 Pod 是那些通过了就绪探测(readiness probe)的 Pod。READY 表示能够正常处理工作负载的 Pod 数量,这个值通常应和 DESIRED 相等,但在更新、启动或故障期间可能会短暂不一致。
  • UP-TO-DATE:已经更新到最新版本的 Pod 数量。当 DaemonSet 配置发生更新时,可能会触发滚动更新。UP-TO-DATE 表示已经更新为最新版本的 Pod 数量。滚动更新过程中,这个数字逐步增大,直到所有 Pod 都更新到最新版本。
  • AVAILABLE:当前可用的 Pod 数量。可用的 Pod 是那些处于就绪状态,并且运行了一段时间、足够稳定的 Pod。AVAILABLE 通常应该和 DESIREDREADY 相等,确保应用在集群中正常提供服务。Kubernetes 会根据 minReadySeconds 设置判断 Pod 是否足够稳定才算为可用。

6.6 Job

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

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

Job 的工作原理:Job 控制器创建一个或多个 Pod 来执行用户定义的任务。它监控这些 Pod 的状态并根据任务的定义来决定 Pod 是否需要重新创建。

主要参数:

  • .spec.completions:定义 Job 成功完成所需的 Pod 数量。默认是 1,表示只要一个 Pod 成功执行任务就认为 Job 成功。
  • .spec.parallelism:定义可以同时并行运行的 Pod 数量。例如,设置 parallelism 为 3,意味着可以同时运行 3 个 Pod,但 completions 仍然决定任务完成的总次数。
  • .spec.backoffLimit:定义 Pod 失败后最多允许重试的次数。如果 Pod 的重试次数超过这个值,Job 将被标记为失败。

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"]

[!note]

这里关于重启策略设置需要说明一下:

  • 如果指定为OnFailure,则job会在pod出现故障时重启容器,而不是创建pod,failed次数不变。

  • 如果指定为Never,则job会在pod出现故障时创建新的pod,并且故障pod不会消失,也不会重启,failed次数加1。

  • 如果指定为Always的话,就意味着一直重启,意味着job任务会重复去执行了,当然不对,所以不能设置为Always


下面我们来简单示范一下:创建pc-job.yaml,内容如下:

apiVersion: batch/v1
kind: Job      
metadata:
  name: pc-job
  namespace: dev
spec:
  manualSelector: true
  selector:
    matchLabels:
      app: 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 3;done"]

这里我会让容器循环执行命令,每次休眠3S中来模拟任务耗时。

现在我们开启多个窗口,来持续监听job、pod的状态:

通过观察pod和job状态可以看到,在运行完毕任务后,就会变成Completed状态。

接下来,调整下pod运行的总数量和并行数量 即:在spec下设置下面两个选项:

completions: 6 # 指定job需要成功运行Pods的次数为6
parallelism: 3 # 指定job并发运行Pods的数量为3

然后重新运行job,观察效果,此时会发现,job会每次运行3个pod,总共执行了6个pod:

6.7 CronJob(CJ)

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

CronJob的资源清单文件:

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: 替换,取消当前正在运行的作业并用新作业替换它

下面我们来简单演示一下:创建pc-cronjob.yaml,内容如下:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: pc-cronjob
  namespace: dev
  labels:
    controller: cronjob
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    metadata:
    spec:
      template:
        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 3;done"]

7. Service详解

7.1 Service介绍

在 Kubernetes 中,Service 是一个非常重要的概念,专门用于提供持久、稳定的网络访问。由于 Kubernetes 中的 Pod 是短暂的(它们可能随时终止或重新创建),每个 Pod 都有自己独立的 IP 地址,这会导致 Pod 重启后 IP 地址发生变化。如果要直接访问 Pod,IP 地址的变动会带来管理和访问上的难题。为了解决这一问题,Kubernetes 提供了 Service

Service 是一种抽象,它为一组 Pod 提供一个稳定的网络端点(即固定的 IP 和端口),并能自动负载均衡流量到这些 Pod。即使 Pod 被重新调度到不同的节点或重新创建,Service 的 IP 和端口依然保持不变。

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

# 10.97.97.97:80 是service提供的访问入口
# 当访问这个入口的时候,可以发现后面有三个pod的服务在等待调用,
# kube-proxy会基于rr(轮询)的策略,将请求分发到其中一个pod上去
# 这个规则会同时在集群内的所有节点上都生成,所以在任何一个节点上访问都可以。
[root@node1 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.97.97.97:80 rr
  -> 10.244.1.39:80               Masq    1      0          0
  -> 10.244.1.40:80               Masq    1      0          0
  -> 10.244.2.33:80               Masq    1      0          0

kube-proxy目前支持三种工作模式:

  1. 用户空间模式(Userspace Mode)

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

  2. iptables模式(Iptables Mode)

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

  3. IPVS模式(IP Virtual Server Mode)

    ipvs模式和iptables类似,kube-proxy监控Pod的变化并创建相应的ipvs规则。ipvs相对iptables转发效率更高。除此以外,ipvs支持更多的LB算法。

三种模式的比较:

模式 性能 负载均衡策略 优缺点 适用场景
用户空间 较低 轮询 简单实现,但性能低 小规模集群或开发环境
iptables 中等 轮询 性能好,规则多时扩展性差 中等规模的集群
IPVS 最佳 多种算法(轮询、最少连接等) 高效、灵活、支持大规模集群 大规模、高并发生产环境

想要使用IPVS模式,就必须安装IPVS内核模块,否则会降级为iptables:

修改kube-system命名空间下的kube-proxy的配置,修改mode为ipvs来开启IPVS:

kubectl edit cm kube-proxy -n kube-system

删除与kube-proxy相关的pod,让其自动重建:

kubectl delete pod -l k8s-app=kube-proxy -n kube-system

之后查看IPVS路由表:

ipvsadm -Ln

含义解释:

  1. IP Virtual Server version 1.2.1 (size=4096)

    • IP Virtual Server version 1.2.1:表示当前使用的 IPVS 版本是 1.2.1。
    • size=4096:表示 IPVS 的哈希表大小,IPVS 使用一个哈希表来存储服务信息,大小为 4096,意味着它可以存储 4096 个服务的转发规则。
  2. Prot LocalAddressScheduler Flags

    • Prot:表示协议类型,分为 TCPUDP

    • LocalAddress:本地虚拟服务的 IP 地址和端口,即 Kubernetes 中的 ClusterIP 和服务端口。例如:

      • 10.96.0.1:443:这是 Kubernetes API 服务器的服务地址和端口,10.96.0.1 是 ClusterIP,443 是 HTTPS 的端口。
      • 10.96.0.10:53:这是 Kubernetes DNS 服务的 ClusterIP 和端口,53 是 DNS 解析的端口。
      • 10.96.0.10:9153:这是 CoreDNS 的 Metrics 服务,端口是 9153
    • Scheduler:调度算法,rr 代表 轮询(Round Robin)调度算法,表示流量会依次轮询地分发给后端的各个 Pod。

    • Flags:标识,通常包括转发模式等信息。这里的 Masq 代表 NAT 模式,即 IP 伪装模式。

  3. Forward Weight ActiveConn InActConn

    • Forward:表示流量的转发模式,这里是 Masq,代表 IP 伪装转发模式,流量会从虚拟 IP 转发到真实的 Pod 时,源 IP 会被替换为代理服务器的 IP。

    • Weight:权重,值越大表明流量分配到该后端的比例越高。权重为 1 表示默认权重。

    • ActiveConn:当前服务的活跃连接数,表示当前正在处理的连接数量。在这个输出中,所有的活跃连接数都是 0,说明没有正在处理的请求。

    • InActConn:非活跃连接数,表示处于空闲或等待状态的连接数量。这些连接可能是等待关闭或处于长时间不活动状态。在此输出中,非活跃连接数也是 0

7.2 Service类型

Kubernetes 提供了四种类型的 Service 来满足不同的网络需求:

  1. ClusterIPClusterIP 是 Kubernetes 中最常见和默认的 Service 类型。它在集群内部创建一个虚拟 IP 地址,其他 Pod 可以通过这个 IP 访问该 Service。ClusterIP 只允许集群内部的访问,无法从集群外部直接访问。
  2. NodePortNodePort Service 在每个节点(Node)上开放一个特定的端口,并将请求转发到 Service 后端的 Pod。通过访问集群中任意节点的 IP 地址和对应的 NodePort,可以从集群外部访问这个 Service。
  3. LoadBalancerLoadBalancer 类型的 Service 主要用于云环境中,它会自动向外部提供一个基于云平台的负载均衡器(如 AWS ELB、GCP LB 等)。外部客户端可以通过负载均衡器的 IP 直接访问该 Service。LoadBalancerNodePort 的扩展,底层会依赖 NodePort 来实现。
  4. ExternalNameExternalName 类型的 Service 不通过代理转发流量,而是返回一个外部的 DNS 名称。它可以将集群内部的服务请求映射到集群外部的 DNS 地址,类似于 DNS 的 CNAME 记录。

Service的资源清单文件:

kind: Service  # 资源类型
apiVersion: v1  # 资源版本
metadata: # 元数据
  name: service # 资源名称
  namespace: dev # 命名空间
spec: # 描述
  selector: # 标签选择器,用于确定当前service代理哪些pod
    app: nginx
  type: # Service类型,指定service的访问方式
  clusterIP:  # 虚拟服务的ip地址
  sessionAffinity: # session亲和性,支持ClientIP、None两个选项
  ports: # 端口信息
    - protocol: TCP 
      port: 3017  # service端口
      targetPort: 5003 # pod端口
      nodePort: 31122 # 主机端口

四种 Service 类型的对比:

Service 类型 访问方式 适用场景 特点
ClusterIP 集群内部访问 内部服务通信 默认类型,集群内通过虚拟 IP 访问
NodePort 集群外部访问 测试环境或简单的外部访问 通过节点 IP 和固定端口访问
LoadBalancer 云平台负载均衡器 生产环境 自动配置云平台负载均衡器
ExternalName DNS 名称 将内部请求转发到外部 仅映射 DNS 名称,不代理流量

7.3 Service使用

7.3.1 实验环境准备

在使用service之前,首先利用Deployment创建出3个pod,注意要为pod设置app=nginx-pod的标签

创建deployment.yaml,内容如下:

apiVersion: apps/v1
kind: Deployment      
metadata:
  name: pc-deployment
  namespace: dev
spec: 
  replicas: 3
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80

image-20240907094813036

为了方便后面的测试,修改下三台nginx的index.html页面(三台修改的IP地址不一致)

kubectl exec -it pod的名称 -n dev -- /bin/sh

现在我将每个nginx的首页改为各个节点的IP地址,方便查看效果:

7.3.2 ClusterIP类型的Service

创建service-clusterip.yaml文件

apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: 10.97.97.97 # service的ip地址,如果不写,默认会生成一个
  type: ClusterIP # Service 类型为 ClusterIP
  ports:
  - port: 80  # Service端口       
    targetPort: 80 # pod端口

访问10.97.97.97:80观察效果:

[!tip]

这里简单拓展一下Endpoint:在 Kubernetes 中,Endpoint 是用来表示和追踪一个 Service 后端的具体 Pod 或其他资源的地址。当一个 Service 将流量转发到后端时,它需要知道有哪些实际的 Pod 可用,这些实际可用的 Pod 的 IP 地址和端口组合就是 Endpoint

Kubernetes 中的 Service 通过 Selector 来选择一组符合条件的 Pod 作为后端,并为这些 Pod 创建相应的 Endpoint。当流量发往 Service 时,Service 实际上会将流量路由到这些 Endpoints 指定的后端 Pod。kube-proxy 负责根据 Endpoint 的信息进行负载均衡和转发。

在开始下一小节测试之前,需要删除本小节的service:

kubectl delete -f service-clusterip.yaml

7.3.3 HeadLiness类型的Service

在 Kubernetes 中,Headless Service 是一种特殊的 Service,其与标准 ClusterIP 类型的 Service 有明显的区别。Headless Service 的特点是它不会分配一个 ClusterIP,也不会提供基于 kube-proxy 的负载均衡功能,而是将客户端直接引导到后端 Pod。Headless Service 适用于一些需要更精细控制的场景,比如数据库集群、状态服务等。

Headless Service 的定义和普通的 Service 大体相同,不同之处在于其 ClusterIP 字段被设置为 None。当一个 Service 被定义为 Headless Service 时,Kubernetes 不会为其分配一个虚拟 IP 地址,也不会为其进行负载均衡操作,而是通过 DNS 直接返回所有关联的 Pod 的 IP 地址。

创建service-headliness.yaml

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

可见命名没有分配ClusterIP,但是该service依然知道应该控制哪些Pod,主要通过两种方法:

  1. 标签选择器机制:每个 Service 通常都有一个 selector 字段,用来指定一组 Pod 标签。当你创建一个 Service(包括 Headless Service)时,Kubernetes 会根据该标签选择器来找到所有匹配的 Pod,并将它们的 IP 地址作为后端资源提供给该 Service。
  2. DNS解析机制:对于 Headless Service,Kubernetes 的 DNS 机制会返回所有与该服务匹配的 Pod 的 IP 地址,而不是像普通 Service 那样返回一个 ClusterIP。

在开始下一小节测试之前,需要删除本小节的service:

kubectl delete -f service-headliness.yaml

7.3.4 NodePort类型的Service

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

NodePort Service 的工作机制:

  1. 服务类型:在创建 Service 时,可以指定其类型为 NodePort。当使用 NodePort 时,Kubernetes 会在每个节点上开放一个端口(NodePort),允许外部请求通过这个端口访问服务。
  2. 端口分配:Kubernetes 会从 30000 到 32767 的范围内随机选择一个端口作为 NodePort(这个范围可以在集群的配置中调整)。这个端口会在所有节点上都开放,允许外部请求通过任一节点的 IP 地址和 NodePort 进行访问。
  3. 流量转发:当外部请求到达某个节点的 NodePort 时,Kubernetes 会根据该 Service 的选择器,将流量转发到与该 Service 关联的 Pod。流量的转发是通过 kube-proxy 进行的,它会处理流量的负载均衡。

创建service-nodeport.yaml

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

接下来可以通过电脑主机的浏览器去访问集群中任意一个nodeip的30002端口,即可访问到pod,之前有小节测试过,这里就不在测试了,你可以自行验证。

在开始下一小节测试之前,需要删除本小节的service:

kubectl delete -f service-nodeport.yaml

7.3.5 LoadBalancer类型的Service

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

7.3.6 ExternalName类型的Service

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

创建service-externalname.yaml:

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

7.4 Ingress介绍

在前面课程中已经提到,Service对集群之外暴露服务的主要方式有两种:NotePortLoadBalancer,但是这两种方式,都有一定的缺点:

  • NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显
  • LB方式的缺点是每个service需要一个LB,浪费、麻烦,并且需要kubernetes之外设备的支持

基于这种现状,kubernetes提供了Ingress资源对象,Ingress只需要一个NodePort或者一个LB就可以满足暴露多个Service的需求。

在 Kubernetes 中,Ingress 是一种用于管理外部访问集群内服务的 API 对象。它提供了一个集中的入口点,允许外部请求通过 HTTP 和 HTTPS 协议访问集群中的服务。Ingress 通常用于处理路由、负载均衡、SSL/TLS 终止以及基于主机或路径的路由。

Ingress 的工作机制:

  1. Ingress Controller:Ingress 本身并不提供流量路由功能,它依赖于 Ingress Controller 来处理外部请求。Ingress Controller 是一个运行在集群中的 Pod,负责根据 Ingress 资源的定义配置外部负载均衡器或反向代理。
  2. 路由配置:Ingress 资源定义了如何将外部请求路由到内部服务。用户可以在 Ingress 资源中指定路由规则,例如基于请求的主机名或路径将流量转发到不同的服务。
  3. 负载均衡和 SSL/TLS 终止:Ingress Controller 可以提供负载均衡功能,将流量均匀分发到后端服务。它还可以处理 SSL/TLS 终止,即在 Ingress 层解密 HTTPS 流量,减少后端服务的负担。

工作机制大致如下图表示:

实际上,Ingress相当于一个7层的负载均衡器,是kubernetes对反向代理的一个抽象,它的工作原理类似于Nginx,可以理解成在Ingress里建立诸多映射规则,Ingress Controller通过监听这些配置规则并转化成Nginx的反向代理配置 , 然后对外部提供服务。在这里有两个核心概念:

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

Ingress(以Nginx为例)的工作原理如下:

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

7.5 Ingress使用

7.5.1 环境准备

ingress环境:https://github.com/kubernetes/ingress-nginx

查看版本信息:

我的k8s是用的1.30,所以我直接用最新的v1.11.2。

官方安装文档:https://kubernetes.github.io/ingress-nginx/deploy/ 中有这么一句话:

我们这里没有Helm,所以安装他的提示去做即可。

先把这个deploy.yaml文件下载下来:

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml

如果你下载不下了,可以去官网手动下载,然后上传服务器即可:https://github.com/kubernetes/ingress-nginx/tree/main/deploy/static/provider/cloud

之后修改deploy.yaml文件中的镜像地址,改为国内阿里的:registry.cn-hangzhou.aliyuncs.com/google_containers/

将deploy.yaml文件中凡是设计到image的地方都要改为国内的镜像源:

  1. controller镜像替换

    原镜像:

    image: registry.k8s.io/ingress-nginx/controller:v1.11.2@sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce
    

    替换为:

    image: registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.11.2
    

  2. kube-webhook-certgen镜像替换

    原镜像:

    image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.3@sha256:a320a50cc91bd15fd2d6fa6de58bd98c1bd64b9a6f926ce23a600d87043455a3
    

    替换为:

    image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v1.4.3
    

[!caution]

凡是image配置项都要改为国内的,并且要去掉digest信息。

之后执行安装即可:

kubectl apply -f deploy.yaml

最后查看各个资源是否建立成功:

kubectl get all -n ingress-nginx

看到上诉内容,就说明成功了。


准备service和pod,为了后面的实验比较方便,创建如下图所示的模型:

创建tomcat-nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-deployment
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: tomcat-pod
  template:
    metadata:
      labels:
        app: tomcat-pod
    spec:
      containers:
      - name: tomcat
        image: tomcat:8.5-jre10-slim
        ports:
        - containerPort: 8080

---

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: None
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80

---

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
  namespace: dev
spec:
  selector:
    app: tomcat-pod
  clusterIP: None
  type: ClusterIP
  ports:
  - port: 8080
    targetPort: 8080

好了现在环境已经准备好了,现在我们分别来测试Http和Https。

7.5.2 Http代理

创建ingress-http.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-http
  namespace: dev
spec:
  ingressClassName: nginx #指定要使用的 Ingress 控制器,这里我安装的就是ingress-nginx,所以就用nginx;最好加上这个配置,不然你访问可能404
  rules: # 指定了HTTP的路由规则
  - host: nginx.test.com # 当用户访问nginx.text.com,访问资源路径为/的时候请求就会被转发到backend配置项
    http:
      paths:
      - path: /
        pathType: Prefix
        backend: # 后端服务由名称为nginx-service的Service提供服务,nginx-service服务的端口为80
          service:
            name: nginx-service 
            port:
              number: 80
  - host: tomcat.test.com # 参考上面的配置一个道理
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service: 
            name: tomcat-service
            port:
              number: 8080

查看详情:

可见详情中有多个IP,每一个Host都对应3个,说明我的配置是生效了的。

接下来,由于你的域名并不是真的域名,所以你需要在本地电脑上配置host文件,解析上面的两个域名到master节点上。

host文件地址一般为:

C:\Windows\System32\drivers\etc

然后,就可以分别访问tomcat.test.com:端口和 nginx.test.com:端口查看效果了。

这个端口是哪里来呢,通过如下命令查看:

 kubectl get svc -n ingress-nginx

所以这里我在浏览器访问:tomcat.test.com:31034、nginx.test.com:31034:

可见均访问成功。

7.5.3 Https代理

测试之前先删除上一小节的相关文件配置,以防干扰:

kubectl delete -f ingress-http.yaml

创建证书

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt(证书名称) -subj "/C=CN/ST=BJ/L=BJ/O=nginx/CN=你的顶级域名,我这里的就是(test.com)"

生成密钥:

kubectl create secret tls tls-secret --key tls.key --cert tls.crt(证书名称)

创建ingress-https.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-https
  namespace: dev
spec:
  tls:
    - hosts:
      - nginx.test.com
      - tomcat.test.com
      secretName: tls-secret # 指定秘钥
  ingressClassName: nginx #指定要使用的 Ingress 控制器,这里我安装的就是ingress-nginx,所以就用nginx;最好加上这个配置,不然你访问可能404
  rules: # 指定了HTTP的路由规则
  - host: nginx.test.com # 当用户访问nginx.text.com,访问资源路径为/的时候请求就会被转发到backend配置项
    http:
      paths:
      - path: /
        pathType: Prefix
        backend: # 后端服务由名称为nginx-service的Service提供服务,nginx-service服务的端口为80
          service:
            name: nginx-service 
            port:
              number: 80
  - host: tomcat.test.com # 参考上面的配置一个道理
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service: 
            name: tomcat-service
            port:
              number: 8080

下面可以通过浏览器访问https://nginx.test.com:30779https://tomcat.test.com:30779来查看了。

8. 数据存储

在前面已经提到,容器的生命周期可能很短,会被频繁地创建和销毁。那么容器在销毁时,保存在容器中的数据也会被清除。这种结果对用户来说,在某些情况下是不乐意看到的。为了持久化保存容器的数据,kubernetes引入了Volume的概念。

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

kubernetes的Volume支持多种类型,比较常见的有下面几个:

  • 简单存储:EmptyDirHostPathNFS
  • 高级存储:PVPVC
  • 配置存储:ConfigMapSecret

Volume的配置文件:

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  containers:
    - name: example-container
      image: example-image
      volumeMounts:
        - mountPath: /data
          name: my-volume
  volumes: #定义volume
    - name: my-volume
      emptyDir: {}

8.1 基本存储

8.1.1 EmptyDir

EmptyDir是最基础的Volume类型,一个EmptyDir就是Host上的一个空目录

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

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

接下来,通过一个容器之间文件共享的案例来使用一下EmptyDir。

在一个Pod中准备两个容器nginx和busybox,然后声明一个Volume分别挂在到两个容器的目录中,然后nginx容器负责向Volume中写日志,busybox中通过命令将日志内容读到控制台。

创建一个volume-emptydir.yaml

apiVersion: v1
kind: Pod
metadata:
  name: volume-emptydir
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - containerPort: 80
    volumeMounts:  # 将logs-volume挂在到nginx容器中,对应的目录为 /var/log/nginx
    - name: logs-volume
      mountPath: /var/log/nginx
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件中内容
    volumeMounts:  # 将logs-volume 挂在到busybox容器中,对应的目录为 /logs
    - name: logs-volume
      mountPath: /logs
  volumes: # 声明volume, name为logs-volume,类型为emptyDir
  - name: logs-volume
    emptyDir: {}

创建并查看Pod:

现在我们新开一个窗口,通过kubectl logs命令查看指定容器的标准输出:

kubectl logs -f pod名称 -n 命名空间名称 -c 容器名称

例如这里就是:

kubectl logs -f volume-emptydir -n dev -c busybox

命令解读:

  • logs:这个子命令用于查看指定 Pod 的容器日志。

  • -f:这是一个标志,用于持续跟踪日志输出。使用这个标志后,命令会保持打开状态,实时显示日志更新,相当于使用 tail -f 命令。

  • volume-emptydir:这是指定的 Pod 的名称。你想要查看其日志的 Pod 叫做 volume-emptydir

  • -n dev:这个标志指定命名空间(namespace)。Kubernetes 中的资源可以被组织在不同的命名空间中,以实现资源的隔离和管理。在这里,dev 是目标命名空间的名称,表示你要查看的 Pod 位于 dev 命名空间下。

  • -c busybox:这个标志用于指定容器的名称。在一个 Pod 中,可能有多个容器在运行,而 -c 标志用于指定你想查看日志的特定容器。在这里,busybox 是你要查看日志的容器名称。

可见他现在就在持续监听,现在我们访问一下nginx容器就会参数一些日志,然后观察:

可见确实有日志输出,这就说明我们访问nginx的时候,将访问的日志写入了名为logs-volume的Volumn,然后容器busybox才能够从中读取到日志。

8.1.2 HostPath

上节课提到,EmptyDir中数据不会被持久化,它会随着Pod的结束而销毁,如果想简单的将数据持久化到主机中,可以选择HostPath。

HostPath就是将Node主机中一个实际目录挂在到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据依据可以存在于Node主机上。

hostPath配置文件示例:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
    - name: my-container
      image: my-image
      volumeMounts:
        - mountPath: /data
          name: my-hostpath
  volumes:
    - name: my-hostpath
      hostPath:
        path: /mnt/data  # 宿主节点上的路径
        type: Directory  # 指定路径的类型(可选)

hostPath可以配置类型(type),主要有以下几种类型:

  • File:路径指向一个文件。
  • Directory:路径指向一个目录。
  • DirectoryOrCreate:如果路径不存在,则创建一个目录。
  • FileOrCreate:如果路径不存在,则创建一个文件。
  • Socket:路径指向一个 UNIX 套接字。
  • Char:路径指向一个字符设备。
  • Block:路径指向一个块设备。

[!caution]

这里的宿主机节点是指你这个Pod被指派到那个节点上运行的,那么该节点就是宿主机。

现在我们来测试一下,创建一个volume-hostpath.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: volume-hostpath
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - containerPort: 80
    volumeMounts:
    - name: logs-volume
      mountPath: /var/log/nginx
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","tail -f /logs/access.log"]
    volumeMounts:
    - name: logs-volume
      mountPath: /logs
  volumes:
  - name: logs-volume
    hostPath: 
      path: /root/logs #目录为宿主机的/root/logs目录
      type: DirectoryOrCreate  # 目录存在就使用,不存在就先创建后使用

可见我的Pod是在k8s-node1节点上运行的,所以接下来就可以去k8s-node1节的主机上的/root/logs目录下查看存储的文件了。

同样的道理,如果在此目录下创建一个文件,到容器中也是可以看到的,因为挂载是双向的。

8.1.3 NFS

HostPath可以解决数据持久化的问题,但是一旦Node节点故障了,Pod如果转移到了别的节点,又会出现问题了,因为新的Pod在新的Node上是无法访问到以前老的Node上的文件的,此时需要准备单独的网络存储系统,比较常用的用NFSCIFS

NFS(Network File System)是一种允许不同计算机之间通过网络共享文件系统的协议,可以搭建一台NFS服务器,然后将Pod中的存储直接连接到NFS系统上,这样的话,无论Pod在节点上怎么转移,只要Node跟NFS的对接没问题,数据就可以成功访问。

  1. 首先要准备nfs的服务器,这里为了简单,直接是master节点做NFS服务器

    在nfs上安装nfs服务:

    yum install -y nfs-utils
    

    准备一个共享目录:

    mkdir /root/data/nfs -pv
    

    将共享目录以读写权限暴露给192.168.254.0/24网段中的所有主机:

    vim /etc/exports
    

    加入如下信息:

    /root/data/nfs     192.168.5.0/24(rw,no_root_squash)
    

    启动nfs服务:

    systemctl restart nfs
    
  2. 接下来,要在的每个node节点上都安装下nfs,这样的目的是为了node节点可以驱动nfs设备

    yum install nfs-utils -y
    

    [!caution]

    注意不需要启动,只需要保证有这个服务即可。

  3. 接下来,就可以编写pod的配置文件了,创建volume-nfs.yaml

    apiVersion: v1
    kind: Pod
    metadata:
      name: volume-nfs
      namespace: dev
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80
        volumeMounts:
        - name: logs-volume
          mountPath: /var/log/nginx
      - name: busybox
        image: busybox:1.30
        command: ["/bin/sh","-c","tail -f /logs/access.log"] 
        volumeMounts:
        - name: logs-volume
          mountPath: /logs
      volumes:
      - name: logs-volume
        nfs:
          server: 192.168.254.142  #nfs服务器地址
          path: /root/data/nfs #共享文件路径
    
  4. 最后,运行下pod,观察结果

8.2 高级存储

前面已经学习了使用NFS提供存储,此时就要求用户会搭建NFS系统,并且会在yaml配置nfs。由于kubernetes支持的存储系统有很多,要求客户全都掌握,显然不现实。为了能够屏蔽底层存储实现的细节,方便用户使用, kubernetes引入PV和PVC两种资源对象。

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

PVC(Persistent Volume Claim)是持久卷声明的意思,是用户对于存储需求的一种声明。换句话说,PVC其实就是用户向kubernetes系统发出的一种资源需求申请。

上图解析

  1. PV将底层的存储资源(如NFS、CIFS、云存储等)进行抽象,为上层应用提供统一的存储接口,这个接口其实就是PV,由管理员统一维护,用户无需过多关注。并且PV是跨命名空间的,作用域为整个集群。
  2. 其实每一个PV都可以限制存储的能力,比如pv1最高允许存储10G,pv2最高运行存储5G等。
  3. 对于用户层,用户每创建一个Pod都会有存储数据的需求,这些需求就可以理解为PVC,用户只需要提出存储的需求,而不用去了解底层存储的具体实现。
  4. 对于PV和PVC的关系,图中红线就是PV和PVC的绑定关系,这个绑定过程是根据用户提出的存储需求(PVC)与PV的存储能力相互匹配的,并且是由Kubernets自动完成的。比如用户提出的存储需求是10G,假如现在有3个PV,分别对应的存储能力为5G、10G、20G,那么系统就会将用户的存储需求与10G的那个PV相绑定。

使用了PV和PVC之后,工作可以得到进一步的细分:

  • 存储:存储工程师维护。
  • PV: kubernetes管理员维护,用户无需关注。
  • PVC:kubernetes用户维护,管理员无需关注。

8.2.1 PV

PV是存储资源的抽象,下面是资源清单文件:

apiVersion: v1  
kind: PersistentVolume #表示该对象的类型是PersistentVolume,即持久化卷,也就是PV
metadata:
  name: pv2
spec:
  nfs: # 存储类型,与底层真正存储对应,如果底层是用的nfs,这里就写nfs
  capacity:  # 存储能力,目前只支持存储空间的设置
    storage: 2Gi
  accessModes:  # 访问模式
  storageClassName: # 存储类别
  persistentVolumeReclaimPolicy: # 回收策略

PV 的关键配置参数说明:

  • 存储类型

    底层实际存储的类型,kubernetes支持多种存储类型,每种存储类型的配置都有所差异。

  • 存储能力(capacity)

​ 表示存储卷的容量,具体配置了该 PV 提供的存储大小,它可以是 Gi(GiB)、Mi(MiB)等。

  • 访问模式(accessModes)

    用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:

    • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载。
    • ReadOnlyMany(ROX): 只读权限,可以被多个节点挂载。
    • ReadWriteMany(RWX):读写权限,可以被多个节点挂载。

    [!caution]

    需要注意的是,底层不同的存储类型可能支持的访问模式不同。

  • 回收策略(persistentVolumeReclaimPolicy)

    当PV不再被使用了之后,对其的处理方式。目前支持三种策略:

    • Retain(保留):保留策略,PV 和其数据不会被删除,管理员需要手动处理。
    • Recycle(回收):回收策略,删除 PVC 后,Kubernetes 会删除 PV 上的数据并将其标记为可用状态。效果相当于执行 rm -rf /thevolume/*
    • Delete(删除):删除策略,PVC 被删除后,PV 及其对应的存储资源都会被删除(适用于动态供应的存储,如云存储)。

    [!caution]

    需要注意的是,底层不同的存储类型可能支持的回收策略不同。

  • 存储类别(storageClassName)

    PV可以通过storageClassName参数指定一个存储类别:

    • 具有特定类别的PV只能与请求了该类别的PVC进行绑定。
    • 未设定类别的PV则只能与不请求任何类别的PVC进行绑定。

现在我们来测试,使用NFS作为存储,来演示PV的使用,创建3个PV,对应NFS中的3个暴露的路径。

  1. 准备NFS环境,创建目录并且暴露服务

    mkdir /root/data/{pv1,pv2,pv3} -pv
    
    vim /etc/exports
    

    加入如下内容:

    /root/data/pv1     192.168.254.0/24(rw,no_root_squash)
    /root/data/pv2     192.168.254.0/24(rw,no_root_squash)
    /root/data/pv3     192.168.254.0/24(rw,no_root_squash)
    

    重启nfs服务:

    systemctl restart nfs
    
  2. 创建pv.yaml

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name:  pv1
    spec:
      capacity: 
        storage: 1Gi
      accessModes:
      - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain
      nfs:
        path: /root/data/pv1
        server: 192.168.254.142
    
    ---
    
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name:  pv2
    spec:
      capacity: 
        storage: 2Gi
      accessModes:
      - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain
      nfs:
        path: /root/data/pv2
        server: 192.168.254.142
        
    ---
    
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name:  pv3
    spec:
      capacity: 
        storage: 3Gi
      accessModes:
      - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain
      nfs:
        path: /root/data/pv3
        server: 192.168.5.6
    
  3. 创建PV并查看

    上图各个字段含义如下:

    NAME(名字):PV 的名称,用来唯一标识每一个 PV。在图中有三个 PV:pv1pv2pv3

    CAPACITY(容量):表示该 PV 提供的存储空间大小。单位可以是 Gi、Mi 等。

    ACCESS MODES(访问模式):定义了该 PV 可以被挂载的方式,即 Pod 可以如何访问该存储卷。

    RECLAIM POLICY(回收策略):当与 PV 绑定的 PVC 被删除后,如何处理该 PV 上的数据。

    STATUS(状态):显示 PV 当前的状态,常见状态有:

    • Available:PV 尚未被 PVC 绑定,可以分配给新的 PVC。
    • Bound:PV 已经绑定到某个 PVC,无法再绑定到其他 PVC。
    • Released:PVC 已经被删除,但 PV 尚未被回收。
    • Failed:PV 因某些原因无法被回收或删除。

    CLAIM(声明):表示哪个 PVC 声明了该 PV,或者哪个 PVC 使用了这个 PV。如果是 <unset>,表示该 PV 没有被任何 PVC 使用。

    STORAGECLASS(存储类):定义了与 PV 相关联的存储类。存储类用于动态供应 PV,如果这个字段为 <unset>,则表示 PV 没有绑定特定的存储类。

    VOLUMEATTRIBUTESCLASS:这个字段不常见,通常会用于标记与存储卷相关的属性信息。

    REASON:如果 PV 状态为 FailedREASON 字段将会显示失败的原因。

    VOLUMEMODE(卷模式):定义 PV 的存储卷模式,常见的有:

    • Filesystem:作为文件系统使用(默认)。
    • Block:作为裸块设备使用。

8.2.2 PVC

PVC是资源的申请,用来声明对存储空间、访问模式、存储类别需求信息。下面是资源清单文件:

apiVersion: v1
kind: PersistentVolumeClaim #持久卷申明
metadata:
  name: pvc
  namespace: dev
spec:
  accessModes: # 访问模式
  selector: # 采用标签对PV选择
  storageClassName: # 存储类别
  volumeMode: Filesystem #存储卷模式
  resources: # 请求空间
    requests:
      storage: 5Gi

PVC 的关键配置参数说明:

  • 访问模式(accessModes)

​ 用于描述用户应用对存储资源的访问权限。

  • 选择条件(selector)

    通过Label Selector的设置,可使PVC对于系统中己存在的PV进行筛选。

  • 存储类别(storageClassName)

    PVC在定义时可以设定需要的后端存储的类别,只有设置了该class的pv才能被系统选出。

  • 资源请求(Resources )

    描述对存储资源的请求。

  • 存储卷模式(volumeMode)

    • Filesystem: 文件系统模式(默认)。

    • Block: 块设备模式。


现在我们结合上一小节PV来做测试:

  1. 创建pvc.yaml,申请pv

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: pvc1
      namespace: dev
    spec:
      accessModes: 
      - ReadWriteMany
      resources:
        requests:
          storage: 1Gi #申请1G
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: pvc2
      namespace: dev
    spec:
      accessModes: 
      - ReadWriteMany
      resources:
        requests:
          storage: 1Gi #申请1G
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: pvc3
      namespace: dev
    spec:
      accessModes: 
      - ReadWriteMany
      resources:
        requests:
          storage: 10Gi #申请10G
    

    创建pvc并且查看:

    我们再来看看PV:

  2. 创建pods.yaml, 使用pv

    这里要创建具体的Pod了,其实这里就是用户层了,包括上面申请PV也是,用户只需要把申请的情况告知Pod就可以了

    apiVersion: v1
    kind: Pod
    metadata:
      name: pod1
      namespace: dev
    spec:
      containers:
      - name: busybox
        image: busybox:1.30
        command: ["/bin/sh","-c","while true;do echo pod1 >> /root/out.txt; sleep 10; done;"]
        volumeMounts:
        - name: volume
          mountPath: /root/
      volumes:
        - name: volume
          persistentVolumeClaim:
            claimName: pvc1 #告知用pvc1
            readOnly: false
    ---
    apiVersion: v1
    kind: Pod
    metadata:
      name: pod2
      namespace: dev
    spec:
      containers:
      - name: busybox
        image: busybox:1.30
        command: ["/bin/sh","-c","while true;do echo pod2 >> /root/out.txt; sleep 10; done;"]
        volumeMounts:
        - name: volume
          mountPath: /root/
      volumes:
        - name: volume
          persistentVolumeClaim:
            claimName: pvc2 #告知用pvc2
            readOnly: false
    

    创建并且查看相关信息:

    现在我们来查看一下nfs中的文件存储:

    可见确实是绑定上了的。

8.2.3 生命周期

PVC和PV是一一对应的,PV和PVC之间的相互作用遵循以下生命周期:

  • 资源供应:管理员手动创建底层存储和PV。

  • 资源绑定:用户创建PVC,kubernetes负责根据PVC的声明去寻找PV,并绑定

    在用户定义好PVC之后,系统将根据PVC对存储资源的请求在已存在的PV中选择一个满足条件的:

    • 一旦找到,就将该PV与用户定义的PVC进行绑定,用户的应用就可以使用这个PVC了
    • 如果找不到,PVC则会无限期处于Pending状态,直到等到系统管理员创建了一个符合其要求的PV

    PV一旦绑定到某个PVC上,就会被这个PVC独占,不能再与其他PVC进行绑定了。

  • 资源使用:用户可在Pod中像Volume一样使用PVC,Pod使用Volume的定义,将PVC挂载到容器内的某个路径进行使用。

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

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

PV 生命周期的各个阶段

  1. Provisioning(创建)
    • 静态创建:由管理员手动预先创建 PV,并将其注册到 Kubernetes 集群中。这些 PV 可供任何用户的 PVC 使用。
    • 动态创建:当用户创建 PVC 并指定存储类(StorageClass)时,Kubernetes 可以根据定义的 StorageClass 动态创建 PV。StorageClass 定义了如何动态配置 PV,如存储类型、参数等。
  2. Available(可用):当 PV 被创建后,但还没有被绑定到任何 PVC 时,它处于可用状态,表示该卷可以被某个 PVC 绑定使用。
  3. Bound(已绑定):当一个 PVC 申请存储卷并且 Kubernetes 找到合适的 PV 匹配时,PVC 会与 PV 绑定。此时 PV 的状态变为 Bound,表示该卷已经被特定的 PVC 预留使用。
  4. Released(已释放):当 PVC 被删除时,与之绑定的 PV 会变为 Released 状态。这意味着该 PV 还存在数据,但 PVC 已经不再使用它。PV 并不会立即再次可用,因为卷中的数据需要手动处理(如清理或保留)。
  5. Reclaimed(回收):在 PV 被释放后,Kubernetes 需要根据 PV 的回收策略来决定接下来的处理方式。常见的回收策略有:
    • Retain(保留):PV 将不会被自动删除,需要管理员手动清理或重新绑定到新的 PVC。
    • Delete(删除):PV 及其数据将被自动删除。
    • Recycle(回收):PV 将被简单清理,并重新标记为 Available 供其他 PVC 使用。

PVC 生命周期的各个阶段

  1. Pending(等待):当 PVC 被创建后,Kubernetes 会根据 PVC 中的请求条件(如存储容量、存储类型等)在集群中查找合适的 PV。如果没有合适的 PV 满足要求,PVC 会一直处于 Pending 状态,直到找到匹配的 PV 或动态创建 PV。
  2. Bound(已绑定):当 Kubernetes 找到合适的 PV 并绑定到 PVC 时,PVC 的状态变为 Bound。此时,PVC 已经可以为 Pod 提供持久化存储。
  3. Released(已释放):当与 PVC 关联的 Pod 被删除或 PVC 本身被删除时,PVC 状态将变为 Released。这表示 PVC 不再绑定到任何 PV,PV 可以进入回收阶段。
  4. Deleted(已删除):当 PVC 被删除后,它与之绑定的 PV 的状态将取决于 PV 的回收策略。PV 可能会进入回收阶段或被重新利用。

8.3 配置存储

8.3.1 ConfigMap

在 Kubernetes 中,ConfigMap是一种用于存储非机密数据的对象,主要用于将配置数据以键值对的形式与应用程序分离。它允许你将配置与容器化应用程序分离开来,从而避免硬编码配置到容器镜像中。这种设计可以提高灵活性和可维护性,因为当配置改变时,你可以不必重新构建和部署容器镜像。

ConfigMap 通常用于存储:

  • 应用程序的环境变量。
  • 配置文件或其片段。
  • 命令行参数。
  • 特定的配置属性(如数据库连接字符串、API 访问 URL 等)。

ConfigMap是一种比较特殊的存储卷,它的主要作用是用来存储配置信息的。

以下是一个简单的 ConfigMap 配置的示例:

apiVersion: v1
kind: ConfigMap #资源类型为ConfigMap
metadata:
  name: example-configmap
data: #data收是ConfigMap的顶级配置
  database_url: "mongodb://localhost:27017"
  api_key: "12345"

下面简单来测试一下,创建configmap.yaml,内容如下:

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

接下来,使用此配置文件创建configmap并查看信息:

接下来创建一个pod-configmap.yaml,将上面创建的configmap挂载进去

apiVersion: v1
kind: Pod
metadata:
  name: pod-configmap
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    volumeMounts: # 将configmap挂载到目录
    - name: config
      mountPath: /configmap/config
  volumes: # 引用configmap
  - name: config #卷的名称
    configMap:
      name: configmap #要与之前的configmap的名称要对应上

现在我们进入容器中,查看之前挂载的信息:

可以看到映射已经成功,每个configmap都映射成了一个目录,Key就是文件,Value为文件中的内容,此时如果更新configmap的内容, 容器中的值也会动态更新,只不过要等一会:

现在我们过一会,大概几十秒中把,再次进入到容器中查看:

8.3.2 Secret

在 Kubernetes 中,Secret是一种用于存储和管理敏感信息的资源对象,例如密码、OAuth 令牌、SSH 密钥等。Secret 通过加密或编码的方式存储数据,并可以安全地传递到 Pod 中,以确保敏感信息不会被硬编码到应用程序代码或容器镜像中。

Kubernetes 中的 Secret 主要用于存储以下敏感信息:

  • 数据库连接密码
  • API 密钥或 OAuth 令牌
  • TLS 证书和密钥
  • SSH 密钥

通过使用 Secret,敏感信息可以安全地管理,并且避免在容器镜像或代码中泄露这些信息。

Secret 以键值对的形式存储敏感数据。Secret 的值会使用 Base64 编码进行存储,以避免明文存储。

以下是一个简单的 Secret YAML 配置示例:

apiVersion: v1
kind: Secret
metadata:
  name: example-secret
type: Opaque
data:
  username: YWRtaW4=   # Base64 编码后的 "admin"
  password: MTIzNDU2   # Base64 编码后的 "123456"

data 部分包含了敏感数据,所有值都使用 Base64 编码。

type 字段表明 Secret 的类型,Opaque 是默认的 Secret 类型,表示通用的键值对。

Kubernetes 支持多种 Secret 类型,主要类型如下:

  1. Opaque:默认类型,用于存储任意的键值对。
  2. kubernetes.io/dockerconfigjson:用于存储 Docker registry 的认证信息。
  3. kubernetes.io/tls:用于存储 TLS 证书和密钥。
  4. 其他类型:可以自定义类型以适应特定的应用场景。

现在我们来测试一下:

  1. 首先使用base64对数据进行编码

    echo -n '需要编码的数据' | base64 
    

    例如:

  2. 接下来编写secret.yaml,并创建Secret

    apiVersion: v1
    kind: Secret
    metadata:
      name: secret
      namespace: dev
    type: Opaque
    data:
      username: YWRtaW4=
      password: MTIzNDU2
    

  3. 创建pod-secret.yaml,将上面创建的secret挂载进去:

    apiVersion: v1
    kind: Pod
    metadata:
      name: pod-secret
      namespace: dev
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        volumeMounts: # 将secret挂载到目录
        - name: config
          mountPath: /secret/config
      volumes:
      - name: config
        secret:
          secretName: secret #与之前创建的secret资源的名称要对应上
    

至此,已经实现了利用secret实现了信息的编码。

9. 安全认证

9.1 访问控制概述

Kubernetes作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务。所谓的安全性其实就是保证对Kubernetes的各种客户端进行认证和鉴权操作。

客户端

在Kubernetes集群中,客户端通常有两类:

  • User Account:一般是独立于kubernetes之外的其他服务管理的用户账号。
  • Service Account:kubernetes管理的账号,用于为Pod中的服务进程在访问Kubernetes时提供身份标识。

认证、授权与准入控制

ApiServer是访问及管理资源对象的唯一入口。任何一个请求访问ApiServer,都要经过下面三个流程:

  • Authentication(认证):身份鉴别,只有正确的账号才能够通过认证
  • Authorization(授权): 判断用户是否有权限对访问的资源执行特定的动作
  • Admission Control(准入控制):用于补充授权机制以实现更加精细的访问控制功能。

9.2 认证管理

Kubernetes集群安全的最关键点在于如何识别并认证客户端身份,它提供了3种客户端身份认证方式:

  • HTTP Base认证:通过用户名+密码的方式认证

    这种认证方式是把“用户名:密码”用BASE64算法进行编码后的字符串放在HTTP请求中的Header Authorization域里发送给服务端。服务端收到后进行解码,获取用户名及密码,然后进行用户身份认证的过程。

  • HTTP Token认证:通过一个Token来识别合法用户

    这种认证方式是用一个很长的难以被模仿的字符串–Token来表明客户身份的一种方式。每个Token对应一个用户名,当客户端发起API调用请求时,需要在HTTP Header里放入Token,API Server接到Token后会跟服务器中保存的token进行比对,然后进行用户身份认证的过程。

  • HTTPS证书认证:基于CA根证书签名的双向数字证书认证方式

    这种认证方式是安全性最高的一种方式,但是同时也是操作起来最麻烦的一种方式。

HTTPS认证大体分为3个过程:

  1. 证书申请和下发

    HTTPS通信双方的服务器向CA机构申请证书,CA机构下发根证书、服务端证书及私钥给申请者

  2. 客户端和服务端的双向认证

    • 客户端向服务器端发起请求,服务端下发自己的证书给客户端,
    • 客户端接收到证书后,通过私钥解密证书,在证书中获得服务端的公钥,
    • 客户端利用服务器端的公钥认证证书中的信息,如果一致,则认可这个服务器
    • 客户端发送自己的证书给服务器端,服务端接收到证书后,通过私钥解密证书
    • 在证书中获得客户端的公钥,并用该公钥认证证书信息,确认客户端是否合法
  3. 服务器端和客户端进行通信

    服务器端和客户端协商好加密方案后,客户端会产生一个随机的秘钥并加密,然后发送到服务器端。

    服务器端接收这个秘钥后,双方接下来通信的所有内容都通过该随机秘钥加密

[!caution]

Kubernetes允许同时配置多种认证方式,只要其中任意一个方式认证通过即可。

9.3 授权管理

授权发生在认证成功之后,通过认证就可以知道请求用户是谁, 然后Kubernetes会根据事先定义的授权策略来决定用户是否有权限访问,这个过程就称为授权。

每个发送到ApiServer的请求都带上了用户和资源的信息:比如发送请求的用户、请求的路径、请求的动作等,授权就是根据这些信息和授权策略进行比较,如果符合策略,则认为授权通过,否则会返回错误。

API Server目前支持以下几种授权策略:

  • AlwaysDeny:表示拒绝所有请求,一般用于测试
  • AlwaysAllow:允许接收所有请求,相当于集群不需要授权流程(Kubernetes默认的策略
  • ABAC:基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制
  • Webhook:通过调用外部REST服务对用户进行授权
  • Node:是一种专用模式,用于对kubelet发出的请求进行访问控制
  • RBAC:基于角色的访问控制(kubeadm安装方式下的默认选项

RBAC(Role-Based Access Control) 基于角色的访问控制,主要是在描述一件事情:给哪些对象授予了哪些权限

其中涉及到了下面几个概念:

  • 对象:User、Groups、ServiceAccount
  • 角色:代表着一组定义在资源上的可操作动作(权限)的集合
  • 绑定:将定义好的角色跟用户绑定在一起

RBAC引入了4个顶级资源对象:

  • Role、ClusterRole:角色,用于指定一组权限
  • RoleBinding、ClusterRoleBinding:角色绑定,用于将角色(权限)赋予给对象

Role、ClusterRole

一个角色就是一组权限的集合,这里的权限都是许可形式的(白名单)。

# Role只能对命名空间内的资源进行授权,需要指定nameapce
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  namespace: dev
  name: authorization-role
rules:
- apiGroups: [""]  # 支持的API组列表,"" 空字符串,表示核心API群
  resources: ["pods"] # 支持的资源对象列表
  verbs: ["get", "watch", "list"] # 允许的对资源对象的操作方法列表
# ClusterRole可以对集群范围内资源、跨namespaces的范围资源、非资源类型进行授权
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
 name: authorization-clusterrole
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

需要详细说明的是,rules中的参数:

  • apiGroups: 支持的API组列表

    "","apps", "autoscaling", "batch"
    
  • resources:支持的资源对象列表

    "services", "endpoints", "pods","secrets","configmaps","crontabs","deployments","jobs",
    "nodes","rolebindings","clusterroles","daemonsets","replicasets","statefulsets",
    "horizontalpodautoscalers","replicationcontrollers","cronjobs"
    
  • verbs:对资源对象的操作方法列表

    "get", "list", "watch", "create", "update", "patch", "delete", "exec"
    

RoleBinding、ClusterRoleBinding

角色绑定用来把一个角色绑定到一个目标对象上,绑定目标可以是User、Group或者ServiceAccount。

# RoleBinding可以将同一namespace中的subject绑定到某个Role下,则此subject即具有该Role定义的权限
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: authorization-role-binding
  namespace: dev
subjects:
- kind: User
  name: heima
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: authorization-role
  apiGroup: rbac.authorization.k8s.io
# ClusterRoleBinding在整个集群级别和所有namespaces将特定的subject与ClusterRole绑定,授予权限
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
 name: authorization-clusterrole-binding
subjects:
- kind: User
  name: heima
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: authorization-clusterrole
  apiGroup: rbac.authorization.k8s.io

RoleBinding引用ClusterRole进行授权

RoleBinding可以引用ClusterRole,对属于同一命名空间内ClusterRole定义的资源主体进行授权。

一种很常用的做法就是,集群管理员为集群范围预定义好一组角色(ClusterRole),然后在多个命名空间中重复使用这些ClusterRole。这样可以大幅提高授权管理工作效率,也使得各个命名空间下的基础性授权规则与使用体验保持一致。

# 虽然authorization-clusterrole是一个集群角色,但是因为使用了RoleBinding
# 所以heima只能读取dev命名空间中的资源
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: authorization-role-binding-ns
  namespace: dev
subjects:
- kind: User
  name: heima
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: authorization-clusterrole
  apiGroup: rbac.authorization.k8s.io

实战:创建一个只能管理dev空间下Pods资源的账号

  1. 创建账号
# 1) 创建证书
[root@k8s-master01 pki]# cd /etc/kubernetes/pki/
[root@k8s-master01 pki]# (umask 077;openssl genrsa -out devman.key 2048)

# 2) 用apiserver的证书去签署
# 2-1) 签名申请,申请的用户是devman,组是devgroup
[root@k8s-master01 pki]# openssl req -new -key devman.key -out devman.csr -subj "/CN=devman/O=devgroup"     
# 2-2) 签署证书
[root@k8s-master01 pki]# openssl x509 -req -in devman.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out devman.crt -days 3650

# 3) 设置集群、用户、上下文信息
[root@k8s-master01 pki]# kubectl config set-cluster kubernetes --embed-certs=true --certificate-authority=/etc/kubernetes/pki/ca.crt --server=https://192.168.109.100:6443

[root@k8s-master01 pki]# kubectl config set-credentials devman --embed-certs=true --client-certificate=/etc/kubernetes/pki/devman.crt --client-key=/etc/kubernetes/pki/devman.key

[root@k8s-master01 pki]# kubectl config set-context devman@kubernetes --cluster=kubernetes --user=devman

# 切换账户到devman
[root@k8s-master01 pki]# kubectl config use-context devman@kubernetes
Switched to context "devman@kubernetes".

# 查看dev下pod,发现没有权限
[root@k8s-master01 pki]# kubectl get pods -n dev
Error from server (Forbidden): pods is forbidden: User "devman" cannot list resource "pods" in API group "" in the namespace "dev"

# 切换到admin账户
[root@k8s-master01 pki]# kubectl config use-context kubernetes-admin@kubernetes
Switched to context "kubernetes-admin@kubernetes".

2) 创建Role和RoleBinding,为devman用户授权

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  namespace: dev
  name: dev-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
  
---

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: authorization-role-binding
  namespace: dev
subjects:
- kind: User
  name: devman
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: dev-role
  apiGroup: rbac.authorization.k8s.io
[root@k8s-master01 pki]# kubectl create -f dev-role.yaml
role.rbac.authorization.k8s.io/dev-role created
rolebinding.rbac.authorization.k8s.io/authorization-role-binding created
  1. 切换账户,再次验证
# 切换账户到devman
[root@k8s-master01 pki]# kubectl config use-context devman@kubernetes
Switched to context "devman@kubernetes".

# 再次查看
[root@k8s-master01 pki]# kubectl get pods -n dev
NAME                                 READY   STATUS             RESTARTS   AGE
nginx-deployment-66cb59b984-8wp2k    1/1     Running            0          4d1h
nginx-deployment-66cb59b984-dc46j    1/1     Running            0          4d1h
nginx-deployment-66cb59b984-thfck    1/1     Running            0          4d1h

# 为了不影响后面的学习,切回admin账户
[root@k8s-master01 pki]# kubectl config use-context kubernetes-admin@kubernetes
Switched to context "kubernetes-admin@kubernetes".

9.4 准入控制

通过了前面的认证和授权之后,还需要经过准入控制处理通过之后,apiserver才会处理这个请求。

准入控制是一个可配置的控制器列表,可以通过在Api-Server上通过命令行设置选择执行哪些准入控制器:

--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,
                      DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds

只有当所有的准入控制器都检查通过之后,apiserver才执行该请求,否则返回拒绝。

当前可配置的Admission Control准入控制如下:

  • AlwaysAdmit:允许所有请求
  • AlwaysDeny:禁止所有请求,一般用于测试
  • AlwaysPullImages:在启动容器之前总去下载镜像
  • DenyExecOnPrivileged:它会拦截所有想在Privileged Container上执行命令的请求
  • ImagePolicyWebhook:这个插件将允许后端的一个Webhook程序来完成admission controller的功能。
  • Service Account:实现ServiceAccount实现了自动化
  • SecurityContextDeny:这个插件将使用SecurityContext的Pod中的定义全部失效
  • ResourceQuota:用于资源配额管理目的,观察所有请求,确保在namespace上的配额不会超标
  • LimitRanger:用于资源限制管理,作用于namespace上,确保对Pod进行资源限制
  • InitialResources:为未设置资源请求与限制的Pod,根据其镜像的历史资源的使用情况进行设置
  • NamespaceLifecycle:如果尝试在一个不存在的namespace中创建资源对象,则该创建请求将被拒绝。当删除一个namespace时,系统将会删除该namespace中所有对象。
  • DefaultStorageClass:为了实现共享存储的动态供应,为未指定StorageClass或PV的PVC尝试匹配默认的StorageClass,尽可能减少用户在申请PVC时所需了解的后端存储细节
  • DefaultTolerationSeconds:这个插件为那些没有设置forgiveness tolerations并具有notready:NoExecute和unreachable:NoExecute两种taints的Pod设置默认的“容忍”时间,为5min
  • PodSecurityPolicy:这个插件用于在创建或修改Pod时决定是否根据Pod的security context和可用的PodSecurityPolicy对Pod的安全策略进行控制

10. DashBoard

之前在kubernetes中完成的所有操作都是通过命令行工具kubectl完成的。其实,为了提供更丰富的用户体验,kubernetes还开发了一个基于web的用户界面(Dashboard)。用户可以使用Dashboard部署容器化的应用,还可以监控应用的状态,执行故障排查以及管理kubernetes中各种资源。

[!tip]

公司中应该用蓝鲸容器管理平台较多我这里演示的是Kubernetes原生的Dashboard。

蓝鲸容器管理平台:https://github.com/TencentBlueKing/bk-bcs

10.1 部署Dashboard

由于我这里是用的最新版本的k8s,版本:1.30;这里部署只能用用版本匹配的Dashboard。

官网查看:https://github.com/kubernetes/dashboard/releases

由于从7.0.0版本开始的Dashboard安装只能使用Helm包管理的方式来安装,所以,我们先安装Helm。

来到官网下载Helm:https://github.com/helm/helm/tags

通过命令查看自己Linux服务器的架构:

uname -m

输出 x86_64 表示是 AMD64 架构,输出 aarch64 表示是 ARM64 架构。

下载好了之后解压:

tar -zxvf helm-v3.15.4-linux-amd64.tar.gz

解压之后将命令复制到/usr/bin/目录下,以便能够直接使用:

mv linux-amd64/helm /usr/bin/

通过命令查看helm是否安装成功:

helm version

显示如图就是安装成功了。

启用Helm的Tab补全:

source <(helm completion bash)
source /etc/profile

之后我们可以添加几个常用的Chart仓库:

helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
helm repo add kaiyuanshe http://mirror.kaiyuanshe.cn/kubernetes/charts
helm repo add azure http://mirror.azure.cn/kubernetes/charts
helm repo add dandydev https://dandydeveloper.github.io/charts
helm repo add bitnami https://charts.bitnami.com/bitnami

下面我们就可以正式安装Dashboard了:

  1. 安装Dashboard:

    helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
    helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard
    

  2. 修改Service的类型为NodePort,让其外部可以访问

    kubectl edit svc  -n kubernetes-dashboard  kubernetes-dashboard-kong-proxy
    

    将type: ClusterIP改为type: NodePort

    查看响应的Service:

    kubectl get svc -A |grep kubernetes-dashboard
    

    之后你就能够输入节点IP:端口的形式访问:

    之后新建dashboard-user.yaml文件:

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: admin-user
      namespace: kubernetes-dashboard
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: admin-user
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: cluster-admin
    subjects:
    - kind: ServiceAccount
      name: admin-user
      namespace: kubernetes-dashboard
    

    之后获取token:

    kubectl -n kubernetes-dashboard  create token admin-user
    

    之后把Token复制到之前登陆的地方就可以登陆了:

10.2 使用DashBoard

本章节以Deployment为例演示DashBoard的使用

查看

选择指定的命名空间dev,然后点击Deployments,查看dev空间下的所有deployment

扩缩容

Deployment上点击规模,然后指定目标副本数量,点击确定

编辑

Deployment上点击编辑,然后修改yaml文件,点击确定

查看Pod

点击Pods, 查看pods列表

操作Pod

选中某个Pod,可以对其执行日志(logs)、进入执行(exec)、编辑、删除操作

Dashboard提供了kubectl的绝大部分功能,这里不再一一演示。


文章作者: 念心卓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 念心卓 !
  目录