Kubernetes(K8s)学习笔记(一)
kubernetes(k8s)学习笔记(一)
1、Kubernetes介绍
1.1 应用部署方式演变
在部署应用程序的方式上,主要经历了三个时代:
- 传统部署:互联网早期,会直接将应用程序部署在物理机上.
- 优点:简单,不需要其它技术的参与;
- 缺点:不能为应用程序定义资源使用边界,很难合理地分配计算资源,而且程序之间容易产生影响。
- 虚拟化部署:可以在一台物理机上运行多个虚拟机,每个虚拟机都是独立的一个环境
- 优点:程序环境不会相互产生影响,提供了一定程度的安全性;
- 缺点:增加了操作系统,浪费了部分资源
- 容器化部署:与虚拟化类似,但是共享了操作系统,有以下优点:
- 可以保证每个容器拥有自己的文件系统、CPU、内存、进程空间等;
- 运行应用程序所需要的资源都被容器包装,并和底层基础架构解耦;
- 容器化的应用程序可以跨云服务商、跨Linux操作系统发行版进行部署.
三种部署方式如下图所示:
容器化部署方式给带来很多的便利,但是也会出现一些问题,比如说:
- 一个容器故障停机了,怎么样让另外一个容器立刻启动去替补停机的容器;
- 当并发访问量变大的时候,怎么样做到横向扩展容器数量。
这些容器管理的问题统称为容器编排问题,为了解决这些容器编排问题,就产生了一些容器编排的软件,比如:
- Swarm:Docker自己的容器编排工具;
- Mesos:Apache的一个资源统一管控的工具,需要和Marathon结合使用;
- Kubernetes:Google开源的的容器编排工具。
其中,Google的Kubernetes市场占有率独占鳌头,如今已经成为了容器编排的标准。
1.2 Kubernetes简介
Kubernetes,是一个全新的基于容器技术的分布式架构领先方案,是谷歌严格保密十几年的秘密武器----Borg系统的一个开源版本,于2014年9月发布第一个版本,2015年7月发布第一个正式版本。
Kubernetes的本质是一组服务器集群,它可以在集群的每个节点上运行特定的程序,来对节点中的容器进行管理。目的是实现资源管理的自动化,主要提供了如下的主要功能:
- 自动修复:一旦某一个容器崩溃,能够在1秒中左右迅速启动新的容器;
- 弹性伸缩:可以根据需要,自动对集群中正在运行的容器数量进行调整;
- 服务发现:服务可以通过自动发现的形式找到它所依赖的服务;
- 负载均衡:如果一个服务启动了多个容器,能够自动实现请求的负载均衡;
- 版本回退:如果发现新发布的程序版本有问题,可以立即回退到原来的版本;
- 存储编排:可以根据容器自身的需求自动创建存储卷。
1.3 Kubernetes中的组件
一个Kubernetes集群主要是由控制节点(master),工作节点(node) 构成,每个节点上都会安装不同的组件。
master:集群的控制平面,负责集群的决策 ( 管理 )
- ApiServer : 资源操作的唯一入口,接收用户输入的命令,提供认证、授权、API注册和发现等机制;
- Scheduler : 负责集群资源调度,按照预定的调度策略将Pod调度到相应的node节点上;
- ControllerManager : 负责维护集群的状态,比如程序部署安排、故障检测、自动扩展、滚动更新等;
- Etcd :负责存储集群中各种资源对象的信息。
node:集群的数据平面,负责为容器提供运行环境 ( 干活 )
- Kubelet : 负责维护容器的生命周期,即通过控制docker,来创建、更新、销毁容器;
- KubeProxy : 负责提供集群内部的服务发现和负载均衡;
- Docker : 负责节点上容器的各种操作。
下面,以部署一个nginx服务来说明Kubernetes系统各个组件调用关系:
1、首先要明确,一旦Kubernetes环境启动之后,master和node都会将自身的信息存储到etcd数据库中。
2、一个nginx服务的安装请求会首先被发送到master节点的apiServer组件。
3、apiServer组件会调用scheduler组件来决定到底应该把这个服务安装到哪个node节点上;在此时,它会从etcd中读取各个node节点的信息,然后按照一定的算法进行选择,并将结果告知apiServer。
4、apiServer调用controller-manager去调度Node节点安装nginx服务。
5、kubelet接收到指令后,会通知docker,然后由docker来启动一个nginx的pod;pod是kubernetes的最小操作单元,容器必须跑在pod中。
6、至此,一个nginx服务就运行了,如果需要访问nginx,就需要通过kube-proxy来对pod产生访问的代理。
这样,外界用户就可以访问集群中的nginx服务了。
1.4 Kubernetes中的概念
Master:集群控制节点,每个集群需要至少一个master节点负责集群的管控;
Node:工作负载节点,由master分配容器到这些node工作节点上,然后node节点上的docker负责容器的运行;
Pod:kubernetes的最小控制单元,容器都是运行在pod中的,一个pod中可以有1个或者多个容器;
Controller:控制器,通过它来实现对pod的管理,比如启动pod、停止pod、伸缩pod的数量等等;(控制器在kubernetes中是一类概念,也就是说,不仅仅只有一种控制器,而是有多种控制器,每一种都各自的使用场景)
Service:pod对外服务的统一入口,下面可以维护着同一类的多个pod;
Label:标签,用于对pod进行分类,同一类pod会拥有相同的标签;
NameSpace:命名空间,用来隔离pod的运行环境。
2、Kubernetes集群环境搭建
2.1 环境规划
2.1.1 集群类型
Kubernetes集群大致分为两类:一主多从和多主多从。
● 一主多从:一个Master节点和多台Node节点,搭建简单,但是有单机故障风险,适合用于测试环境。
● 多主多从:多台Master和多台Node节点,搭建麻烦,安全性高,适合用于生产环境。
为了测试简单,本次搭建的是一主两从类型的集群.
2.1.2 安装方式
目前生产部署Kubernetes 集群主要有两种方式:
- Kubeadm
Kubeadm 是一个K8s 部署工具,提供kubeadm init 和kubeadm join,用于快速部署Kubernetes 集群。
官方文档:https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/
- 二进制包
从github 下载发行版的二进制包,手动部署每个组件,组成Kubernetes 集群。
Kubeadm降低了部署门槛,但屏蔽了很多细节,遇到问题很难排查。如果想更容易可控,推荐使用二进制包部署Kubernetes 集群,虽然手动部署麻烦点,期间可以学习很多工作原理,也利于后期维护。
2.1.3 主机规划
2.2 环境搭建
本次环境搭建需要三台CentOS服务器(一主二从),然后在每台服务器中分别安装Docker(18.06.3)、kubeadm(1.17.4)、kubectl(1.17.4)和kubelet(1.17.4)。
2.2.1 主机安装
略。
2.2.2 环境初始化
(1) 检查操作系统版本
要求操作系统的版本至少在7.5以上.
(2) 主机名解析
为了方便后面集群节点间的直接调用,在这配置一下主机名解析。企业中推荐使用内部DNS服务器。
# 主机名解析,编辑三台服务器 /etc/hosts文件,添加下面内容:
192.168.166.111 k8s-master01
192.168.166.112 k8s-node01
192.168.166.113 k8s-node02
(3) 时间同步
kubernetes要求集群中的节点时间必须精确一致,这里直接使用chronyd服务从网络同步时间。 企业中建议配置内部的时间同步服务器。
# 启动 chronyd服务
[root@k8s-master01 ~]# systemctl start chronyd
# 设置chronyd服务开机自启
[root@k8s-master01 ~]# systemctl enable chronyd
# chronyd服务启动稍等几秒钟,就可以使用date命令验证时间了。
[root@k8s-master01 ~]# date
(4) 禁用iptables和firewalld服务
kubernetes和docker在运行中会产生大量的iptables规则,为了不让系统规则跟它们混淆,直接关闭系统的规则。
# 1、关闭firewalld服务
[root@k8s-master01 ~]# systemctl stop firewalld
[root@k8s-master01 ~]# systemctl disable firewalld
# 2、关闭iptables服务
[root@k8s-master01 ~]# systemctl stop iptables
[root@k8s-master01 ~]# systemctl disable iptables
(5) 禁用selinux
selinux是linux系统下的一个安全服务,如果不关闭它,在安装集群中会产生各种各样的奇葩问题。
# 编辑 /etc/selinux/config 文件,修改SELINUX的值为disabled
# 注意修改完毕之后需要重启linux服务器
SELINUX=disabled
(6) 禁用swap分区
swap分区指的是虚拟内存分区,它的作用是在物理内存使用完之后,将磁盘空间虚拟成内存来使用。启用swap分区会对系统的性能产生非常负面的影响,因此kubernetes要求每个节点都要禁用swap设备,但如果因为某些原因不能关闭swap分区,就需要在集群安装过程中通过明确的参数进行配置说明。
# 编辑分区配置文件 /etc/fstab,注释掉swap分区一行
# 注意修改完毕之后需要重启linux服务器
(7) 修改linux的内核参数
# 修改linux的内核参数,添加网桥过滤和地址转发功能
# 编辑 /etc/sysctl.d/kubernetes.conf 文件,添加如下配置:
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
# 重新加载配置
[root@k8s-master01 ~]# sysctl -p
# 加载网桥过滤模块
[root@k8s-master01 ~]# modprobe br_netfilter
# 查看网桥过滤模块是否加载成功
[root@k8s-master01 ~]# lsmod | grep br_netfilter
(8) 配置ipvs功能
在kubernetes中service有两种代理模型,一种是基于iptables的,一种是基于ipvs的,两者比较的话,ipvs的性能明显要高一些,但如果要使用它,需要手动载入ipvs模块。
# 1、安装ipset和ipvsadm
[root@k8s-master01 ~]# yum install ipset ipvsadmin -y
# 2、添加需要加载的模块写入脚本文件
[root@k8s-master01 ~]# 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、为脚本文件添加执行权限
[root@k8s-master01 ~]# chmod +x /etc/sysconfig/modules/ipvs.modules
# 4、执行脚本文件
[root@k8s-master01 ~]# /bin/bash /etc/sysconfig/modules/ipvs.modules
# 5、查看对应的模块是否加载成功
[root@k8s-master01 ~]# lsmod |grep -e ip_vs -e nf_conntrack_ipv4
(9) 重启服务器
上面步骤完成之后,需重启linux服务器
[root@k8s-master01 ~]# reboot
2.2.3 安装docker
# 1、切换镜像源
[root@k8s-node01 ~]# wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
# 2、查看当前镜像源中支持的docker版本
[root@k8s-node01 ~]# yum list docker-ce --showduplicates
# 3、安装特定版本的docker-ce
# 必须指定 --setopt=obsoletes=0, 否则yum会自动安装更高版本
[root@k8s-node01 ~]# yum install --setopt=obsoletes=0 docker-ce-18.06.3.ce-3.el7 -y
# 4、添加一个配置文件
# Docker在默认情况下使用的Cgroup Driver为cgroupfs,而kubernetes推荐使用systemd来替代cgroupfs.
[root@k8s-node01 ~]# mkdir /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors": ["https://bhu1x6ya.mirror.aliyuncs.com"]
}
EOF
# 5、启动docker、并设置开机启动
[root@k8s-node01 ~]# systemctl restart docker
# 6、检查docker状态和版本
[root@k8s-node01 ~]# docker version
2.2.4 安装kubernetes组件
# 由于kubenetes的镜像源在国外,速度比较慢,这里切换成国内的镜像源
# 编辑 /etc/yum.repos.d/kubernetes.repo,添加下面的配置
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgchech=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
# 安装kubeadm、kubelet和kubectl
[root@k8s-master01 ~]# yum install --setopt=obsoletes=0 kubeadm-1.17.4-0 kubelet-1.17.4-0 kubectl-1.17.4-0 -y
# 配置kubelet的cgroup
# 编辑 /etc/sysconfig/kubelet,添加下面的配置
KUBELET_CGROUP_ARGS="--cgroup-driver=systemd"
KUBE_PROXY_MODE="ipvs"
# 设置kubelet 开机自启
[root@k8s-master01 ~]# systemctl enable kubelet
2.2.5 准备集群镜像
# 在安装kubernetes集群之前,必须要提前准备好集群需要的镜像,所需镜像可以通过下面命令查看
[root@k8s-master01 ~]# kubeadm config images list
# 下载镜像
# 此镜像在kubernetes的仓库中,由于网络原因,无法连接,下面提供了一种替代方案
images=(
kube-apiserver:v1.17.4
kube-controller-manager:v1.17.4
kube-scheduler:v1.17.4
kube-proxy:v1.17.4
pause:3.1
etcd:3.4.3-0
coredns:1.6.5
)
for imageName in ${images[@]}; do
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName k8s.gcr.io/$imageName
docker rmi registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
done
下载完成后如下图:
2.2.6 集群初始化
下面开始对集群进行初始化,并将node节点加入到集群中。
下面的操作只需在master节点上操作。
[root@k8s-master01 ~]# kubeadm init \
--kubernetes-version=v1.17.4 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12 \
--apiserver-advertise-address=192.168.166.111
# 创建必要文件
[root@k8s-master01 ~]# mkdir -p $HOME/.kube
[root@k8s-master01 ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@k8s-master01 ~]# chown $(id -u):$(id -g) $HOME/.kube/config
下面的操作只需在node节点上操作。
# 将node节点加入到集群中
[root@k8s-node01 ~]# kubeadm join 192.168.166.111:6443 --token ke6low.t57x5lcgfs3mv3s2 \
--discovery-token-ca-cert-hash sha256:8e8a425856325997b7f4d2370d6f0e0d49304641a9115e79a74adb524ec607f3
此时通过kubectl get nodes
查看节点状态,发现 STATUS
处显示为 NotReady状态。表示节点之间网络还不通,需要安装网络插件。
2.2.7 安装网络插件
kubenetes支持多种网络插件,比如flannel、calico、canal等等,任选一种使用即可,本次选择flannel.
以下操作依旧只在master节点执行即可,插件使用的是DaemonSet的控制器,它会在每个节点上运行。
# 获取fannel的配置文件
[root@k8s-master01 ~]# wget
https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
# 修改文件中 quay.io 参控股为 quay-mirror.qiniu.com
# 使用配置文件启动 fannel
[root@k8s-master01 ~]# kubectl apply -f kube-flannel.yml
# 稍等片刻,再次查看集群节点的状态
[root@k8s-master01 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master01 Ready master 15m v1.17.4
k8s-node01 Ready <none> 13m v1.17.4
k8s-node02 Ready <none> 12m v1.17.4
至此,kubernetes的集群环境搭建完成。
2.3 部署服务
接下来在kubernetes集群中部署一个Nginx服务,测试下集群是否正常工作。
# 部署nginx
[root@k8s-master01 ~]# kubectl create deployment nginx --image=nginx:1.21-alpine
# 暴露端口
[root@k8s-master01 ~]# kubectl expose deployment nginx --port=80 --type=NodePort
# 查看服务状态
[root@k8s-master01 ~]# kubectl get pods,services
NAME READY STATUS RESTARTS AGE
pod/nginx-655cb49fc5-j9m4d 1/1 Running 0 3m13s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10h
service/nginx NodePort 10.99.210.176 <none> 80:32203/TCP 2m14s
# 最后在浏览器上访问部署的nginx服务
3、资源管理
本章节主要介绍yaml语法和kubernetes的资源管理方式。
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 yaml语言介绍
YAML是一个类似XML、JSON的标记性语言。它强调以数据为中心,并不是以标识语言为重点。因而YAML本身的定义比较简单,号称"一种人性化的数据格式语言"。
xml 示例
<person>
<age>15</age>
<address>Beijing</address>
</person>
yaml 示例
person:
age: 15
address: Beijing
YAML的语法比较简单,主要有以下几个:
- 大小写敏感;
- 使用缩进标识层级关系;
- 缩进不允许使用tab,只允许空格;
- 缩进的空格数不重要,只要相同层级的元素左对齐即可;
#
表示注释。
YAML支持以下几种数据类型:
- 纯量:单个的、不可再分的值;
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hash)/ 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence)/ 列表(list)
Tips:
(1) 书写yaml切记:后面要加一个空格;
(2) 如果需要将多段yaml配置放在一个文件中,中间要使用---
分割
分享一个json和yaml的对比、互相转换的网站,可用来验证yaml的书写是否正确:
http://json2yaml.com/convert-yaml-to-json
3.3 资源管理方式
- 命令式对象管理:直接使用命令去操作kubernetes资源
kubectl run nginx-pod --image=nginx:1.17.1 --port=80
- 命令式对象配置:通过命令配置和配置文件去操作kubernetes资源
kubectl create/patch -f nginx-pod.yaml
- 声明式对象配置:通过apply命令和配置文件去操作kubernetes资源
kubectl apply -f nginx-pod.yaml
3.3.1 命令式对象管理
kubectl命令
kubectl是kubernetes集群的命令行工具,通过它能够对集群本身进行管理,并能够在集群上进行容器化应用的安装部署。kubectl命令的语法如下:
kubectl [command] [type] [name] [flags]
- command:指定要对资源执行的操作,如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
资源类型
kubernetes中所有的内容都抽象为资源,可通过下面的命令进行查看:
kubectl api-resources
经常使用的有如下这些:
操作
kubernetes允许对资源进行多种操作,可以通过--help
查看详细的操作命令。
kubectl --help
经常使用的操作有如下这些:
下面以一个namespace / pod 的创建和删除简单演示下命令的使用:
# 创建一个namespace
[root@k8s-master01 ~]# kubectl create namespace dev
namespace/dev created
# 获取namespace
[root@k8s-master01 ~]# kubectl get namespaces
NAME STATUS AGE
default Active 16h
dev Active 3s
kube-node-lease Active 16h
kube-public Active 16h
kube-system Active 16h
# 在此namespace下创建并运行一个nginx的Pod
[root@k8s-master01 ~]# kubectl run pod --image=nginx --namespace=dev
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/pod created
# 查看新创建的Pod
[root@k8s-master01 ~]# kubectl get pods --namespace=dev
NAME READY STATUS RESTARTS AGE
pod-864f9875b9-b489g 1/1 Running 0 5m59s
# 删除指定的Pod
[root@k8s-master01 ~]# kubectl delete pod pod-864f9875b9-b489g --namespace=dev
pod "pod-864f9875b9-b489g" deleted
3.3.2 命令式对象配置
命令式对象配置就是使用命令配置配置文件一起来操作kubernetes资源。
(1) 创建一个nginxpod.yaml,内容如下:
apiVersion: v1
kind: Namespace
metadata:
name: dev
---
apiVersion: v1
kind: Pod
metadata:
name: nginxpod
namespace: dev
spec:
containers:
- name: nginx-containers
- image: nginx:1.17.1
(2) 执行create命令,创建资源
[root@k8s-master01 ~]# kubectl create -f nginxpod.yml
namespace/dev created
pod/nginxpod created
此时发现创建了两个资源对象,分别是namespace和pod.
(3) 执行get命令,查看资源:
[root@k8s-master01 ~]# kubectl get -f nginxpod.yml
NAME STATUS AGE
namespace/dev Active 82m
NAME READY STATUS RESTARTS AGE
pod/nginxpod 1/1 Running 1 82m
这样就显示了两个资源对象的信息。
(4) 执行delege命令,删除资源:
[root@k8s-master01 ~]# kubectl delete -f nginxpod.yml
namespace "dev" deleted
pod "nginxpod" deleted
此时发现两个资源对象被删除了。
总结:命令式对象配置的方式操作资源,可以简单的认为是:命令 + yaml配置文件(里面是命令需要的各种参数)
3.3.3 声明式对象配置
声明式对象配置跟命令式对象配置很相似,但它只有一个命令:apply
。
# 首先执行一次kubectl apply -f <yaml文件>,发现创建了资源
[root@k8s-master01 ~]# kubectl apply -f nginxpod.yml
namespace/dev created
pod/nginxpod created
# 再次执行一次kubectl apply -f yaml文件,发现说资源没有变动。
[root@k8s-master01 ~]# kubectl apply -f nginxpod.yml
namespace/dev unchanged
pod/nginxpod unchanged
总结:
其实声明式对象配置就是使用apply描述一个资源的最终状态(在yaml中定义状态)
使用apply操作资源:
- 如果资源不存在,就创建,相当于
kubectl create
; - 如果资源已存在,就更新, 相当于
kubectl patch
。
扩展:kubectl可以在node节点上运行吗?
kubectl的运行时需要进行配置的,它的配置文件是$HOME/.kube
,如果想要在node节点运行此命令,需要将master节点上的.kube
文件复制到node节点上,即在master节点上执行以下操作:
scp -r HOME/.kube node1:$HOME/
3.3.4 使用推荐
使用推荐: 三种方式应该怎么用?
- 创建/更新资源:使用声明式对象配置(
kubectl apply -f xxx.yaml
) - 删除资源:使用命令式对象配置(
kubectl delete -f xxx.yaml
) - 查询资源:使用命令式对象管理(
kubectl get(describe) <资源名称>
)
4、实战入门
4.1 Namespace
Namespace是kubernetes系统中的一种非常重要的资源,它的主要作用是用来实现多套环境的资源隔离或者租户的资源隔离。
默认情况下,kubernetes集群中的所有的Pod都是可以相互访问的。但在实际中,可能不想让两个Pod之间进行互相访问,那此时就可以将两个Pod划分到不同的namespace下。kubernetes通过将集群内部的资源分配到不同的Namespace中,可以形成逻辑上的"组",以方便不同的组的资源进行隔离使用和管理。
可以通过kubernetes的授权机制,将不同的namespace交给不同租户进行管理,这样就实现了多租户的资源隔离。此时还能结合kubernetes的资源配额机制,限定不同租户能占用的资源,例如CPU使用量、内存使用量等等,来实现租户可用资源的管理。
kubernetes在集群启动之后,会默认创建几个namespace.
下面来看namespace资源的具体操作。
查看
# 1、查看所有的namespace,命令:kubectl get ns/namespaces
[root@k8s-master01 ~]# kubectl get namespaces
NAME STATUS AGE
default Active 23h
kube-node-lease Active 23h
kube-public Active 23h
kube-system Active 23h
# 2、查看指定的namespace,命令:kubectl get namespace <ns名称>
[root@k8s-master01 ~]# kubectl get namespace default
NAME STATUS AGE
default Active 23h
# 3、指定输出格式 命令:kubectl get ns <ns名称> -o 格式参数
# kubernetes支持的格式很多,常见的是wide、json、yaml
[root@k8s-master01 ~]# kubectl get namespaces -o yaml
apiVersion: v1
items:
- apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: "2022-03-11T14:18:39Z"
name: default
resourceVersion: "146"
selfLink: /api/v1/namespaces/default
uid: 57dddb3d-7645-4be1-878f-afc7b4486f4e
spec:
finalizers:
- kubernetes
status:
phase: Active
...
# 4、查看ns详情 命令:kubectl describe ns ns名称
[root@k8s-master01 ~]# kubectl describe namespaces default
Name: default
Labels: <none>
Annotations: <none>
Status: Active # Active:命名空间正在使用。Terminating 正在删除命名空间
# ResourceQuota:针对namespace做的资源限制
# LimitRange:针对namespace中的每个组件做的资源限制
No resource quota.
No LimitRange resource.
创建
# 创建namespace
[root@k8s-master01 ~]# kubectl create ns dev
namespace/dev created
删除
# 删除namespace
[root@k8s-master01 ~]# kubectl delete ns dev
namespace "dev" deleted
配置方式
首先准备一个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中可以存在一个或多个容器。
[root@k8s-master01 ~]# kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-6955765f44-4pwjb 1/1 Running 1 24h
coredns-6955765f44-h2vpk 1/1 Running 1 24h
etcd-k8s-master01 1/1 Running 2 24h
kube-apiserver-k8s-master01 1/1 Running 2 24h
kube-controller-manager-k8s-master01 1/1 Running 2 24h
kube-flannel-ds-7xx6d 1/1 Running 1 14h
kube-flannel-ds-cmmv9 1/1 Running 1 14h
kube-flannel-ds-vh55n 1/1 Running 1 14h
kube-proxy-f9hmd 1/1 Running 2 24h
kube-proxy-qqnlw 1/1 Running 2 24h
kube-proxy-vd7dx 1/1 Running 2 24h
kube-scheduler-k8s-master01 1/1 Running 2 24h
创建并运行
kubernetes没有提供单独运行Pod命令,都是通过Pod控制器来实现的。
# 命令格式:kubectl run <pod控制器名称> [参数]
# --image 指定Pod的镜像
# --port 指定端口
# --namespace 指定namespace
[root@k8s-master01 ~]# kubectl run nginx --image=nginx:1.17.1 --port=80 --namespace dev
deployment.apps/nginx created
查看pod信息
# 查看Pod基本信息
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx17-96944c7cd-945rx 1/1 Running 0 15m
# 查看Pod的详细信息
[root@k8s-master01 ~]# kubectl describe pod nginx17-96944c7cd-945rx -n dev
Name: nginx17-96944c7cd-945rx
Namespace: dev
Priority: 0
Node: k8s-node02/192.168.166.113
Start Time: Sat, 12 Mar 2022 10:02:54 -0500
Labels: pod-template-hash=96944c7cd
run=nginx17
Annotations: <none>
Status: Running
IP: 10.244.2.7
IPs:
IP: 10.244.2.7
Controlled By: ReplicaSet/nginx17-96944c7cd
Containers:
nginx17:
Container ID: docker://c08ddb4bff649ac2cdc190117841e78bc737cbec9074d04d88a0060ee98b13dc
Image: nginx:1.17.1
Image ID: docker-pullable://nginx@sha256:b4b9b3eee194703fc2fa8afa5b7510c77ae70cfba567af1376a573a967c03dbb
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Sat, 12 Mar 2022 10:02:55 -0500
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-mkgg5 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-mkgg5:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-mkgg5
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 16m default-scheduler Successfully assigned dev/nginx17-96944c7cd-945rx to k8s-node02
Normal Pulled 16m kubelet, k8s-node02 Container image "nginx:1.17.1" already present on machine
Normal Created 16m kubelet, k8s-node02 Created container nginx17
Normal Started 16m kubelet, k8s-node02 Started container nginx17
访问Pod
# 获取pod IP
[root@k8s-master01 ~]# kubectl get pod -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx17-96944c7cd-945rx 1/1 Running 0 17m 10.244.2.7 k8s-node02 <none> <none>
# 访问pod
[root@k8s-master01 ~]# curl http://10.244.2.7
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
删除指定Pod
# 删除指定Pod
[root@k8s-master01 ~]# kubectl delete pod nginx17-96944c7cd-945rx -n dev
pod "nginx17-96944c7cd-945rx" deleted
# 此时,显示删除指定Pod成功,但再次查询,发现又新生成了一个
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx17-96944c7cd-k5hgm 1/1 Running 0 12s
# 这是因为当前Pod是由Pod控制器创建的,控制器会监控Pod,一旦发现Pod私网,会立即重建.
# 此时要想删除Pod,必须删除Pod控制器.
# 先来查询一下当前namespace下的Pod控制器
[root@k8s-master01 ~]# kubectl get deployments.apps -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
nginx17 1/1 1 1 21m
# 接下来,删除此Pod控制器
[root@k8s-master01 ~]# kubectl delete deployments.apps -n dev nginx17
deployment.apps "nginx17" deleted
# 稍等片刻,再查询Pod,发现Pod被删除了.
[root@k8s-master01 ~]# kubectl get pod -n dev
No resources found in dev namespace.
配置操作
创建一个pod-nginx.yaml,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: dev
spec:
containers:
- image: nginx:1.17.1
imagePullPolicy: IfNotPresent
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键值对的形式附加到各种对象上,如Node、Pod、Service等等。
- 一个资源对象可以定义任意数量的Label,同一个Label也可以被添加到任意数量的资源对象上去。
- Label通常在资源对象定义时确定,当然也可以在对象创建后动态添加或删除。
可以通过Label实现资源的多维度分组,以便灵活、方便地进行资源分配、调度、配置、部署等管理工作。
一些常用的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
命令方式
# 为pod资源打标签
[root@k8s-master01 ~]# kubectl label pod nginx-pod version=1.0 -n dev
pod/nginx-pod labeled
# 为pod资源更新标签
[root@k8s-master01 ~]# kubectl label pod nginx-pod version=2.0 -n dev --overwrite
pod/nginx-pod labeled
# 查看标签
[root@k8s-master01 ~]# kubectl get pod nginx-pod -n dev --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-pod 1/1 Running 0 4m21s version=2.0
# 筛选标签
[root@k8s-master01 ~]# kubectl get pod -l version=2.0 -n dev --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-pod 1/1 Running 0 6m43s version=2.0
[root@k8s-master01 ~]# kubectl get pod -l version!=2.0 -n dev --show-labels
No resources found in dev namespace.
# 删除标签
[root@k8s-master01 ~]# kubectl label pod nginx-pod version- -n dev
pod/nginx-pod labeled
[root@k8s-master01 ~]#
[root@k8s-master01 ~]# kubectl get pod nginx-pod --show-labels -n dev
NAME READY STATUS RESTARTS AGE LABELS
nginx-pod 1/1 Running 0 8m32s <none>
配置方式
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: dev
labels:
version: "3.0"
env: "test"
spec:
containers:
- image: nginx:1.17.1
name: pod
ports:
- name: nginx-port
containerPort: 80
protocol: TCP
然后就可以执行对应的更新命令了:kubectl apply -f pod-nginx.yml
4.4 Deployment
在kubernetes中,Pod是最小的控制单元,但kubernetes很少直接控制Pod,一般都是通过Pod控制器来完成的。Pod控制器用于pod的管理,确保pod资源符合预期的状态,当pod的资源出现故障时,会尝试进行重启或重建Pod。
在kubernetes中Pod控制器的种类有很多,本章只介绍一种:Deployment.
命令操作
# 命令格式:kubectl run <deployment名称> [参数]
# --image 指定pod的镜像
# --port 指定端口
# --replicas 指定创建pod数量
# --namespace 指定namespace
[root@k8s-master01 ~]# kubectl run nginx --image=nginx:1.17.1 --port=80 --replicas=3 -n dev
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx created
# 查看创建的pod
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx-64777cd554-fbcdr 1/1 Running 0 2m20s
nginx-64777cd554-kmm6t 1/1 Running 0 2m20s
nginx-64777cd554-mv27v 1/1 Running 0 2m20s
# 查看deployment的信息
[root@k8s-master01 ~]# kubectl get deployments.apps -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 3/3 3 3 4m42s
# UP-TO-DATE:成功升级的副本数量
# AVALIABLE: 可用副本的数量
[root@k8s-master01 ~]# kubectl get deployments.apps -n dev -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx 3/3 3 3 5m50s nginx nginx:1.17.1 run=nginx
# 查看deployment的详细信息
[root@k8s-master01 ~]# kubectl describe deployments.apps nginx -n dev
Name: nginx
Namespace: dev
CreationTimestamp: Sat, 12 Mar 2022 22:30:44 -0500
Labels: run=nginx
Annotations: deployment.kubernetes.io/revision: 1
Selector: run=nginx
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: run=nginx
Containers:
nginx:
Image: nginx:1.17.1
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-64777cd554 (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 6m58s deployment-controller Scaled up replica set nginx-64777cd554 to 3
# 删除deployment
[root@k8s-master01 ~]# kubectl delete deployments.apps nginx -n dev
deployment.apps "nginx" deleted
配置操作
创建一个deploy-nginx.yaml,内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
run: nginx
template:
metadata:
labels:
run: nginx
spec:
containers:
- image: nginx:1.21.5
name: nginx
ports:
- containerPort: 80
protocol: TCP
然后,就可以执行对应的创建和删除命令了:
创建:kubectl create -f deploy-nginx.yml
删除:kubectl delete -f deploy-nginx.yml
4.5 Service
通过上一节的学习,已经能够利用Deployment 来创建一组Pod来提供具有高可用性的服务。
虽然每个Pod都会分配一个单独的Pod IP,然而却存在如下两个问题:
- Pod IP会随着Pod的重建产生变化;
- Pod IP仅仅是集群内可见的虚拟IP,外部无法访问。
这样对于访问这个服务带来了难度。因此,kubernetes设计了Service来解决这个问题。
Service可以看作是一组同类Pod对外的访问接口。借助Service,应用可以方便地实现服务发现和负载均衡。
操作一:创建集群内部可访问的Service
# 暴露Service
[root@k8s-master01 ~]# kubectl expose deployment nginx --name=svc-nginx1 --type=ClusterIP --port=80 --target-port=80 -n dev
service/svc-nginx1 exposed
# 查看service
[root@k8s-master01 ~]# kubectl get service svc-nginx1 -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
svc-nginx1 ClusterIP 10.100.224.218 <none> 80/TCP 54s run=nginx
# 这里产生了一个CLUSTER-IP,这就是service的IP,在service的生命周期中,这个地址是不会变动的
# 可以通过这个IP访问当前service对应的Pod
[root@k8s-master01 ~]# curl http://10.100.224.218
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
...
</html>
操作二:创建集群外部可访问的Service
# 上面创建的Service的type类型为ClusterIP,这个IP地址只能集群内部访问;
# 如果需要创建外部也可以访问的Service,需要修改type为NodePort
[root@k8s-master01 ~]# kubectl expose deployment nginx --name=svc-nginx2 --type=NodePort --port=80 --target-port=80 -n dev
service/svc-nginx2 exposed
# 此时查看,会发现出现了NodePort类型的service, 而且又一对Port(80:31529/TCP)
[root@k8s-master01 ~]# kubectl get service svc-nginx2 -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
svc-nginx2 NodePort 10.106.124.15 <none> 80:31529/TCP 35s run=nginx
# 接下来就可以通过集群外的主机访问 `节点IP:31529` 来访问服务了。
# 例如在电脑主机上通过浏览器访问 http://192.168.166.113:31529
删除Service
[root@k8s-master01 ~]# kubectl delete service svc-nginx1 -n dev
service "svc-nginx1" deleted
配置方式
创建一个svc-nginx.yml,内容如下:
apiVersion: v1
kind: Service
metadata:
name: svc-nginx1
namespace: dev
spec:
clusterIP: 10.100.224.219
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
run: nginx
type: ClusterIP
然后就可以执行对应的创建和删除操作了:
创建:kubectl create -f svc-nginx.yml
删除:kubectl delete -f svc-nginx.yml
小结
至此,已经掌握了Namespace、Pod、Deployment、Service资源的基本操作,有了这些操作,就可以在Kubernetes集群中实现一个服务的简单部署和访问了。但如果想要更好的使用kubernetes,就需要深入学习这几种资源的细节和原理。
5、Pod详解
本章将详细介绍Pod资源的各种配置(yaml)和原理。
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的资源清单:
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内各容器健康检查的设置,当探测无响应几次后将自动重启该容器
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: object #设置NodeSelector表示将该Pod调度到包含这个label的node上
imagePullSecrets: #Pull镜像时使用的secret名称,以key: secretkey格式指定
- name: string
hostNetwork: false #是否使用主机网络模式,默认false,如果设置true,表示使用宿主机网络
volumes: #在该pod上定义共享存储卷列表
- name: string #共享存储卷名称(volumes类型有很多种)
emptyDir: {} #类型为emptyDir的存储卷,与Pod同生命周期的一个临时目录,为空值
hostPath: string #类型为hostPath的存储卷,表示挂载Pod苏偶在宿主机的目录
path: string #Pod所在宿主机的目录,将被用于同期中mount的目录
Tips:
可通过一个命令来查看每种资源的可配置项:
- kubectl explain <资源类型> //查看某种资源可配置的一级属性
- kubectl explain <资源类型.属性> //查看属性的子属性
[root@k8s-master01 ~]# kubectl explain pod
KIND: Pod
VERSION: v1
DESCRIPTION:
Pod is a collection of containers that can run on a host. This resource is
created by clients and scheduled onto hosts.
FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
metadata <Object>
Standard object's metadata. More info:
spec <Object>
Specification of the desired behavior of the pod. More info:
status <Object>
Most recently observed status of the pod. This data may not be up to date.
Populated by the system. Read-only. More info:
在kubernetes中基本所有资源的一级属性都是一样的,主要包含5个部分:
- apiVersion <string> 版本,由kubernetes内部定义,版本号可以用kubectrl api-versions查询
- kind <string> 类型,由kubernetes内部定义,版本号可以用kubectrl api-resources查询
- metadata <Object> 元数据,主要是资源标识和说明,常用的有name, namespace, labels等
- spec <Object> 描述,这是配置中最重要的一部分,里面是对各种资源配置的详细描述
- status <Object> 状态信息,里面的内容不需要定义,由kubernetes自动生成。
在上面的属性中,spec是接下来研究的终端,继续看下它的常见子属性:
- containers <[]Object> 容器列表,用于定义容器的详细信息
- nodeName <String> 根据nodeName的值将pod调度到指定的Node节点上
- nodeSelector <map[]> 根据nodeSelector中定义的信息选择将该pod调度到包含这些label的Node上.
- hostNetwork <boolean> 是否使用主机网络模式,默认false,如果设为true则表示使用宿主机网络
- volumes <[]Object> 存储卷,用于定义pod上面挂载的存储信息
- restartPolicy <string> 重启策略,表示pod在遇到故障时的处理策略.
5.2 Pod配置
本小姐主要介绍pod.spec.containers
属性,这也是Pod配置中最为关键的一项配置。
[root@k8s-master01 ~]# kubectl explain pod.spec.containers
KIND: Pod
VERSION: v1
RESOURCE: containers <[]Object>
DESCRIPTION:
List of containers belonging to the pod. Containers cannot currently be
added or removed. There must be at least one container in a Pod. Cannot be
updated.
A single application container that you want to run within a pod.
FIELDS:
args <[]string> #容器名称
command <[]string> #容器
env <[]Object> #容器环境变量的配置
image <string> #容器需要的镜像地址
imagePullPolicy <string> #镜像拉取策略
name <string> #容器名称
ports <[]Object> #容器需要暴露的端口号列表
resources <Object> #资源限制和资源请求的设置
5.2.1 基本配置
创建pod-base.yml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-base
namespace: dev
labels:
user: m01e
spec:
containers:
- name: nginx
image: nginx:1.17.1
- name: busybox
image: busybox:1.30
上面定义了一个比较简单的Pod的配置,里面有两个容器:
- nginx:用1.17.1 版本的nginx镜像创建
- busybox: 用1.30版本的busybox镜像创建(busybox是一个小巧的linux命令集合)
# 创建Pod
[root@k8s-master01 ~]# kubectl apply -f pod-base.yml
pod/pod-base created
# 查看pod状况
# READY 1/2:表示当前pod中有两个容器,其中1个准备就绪,1个未就绪;
# RESTARTS:重启次数,因为有1个容器故障了,Pod一直在重启视图恢复它.
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pod-base 1/2 CrashLoopBackOff 4 2m1s
# 可以通过describe查看内部详情
# 测试已经运行起来了一个基本的Pod,虽然它暂时有问题
[root@k8s-master01 ~]# kubectl describe pod pod-base -n dev
5.2.2 镜像拉取
创建pod-imagepullpolicy.yml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-imagepullpolicy
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
imagePullPolicy: Always #用于设置镜像拉取策略
- name: busybox
image: busybox:1.30
imagePullPolicy, 用于设置镜像拉取策略,kubernetes支持配置三种拉取策略:
- Always:总是从远程仓库拉取镜像;
- IfNotPresent:本地有则使用本地镜像,否则从远程仓库拉取镜像;
- Never:只使用本地镜像,本地没有就报错.
默认值说明:
如果镜像tag为具体版本号,默认策略是:IfNotPresent
如果镜像tag为 latest(最新版本),默认策略是always
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-imagepullpolicy.yml
pod/pod-imagepullpolicy created
# 查看pod详情
# 此时明显可以看到nginx镜像有一步Pulling image "nginx:1.17.2" 的过程
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 48s kubelet, k8s-node01 Pulling image "nginx:1.17.2"
Normal Scheduled 47s default-scheduler Successfully assigned dev/pod-imagepullpolicy to k8s-node01
Normal Pulled 17s kubelet, k8s-node01 Successfully pulled image "nginx:1.17.2"
Normal Created 13s kubelet, k8s-node01 Created container nginx
Normal Started 13s kubelet, k8s-node01 Started container nginx
Normal Pulling 13s kubelet, k8s-node01 Pulling image "busybox:1.30"
5.2.3 启动命令
在前面的案例中,一直有一个问题没有解决,就是busybox容器一直没有成功运行,到底是什么原因导致这个容器的故障呢?
原来busybox并不是一个程序,而是类似于一个工具类的集合,kubernetes集群启动管理后,它会自动关闭。解决方法就是让其一直在运行,这就用到了command配置项。
创建pod-command.yml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-command
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 $(datte +%T) >> /tmp/hello.txt; sleep 3; done"]
# 创建Pod
[root@k8s-master01 ~]# kubectl create -f pod-command.yml
pod/pod-command created
# 查看pod状态
# 此时发现pod的两个容器均正常运行了
[root@k8s-master01 ~]# kubectl get pod pod-command -n dev
NAME READY STATUS RESTARTS AGE
pod-command 2/2 Running 0 2m23s
# 进入pod中的busybox容器,查看文件内容
# 补充一个命令: kubectl exec <pod名称> -n <命名空间> -it -c <容器名称> /bin/sh
# 使用这个命令就可以进入某个容器的内部,然后进行相关操作了
# 比如:
[root@k8s-master01 ~]# kubectl exec pod-command -n dev -it -c busybox /bin/sh
/ # id
uid=0(root) gid=0(root) groups=10(wheel)
/ # tail -f /tmp/hello.txt
00:39:34
00:39:37
00:39:40
00:39:43
00:39:46
特别说明:
通过上面发现command已经可以完成启动命令和传递参数的功能,为什么这里还要提供一个args选项,用于传递参数呢?这其实跟docker优点观念西,kubernetes中的command、args两项其实是实现覆盖Dockerfile中ENTRYPOINT的功能。
(1) 如果command和args均没有写,那么用Dockerfile的配置.
(2) 如果command写了,但args没有写,那么Dockerfile默认的配置会被忽略,执行输入的command.
(3) 如果command没写,但args写了,那么Dockerfile中配置的ENTRYPOINT的命令会被执行,使用当前args的参数;
(4) 如果command和args都写了,那么Dockerfile的配置被忽略,执行command并追加上args参数。
5.2.4 环境变量
创建pod-env.yml文件,内容如下:
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中的容器设置环境变量。
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-env.yml
pod/pod-env created
# 进入容器,输出环境变量
[root@k8s-master01 ~]# kubectl exec pod-env -n dev -it -c busybox /bin/sh
/ #
/ # echo $username
admin
/ #
/ # echo $password
123456
# 还可以通过attach命令(对应docker里的docker attach)进入容器当前的进程, 可看到每隔1分钟输出一次时间
[root@k8s-master01 ~]# kubectl attach pod-env -n dev -c busybox
If you don't see a command prompt, try pressing enter.
01:44:34
这种方式不是很推荐,推荐将这些配置单独存储在配置文件中,这种方式将在后面介绍。
5.2.5 端口设置
本小结来介绍容器的端口设置,也就是containers的ports选项。
首先看下ports支持的子选项:
[root@k8s-master01 ~]# kubectl explain pod.spec.containers.ports
KIND: Pod
VERSION: v1
RESOURCE: ports <[]Object>
FIELDS:
name <string> #端口名称,如果指定,必须保证name在pod中是唯一的
containerPort <Integer> #容器要监听的端口(0<x<65536)
hostPort <Integer> #容器要再主机上公开的端口,如果设置,主机上只能运行容器的一个副本(一般省略)
hostIP <string> #要讲外部端口绑定到的主机IP(一般省略)
protocol <string> #端口协议,必须是UDP/TCP/SCTP 三者之一 ,默认为TCP。
接下来,编写一个测试案例,创建pod-ports.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-ports
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports: #设置容器暴露的端口列表
- name: nginx-port
containerPort: 80
protocol: TCP
# 创建Pod
[root@k8s-master01 ~]# kubectl create -f pod-ports.yml
pod/pod-ports created
# 查看pod
# 在下面可以明显看到配置信息
[root@k8s-master01 ~]# kubectl get pod pod-ports -n dev -o yaml
...
[root@k8s-master01 ~]# kubectl get pod pod-ports -n dev -o yaml |grep IP
hostIP: 192.168.166.112
podIP: 10.244.1.15
podIPs:
[root@k8s-master01 ~]#
[root@k8s-master01 ~]# kubectl get pod pod-ports -n dev -o yaml |grep Port
- containerPort: 80
访问容器中的对外服务/应用,使用的是 podIP:containerPort。
5.2.6 资源配额
容器中的程序要运行,肯定是要占用一定资源的,比如cpu和内存等,如果不对某个容器的资源做限制,那么它就可能吃掉大量资源,导致其它容器无法运行。针对这种情况,kubernetes提供了对内存和CPU的资源进行配额的机制,这种机制主要通过resources选项实现,他有两个子选项:
- limits: 用于限制运行时容器的最大占用资源,当容器申请内存超过limits时会被终止,并进行重启;
- requests:用于设置容器需要的最小资源,如果资源不够,容器将无法启动。
可以通过上面两个选项设置资源的上下限。
接下来,编写一个测试案例,创建pod-resources.yml
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: "3Gi" #内存限制
requests: #请求资源(下限)
cpu: "1" #cpu限制,单位是core数
memory: "10Mi" #内存限制
在这对cpu和memory的单位做一个说明:
- cpu:core数,可以为整数或小数;
- memory: 内存大小,可以使用Gi、 Mi、 G、M等形式。
# 运行pod
[root@k8s-master01 ~]# kubectl create -f pod-resources.yml
pod/pod-resources created
# 查看发现pod运行正常
[root@k8s-master01 ~]# kubectl get pod pod-resources -n dev
NAME READY STATUS RESTARTS AGE
pod-resources 1/1 Running 0 28s
# 接下来,停止pod
[root@k8s-master01 ~]# kubectl delete -f pod-resources.yml
pod "pod-resources" deleted
5.3 Pod生命周期
我们一般将pod对象从创建到终止的这段时间范围称为pod的生命周期,它主要包含下面的过程:
- pod创建过程;
- 运行初始化容器(init container)过程;
- 运行主容器(main container)过程;
- 容器启动后钩子(post start)、容器终止前钩子(pre stop)
- 容器的存活性探测(liveness probe)、就绪性探测(readiness probe)
- pod终止过程。
在整个生命周期中,pod会出现5种状态(相位),如下:
- 挂起(Pending):apiserver已经创建了pod资源对象,但它尚未被调度完成或仍处于下载镜像的过程中;
- 运行中(Running):pod已经被调度至某节点,并且所有容器都已经被kubelet创建完成;
- 成功(Succeeded):pod中的所有容器都已经成功终止并且不会被重启;
- 失败(Failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态;
- 未知(Unknown):apiserver无法正常获取到pod对象的状态信息,通常由网络通信失败所导致。
5.3.1 创建和终止
pod的创建过程
(1) 用户通过kubectl或其他api客户端提交需要创建的pod信息给apiServer;
(2) apiServer开始生成pod对象的信息,并将信息存入etcd,然后返回确认信息至客户端;
(3) apiServer开始反映etcd中的pod对象的变化,其它组件使用watch机制来跟踪检查apiServer上的变动;
(4) scheduler发现又新的pod对象要创建,开始为pod分配主机并将结果信息更新至apiServer
(5) node节点上的kubelet发现有pod调度过来,尝试调用docker启动容器,并将结果回送至apiServer;
(6) apiServer将接收到的pod状态信息存入etcd中。
pod的终止过程
(1) 用户向apiServer发送删除Pod对象的命令;
(2) apiServer中的pod对象信息会随着时间的推移而更新,在宽限期内(默认30s),Pod被视为dead;
(3) 将pod标记为terminating状态;
(4) kubelet在监控到pod对象转为terminating状态的同时启动pod关闭过程;
(5) 端点控制器监控到pod对象的关闭行为时将其从所有匹配到此端点的service资源的端点列表中移除;
(6) 如果当前pod对象定义了preStop钩子处理器,则在其标记为terminating后即会以同步的方式启动执行;
(7) pod对象中的容器进程收到停止信号;
(8) 宽限期结束后,若pod中还存在奶仍在运行的进程,那么pod对象会收到立即终止的信号;
(9) kubelet请求apiServer将此pod资源的宽限期设置为0从而完成删除操作,此时pod对于用户已不可见。
5.3.2 初始化容器
初始化容器是在pod的主容器启动之前要运行的容器,主要是做一些主容器的前置工作,它具有两大特征:
(1) 初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么kubernetes需要重启它直到成功完成;
(2) 初始化容器必须按照定义的顺序执行,当且仅当前一个成功之后,后面的一个才能运行。
初始化容器有很多的应用场景,下面列出的是最常见的几个:
- 提供主容器镜像中不具备的工具程序或自定义代码;
- 初始化容器要先于应用容器串行启动并运行完成,因此可用于延后应用容器的启动直至其依赖的条件得到满足。
接下来做一个案例,模拟下面这个需求:
假设要以主容器来运行Nginx,但是要求在运行nginx之前先要能够连接上mysql和redis所在服务器。
为了简化测试,事先规定好mysql(192.168.109.201
)和redis(192.168.109.202
)服务器的地址。
创建pod-initcontainer.yml,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-initcontainer
namespace: dev
spec:
containers:
- name: main-container
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
initContainers:
- name: test-mysql
image: busybox:1.30
command: ['sh','-c','until ping 192.168.166.201 -c 1; do echo waiting for mysql...;sleep 2; done']
- name: test-redis
image: busybox:1.30
command: ['sh','-c','until ping 192.168.166.202 -c 1; do echo waiting for redis...;sleep 2; done']
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-initcontainer.yml
pod/pod-initcontainer created
# 查看pod状态
# 发现pod卡在启动第一个初始化容器过程中,后面的容器不会运行
[root@k8s-master01 ~]# kubectl describe pod pod-initcontainer -n dev
...
# 动态查看pod
[root@k8s-master01 ~]# kubectl get pod pod-initcontainer.yml -n dev -w
Error from server (NotFound): pods "pod-initcontainer.yml" not found
[root@k8s-master01 ~]# kubectl get pod pod-initcontainer -n dev -w
NAME READY STATUS RESTARTS AGE
pod-initcontainer 0/1 Init:0/2 0 15s
pod-initcontainer 0/1 Init:1/2 0 26s
pod-initcontainer 0/1 Init:1/2 0 27s
pod-initcontainer 0/1 PodInitializing 0 39s
pod-initcontainer 1/1 Running 0 40s
# 接下来新开一个shell,为当前服务器新增两个ip,观察pod的变化
[root@k8s-master01 ~]# ifconfig ens33:1 192.168.166.201 netmask 255.255.255.0 up
[root@k8s-master01 ~]# ifconfig ens33:2 192.168.166.202 netmask 255.255.255.0 up
5.3.3 钩子函数
钩子函数能够感知自身生命周期中的事件,并在相应的时刻到来时运行用户指定的程序代码。
kubernetes在主容器的启动之后和停止之前提供了两个钩子函数:
- post start: 容器创建之后执行,如果失败了会重启容器;
- pre stop: 容器终止之前执行,执行完成之后容器将成功终止,在其完成之前会阻塞删除容器的操作。
钩子处理器支持使用下面三种方式定义动作:
- Exec命令:在容器内执行一次命令
...
lifecycle:
postStart:
exec:
command:
- cat
- /tmp/healthy
...
- TCPSocket:在当前容器尝试访问指定的socket
...
lifecycle:
postStart:
tcpSocket:
port: 8080
...
- HTTPGet: 在当前容器中向某url发起Http请求
...
lifecycle:
postStart:
httpGet:
path: / #URI地址
port: 80 #端口号
host: 192.168.166.111 #主机地址
scheme: HTTP #支持的协议:http/https
...
接下来,以exec方式为例,演示下钩子函数的使用,创建pod-hook-exec.yml文件,内容如下:
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"]
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-hook-exec.yml
pod/pod-hook-exec created
# 查看pod
[root@k8s-master01 ~]# kubectl get pod pod-hook-exec -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-hook-exec 1/1 Running 0 27s 10.244.2.32 k8s-node02 <none> <none>
# 访问pod
[root@k8s-master01 ~]# curl http://10.244.2.32
postStart...
5.3.4 容器探测
容器探测用于检测容器中的应用实例是否正常工作,是保障业务可用性的一种传统机制。如果经过探测,实例的状态不符合预期,那么kubernetes就会把该问题实例"摘除",不承担业务流量。kubernetes提供了两种探针来实现容器探测,分别是:
- liveness probes: 存活性探针,用于检测应用实例当前是否处于正常运行状态,如果不是,k8s会重启容器;
- readiness probes:就绪性探针,用于检测应用实例当前是否可以接收请求,如果不能,k8s不会转发流量。
livenessProbe 决定是否重启容器,readinessProbe 决定是否将请求转发给容器。
上面两种探针目前均支持三种探测方式:
- Exec命令:在容器内执行一次命令,如果命令执行的退出码为0,则认为程序正常,否则不正常;
...
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
...
- TCPSocket: 将会尝试访问一个用户容器的端口,如果能够建立这条连接,则认为程序正常,否则不正常;
...
livenessProbe:
tcpSocket:
port: 8080
...
- HTTPGet:调用容器内Web应用的URL,如果返回的状态码在200和399之间,则认为程序正常,否则不正常。
...
livenessProbe:
httpGet:
path: / #URI地址
port: 80 #端口号
host: 127.0.0.1 #主机地址
scheme: HTTP #支持的协议,http/https
...
下面以liveness probes为例,做几个演示:
方式一:Exec
创建pod-liveness-exec.yml
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"] #执行一个查看文件的命令
创建pod,观察结果
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-liveness-exec.yml
pod/pod-liveness-exec created
# 查看pod详情
[root@k8s-master01 ~]# kubectl describe pod pod-liveness-exec -n dev
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 75s default-scheduler Successfully assigned dev/pod-liveness-exec to k8s-node01
Normal Pulled 20s (x3 over 76s) kubelet, k8s-node01 Container image "nginx:1.17.1" already present on machine
Normal Created 20s (x3 over 76s) kubelet, k8s-node01 Created container nginx
Normal Started 20s (x3 over 76s) kubelet, k8s-node01 Started container nginx
Normal Killing 20s (x2 over 50s) kubelet, k8s-node01 Container nginx failed liveness probe, will be restarted
Warning Unhealthy 10s (x7 over 72s) kubelet, k8s-node01 Liveness probe failed: /bin/cat: /tmp/hello.txt: No such file or directory
# 观察上面的信息会发现nginx容器启动之后就进行了健康检查
# 检查失败之后,容器被kill掉,然后尝试进行重启(这是重启策略的作用,后面会讲解)
# 稍等一会之后,再观察pod信息,就可以看到RESTARTS不再是0,而是一直增长。
[root@k8s-master01 ~]# kubectl get pod pod-liveness-exec -n dev
NAME READY STATUS RESTARTS AGE
pod-liveness-exec 1/1 Running 5 3m22s
# 接下来,可以修改成一个存在的文件,再试,结果就正常了...
方式二:TCPSocket
创建pod-liveness-tcpsocket.yml
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端口
创建pod,观察效果
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-liveness-tcpsocket.yml
pod/pod-liveness-tcpsocket created
# 查看Pod详情
[root@k8s-master01 ~]# kubectl describe pod pod-liveness-tcpsocket -n dev
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 26s default-scheduler Successfully assigned dev/pod-liveness-tcpsocket to k8s-node01
Normal Pulled 26s kubelet, k8s-node01 Container image "nginx:1.17.1" already present on machine
Normal Created 26s kubelet, k8s-node01 Created container nginx
Normal Started 25s kubelet, k8s-node01 Started container nginx
Warning Unhealthy 11s (x2 over 21s) kubelet, k8s-node01 Liveness probe failed: dial tcp 10.244.1.18:8080: connect: connection refused
# 观察上面的信息,发现尝试访问8080端口,但失败了
# 稍等一会之后再观察pod信息,就可以看到RESTARTS不再是0,而是一直增长。
[root@k8s-master01 ~]# kubectl get pod pod-liveness-tcpsocket -n dev
NAME READY STATUS RESTARTS AGE
pod-liveness-tcpsocket 1/1 Running 3 106s
# 可以修改成一个可以访问的端口,比如80,再试,结果就正常了。
方式三:HTTPGet
创建pod-liveness-httpget.yml
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
protocol: TCP
livenessProbe:
httpGet: #其实就是访问scheme://host:port/path
scheme: HTTP #支持的协议,Http或https
port: 80 #端口号
path: /hello #URI地址
创建pod,观察效果
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-liveness-httpget.yml
pod/pod-liveness-httpget created
# 查看pod详情
[root@k8s-master01 ~]# kubectl describe pod pod-liveness-httpget -n dev
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 25s default-scheduler Successfully assigned dev/pod-liveness-httpget to k8s-node01
Normal Pulled 25s kubelet, k8s-node01 Container image "nginx:1.17.1" already present on machine
Normal Created 25s kubelet, k8s-node01 Created container nginx
Normal Started 24s kubelet, k8s-node01 Started container nginx
Warning Unhealthy 10s (x2 over 20s) kubelet, k8s-node01 Liveness probe failed: HTTP probe failed with statuscode: 404
# 观察上面信息,尝试访问路径,但是未找到,出现404错误
# 稍等一会,再观察pod信息,就可以看到RESTARTS不再是0,而是一直增长
[root@k8s-master01 ~]# kubectl get pod pod-liveness-httpget -n dev
NAME READY STATUS RESTARTS AGE
pod-liveness-httpget 1/1 Running 2 31s
至此,已经使用livenessProbe演示了三种探测方式,但查看liveProbe的子属性,会发现除了这三种方式,还有一些其他的配置,这里一并解释一下:
[root@k8s-master01 ~]# kubectl explain pod.spec.containers.livenessProbe
KIND: Pod
VERSION: v1
...
FIELDS:
exec <Object>
tcpSocket <Object>
httpGet <Object>
initialDelaySeconds <integer> #容器启动后等待多少秒执行第一次探测
timeoutSeconds <integer> #探测超时时间。默认1秒,最小1秒
periodSeconds <integer> #执行探测的频率。默认10秒,最小1秒
failureThreshold <integer> #连续探测失败多少次才被认定为失败。默认是3,最小值是1
successThreshold <integer> #连续探测成功多少次才被认定为成功。默认是1
5.3.5 重启策略
在上一节中,一旦容器探测出现了问题,kubernetes就会对容器所在的pod进行重启,其实这是由pod的重启策略决定的,pod的重启策略有3种,分别如下:
- Always:容器失效时,自动重启该容器,这也是默认值。
- OnFailure:容器终止运行且退出码不为0时重启。
- Never:不论状态为何,都不重启该容器
重启策略适用于pod对象中的所有容器,首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长为:10s、20s、40s、80s、160s和300s,300s是最大延迟时长。
创建pod-restartpolicy.yml
apiVersion: v1
kind: Pod
metadata:
name: pod-restartpolicy
namespace: dev
spec:
containers:
- name: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
httpGet:
scheme: HTTP
port: 80
path: /hello
restartPolicy: Never #设置重启策略为Never
运行pod测试
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-restartPolicy.yml
pod/pod-restartpolicy created
# 查看pod详情,发现nginx容器失败
[root@k8s-master01 ~]# kubectl describe pod pod-restartpolicy -n dev
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 76s default-scheduler Successfully assigned dev/pod-restartpolicy to k8s-node01
Normal Pulled 75s kubelet, k8s-node01 Container image "nginx:1.17.1" already present on machine
Normal Created 75s kubelet, k8s-node01 Created container nginx-port
Normal Started 75s kubelet, k8s-node01 Started container nginx-port
Warning Unhealthy 46s (x3 over 66s) kubelet, k8s-node01 Liveness probe failed: HTTP probe failed with statuscode: 404
Normal Killing 46s kubelet, k8s-node01 Stopping container nginx-port
#多等一会,再观察pod的重启次数,发现一直是0,并未重启,且STATUS变为了Completed
[root@k8s-master01 ~]# kubectl get pod pod-restartpolicy -n dev
NAME READY STATUS RESTARTS AGE
pod-restartpolicy 0/1 Completed 0 2m5s
5.4 Pod调度
在默认情况下,一个Pod在哪个Node节点上运行,是由Scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的。但是在实际使用中,这并不满足需求。因为在很多情况下,我们想控制某些Pod到达某些节点上,那么应该怎么做呢?这就要求了解kubernetes对Pod的调度规则,kubernetes提供了四大类调度方式:
- 自动调度:运行在哪个节点上完全由Scheduler经过一系列的算法计算得出;
- 定向调度:NodeName、NodeSelector;
- 亲和性调度:NodeAffinity、PodAffinity、PodAntiAffinity
- 污点(容忍)调度:Taints、Toleration
5.4.1 定向调度
定向调度,指的是利用在pod上声明nodeName或nodeSelector,以此将pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使要调度的目标Node不存在,也会向上面进行调度,只不过pod运行失败而已。
NodeName
NodeName用于强制约束将Pod调度到指定的Name的Node节点上。这种方式,其实是直接跳过Scheduler的调度逻辑,直接将Pod调度到指定名称的节点。
实验一下:创建一个Pod-nodename.yml 文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-nodename
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeName: k8s-node01 #指定调度到k8s-node01节点上
# 创建Pod
[root@k8s-master01 ~]# kubectl create -f pod-nodename.yml
pod/pod-nodename created
# 查看pod,发现确实调度到了k8s-node01节点上
[root@k8s-master01 ~]# kubectl get pod pod-nodename -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-nodename 1/1 Running 0 18s 10.244.1.22 k8s-node01 <none> <none>
# 如果pod-nodename.yml文件中指定将pod调度到一个不存在的Node节点上,如下:
[root@k8s-master01 ~]# kubectl get pod pod-nodename -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-nodename 0/1 Pending 0 43s <none> k8s-node1 <none> <none>
NodeSelector
NodeSelector用于将pod调度到添加了指定标签的node节点上。它是通过kubernetes的label-selector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后将pod调度到目标节点,该匹配规则是强制约束。
接下来实验一下:
(1) 首先分别为node节点添加标签
[root@k8s-master01 ~]# kubectl label node k8s-node01 nodeenv=pro
node/k8s-node01 labeled
[root@k8s-master01 ~]# kubectl label node k8s-node02 nodeenv=test
node/k8s-node02 labeled
(2) 创建一个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: pro #指定调度到具有nodeenv=pro标签的节点上
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-nodeselector.yml
pod/pod-nodeselector created
# 查看pod,确实调度到了k8s-node01节点上
[root@k8s-master01 ~]# kubectl get pod pod-nodeselector -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-nodeselector 1/1 Running 0 43s 10.244.1.23 k8s-node01 <none> <none>
# 删除pod,修改nodeSelector的值为nodeenv: xxxx(不存在打有此标签的节点)
# 再次查看,发现pod无法正常运行,NODE的值为none
[root@k8s-master01 ~]# kubectl get pod pod-nodeselector -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-nodeselector 0/1 Pending 0 14s <none> <none> <none> <none>
# 查看详情,发现node selector匹配失败的提示
[root@k8s-master01 ~]# kubectl describe pods pod-nodeselector -n dev
...
5.4.2 亲和性调度
上一节,介绍了两种定向调度的方式,使用起来非常方便,但也有一定的问题,那就是如果满足条件的Node,那么pod将不会被执行,即使在集群中海油可用Node列表也不行,这就限制了它的使用场景。
基于上面的问题,kubernetes还提供了一种亲和性调度(Affinity)。它在NodeSelector的基础之上进行了扩展,可以通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足条件的节点上,使调度更加灵活。
Affinity主要分为三类:
- nodeAffinity(node亲和性):以node为目标,解决pod可以调度到哪些node的问题;
- podAffinity(pod亲和性):以pod为目标,解决pod可以和哪些已存在的pod部署在同一个拓扑域中的问题;
- podAntiAffinity(pod反亲和性):以pod为目标,解决pod不能和哪些已存在pod部署在同一个拓扑域中的问题。
关于亲和性(反亲和性)使用场景的说明:
亲和性:如果两个应用频繁交互,那就有必要利用亲和性让两个应用尽可能的靠近,这样可以减少因网络通信带来的性能损耗。
反亲和性:当应用采用多副本部署时,有必要采用反亲和性让各个应用实例打散分布在各个node上,这样可以提高服务的高可用性。
首先来看一下nodeAffinity的可配置项:
kubectl explain pod.spec.affinity.nodeAffinity
...
nodeAffinity
requiredDuringSchedulingIgnoredDuringExecution #Node节点必须满足指定的所有规则才可以,相当于硬限制
nodeSelectorTerms #节点选择列表
matchFields #按节点字段列出的节点选择器要求列表
matchExpressions #按节点标签列出的节点选择器要求列表(推荐)
key #键
value #值
operator #关系符,支持Exists、DoesNotExist、In、NotIn、Gt、Lt
preferredDuringSchedulingIgnoredDuringExecution #优先调度到满足指定规则的Node,相当于软限制(倾向)
preference #一个节点选择器项,与相应的权重相关联
matchFields #按节点字段列出的节点选择器要求列表
matchExpressions #按节点标签列出的节点选择器要求列表(推荐)
key #键
value #值
operator #关系符,支持In、NotIn、Exists、DoesNotExist、Gt、Lt
weight #倾向权重,范围:1-100
# 关系符的使用说明:
- 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.yml,内容为:
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"]
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-nodeaffinity-required.yml
pod/pod-nodeaffinity-required created
# 查看pod状态(运行失败)
[root@k8s-master01 ~]# kubectl get pod pod-nodeaffinity-required -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-nodeaffinity-required 0/1 Pending 0 34s <none> <none> <none> <none>
# 查看pod的详情
# 发现调度失败,提示node选择失败
[root@k8s-master01 ~]# kubectl describe pod pod-nodeaffinity-required -n dev
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 29s (x2 over 117s) default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
# 接下来,停止pod
[root@k8s-master01 ~]# kubectl delete -f pod-nodeaffinity-required.yml
pod "pod-nodeaffinity-required" deleted
# 修改文件,将values: ["xxx","yyy"] --> ["pro","yyy"]
# 再次启动
[root@k8s-master01 ~]# kubectl create -f pod-nodeaffinity-required.yml
pod/pod-nodeaffinity-required created
# 此时查看,调度成功,,已将pod调度到了k8s-node01上。
[root@k8s-master01 ~]# kubectl get pod pod-nodeaffinity-required -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-nodeaffinity-required 1/1 Running 0 52s 10.244.1.26 k8s-node01 <none> <none>
接下来再演示一下 preferredDuringSchedulingIgnoredDuringExecution
创建 pod-nodeaffinity-preferred.yml
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"]
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-nodeaffinity-preferred.yml
pod/pod-nodeaffinity-preferred created
# 查看pod状态(运行成功)
[root@k8s-master01 ~]# kubectl get pod pod-nodeaffinity-preferred -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-nodeaffinity-preferred 1/1 Running 0 6s 10.244.1.27 k8s-node01 <none> <none>
NodeAffinity规则设置的注意事项:
(1) 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,pod才能运行在指定的node上;
(2) 如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可;
(3) 如果一个nodeSelectorTerms中又多个matchExpressions,则一个节点必须满足所有的才能匹配成功;
(4) 如果一个pod所在的node在pod运行期间其标签发生了改变,不再符合该pod的节点亲和性需求,则系统将忽略此变化。
PodAffinity
PodAffinity主要实现以运行的pod为参照,实现让新创建的Pod跟参照pod在一个区域的功能。
首先来看一下PodAffinity
的可配置项:
kubectl explain pod.spec.affinity.podAffinity
...
requiredDuringSchedulingIgnoredDuringExecution #硬限制
namespaces #指定参照pod的namespace
topologyKey #指定调度作用域
labelSelector #标签选择器
matchExpressions #按节点标签列出的节点选择器要求列表(推荐)
key #键
value #值
operator #关系符,支持In、NotIn、Exists、DoesNotExist.
matchLabels #指多个matchExpressions映射的内容
preferredDuringSchedulingIgnoredDuringExecution #软限制
podAffinityTerm #选项
namespaces
toppologyKey
labelSelector
matchExpressions
key #键
values #值
operator
matchLabels
weight #倾向权重,在范围1-100
topologyKey用于指定调度时作用域,例如:
- 如果指定为kubernetes.io/hostname,那就是以node节点为区分范围
- 如果指定为 beta.kubernetes.io/os,则以node节点的操作系统类型来区分.
接下来,演示一下 requiredDuringSchedulingIgnoredDuringExecution
,
(1) 首先创建一个参照pod,pod-podaffinity-target.yml:
apiVersion: v1
kind: Pod
metadata:
name: pod-podaffinity-target
namespace: dev
labels:
podenv: pro #设置标签
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeName: k8s-node01 #将目标pod明确指定到k8s-node01节点上
# 启动目标pod(参照pod)
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-target.yml
pod/pod-podaffinity-target created
# 查看pod状况
[root@k8s-master01 ~]# kubectl get pod pod-podaffinity-target -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-podaffinity-target 1/1 Running 0 81s 10.244.1.28 k8s-node01 <none> <none>
(2) 创建 pod-podaffinity-required.yml,内容如下:
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必须要与拥有标签podenv=xxx或podenv=yyy的pod在同一节点上,显然现在没有这样的pod,接下来,运行测试一下。
# 启动pod
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-required.yml
pod/pod-podaffinity-required created
# 查看pod状态,发现未运行
[root@k8s-master01 ~]# kubectl get pod pod-podaffinity-required -n dev
NAME READY STATUS RESTARTS AGE
pod-podaffinity-required 0/1 Pending 0 2m39s
# 查看详细信息
[root@k8s-master01 ~]# kubectl describe pod -n dev pod-podaffinity-required
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 29s (x4 over 3m7s) default-scheduler 0/3 nodes are available: 1 node(s) had taints that the pod didn't tolerate, 2 node(s) didn't match pod affinity rules.
# 接下来修改,values:["xxx", "yyy"] ----> values:["pro", "yyy"]
# 意思是:新Pod必须要与拥有标签podenv=pro或podenv=yyy的pod在同一node上
# 然后重新创建pod,查看结果
[root@k8s-master01 ~]# kubectl delete -f pod-podaffinity-required.yml
pod "pod-podaffinity-required" deleted
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-required.yml
pod/pod-podaffinity-required created
[root@k8s-master01 ~]# kubectl get pod -n dev pod-podaffinity-required -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-podaffinity-required 1/1 Running 0 20s 10.244.1.29 k8s-node01 <none> <none>
关于PodAffinity
的preferredDuringSchedulingIgnoredDuringExecution
,这里不再演示。
PodAntiAffinity
PodAntiAffinity 主要实现以运行的Pod为参照,让新创建的Pod跟参照pod不再一个区域中的功能。
它的配置方式和选项跟PodAffinity是一样的,这里不再详细解释,直接做一个测试案例。
(1) 继续使用上一个案例中的目标pod
[root@k8s-master01 ~]# kubectl get pod -n dev -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pod-podaffinity-required 1/1 Running 0 6m1s 10.244.1.30 k8s-node01 <none> <none> <none>
pod-podaffinity-target 1/1 Running 0 35m 10.244.1.28 k8s-node01 <none> <none> podenv=pro
(2) 创建pod-podantiaffinity-required.yml,内容如下:
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: #匹配podenv的值在["pro"]中的标签
- key: podenv
operator: In
values: ["pro"]
topologyKey: kubernetes.io/hostname
上面配置表达的意思是:新Pod必须要与拥有标签podenv=pro的pod不再同一Node上。
运行测试一下。
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-podantiaffinity-required.yml
pod/pod-podantiaffinity-required created
#查看pod
# 发现调度到了k8s-node02节点上
[root@k8s-master01 ~]# kubectl get pod -n dev -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pod-podaffinity-required 1/1 Running 0 68m 10.244.1.30 k8s-node01 <none> <none> <none>
pod-podaffinity-target 1/1 Running 0 98m 10.244.1.28 k8s-node01 <none> <none> podenv=pro
pod-podantiaffinity-required 1/1 Running 0 5s 10.244.2.36 k8s-node02 <none> <none> <none>
5.4.3 污点和容忍
污点(Taints)
前面的调度方式都是站在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 nodes node1 key=value:effect
# 去除污点
kubectl taint nodes node1 key:effect-
# 去除所有污点
kubectl taint nodes node1 key-
接下来,演示下污点的效果:
(1) 准备节点node1(为了演示效果更加明显,暂时停止node2节点(把虚拟机挂起即可))
(2) 为node1节点设置一个污点:tag=mole:PreferNoSchedule
,然后创建pod1(pod1正常)
(3) 修改为node1节点设置一个污点:tag=mole:NoSchedule
,然后创建pod2(pod1正常,pod2失败)
(4) 修改为node1节点设置一个污点:tag=mole:NoExecute
,然后创建pod3(3个pod都失败)
# 为node1设置污点(PreferNoSchedule)
[root@k8s-master01 ~]# kubectl taint node k8s-node01 tag=mole:PreferNoSchedule
node/k8s-node01 tainted
# 创建pod1,pod1成功运行并被调度到node1节点上
[root@k8s-master01 ~]# kubectl run taint1 --image=nginx:1.17.1 -n dev
deployment.apps/taint1 created
[root@k8s-master01 ~]# kubectl get pod -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
taint1-766c47bf55-xgmfv 1/1 Running 0 43s 10.244.1.36 k8s-node01 <none> <none>
# 为node1设置污点(取消PreferNoSchedule, 设置NoSchedule)
[root@k8s-master01 ~]# kubectl taint node k8s-node01 tag:PreferNoSchedule-
node/k8s-node01 untainted
[root@k8s-master01 ~]# kubectl taint node k8s-node01 tag=mole:NoSchedule
node/k8s-node01 tainted
# 创建pod2,pod2运行失败,无法被调度,而pod1依旧正常运行
[root@k8s-master01 ~]# kubectl run taint2 --image=nginx:1.17.1 -n dev
deployment.apps/taint2 created
[root@k8s-master01 ~]# kubectl get pod -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
taint1-766c47bf55-xgmfv 1/1 Running 0 5m35s 10.244.1.36 k8s-node01 <none> <none>
taint2-84946958cf-77jcm 0/1 Pending 0 110s <none> <none> <none> <none>
# 为node1设置污点(取消NoSchedule,设置NoExecute)
[root@k8s-master01 ~]# kubectl taint node k8s-node01 tag:NoSchedule-
node/k8s-node01 untainted
[root@k8s-master01 ~]# kubectl taint node k8s-node01 tag=mole:NoExecute
node/k8s-node01 tainted
# 创建pod3,pod3运行失败,无法被调度,且此时pod1、pod2也运行失败
[root@k8s-master01 ~]# kubectl run taint3 --image=nginx:1.17.1 -n dev
deployment.apps/taint3 created
[root@k8s-master01 ~]# kubectl get pod -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
taint1-766c47bf55-v8kjq 0/1 Pending 0 82s <none> <none> <none> <none>
taint2-84946958cf-6nxcw 0/1 Pending 0 82s <none> <none> <none> <none>
taint3-57d45f9d4c-x82rm 0/1 Pending 0 10s <none> <none> <none> <none>
使用kubeadm搭建的集群,默认会给master节点添加一个污点标记,所以pod不会调度到master节点上。
容忍(Toleration)
上面介绍了污点的作用,我们可以在node上添加污点用于拒绝pod调度上来,但如果就是想将一个pod调度到一个有污点的node上去,这时应该怎么做呢?这就要使用到容忍。
污点就是拒绝,容忍就是忽略,Node通过污点拒绝pod调度上去,pod通过容忍忽略拒绝。
下面通过一个案例看一下效果:
(1) 上一小节,已经在node1节点上打上了NoExecute
的污点,此时pod是调度不上去的;
(2) 本小节,可以通过给pod添加容忍,然后将其调度上去。
创建pod-toleration.yml,内容如下:
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: "mole" #容忍的污点的value
effect: "NoExecute" #添加容忍的规则,这里必须和标记的污点规则相同
# 添加容忍之后,pod-toleration成功被调度到node1节点
[root@k8s-master01 ~]# kubectl create -f pod-toleration.yml
pod/pod-toleration created
[root@k8s-master01 ~]# kubectl get pod pod-toleration -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-toleration 1/1 Running 0 9s 10.244.1.38 k8s-node01 <none> <none>
下面看一下容忍的详细配置
[root@k8s-master01 ~]# kubectl explain pod.spec.tolerations
...
FIELDS:
key #对应着容忍的污点的键,空的话意味着匹配所有的键
value #对应着要容忍的污点的值
operator #key-value的运算符,支持Equal和Exists(默认)
effect #对应污点的effect,空意味着匹配所有影响
tolerationSeconds #容忍时间,当effect为NoExecute时生效,表示pod在Node上的停留时间
6、Pod控制器详解
6.1 Pod控制器介绍
在kubernetes中,按照pod的创建方式可以将其分为两类:
- 自主式pod:kubernetes直接创建出来的pod,这种pod删除后就没有了,也不会重建;
- 控制器创建的pod:通过控制器创建的pod,这种pod删除之后还会自动重建。
什么是Pod控制器?
Pod控制器是管理pod的中间层,使用了pod控制器之后,我们只需要告诉pod控制器,想要多少个什么样的pod就可以了,它就会创建出满足条件的pod并确保每一个pod处于用户期望的状态,如果pod在运行中出现故障,控制器会基于指定策略重启动或者重建pod。
在kubernetes中,有很多类型的pod控制器,每种都有自己的适合的场景,常见的有下面这些:
- ReplicationController:比较原始的pod控制器,已经被废弃,由ReplicaSet替代;
- ReplicaSet:保证指定数量的pod运行,并支持pod数量变更,镜像版本变更;
- Deployment:通过控制ReplicaSet来控制pod,并支持滚动升级、版本回退;
- Horizontal Pod Autoscaler:可以根据集群负载自动调整pod的数量,实现削峰填谷;
- DaemonSet:在集群中的指定Node上都运行一个副本,一般用于守护进程类的任务;
- Job:它创建出来的pod只要完成任务就立即退出,用于执行一次性任务;
- CronJob:它创建的pod会周期性的执行,用于执行周期性任务;
- StatefulSet:管理有状态的应用。
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机制;
- template: 模板。就是当前控制器创建pod所使用的模板,里面其实就是前一章学过的pod的定义。
创建ReplicaSet
创建pc-replicaset.yml,内容如下:
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
# 创建rs
[root@k8s-master01 ~]# kubectl create -f pc-replicaset.yml
replicaset.apps/pc-replicaset created
# 查看rs
[root@k8s-master01 ~]# kubectl get rs pc-replicaset -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-replicaset 3 3 3 46s nginx nginx:1.17.1 app=nginx-pod
# 查看当前控制器创建出来的pod
# 这里发现控制器创建出来的pod的名称是在控制器名称后面拼接了-xxxxx随机码
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-replicaset-9xc4p 1/1 Running 0 8s
pc-replicaset-kz97b 1/1 Running 0 8s
pc-replicaset-qb7l7 1/1 Running 0 8s
扩缩容
# 编辑rs的副本数量,修改spec:replicas: 6即可
[root@k8s-master01 ~]# kubectl edit replicasets.apps pc-replicaset -n dev
replicaset.apps/pc-replicaset edited
# 查看pod
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-replicaset-6n8qc 1/1 Running 0 20s
pc-replicaset-9xc4p 1/1 Running 0 3m44s
pc-replicaset-ff4x7 1/1 Running 0 20s
pc-replicaset-kz97b 1/1 Running 0 3m44s
pc-replicaset-mgf6s 1/1 Running 0 20s
pc-replicaset-qb7l7 1/1 Running 0 3m44s
# 当然也可以直接使用命令实现
# 使用scale命令实现扩缩容,后面--replicas=n 直接指定目标数量即可
[root@k8s-master01 ~]# kubectl scale rs pc-replicaset --replicas=2 -n dev
replicaset.apps/pc-replicaset scaled
# 命令运行完毕,立即查看,发现已有4个开始准备退出了
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-replicaset-9xc4p 1/1 Running 0 6m5s
pc-replicaset-gb79z 0/1 Terminating 0 11s
pc-replicaset-hkjg2 0/1 Terminating 0 11s
pc-replicaset-k8jlb 0/1 Terminating 0 11s
pc-replicaset-lcrm5 0/1 Terminating 0 11s
pc-replicaset-qb7l7 1/1 Running 0 6m5s
# 稍等片刻,只剩2个
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-replicaset-9xc4p 1/1 Running 0 7m21s
pc-replicaset-qb7l7 1/1 Running 0 7m21s
镜像升级
# 编辑rs的容器镜像 - image: nginx:1.17.2
[root@k8s-master01 ~]# kubectl edit rs pc-replicaset -n dev
replicaset.apps/pc-replicaset edited
# 再次查看,发现镜像版本已经变更了
[root@k8s-master01 ~]# kubectl get rs -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-replicaset 2 2 2 9m nginx nginx:1.17.2 app=nginx-pod
# 同样的道理,也可以使用命令完成这个工作
# kubectl set image rs <rs名称> 容器=镜像版本 -n <namespace>
[root@k8s-master01 ~]# kubectl set image rs pc-replicaset nginx=nginx:1.17.1 -n dev
replicaset.apps/pc-replicaset image updated
# 再次查看,发现镜像版本已变更
[root@k8s-master01 ~]# kubectl get rs -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-replicaset 2 2 2 11m nginx nginx:1.17.1 app=nginx-pod
删除ReplicaSet
# 使用kubectl delete命令会删除此RS以及它管理的Pod
# 在kubernetes删除RS前,会将RS的replicasclear调整为0,等待所有的pod被删除后,在执行rs对象的删除
[root@k8s-master01 ~]# kubectl delete rs pc-replicaset -n dev
repicaset.apps "pc-replicaset" deleted
# 如果希望仅仅删除rs对象(保留pod),可以使用kubectl delete命令时添加--cascade=false选项(不推荐)。
[root@k8s-master01 ~]# kubectl delete rs pc-replicaset -n dev --cascade=false
# 也可以使用yaml直接删除(推荐)
[root@k8s-master01 ~]# kubectl delete -f pc-replicaset.yml
replicaset.apps "pc-replicaset" deleted
6.3 Deployment(Deploy)
为了更好的解决服务编排的问题,kubernetes在V1.2版本开始,引入了Deployment控制器。值得一提的是,这种控制器并不直接管理pod,而是通过管理ReplicaSet来间接管理Pod,即:Deployment管理ReplicaSet,ReplicaSet管理Pod。所以Deployment比ReplicaSet功能更加强大。
Deployment主要功能有下面几个:
- 支持ReplicaSet的所有功能;
- 支持发布的停止、继续;
- 支持版本滚动升级和版本回退。
Deployment的资源清单文件:
创建deployment
创建pc-deployment.yml,内容如下:
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
# 创建deployment
# --record=true 记录每次的版本变化
[root@k8s-master01 ~]#
[root@k8s-master01 ~]# kubectl create -f pc-deployment.yml --record=true
deployment.apps/pc-deployment created
# 查看deployment
# UP-TO-DATE 最新版本的pod的数量
# AVAILABLE 当前可用的pod的数量
[root@k8s-master01 ~]# kubectl get deployments.apps pc-deployment -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
pc-deployment 3/3 3 3 48m
# 查看rs
# 发现rs的名称是在原来deployment的名字后面添加了一个10位数的随机串
[root@k8s-master01 ~]# kubectl get rs -n dev
NAME DESIRED CURRENT READY AGE
pc-deployment-5d89bdfbf9 3 3 3 49m
# 查看pod
# 发现pod的名称是在原来rs的名字后面添加一个5位数的随机串
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-deployment-5d89bdfbf9-5n2vs 1/1 Running 0 49m
pc-deployment-5d89bdfbf9-5pvwt 1/1 Running 0 49m
pc-deployment-5d89bdfbf9-7gwk7 1/1 Running 0 49m
扩缩容
# 变量副本数量为5个
[root@k8s-master01 ~]# kubectl scale deploy pc-deployment --replicas=5 -n dev
deployment.apps/pc-deployment scaled
# 查看deployment
[root@k8s-master01 ~]# kubectl get deployments.apps -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
pc-deployment 5/5 5 5 52m
# 查看pod
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-deployment-5d89bdfbf9-5n2vs 1/1 Running 0 52m
pc-deployment-5d89bdfbf9-5pvwt 1/1 Running 0 52m
pc-deployment-5d89bdfbf9-7gwk7 1/1 Running 0 52m
pc-deployment-5d89bdfbf9-wjbt6 1/1 Running 0 11s
pc-deployment-5d89bdfbf9-x6b88 1/1 Running 0 11s
# 编辑deployment的副本数量,修改spec:replicas: 4即可
[root@k8s-master01 ~]# kubectl edit deployments.apps pc-deployment -n dev
deployment.apps/pc-deployment edited
# 查看deployment和pod
[root@k8s-master01 ~]# kubectl get deployments.apps -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
pc-deployment 4/4 4 4 54m
[root@k8s-master01 ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-deployment-5d89bdfbf9-5n2vs 1/1 Running 0 54m
pc-deployment-5d89bdfbf9-5pvwt 1/1 Running 0 54m
pc-deployment-5d89bdfbf9-7gwk7 1/1 Running 0 54m
pc-deployment-5d89bdfbf9-x6b88 1/1 Running 0 2m10s
镜像更新
Deployment支持两种镜像更新的策略:重建更新和滚动更新(默认),可以通过strategy
选项进行配置。
...
strategy: #指定新的pod替换旧的pod的策略,支持两个属性:
type: #指定策略类型,支持两种策略
Recreate: #在创建出新的pod之前会先杀掉所有已存在的pod
RollingUpdate: #滚动更新,就是杀死一部分,就启动一部分,在更新过程中,存在两个版本的pod
rollingUpdate: #当type未RollingUpdate时生效,用于为RollingUpdate设置参数,支持两个属性:
maxUnavailable: #用来指定在升级过程中不可用pod的最大数量,默认为25%
maxSurge: #用来指定在升级过程中可以超过期望的pod的最大数量,默认为25%
..
重建更新
(1) 编辑pc-deployment.yml,在spec节点下添加更新策略:
spec:
strategy: #策略
type: Recreate #重建更新策略
(2) 创建deploy进行验证
# 添加策略后,使用apply命令更新配置
[root@k8s-master01 ~]# kubectl apply -f pc-deployment.yml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment.apps/pc-deployment configured
# 变更镜像
[root@k8s-master01 ~]# kubectl set image deployment pc-deployment nginx=nginx:1.17.2 -n dev
deployment.apps/pc-deployment image updated
# 观察升级过程
[root@k8s-master01 ~]# kubectl get pod -n dev -w
NAME READY STATUS RESTARTS AGE
pc-deployment-5d89bdfbf9-5n2vs 1/1 Running 0 66m
pc-deployment-5d89bdfbf9-5pvwt 1/1 Running 0 66m
pc-deployment-5d89bdfbf9-7gwk7 1/1 Running 0 66m
pc-deployment-5d89bdfbf9-5pvwt 1/1 Terminating 0 70m
pc-deployment-5d89bdfbf9-5n2vs 1/1 Terminating 0 70m
pc-deployment-5d89bdfbf9-7gwk7 1/1 Terminating 0 70m
pc-deployment-5d89bdfbf9-5pvwt 0/1 Terminating 0 70m
pc-deployment-5d89bdfbf9-5n2vs 0/1 Terminating 0 70m
pc-deployment-5d89bdfbf9-7gwk7 0/1 Terminating 0 70m
pc-deployment-675d469f8b-sxgtj 0/1 Pending 0 0s
pc-deployment-675d469f8b-6cjnd 0/1 Pending 0 0s
pc-deployment-675d469f8b-tdzdb 0/1 Pending 0 0s
pc-deployment-675d469f8b-sxgtj 0/1 ContainerCreating 0 1s
pc-deployment-675d469f8b-tdzdb 0/1 ContainerCreating 0 1s
pc-deployment-675d469f8b-6cjnd 0/1 ContainerCreating 0 1s
pc-deployment-675d469f8b-tdzdb 1/1 Running 0 34s
pc-deployment-675d469f8b-sxgtj 1/1 Running 0 45s
pc-deployment-675d469f8b-6cjnd 1/1 Running 0 59s
滚动更新
(1) 编辑pc-deployment.yml,在spec节点下添加更新策略
spec:
strategy: #策略
type: RollingUpdate: #滚动更新策略
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
(2) 创建deploy进行验证
# 变更镜像
[root@k8s-master01 ~]# kubectl set image deployment pc-deployment -n dev nginx=nginx:1.17.3
deployment.apps/pc-deployment image updated
# 观察升级过程
[root@k8s-master01 ~]# kubectl get pod -n dev -w
NAME READY STATUS RESTARTS AGE
pc-deployment-5d89bdfbf9-nddvr 1/1 Running 0 40m
pc-deployment-5d89bdfbf9-ppnb4 1/1 Running 0 39m
pc-deployment-5d89bdfbf9-tf9m9 1/1 Running 0 40m
pc-deployment-7865c58bdf-88b94 0/1 Pending 0 0s
pc-deployment-7865c58bdf-88b94 0/1 ContainerCreating 0 0s
pc-deployment-7865c58bdf-88b94 1/1 Running 0 41s
pc-deployment-5d89bdfbf9-ppnb4 1/1 Terminating 0 42m
pc-deployment-7865c58bdf-wvdmx 0/1 Pending 0 0s
pc-deployment-7865c58bdf-wvdmx 0/1 ContainerCreating 0 0s
pc-deployment-7865c58bdf-wvdmx 1/1 Running 0 2s
pc-deployment-5d89bdfbf9-tf9m9 1/1 Terminating 0 42m
pc-deployment-7865c58bdf-582xd 0/1 Pending 0 0s
pc-deployment-7865c58bdf-582xd 0/1 ContainerCreating 0 0s
pc-deployment-7865c58bdf-582xd 1/1 Running 0 2s
pc-deployment-5d89bdfbf9-nddvr 1/1 Terminating 0 42m
滚动更新的过程:
镜像更新中rs的变化:
# 查看rs,发现原来的rs依旧存在,只是pod数量变为了0,而后又新产生了一个rs,pod数量为3
# 其实这就是deployment能够进行版本回退的奥妙所在,后面会详细解释。
[root@k8s-master01 ~]# kubectl get rs -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-deployment-5d89bdfbf9 0 0 0 128m nginx nginx:1.17.1 app=nginx-pod,pod-template-hash=5d89bdfbf9
pc-deployment-675d469f8b 0 0 0 57m nginx nginx:1.17.2 app=nginx-pod,pod-template-hash=675d469f8b
pc-deployment-7865c58bdf 3 3 3 9m30s nginx nginx:1.17.3 app=nginx-pod,pod-template-hash=7865c58bdf
版本回退
deployment支持版本升级过程中的暂停、继续功能,以及版本回退等诸多功能,下面具体来看。
kubectl rollout:版本升级相关功能,支持下面的选项:
- status:显示当前升级状态
- history:显示升级历史记录
- pause:暂停版本升级过程
- resume:继续已经暂停的版本升级过程
- restart:重启版本升级过程
- undo:回滚到上一级版本(可以使用–to-revision回滚到指定版本)
# 查看升级历史记录
[root@k8s-master01 ~]# kubectl rollout history deployment -n dev pc-deployment
deployment.apps/pc-deployment
REVISION CHANGE-CAUSE
2 kubectl create --filename=pc-deployment.yml --record=true
3 kubectl create --filename=pc-deployment.yml --record=true
4 kubectl create --filename=pc-deployment.yml --record=true
# 可以发现又三次版本记录,说明完成过两次升级。
# 版本回滚
# 这里直接使用--to-revision=2回滚到了2版本
[root@k8s-master01 ~]# kubectl rollout undo deployment -n dev pc-deployment --to-revision=2
deployment.apps/pc-deployment rolled back
[root@k8s-master01 ~]# kubectl get rs -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-deployment-5d89bdfbf9 0 0 0 143m nginx nginx:1.17.1 app=nginx-pod,pod-template-hash=5d89bdfbf9
pc-deployment-675d469f8b 3 3 3 72m nginx nginx:1.17.2 app=nginx-pod,pod-template-hash=675d469f8b
pc-deployment-7865c58bdf 0 0 0 24m nginx nginx:1.17.3 app=nginx-pod,pod-template-hash=7865c58bdf
#回滚到3版本
[root@k8s-master01 ~]# kubectl rollout undo deployment -n dev pc-deployment --to-revision=3
deployment.apps/pc-deployment rolled back
[root@k8s-master01 ~]# kubectl get rs -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-deployment-5d89bdfbf9 3 3 3 144m nginx nginx:1.17.1 app=nginx-pod,pod-template-hash=5d89bdfbf9
pc-deployment-675d469f8b 0 0 0 73m nginx nginx:1.17.2 app=nginx-pod,pod-template-hash=675d469f8b
pc-deployment-7865c58bdf 0 0 0 25m nginx nginx:1.17.3 app=nginx-pod,pod-template-hash=7865c58bdf
#如上,其实deployment之所以可以实现版本的回滚,就是通过记录下历史rs来实现的。
# 一旦想回滚到哪个版本,只需要将当前版本pod数量降为0,然后将回滚版本的pod提升为目标数量就可以了。
金丝雀发布
Deployment控制器支持自定义控制更新过程中的滚动节奏,如"暂停(pause)" 或"继续(resume)" 更新操作。比如等待第一批新的Pod资源创建完成后立即暂停更新过程,此时,仅存在一部分新版本的应用,主体部分还是旧的版本。然后,再筛选一小部分的用户请求路由到新版本的Pod应用,继续观察能否稳定地按期望的方式运行。确定没问题之后再继续完成余下的Pod资源滚动更新,否则立即回滚更新操作。这就是所谓的金丝雀发布。
# 更新deployment的版本,并配置暂停deployment
[root@k8s-master01 ~]# kubectl set image deploy pc-deployment nginx=nginx:1.17.5 -n dev && kubectl rollout pause deployment pc-deployment -n dev
deployment.apps/pc-deployment image updated
deployment.apps/pc-deployment paused
# 观察更新状态
[root@k8s-master01 ~]# kubectl rollout status deployment pc-deployment -n dev
Waiting for deployment "pc-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
# 监控更新的过程,可以看到已经新增了一个资源,但是并未按照预期的状态去删除一个旧的资源,就是因为使用了pause暂停命令
[root@k8s-master01 ~]# kubectl get rs,pod -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/pc-deployment-5d89bdfbf9 0 0 0 14h nginx nginx:1.17.1 app=nginx-pod,pod-template-hash=5d89bdfbf9
replicaset.apps/pc-deployment-675d469f8b 0 0 0 12h nginx nginx:1.17.2 app=nginx-pod,pod-template-hash=675d469f8b
replicaset.apps/pc-deployment-6c9f56fcfb 3 3 3 3m29s nginx nginx:1.17.4 app=nginx-pod,pod-template-hash=6c9f56fcfb
replicaset.apps/pc-deployment-6dcd994dc9 1 1 1 2m6s nginx nginx:1.17.5 app=nginx-pod,pod-template-hash=6dcd994dc9
replicaset.apps/pc-deployment-7865c58bdf 0 0 0 12h nginx nginx:1.17.3 app=nginx-pod,pod-template-hash=7865c58bdf
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/pc-deployment-6c9f56fcfb-4trrn 1/1 Running 0 3m29s 10.244.2.76 k8s-node02 <none> <none>
pod/pc-deployment-6c9f56fcfb-glfr5 1/1 Running 0 2m56s 10.244.2.78 k8s-node02 <none> <none>
pod/pc-deployment-6c9f56fcfb-qrk6t 1/1 Running 0 2m57s 10.244.2.77 k8s-node02 <none> <none>
pod/pc-deployment-6dcd994dc9-k4694 1/1 Running 0 2m6s 10.244.2.79 k8s-node02 <none> <none>
# 确保更新的pod没问题了,继续更新
[root@k8s-master01 ~]# kubectl rollout resume deployment pc-deployment -n dev
deployment.apps/pc-deployment resumed
# 查看最后的更新情况
replicaset.apps/pc-deployment-5d89bdfbf9 0 0 0 14h nginx nginx:1.17.1 app=nginx-pod,pod-template-hash=5d89bdfbf9
replicaset.apps/pc-deployment-675d469f8b 0 0 0 12h nginx nginx:1.17.2 app=nginx-pod,pod-template-hash=675d469f8b
replicaset.apps/pc-deployment-6c9f56fcfb 0 0 0 5m10s nginx nginx:1.17.4 app=nginx-pod,pod-template-hash=6c9f56fcfb
replicaset.apps/pc-deployment-6dcd994dc9 3 3 3 3m47s nginx nginx:1.17.5 app=nginx-pod,pod-template-hash=6dcd994dc9
replicaset.apps/pc-deployment-7865c58bdf 0 0 0 12h nginx nginx:1.17.3 app=nginx-pod,pod-template-hash=7865c58bdf
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/pc-deployment-6dcd994dc9-4gcg4 1/1 Running 0 40s 10.244.2.80 k8s-node02 <none> <none>
pod/pc-deployment-6dcd994dc9-j9j92 1/1 Running 0 38s 10.244.2.81 k8s-node02 <none> <none>
pod/pc-deployment-6dcd994dc9-k4694 1/1 Running 0 3m47s 10.244.2.79 k8s-node02 <none> <none>
删除Deployment
# 删除deployment,其下的rs和pod也将被删除
[root@k8s-master01 ~]# kubectl delete -f pc-deployment.yml
deployment.apps "pc-deployment" deleted
[root@k8s-master01 ~]# kubectl get rs,pod -n dev -o wide
No resources found in dev namespace.
6.4 Horizontal Pod Autoscaler(HPA)
1.安装metrics-server
metrics-server可以用来收集集群中的资源使用情况.
# 获取metrics-server,注意使用的版本
[root@k8s-master01 github]# git clone -b v0.3.6 https://github.com/kubernetes-sigs/metrics-server
# 修改deployment,注意修改的是镜像和初始化参数
[root@k8s-master01 1.8+]# pwd
/root/github/metrics-server/deploy/1.8+
[root@k8s-master01 1.8+]# vim metrics-server-deployment.yaml
# 按照下图中添加如下选项:
hostNetwork: true
registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server-amd64:v0.3.6
- --kubelet-insecure-tls
- -- kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP
# 安装metrics-server
[root@k8s-master01 1.8+]# kubectl apply -f ./
# 查看pod运行情况
[root@k8s-master01 1.8+]# kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
metrics-server-6b976979db-tqrjr 1/1 Running 0 137m
# 使用kubecl top node 查看资源使用情况
# 这里解释一下cpu
[root@k8s-master01 1.8+]# kubectl top node (稍等一会才能查到数据)
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
k8s-master01 241m 12% 802Mi 29%
k8s-node01 52m 2% 230Mi 8%
k8s-node02 94m 4% 334Mi 12%
[root@k8s-master01 1.8+]# kubectl top pod -n kube-system
NAME CPU(cores) MEMORY(bytes)
coredns-6955765f44-4pwjb 7m 12Mi
coredns-6955765f44-h2vpk 5m 12Mi
etcd-k8s-master01 30m 54Mi
kube-apiserver-k8s-master01 77m 296Mi
kube-controller-manager-k8s-master01 27m 39Mi
kube-flannel-ds-7xx6d 8m 30Mi
kube-flannel-ds-vh55n 6m 20Mi
kube-proxy-f9hmd 1m 14Mi
kube-proxy-qqnlw 2m 14Mi
kube-proxy-vd7dx 1m 14Mi
kube-scheduler-k8s-master01 7m 17Mi
metrics-server-6b976979db-tqrjr 2m 9Mi
# 至此,metrics-server安装完成
2.准备deployment和service
为了操作简单,直接使用命令
# 创建deployment
[root@k8s-master01 1.8+]# kubectl run nginx --image=nginx:1.17.1 --requests=cpu=100m -n dev
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx created
# 创建service
[root@k8s-master01 1.8+]# kubectl get deployments.apps,pod,service -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 1/1 1 1 40s
NAME READY STATUS RESTARTS AGE
pod/nginx-778cb5fb7b-xb4g9 1/1 Running 0 40s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx NodePort 10.100.88.190 <none> 80:32227/TCP 13s
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使用率指标,3表示3%(为了测试方便才设得比较低)
scaleTargetRef: # 指定要控制的nginx信息
apiVersion: apps/v1
kind: Deployment
name: nginx
# 创建hpa
[root@k8s-master01 1.8+]# kubectl create -f pc-hpa.yml
horizontalpodautoscaler.autoscaling/pc-hpa created
# 查看hpa
[root@k8s-master01 1.8+]# kubectl get hpa -n dev
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
pc-hpa Deployment/nginx 0%/3% 1 10 1 43s
4.测试
使用压测工具对service地址 192.168.166.111:32227
进行压测,然后通过控制台查看hpa和pod的变化. (这里用burpsuite的intruder模块进行大并发访问)
hpa变化
deployment变化
pod 变化
等压测停下来后,要过一段时间(有可能是几分钟,或10分钟左右,不一定),hpa才会自动缩容。
6.5 DaemonSet(DS)
下面先来看一下DaemonSet的资源清单文件:
创建pc-deaemonset.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
# 创建daemonset
[root@k8s-master01 ~]# kubectl create -f pc-daemonset.yml
daemonset.apps/pc-daemonset created
# 查看daemonset
[root@k8s-master01 ~]# kubectl create -f pc-daemonset.yml
daemonset.apps/pc-daemonset created
# 查看daemonset
[root@k8s-master01 ~]# kubectl get ds pc-daemonset -n dev -o wide
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR
pc-daemonset 1 1 1 1 1 <none> 4m2s nginx nginx:1.17.1 app=nginx-pod
# 查看pod,发现在每个node上都运行一个pod
[root@k8s-master01 ~]# kubectl get ds pc-daemonset -n dev -o wide
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR
pc-daemonset 2 2 2 2 2 <none> 4m2s nginx nginx:1.17.1 app=nginx-pod
# 删除daemonset
[root@k8s-master01 ~]# kubectl delete -f pc-daemonset.yml
daemonset.apps "pc-daemonset" deleted
6.6 Job
Job,主要用于负责批量处理(一次要处理指定数量的任务) 短暂的一次性任务。Job特点如下:
- 当Job创建的pod执行成功时,Job将记录成功结束的pod数量;
- 当成功结束的pod达到指定的数量时,Job将完成执行。
Job的资源清单文件:
关于重启策略的说明:
如果指定为onFailure,则Job会在Pod出现故障时重启容器,而不是创建pod,failed次数;
如果指定为Never,则job会在pod出现故障时创建新的pod,并且故障pod不会消失,也不会重启,failed次数加1;
如果指定为Always,就意味着一直重启,意味着job任务会重复去执行,这当然不对,所以Job的重启策略不能设置为Always。
创建pc-job.yml
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"]
# 创建job
[root@k8s-master01 ~]# kubectl create -f pc-job.yml
job.batch/pc-job created
# 查看job
[root@k8s-master01 ~]# kubectl get job -n dev -o wide -w
NAME COMPLETIONS DURATION AGE CONTAINERS IMAGES SELECTOR
pc-job 0/1 0s counter busybox:1.30 app=counter-pod
pc-job 0/1 0s 0s counter busybox:1.30 app=counter-pod
pc-job 1/1 31s 31s counter busybox:1.30 app=counter-pod
# 通过观察pod状态可以看到,pod在运行完毕任务后,就会变成Completed状态
[root@k8s-master01 ~]# kubectl get pod -n dev -o wide -w
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pc-job-dw84w 1/1 Running 0 21s 10.244.2.103 k8s-node02 <none> <none>
pc-job-dw84w 0/1 Completed 0 31s 10.244.2.103 k8s-node02 <none> <none>
# 接下来,调整下pod运行的总数量和并行数量,即:在spec下设置下面两个选项:
# completions: 6 #指定job需要成功运行pod的个数为6
# parallelism:3 #指定job并发运行pod的数量为3
# 然后重新运行job,观察效果,此时会发现,job会每次运行3个pod,总共运行了6个pod
[root@k8s-master01 ~]# kubectl get pod -n dev -w
NAME READY STATUS RESTARTS AGE
pc-job-7rxp6 1/1 Running 0 11s
pc-job-k4f6n 1/1 Running 0 11s
pc-job-kxbz9 1/1 Running 0 11s
pc-job-7rxp6 0/1 Completed 0 30s
pc-job-h8pms 0/1 Pending 0 0s
pc-job-h8pms 0/1 Pending 0 0s
pc-job-kxbz9 0/1 Completed 0 30s
pc-job-qbnss 0/1 Pending 0 0s
pc-job-qbnss 0/1 Pending 0 0s
pc-job-h8pms 0/1 ContainerCreating 0 0s
pc-job-k4f6n 0/1 Completed 0 30s
pc-job-gqrq7 0/1 Pending 0 0s
pc-job-gqrq7 0/1 Pending 0 0s
pc-job-qbnss 0/1 ContainerCreating 0 0s
pc-job-gqrq7 0/1 ContainerCreating 0 1s
pc-job-gqrq7 1/1 Running 0 2s
pc-job-qbnss 1/1 Running 0 3s
pc-job-h8pms 1/1 Running 0 3s
pc-job-gqrq7 0/1 Completed 0 29s
pc-job-qbnss 0/1 Completed 0 29s
pc-job-h8pms 0/1 Completed 0 29s
# 删除job
[root@k8s-master01 ~]# kubectl delete -f pc-job.yml
job.batch "pc-job" deleted
6.7 CronJob(CJ)
CronJob的资源清单文件:
创建pc-cronjob.yml,内容如下:
apiVersion: batch/v1beta1
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"]
# 创建cronjob
[root@k8s-master01 ~]# kubectl create -f pc-cronjob.yml
cronjob.batch/pc-cronjob created
# 查看cronjob
[root@k8s-master01 ~]# kubectl get cj -n dev -o wide -w
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE CONTAINERS IMAGES SELECTOR
pc-cronjob */1 * * * * False 0 <none> 3s counter busybox:1.30 <none>
pc-cronjob */1 * * * * False 1 3s 43s counter busybox:1.30 <none>
pc-cronjob */1 * * * * False 0 33s 73s counter busybox:1.30 <none>
# 查看job
[root@k8s-master01 ~]# kubectl get job -n dev -o wide -w
NAME COMPLETIONS DURATION AGE CONTAINERS IMAGES SELECTOR
pc-cronjob-1647597840 0/1 0s counter busybox:1.30 controller-uid=9e150b5f-d183-4b1d-b053-0e3b474ba253
pc-cronjob-1647597900 0/1 0s counter busybox:1.30 controller-uid=4965b04d-2746-4e50-9b90-4b5c5e91233d
# 删除cronjob
[root@k8s-master01 ~]# kubectl delete -f pc-cronjob.yml
cronjob.batch "pc-cronjob" deleted
6.8 StatefulSet(有状态)
- 无状态应用:
○ 认为Pod都是一样的。
○ 没有顺序要求。
○ 不用考虑在哪个Node节点上运行。
○ 随意进行伸缩和扩展。 - 有状态应用:
○ 有顺序的要求。
○ 认为每个Pod都是不一样的。
○ 需要考虑在哪个Node节点上运行。
○ 需要按照顺序进行伸缩和扩展。
○ 让每个Pod都是独立的,保持Pod启动顺序和唯一性。 - StatefulSet是Kubernetes提供的管理有状态应用的负载管理控制器。
- StatefulSet部署需要HeadLinessService(无头服务)。
为什么需要HeadLinessService(无头服务)?
在用Deployment时,每一个Pod名称是没有顺序的,是随机字符串,因此是Pod名称是无序的,但是在StatefulSet中要求必须是有序 ,每一个Pod不能被随意取代,Pod重建后pod名称还是一样的。
而Pod IP是变化的,所以是以Pod名称来识别。Pod名称是Pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称 。
- StatefulSet常用来部署RabbitMQ集群、Zookeeper集群、MySQL集群、Eureka集群等。
创建pc-statefulset.yml,内容如下:
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 # Service的端口
targetPort: 80 # Pod的端口
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: pc-statefulset
namespace: dev
spec:
replicas: 3
serviceName: service-headliness
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
# 创建statefulset
[root@k8s-master01 ~]# kubectl create -f pc-statefulset.yml
service/service-headliness created
statefulset.apps/pc-statefulset created
# 查看statefulset
[root@k8s-master01 ~]# kubectl get statefulsets.apps pc-statefulset -n dev -o wide
NAME READY AGE CONTAINERS IMAGES
pc-statefulset 3/3 2m37s nginx nginx:1.17.1
# 查看pod
[root@k8s-master01 ~]# kubectl get pod -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pc-statefulset-0 1/1 Running 0 2m56s 10.244.2.116 k8s-node02 <none> <none>
pc-statefulset-1 1/1 Running 0 2m55s 10.244.2.117 k8s-node02 <none> <none>
pc-statefulset-2 1/1 Running 0 2m54s 10.244.2.118 k8s-node02 <none> <none>
# 删除statefulset
[root@k8s-master01 ~]# kubectl delete -f pc-statefulset.yml
service "service-headliness" deleted
statefulset.apps "pc-statefulset" deleted
Deployment和StatefulSet的区别
- Deployment没有唯一标识而StatefulSet有唯一标识。
- StatefulSet的唯一标识是根据主机名+一定规则生成的。
- StatefulSet的唯一标识是主机名.无头Service名称.命名空间.svc.cluster.local。
StatefulSet的金丝雀发布
StatefulSet支持两种更新策略:OnDelete和RollingUpdate(默认),其中OnDelete表示删除之后才更新,RollingUpdate表示滚动更新。
...
updateStrategy:
rollingUpdate: # 如果更新的策略是OnDelete,那么rollingUpdate就失效
partition: 2 # 表示从第2个分区开始更新,默认是0
type: RollingUpdate /OnDelete # 滚动更新
...
示例:pc-statefulset.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 # Service的端口
targetPort: 80 # Pod的端口
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: pc-statefulset
namespace: dev
spec:
replicas: 3
serviceName: service-headliness
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
updateStrategy:
rollingUpdate:
partition: 0
type: RollingUpdate
7、Service详解
本章节主要介绍kubernetes的流量负载组件:Service和Ingress
7.1 Service介绍
在kubernetes中,pod是应用程序的载体,我们可以通过pod的IP来访问应用程序,但是pod的ip不是固定的,这也就意味着不方便直接采用pod的ip对服务进行访问。
为了解决这个问题,kubernetes提供了Service资源,Service对提供同一个服务的多个pod进行聚合,并且提供一个统一的入口地址,通过访问Service的入口地方就能访问到后面的pod服务。
Service在很多情况下只是一个概念,真正起作用的其实是kube-proxy服务进程,每个Node节点上都运行着一个kube-proxy服务进程。当创建Service的时候会通过api-server向etcd写入创建的service的信息,而kube-proxy会基于监听的机制发现这种Service的变动,然后它会将最新的Service信息转换成对应的访问规则。
kube-proxy目前支持三种工作模式:
userspace模式
在userspace模式下,kube-proxy会为每一个Service创建一个监听端口,发向Cluster IP的请求被iptables规则重定向到kube-proxy监听的端口上,kube-proxy根据LB算法选择一个提供服务的Pod并和其建立连接,以将请求转发到pod上。
该模式下,kube-proxy充当了一个四层负载均衡器的角色。由于kube-proxy运行在userspace中,在进行转发处理时增加内核和用户空间之间的数据拷贝,虽然比较稳定,但效率比较低。
iptables模式
在iptables模式下,kube-proxy为service后端的每个pod创建对应的iptables规则,直接将发向Cluster IP的请求重定向到一个Pod IP。
该模式下的kube-proxy不承担四层负载均衡器的角色,只负责创建iptables规则。该模式的优点是较userspace模式效率更高,但不能提供灵活的LB策略,当后端Pod不可用时也无法进行重试。
ipvs模式
ipvs模式和iptables类似,kube-proxy监控pod的变化并创建相应的ipvs规则。ipvs相对iptables转发效率更高。除此之外,ipvs支持更多的LB算法。
# 此模式必须按照ipvs模块,否则会降级为iptables(第二章搭建环境时已按照ipvs)
# 开启ipvs:使用以下命令编辑kube-proxy,将mode项改为:"ipvs",如下图.
[root@k8s-master01 ~]# kubectl edit cm kube-proxy -n kube-system
configmap/kube-proxy edited
# 然后删除标签为 k8s-app=kube-proxy 的pod,删除后pod会自动重建,从而使用ipvs。
[root@k8s-master01 ~]# kubectl delete pod -l k8s-app=kube-proxy -n kube-system
pod "kube-proxy-f9hmd" deleted
pod "kube-proxy-qqnlw" deleted
pod "kube-proxy-vd7dx" deleted
[root@k8s-master01 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 127.0.0.1:30856 rr
-> 10.244.2.122:80 Masq 1 0 0 Masq 1 0 0
TCP 192.168.166.111:30856 rr
-> 10.244.2.122:80 Masq 1 0 2
# 上面规则的含义,以 第二条为例:当访问 192.168.166.111:30856时,
# 会转发到 10.244.2.122:80 。
7.2 Service类型
Service的资源清单文件:
- ClusterIP:默认值,它是kubernetes系统自动分配的虚拟IP,只能在集群内部访问;
- NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务;
- LoadBalancer:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境支持;
- ExternalName:把集群外部的服务引入集群内部,直接使用。
7.3 Service使用
7.3.1 实验环境准备
在使用service之前,首先利用Deployment创建出3个pod,注意要为pod设置 app=nginx-pod
的标签。
创建pc-deployment-2.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
#创建deployment
[root@k8s-master01 ~]# kubectl create -f pc-deployment-2.yml
deployment.apps/pc-deployment created
# 查看pod详情
[root@k8s-master01 ~]# kubectl get pod -n dev -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
pc-deployment-6696798b78-f7426 1/1 Running 0 2m1s 10.244.2.9 k8s-node02 <none> <none> app=nginx-pod,pod-template-hash=6696798b78
pc-deployment-6696798b78-qz8g2 1/1 Running 0 2m1s 10.244.1.7 k8s-node01 <none> <none> app=nginx-pod,pod-template-hash=6696798b78
pc-deployment-6696798b78-rwdqp 1/1 Running 0 2m1s 10.244.2.10 k8s-node02 <none> <none> app=nginx-pod,pod-template-hash=6696798b78
为了方便后面的测试,修改下三台nginx的index.html页面(三台修改的IP地址不一致)
[root@k8s-master01 ~]# kubectl exec -it pc-deployment-6696798b78-f7426 -n dev /bin/sh
# echo "IP=10.244.2.9<br>NODE=k8s-node02" > /usr/share/nginx/html/index.html
# 修改完毕后,访问测试
[root@k8s-master01 ~]# curl http://10.244.2.9
IP=10.244.2.9<br>NODE=k8s-node02
[root@k8s-master01 ~]# curl http://10.244.1.7
IP=10.244.1.7<br>NODE=k8s-node01
[root@k8s-master01 ~]# curl http://10.244.2.10
IP=10.244.2.10<br>NODE=k8s-node02
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
ports:
- port: 80 #service的端口
targetPort: 80 #pod的端口
# 创建service
[root@k8s-master01 ~]# kubectl create -f service-clusterip.yaml
service/service-clusterip created
# 查看service
[root@k8s-master01 ~]# kubectl get service -n dev service-clusterip -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service-clusterip ClusterIP 10.97.97.97 <none> 80/TCP 8m43s app=nginx-pod
# 查看service的详细信息
# 在这里有一个Endpoints列表,里面就是当前service可以负载到的服务入口
[root@k8s-master01 ~]# kubectl describe service -n dev service-clusterip
Name: service-clusterip
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=nginx-pod
Type: ClusterIP
IP: 10.97.97.97
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.8:80,10.244.2.11:80,10.244.2.12:80
Session Affinity: None
Events: <none>
# 查看ipvs的映射规则
[root@k8s-master01 ~]# 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.8:80 Masq 1 0 0
-> 10.244.2.11:80 Masq 1 0 0
-> 10.244.2.12:80 Masq 1 0 0
# 访问10.97.97.97:80 观察效果
Endpoint
Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址,它是根据service配置文件中selector描述产生的。
一个Service由一组pod组成,这些pod通过Endpoints暴露出来,endpoints是实现实际服务的端点集合。换句话说,service和pod之间的联系是通过endpoints实现的。
负载分发策略
对Service的访问被分发到了后端的pod上去,目前kubernetes提供了两种负载分发策略:
- 如果不定义,默认使用kube-proxy的策略,比如随机、轮询;
- 基于客户端地址的会话保证模式,即来自同一个客户端发起的所有请求都会转发到固定的一个pod上,此模式可以使在spec中添加
sessionAffinity:ClientIP
选项。
# 查看ipvs的映射规则【rr:轮询】
[root@k8s-master01 ~]# 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.8:80 Masq 1 0 0
-> 10.244.2.11:80 Masq 1 0 0
-> 10.244.2.12:80
# 循环访问测试
[root@k8s-master01 ~]# while true; do curl 10.97.97.97:80; sleep 5; done;
IP=10.244.2.12<br>NODE=k8s-node02
IP=10.244.2.11<br>NODE=k8s-node02
IP=10.244.1.8<br>NODE=k8s-node01
IP=10.244.2.12<br>NODE=k8s-node02
IP=10.244.2.11<br>NODE=k8s-node02
IP=10.244.1.8<br>NODE=k8s-node01
# 修改分发策略-------- sessionAffinity:ClientIP
# 查看ipvs规则【persistent:代表持久】
[root@k8s-master01 ~]# 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 persistent 10800
-> 10.244.1.8:80 Masq 1 0 0
-> 10.244.2.11:80 Masq 1 0 0
-> 10.244.2.12:80 Masq 1 0 0
# 循环访问测试
[root@k8s-master01 ~]# while true; do curl 10.97.97.97:80; sleep 5; done;
IP=10.244.2.12<br>NODE=k8s-node02
IP=10.244.2.12<br>NODE=k8s-node02
IP=10.244.2.12<br>NODE=k8s-node02
# 删除service
[root@k8s-master01 ~]# kubectl delete -f service-clusterip.yaml
service "service-clusterip" deleted
7.3.3 HeadLiness类型的Service
在某些场景中,开发人员可能不想使用Service提供的负载均衡功能,而希望自己来控制负载均衡策略,针对这种情况,kubernetes提供了HeadLiness Service,这类Service不会分配ClusterIP,如果想要访问service,只能通过service的域名进行查询。
创建 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
# 创建service
[root@k8s-master01 ~]# kubectl create -f service-headliness.yaml
service/service-headliness created
# 获取service,发现CLUSTER-IP未分配
[root@k8s-master01 ~]# kubectl get service service-headliness -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service-headliness ClusterIP None <none> 80/TCP 10s app=nginx-pod
# 查看service详情
[root@k8s-master01 ~]# kubectl describe service service-headliness -n dev
Name: service-headliness
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=nginx-pod
Type: ClusterIP
IP: None
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.8:80,10.244.2.11:80,10.244.2.12:80
Session Affinity: None
Events: <none>
# 查看域名的解析情况
[root@k8s-master01 ~]# kubectl exec -it pc-deployment-6696798b78-4fwr6 -n dev /bin/bash
root@pc-deployment-6696798b78-4fwr6:/#
root@pc-deployment-6696798b78-4fwr6:/# cat /etc/resolv.conf
nameserver 10.96.0.10
search dev.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
[root@k8s-master01 ~]# dig @10.96.0.10 service-headliness.dev.svc.cluster.local
...
;; ANSWER SECTION:
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.1.8
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.2.12
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.2.11
...
7.3.4 NodePort类型的Service
在之前的样例中,创建的Service的IP地址只有集群内部才可以访问,如果希望将Service暴露给集群外部使用,那么就要使用到另外一种类型的Service,称为NodePort类型。NodePort的工作原理其实就是将service的端口映射到node的一个端口上,然后就可以通过NodeIP:NodePort
来访问service了。
创建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: 32227 #指定绑定的node端口(默认取值范围是:30000-32767),如果不指定,会默认分配
targetPort: 80
# 创建service
[root@k8s-master01 ~]# kubectl create -f service-nodeport.yaml
service/service-nodeport created
# 查看service
[root@k8s-master01 ~]# kubectl get service -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service-nodeport NodePort 10.106.176.29 <none> 80:32227/TCP 6m5s app=nginx-pod
# 然后可以通过电脑主机的浏览器访问集群任意一个node ip的32227端口,便可访问到pod的nginx服务
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地址也可以
# 创建service
[root@k8s-master01 ~]# kubectl create -f service-externalname.yaml
service/service-externalname created
# 域名解析
[root@k8s-master01 ~]# dig @10.96.0.10 service-externalname.dev.svc.cluster.local
;; ANSWER SECTION:
service-externalname.dev.svc.cluster.local. 5 IN CNAME www.baidu.com.
www.baidu.com. 5 IN CNAME www.a.shifen.com.
www.a.shifen.com. 5 IN A 182.61.200.6
www.a.shifen.com. 5 IN A 182.61.200.7
7.4 Ingress介绍
在前面课程中已经提到,Service对集群之外暴露服务的主要方式有两种:NodePort和LoadBalancer,但是这两种方式都有缺点:
- NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显;
- LB方式的缺点是每个Service需要一个LB,浪费、麻烦,并且需要kubernetes之外设备的支持。
基于这种现状,kubernetes提供了Ingress资源对象,Ingress只需要一个NodePort或者一个LB就可以满足暴露多个Service的需求。工作机制大致如下图所示:
实际上,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环境
# 创建文件夹
[root@k8s-master01 ~]# mkdir ingress-controller
[root@k8s-master01 ~]# cd ingress-controller/
# 获取ingress-nginx,本次案例使用的是0.30.0版本
[root@k8s-master01 ingress-controller]# wget -e "https_proxy=http://192.168.3.36:1087" https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
[root@k8s-master01 ingress-controller]# wget -e "https_proxy=http://192.168.3.36:1087" https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
# 修改mandatory.yaml文件中的仓库
# 修改quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0 为:
# quay.mirrors.ustc.edu.cn/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0(这里换成中科大的源,因为七牛云quay-mirror.qiniu.com的失效了)
# 创建ingress-nginx
[root@k8s-master01 ingress-controller]# kubectl apply -f ./
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created
service/ingress-nginx created
# 查看ingress-nginx
[root@k8s-master01 ingress-controller]# kubectl get svc,pod -n ingress-nginx -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/ingress-nginx NodePort 10.105.200.169 <none> 80:31470/TCP,443:31569/TCP 22m app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-ingress-controller-6ddf7d6bc4-ccg2k 1/1 Running 0 2m26s 10.244.2.21 k8s-node02 <none> <none>
准备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
# 创建
[root@k8s-master01 ~]# kubectl create ns dev
namespace/dev created
[root@k8s-master01 ~]#
[root@k8s-master01 ~]# kubectl create -f tomcat-nginx.yaml
deployment.apps/nginx-deployment created
deployment.apps/tomcat-deployment created
service/nginx-service created
service/tomcat-service created
# 查看
[root@k8s-master01 ~]# kubectl get service -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx-service ClusterIP None <none> 80/TCP 29s app=nginx-pod
tomcat-service ClusterIP None <none> 8080/TCP 29s app=tomcat-pod
7.5.2 HTTP代理
创建 ingress-http.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-http
namespace: dev
spec:
rules:
- host: nginx.mole.org
http:
paths:
- path: /
backend:
serviceName: nginx-service
servicePort: 80
- host: tomcat.mole.org
http:
paths:
- path: /
backend:
serviceName: tomcat-service
servicePort: 8080
# 创建
[root@k8s-master01 ~]# kubectl create -f ingress-http.yaml
ingress.extensions/ingress-http create
# 查看
[root@k8s-master01 ~]# kubectl get ingresses.extensions -n dev
NAME HOSTS ADDRESS PORTS AGE
ingress-http nginx.mole.org,tomcat.mole.org 10.105.200.169 80 21s
[root@k8s-master01 ~]#
# 查看详情
[root@k8s-master01 ~]# kubectl describe ingresses.extensions -n dev
Name: ingress-http
Namespace: dev
Address: 10.105.200.169
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
nginx.mole.org
/ nginx-service:80 (10.244.1.15:80,10.244.1.16:80,10.244.2.22:80)
tomcat.mole.org
/ tomcat-service:8080 (10.244.1.14:8080,10.244.2.23:8080,10.244.2.24:8080)
...
# 接下来,在本地电脑上配置hosts文件,解析上面的两个域名到 192.168.166.111 (master节点) 上
# 然后,就可以分别访问 http://tomcat.mole.org:31470 和 http://nginx.mole.org:31470 查看效果了
7.5.3 HTTPS代理
创建证书
# 生成证书
[root@k8s-master01 ~]# 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=mole.org"
Generating a 2048 bit RSA private key
......................................................................+++
............................+++
writing new private key to 'tls.key'
-----
# 创建密钥
[root@k8s-master01 ~]# kubectl create secret tls tls-secret --key tls.key --cert tls.crt
secret/tls-secret created
创建ingress-https.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-https
namespace: dev
spec:
tls:
- hosts:
- nginx.mole.org
- tomcat.mole.org
secretName: tls-secret #指定密钥
rules:
- host: nginx.mole.org
http:
paths:
- path: /
backend:
serviceName: nginx-service
servicePort: 80
- host: tomcat.mole.org
http:
paths:
- path: /
backend:
serviceName: tomcat-service
servicePort: 8080
# 创建
[root@k8s-master01 ~]# kubectl create -f ingress-https.yaml
ingress.extensions/ingress-https created
# 查看
[root@k8s-master01 ~]# kubectl get ingresses.extensions ingress-https -n dev
NAME HOSTS ADDRESS PORTS AGE
ingress-https nginx.mole.org,tomcat.mole.org 80, 443 50s
# 查看详情
[root@k8s-master01 ~]# kubectl describe ingresses.extensions ingress-https -n dev
Name: ingress-https
Namespace: dev
Address: 10.105.200.169
Default backend: default-http-backend:80 (<none>)
TLS:
tls-secret terminates nginx.mole.org,tomcat.mole.org
Rules:
Host Path Backends
---- ---- --------
nginx.mole.org
/ nginx-service:80 (10.244.1.15:80,10.244.1.16:80,10.244.2.22:80)
tomcat.mole.org
/ tomcat-service:8080 (10.244.1.14:8080,10.244.2.23:8080,10.244.2.24:8080)
...
# 下面可以通过浏览器访问 https://nginx.mole.org:31569 和 https://tomcat.mole.org:31569 来查看了
更多推荐
所有评论(0)