1、Kubernetes介绍
1.1 什么是Kubernetes
-
Kubernetes是一个可移植的、可扩展的、开源的平台,用于管理容器化的工作负载和服务,它促进了声明式配置和自动化。它有一个庞大的、快速增长的生态系统。Kubernetes的服务、支持和工具可以广泛使用。
-
Kubernetes这个名字源于希腊语,意思是舵手或飞行员。K8s作为一个缩写词,是计算“K”和“s”之间的八个字母的结果。谷歌于2014年开放Kubernetes项目。Kubernetes结合了谷歌超过15年的大规模运行生产负载的经验,以及来自社区的最佳品种的想法和实践。
1.2 容器化的发展历程
-
传统部署时代(Traditional Deployment era):早期,APP之间运行在物理服务器上,无法为其中的应用程序定义资源边界,这就导致了资源分配 问题。例如,如果一个物理服务器上运行着多个应用程序,可能会出现一个应用程序占用大部分资源的情况下,从而影响其它程序的性能。如果一个应用程序发生了内存泄漏,最终可能会耗尽服务器的资源,从而导致所有应用程序都挂掉。解决方案是在不同的物理机器上运行每个应用程序。但是这样的话,会导致资源得不到充分利用,而且维护组织很多物理服务器的成本很高,因此这种方式无法扩展。
-
虚拟化部署时代(Virtualized Deployment era):一种解决方案就是使用虚拟化。它允许在一个物理服务器的CPU上运行多个虚拟机(vm)。虚拟化运行应用程序在虚拟机之间隔离,一个应用程序的信息不能被另一个应用程序自由访问,从而提供了一定安全性。虚拟化可以更好地利用物理服务器中的资源,有着更好的伸缩性,因为可以轻松地添加或更新应用程序,降低硬件成本。使用虚拟化,可以将一组物理资源表示为一次性虚拟机的集群。每个VM都是一台完整的机器,在虚拟硬件之上运行所有组件,包括自己的操作系统,这样也会导致一部分的资源消耗。
-
容器化部署时代(Container Deployment era):容器类似于vm,但是它们有宽松的隔离属性,可以在应用程序之间共享操作系统(OS)。因此,容器被认为是轻量级的。与vm类型,容器有自己的文件系统、共享CPU、内存、进程空间等。由于它们与底层基础设施解耦,因此可以跨云和OS发行版进行移植。
使用容器的优点:
- 应用程序创建和部署更敏捷:与使用vm相比,容器映像创建更加简单和高效。
- 持续开发、集成和部署:通过快速高效的回滚,提供可靠和频繁的容器映像构建和部署。
- 可观察性:不仅显示操作系统级别的信息和指标,还显示应用程序状态和其它信号。
- 跨平台、测试和生产环境的一致性:在笔记本电脑上和云中运行的环境是一样的。
- 云和操作系统发行版的可移植性:可在Ubuntu、RHEL、CoreOS、内部部署、主要的公共云以及其它任何地方运行。
- 以应用程序为中心的管理:将抽象级别从在虚拟硬件上运行操作系统提升到使用逻辑资源在操作系统上运行应用程序。
- 松散耦合、分布式、弹性、自由的微服务:应用程序被分解为更小、独立的部分,可以动态地部署和管理,而不是在一台大型、单一用途的机器上运行一个庞大的堆栈。
- 资源隔离:可预测的应用性能。
- 资源利用:效率高、密度大。
1.3 为什么需要K8S以及它能做什么
容器是捆绑和运行应用程序的好方法。在生产环境中,我们需要管理运行应用程序的容器,并确保没有停机时间。例如,一个容器出现故障,则需要启动另一个容器,如果由系统自动发现来处理那不是更简单吗?这就是K8S拯救我们的方式。K8S为我们提供了一个能够弹性地运行分布式系统的框架。它负责应用程序的扩展和故障转移,提供部署模式等等。
K8S可以提供的服务:
- 服务发现和负载均衡(Service discovery and load balancing):K8s可以使用DNS名称或它们的IP地址开放容器。如果一个容器的流量很大,K8s能够负载均衡和分配网络流量,从而使部署稳定。
- 存储编排(Storage orchestration):K8s运行我们自动挂载选择的存储系统,如本地存储、公共云提供商等。
- 自动部署和回滚(Automated rollouts and rollbacks):我们可以使用K8s自动化地为我们的部署创建新的容器,删除现有的容器,并将它们所有的资源采用到新容器中。
- 自动装箱(Automatic bin packing):在K8s的集群中,告诉K8s每个容器需要多少CPU和内存,K8s可以将容器安装到节点上,以充分利用资源。
- 自愈(Self-healing):K8s会重新启动挂掉的容器,替换容器。
- 机密和配置管理(Secret and configuration management):K8s允许存储和管理敏感信息,如密码OAuth令牌和SSH密钥。在部署和更新机密文件和应用程序配置时无需重新构建容器映像。
2、Kubernetes简介
2.1 Kubernetes简介
Kubernetes是一个全新的基于容器技术的分布式架构领先方案,是谷歌严格保密十几年的秘密武器–Borg系统的一个开源版本,于2014年9月发布第一个版本,2015年7月发布第一个正式版本。
Kubernetes本质是一组服务器集群
,它可以在集群的每个节点上运行特定的程序,来对节点中的容器进行管理。目的是实现资源管理的自动化。
2.2 Kubernetes组件
一个Kubernetes集群主要由控制节点(Master)
和 工作节点(Node)
构成,每个节点上都会安装不同的组件。
Master
:集群的控制平面,复制集群的决策:
ApiServer
:资源操作的唯一入口,接受用户输入的命令,提供认证、授权、API注册和发现等机制。
Scheduler
:负责集群资源调度,安装预定的调度策略将Pod
调度到相应的Node
节点上。
ControllerManager
:负责维护集群的状态,比如程序部署安排、故障检测、自动扩展、滚动更新等。
Etcd
:负责存储集群中各种资源对象的信息。
Node
:集群的数据平面,负责为容器提供运行环境:
Kubelet
:负责维护容器的生命周期,即通过控制Docker来创建、更新、销毁容器。
KubeProxy
:负责提供集群内部的服务发现和负载均衡。
Docker
:负责节点上容器的各种操作。
下面,以部署一个nginx服务来说明Kubernetes系统各个组件调用关系:
-
首先要明确,一旦Kubernetes环境启动成功后,master和node都会将自身的信息存储到etcd数据库中。
-
一个nginx服务的安装请求会首先被发送到master节点的ApiServer组件。
-
ApiServer组件会调用Scheduler组件来决定到的应该把这个服务安装到哪个node节点上,在此时,它会从Etcd读取各个node节点的信息,然后按照一定的算法进行选择,并将结果告知ApiServer。
-
ApiServer调用ControllerManager去调度Node节点安装Nginx服务。
-
Kubelet接收到指令后,会通知Docker,然后由Docker来启动一个nginx的Pod,Pod是Kubernetes的最小操作单元,容器必须跑在Pod中。
-
至此,一个nginx服务就运行了,如果需要访问nginx,就需要通过Kube-proxy来对Pod产生访问的代理,这样,外界用户就可以访问集群中的nginx服务了。
2.3 Kubernetes中的概念
Master
:集群控制节点,每个集群需要至少一个master节点负责集群的管控。
Node
:工作负载节点,由master分配工作到这些node节点上,然后node节点上的docker负责容器的运行。
Pod
:K8s最小控制单元,容器都是运行在Pod中的,一个Pod中可以有一个或多个容器。
Controller
:控制器,通过它来实现对Pod的管理,比如启动Pod、停止Pod、伸缩Pod的数量等等。
Service
:Pod对外服务的统一入口,下面可以维护同一类的多个Pod。
Label
:标签,用来对Pod进行分类,同一类Pod会拥有相同的标签。
NameSpace
:命名空间,用来隔离Pod的运行环境。
3、环境搭建
本次环境搭建需要安装三台centos服务器(一主二从),然后在每台服务器中分别安装docker、kubeadm、kubelet、bubectl程序。
3.1 安装虚拟机
使用的虚拟机配置如下:
名称 | IP地址 | 操作系统 | 配置 |
---|---|---|---|
Master | 192.168.226.168 | centos7 | 2颗CPU 2G内存 50G硬盘 |
Node1 | 192.168.226.169 | centos7 | 2颗CPU 2G内存 50G硬盘 |
Node2 | 192.168.226.170 | centos7 | 2颗CPU 2G内存 50G硬盘 |
3.2 环境初始化
1)检查操作系统版本
# 此方式下安装kubernetes集群要求centos7版本在7.5或之上
[root@master ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
2)主机域名解析
为了方便后面集群的直接调用,在这配置一下主机名解析。
# 编辑三台服务器的/etc/hosts文件,添加下面内容,ip和主机名要换成自己的
192.168.226.168 master
192.168.226.169 node1
192.168.226.170 node2
3)时间同步
Kubernetes要求集群中的节点时间必须精确一致,这里直接使用chronyd服务从网络同步时间。
# 启动chronyd
systemctl start chronyd
# 设置chronyd服务开机自启
systemctl enable chronyd
# 查看时间
date
4)禁用iptables和firewalld服务
Kubernetes和Docker在运行中会产生大量的iptables规则,为了不让系统规则与他们混淆,直接关闭系统的规则。
# 关闭firewalld服务
systemctl stop firewalld
systemctl disable firewalld
# 关闭iptables服务
systemctl stop iptables
systemctl disable iptables
5)禁用selinux
selinu是linux系统下的一个安全服务,如果不关闭他,在安装集群中会产生各种奇葩问题
# 编辑 /etc/selinux/config文件,修改SELINUX的值为disabled
SELINUX=disabled
# 修改完后需要重新启动linux,可以在执行完后面操作后统一重启
6)禁用swap分区
swap分区指的是虚拟内存分区,它的作用是在物理内存使用完之后,将磁盘空间虚拟成内存在使用。
启用swap设备会对系统的性能产生非常负面的影响,因此Kubernetes要求每个节点都要禁用swap设备。
但是如果因为某些原因确实不能关闭swap分区,就需要在集群安装过程中通过明确的参数进行配置说明。
# 编辑分区配置文件 /etc/fstab ,注释掉swap分区一行
# 注意修改后要重启linux
/dev/mapper/centos-root / xfs defaults 0 0
UUID=1b419f2d-536b-4728-a6d2-c8b9d7ea85f0 /boot xfs defaults 0 0
#/dev/mapper/centos-swap swap swap defaults 0 0
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
# 重新加载配置
sysctl -p
# 加载网桥过滤模块
modprobe br_netfilter
# 查看网桥过滤模块是否加载成功
lsmod | grep br_netfilter
[root@master ~]# lsmod | grep br_netfilter
br_netfilter 22256 0
bridge 151336 1 br_netfilter
8)配置ipvs功能
在kubernetes中service有两种代理模型,一种基于iptables的,一种基于ipvs的
两者比较的话,ipvs的性能明显要高一些,但是如果要使用它,需要手动载入ipvs模块
# 1.安装ipset和ipvsadm
yum install -y ipset ipvsadm
# 2.添加需要加载的模块写入脚本
cat <<EOF > /etc/sysconfig/modules/ipvs.modules
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
EOF
# 3.为脚本文件添加执行权限
chmod +x /etc/sysconfig/modules/ipvs.modules
# 4.执行脚本文件
/bin/bash /etc/sysconfig/modules/ipvs.modules
# 5.查看对应的模块是否加载成功
lsmod | grep -e ip_vs -e nf_conntrack_ipv4
[root@master ~]# lsmod | grep -e ip_vs -e nf_conntrack_ipv4
nf_conntrack_ipv4 15053 0
nf_defrag_ipv4 12729 1 nf_conntrack_ipv4
ip_vs_sh 12688 0
ip_vs_wrr 12697 0
ip_vs_rr 12600 0
ip_vs 145458 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr
nf_conntrack 139264 2 ip_vs,nf_conntrack_ipv4
libcrc32c 12644 3 xfs,ip_vs,nf_conntrack
9)重启linux
reboot
# 重启后查看selinux
[root@master ~]# getenforce
Disabled
# 查看swap分区情况,swap全是0代表已经关掉了
[root@master ~]# free -m
total used free shared buff/cache available
Mem: 3770 271 3174 11 324 3271
Swap: 0 0 0
至此环境初始化已经完成了!
3.3 Docker安装
# 1.更新镜像源
$ yum -y update
# 2.安装所需的软件包。yum-utils 提供了 yum-config-manager ,并且 device mapper 存储驱动程序需要 device-mapper-persistent-data 和 lvm2。
$ sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
# 3.使用阿里云镜像源
$ sudo yum-config-manager \
--add-repo \
http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 4.安装最新版本的 Docker Engine-Community 和 containerd
$ sudo yum install docker-ce docker-ce-cli containerd.io
# 5.添加配置文件
# Docker在默认情况下使用的Cgroup Driver为cgroupfs,而kubernetes推荐使用systemd来代替cgroupfs
mkdir /etc/docker
cat <<EOF > /etc/docker/daemon.json
{
"exec-opts":["native.cgroupdriver=systemd"],
"registry-mirrors": ["https://kn0t2bca.mirror.aliyuncs.com"]
}
EOF
# 6.启动Docker
$ sudo systemctl restart docker
# 7.设置docker开机自启
$ sudo systemctl enable docker
# 8.运行hello-world测试是否安装成功
$ docker run hello-world
3.4 安装Kubernetes组件
# 由于Kubernetes的镜像源在国外,速度比较慢,这里切换成国内的镜像源
# 编辑/etc/yum.repos.d/kubernetes.repo,添加下面的配置
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
# 安装kubeadm、kubelet和kubectl
yum install -y kubelet kubeadm kubectl
# 配置kubelet的cgroup
# 编辑/etc/sysconfig/kubelet,添加如下配置
KUBELET_CGROUP_ARGS="--cgroup-driver=systemd"
KUBE_PROXY_MODE="ipvs"
# 设置kubelet开机自启
systemctl enable kubelet
3.5 准备集群镜像
# 在安装kubernetes集群之前,必须要提前准备好集群所需的镜像,所需镜像可以通过下面命令查看
kubeadm config images list
# 下载镜像
# 此镜像在k8s的仓库中,由于网络原因,无法连接,下面提供了一种代替方案
# 我们从阿里云拉取镜像,然后给镜像打上k8s官方的tag,这样他就不会从k8s仓库中拉取了
# 在shell中输入下面命令即可
images=(
kube-apiserver:v1.23.1
kube-controller-manager:v1.23.1
kube-scheduler:v1.23.1
kube-proxy:v1.23.1
pause:3.6
etcd:3.5.1-0
coredns:v1.8.6
)
for imageName in ${images[@]}; do
docker pull registry.aliyuncs.com/google_containers/$imageName
docker tag registry.aliyuncs.com/google_containers/$imageName k8s.gcr.io/$imageName
docker rmi registry.aliyuncs.com/google_containers/$imageName
done
docker pull registry.aliyuncs.com/google_containers/coredns:v1.8.6
docker tag registry.aliyuncs.com/google_containers/coredns:v1.8.6 k8s.gcr.io/coredns/coredns:v1.8.6
docker rmi registry.aliyuncs.com/google_containers/coredns:v1.8.6
# 执行完毕后可以查看镜像
[root@master ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
k8s.gcr.io/kube-apiserver v1.23.1 b6d7abedde39 3 weeks ago 135MB
k8s.gcr.io/kube-proxy v1.23.1 b46c42588d51 3 weeks ago 112MB
k8s.gcr.io/kube-controller-manager v1.23.1 f51846a4fd28 3 weeks ago 125MB
k8s.gcr.io/kube-scheduler v1.23.1 71d575efe628 3 weeks ago 53.5MB
k8s.gcr.io/etcd 3.5.1-0 25f8c7f3da61 2 months ago 293MB
k8s.gcr.io/coredns v1.8.6 a4ca41631cc7 3 months ago 46.8MB
registry.aliyuncs.com/google_containers/coredns v1.8.6 a4ca41631cc7 3 months ago 46.8MB
hello-world latest feb5d9fea6a5 3 months ago 13.3kB
k8s.gcr.io/pause
3.6 集群初始化
下面的操作只需在master节点上执行即可
# 创建集群, 第一个版本可能需要改成你自己的版本,最后一个是master节点的ip地址,需要改成自己的
kubeadm init \
--kubernetes-version=v1.23.1 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12 \
--apiserver-advertise-address=192.168.226.168
# 显示下面这句话说明运行成功
Your Kubernetes control-plane has initialized successfully!
# 然后执行如下命令
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
其它节点加入集群
# 在其他节点上加入集群
# 在master节点上使用下面的命令可以查看加入集群的命令
kubeadm token create --print-join-command
# 然后复制命令,在其它节点上执行
[root@master ~]# kubeadm token create --print-join-command
kubeadm join 192.168.226.168:6443 --token iopvlm.1fdztldlifhh7ej9 --discovery-token-ca-cert-hash sha256:1ce8f02b795807f4c16532da3e1ea2b798e01e6580a59b419abd825d90af8a7d
# 在master节点上查看加入的节点
[root@master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master NotReady control-plane,master 12m v1.23.1
node1 NotReady <none> 21s v1.23.1
node2 NotReady <none> 14s v1.23.1
状态显示NotReady,是因为网络插件还没有安装,下面安装网络插件
3.7 安装网络插件
K8s支持多种网络插件,比如flannel、calico、canal等,任选一种使用即可,在此选择flannel
下面操作依旧只需在
master
节点执行即可,插件使用的是DaemonSet的控制器,它会在每个节点上都运行。
# 获取flannel的配置文件
# 配置文件在下面
# 使用配置文件启动flannel
kubectl apply -f kube-flannel.yml
# 稍等片刻,再次查看集群节点的状态
kubectl get nodes
flannel配置文件
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: psp.flannel.unprivileged
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
spec:
privileged: false
volumes:
- configMap
- secret
- emptyDir
- hostPath
allowedHostPaths:
- pathPrefix: "/etc/cni/net.d"
- pathPrefix: "/etc/kube-flannel"
- pathPrefix: "/run/flannel"
readOnlyRootFilesystem: false
# Users and groups
runAsUser:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
fsGroup:
rule: RunAsAny
# Privilege Escalation
allowPrivilegeEscalation: false
defaultAllowPrivilegeEscalation: false
# Capabilities
allowedCapabilities: ['NET_ADMIN', 'NET_RAW']
defaultAddCapabilities: []
requiredDropCapabilities: []
# Host namespaces
hostPID: false
hostIPC: false
hostNetwork: true
hostPorts:
- min: 0
max: 65535
# SELinux
seLinux:
# SELinux is unused in CaaSP
rule: 'RunAsAny'
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: flannel
rules:
- apiGroups: ['extensions']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['psp.flannel.unprivileged']
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- apiGroups:
- ""
resources:
- nodes
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes/status
verbs:
- patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: flannel
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flannel
subjects:
- kind: ServiceAccount
name: flannel
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: flannel
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: kube-flannel-cfg
namespace: kube-system
labels:
tier: node
app: flannel
data:
cni-conf.json: |
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
hostNetwork: true
priorityClassName: system-node-critical
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay-mirror.qiniu.com/coreos/flannel:v0.13.1-rc1
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.13.1-rc1
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
3.8 服务部署
接下来在k8s集群中部署一个nginx程序,测试下集群是否正常工作。
# 部署nginx
kubectl create deployment nginx --image=nginx:1.14-alpine
# 暴露端口
kubectl expose deployment nginx --port=80 --type=NodePort
# 查看服务状态
[root@master ~]# kubectl get pods,service
NAME READY STATUS RESTARTS AGE
pod/nginx-7cbb8cd5d8-hhnmg 1/1 Running 0 2m11s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 68m
service/nginx NodePort 10.96.52.227 <none> 80:30041/TCP 31m
# 最后在电脑上访问nginx
# 输入master主机的ip和上面最后一条中的30041端口
3.9 可能产生的问题
使用 kubectl get pod -n kube-system
可以查看系统pod的启动情况:
[root@master k8s]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-64897985d-6w7vv 0/1 ImagePullBackOff 0 44s
coredns-64897985d-b577t 0/1 ImagePullBackOff 0 44s
etcd-master 1/1 Running 0 22h
kube-apiserver-master 1/1 Running 0 22h
kube-controller-manager-master 1/1 Running 2 (17h ago) 22h
kube-flannel-ds-amd64-dkh9p 1/1 Running 7 (21h ago) 21h
kube-flannel-ds-amd64-hx9v4 1/1 Running 7 (21h ago) 21h
kube-flannel-ds-amd64-m684w 1/1 Running 7 (21h ago) 21h
kube-flannel-ds-b9xc9 0/1 Init:ImagePullBackOff 0 21h
kube-flannel-ds-pft6v 0/1 Init:ImagePullBackOff 0 21h
kube-flannel-ds-t22gs 0/1 Init:ImagePullBackOff 0 21h
kube-proxy-4kg5z 1/1 Running 0 22h
kube-proxy-c4xqw 1/1 Running 0 22h
kube-proxy-rkdf5 1/1 Running 0 22h
kube-scheduler-master 1/1 Running 2 (17h ago) 22h
可以看到coredns和flannel都没有启动成功,状态是ImagePullBackOff,说明镜像拉取失败,但是明明本地已经下载了镜像。看来应该是标签名不一致,所以它又去拉取,而且没有成功,因此我们需要把本地的镜像打一个新的标签。
使用 kubectl get pod -n kube-system -o wide
可以查看详情:
[root@master k8s]# kubectl get pods -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-64897985d-6w7vv 0/1 ImagePullBackOff 0 92s 10.244.2.10 node2 <none> <none>
coredns-64897985d-b577t 0/1 ImagePullBackOff 0 92s 10.244.1.6 node1 <none> <none>
etcd-master 1/1 Running 0 22h 192.168.226.168 master <none> <none>
kube-apiserver-master 1/1 Running 0 22h 192.168.226.168 master <none> <none>
kube-controller-manager-master 1/1 Running 2 (17h ago) 22h 192.168.226.168 master <none> <none>
kube-flannel-ds-amd64-dkh9p 1/1 Running 7 (21h ago) 21h 192.168.226.170 node2 <none> <none>
kube-flannel-ds-amd64-hx9v4 1/1 Running 7 (21h ago) 21h 192.168.226.169 node1 <none> <none>
kube-flannel-ds-amd64-m684w 1/1 Running 7 (21h ago) 21h 192.168.226.168 master <none> <none>
kube-flannel-ds-b9xc9 0/1 Init:ImagePullBackOff 0 21h 192.168.226.170 node2 <none> <none>
kube-flannel-ds-pft6v 0/1 Init:ErrImagePull 0 21h 192.168.226.168 master <none> <none>
kube-flannel-ds-t22gs 0/1 Init:ImagePullBackOff 0 21h 192.168.226.169 node1 <none> <none>
kube-proxy-4kg5z 1/1 Running 0 22h 192.168.226.169 node1 <none> <none>
kube-proxy-c4xqw 1/1 Running 0 22h 192.168.226.170 node2 <none> <none>
kube-proxy-rkdf5 1/1 Running 0 22h 192.168.226.168 master <none> <none>
kube-scheduler-master 1/1 Running 2 (17h ago) 22h 192.168.226.168 master <none> <none>
通过查看,发现node1和node2的coredns没有启动成功,master和两个node的flannel都没有启动成功。
使用 kubectl describe pod kube-flannel-ds-b9xc9 -n kube-system | grep Image:
来查看需要的镜像名。
[root@master k8s]# kubectl describe pod kube-flannel-ds-b9xc9 -n kube-system | grep Image:
Image: quay-mirror.qiniu.com/coreos/flannel:v0.13.1-rc1
Image: quay.io/coreos/flannel:v0.13.1-rc1
然后使用docker tag 原标签名 目的标签名
来修改标签名即可。
然后这些没有启动的pod就会重启,或者也可以删掉pod,删掉后会重新启动,命令为:
kubectl delete pod pod名 -n kube-system
4、资源管理
4.1 资源管理介绍
在Kubernetes中,所有内容都被抽象为资源,用户需要通过操作资源来管理Kubernetes。
Kubernetes的本质就是一个集群系统,用户可以在集群中部署各种服务,所谓的部署服务,其实就是在Kubernetes集群中运行一个个的容器,并将指定的程序跑在容器中。
Kubernetes的最小管理单元是Pod而不是容器,所以只能将容器放在Pod中,而Kubernetes一般也不会直接管理Pod,而是通过
pod控制器
来管理Pod的。 Pod可以提供服务之后,就需要考虑如何访问Pod中的服务,Kubernetes提供了
Service
资源实现这个功能。 当然,如果Pod中程序的数据需要持久化,Kubernetes还提供了各种
存储
系统。
学习Kubernetes的核心,就是学习如何对集群的Pod、Pod控制器、Service、存储等各种资源进行操作。
4.2 Yaml语法介绍
YAML是一个类似XML、JSON的标记性语言。它强调以数据为中心,并不是以标识语言为重点。因为YAML本身的定义比较简单,号称"一种人性化的数据格式语言"。
YAML的语法比较简单,主要有下面几个:
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格(高版本可以)
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#'表示注释
YAML支持以下几种数据类型:
- 纯量:单个的不可再分的值
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hash)/ 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence)/ 列表(list)
# 纯量,就是指的一个简单的值,字符串、布尔、整数、浮点数、Null、时间、日期
# 布尔类型
c1: true
# 整型
c2: 123456
# 浮点类型
c3: 3.14
# null类型
c4: ~ # 使用~表示null
# 日期类型
c5: 2019-11-11 # 日期类型必须使用ISO 8601格式,即yyyy-MM-dd
# 时间类型
c6: 2019-11-11T15:02:31+08.00 # 时间类型使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区
# 字符串类型
c7: haha # 简单写法,直接写值,如果字符串中间有特殊符号,必须使用双引号或单引号包裹
c8: line1
line2 # 字符串过多的情况可以折成多行,每一行都会转换成一个空格
# 对象
# 形式一(推荐):
xudaxian:
name: kunkun
age: 30
# 形式二(了解):
xuxian: { name: jige, age: 30 }
# 数组
# 形式一(推荐):
address:
- 江苏
- 浙江
# 形式二(了解):
address: [江苏,上海]
小提示:
1 书写yaml切记
:
后面要加一个空格 2 如果需要将多段yaml配置放在一个文件中,中间要用
---
分割
4.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
类型 | 操作对象 | 适用环境 | 优点 | 缺点 |
---|---|---|---|---|
命令式对象管理 | 对象 | 测试 | 简单 | 只能操作活动对象,无法审计、跟踪 |
命令式对象配置 | 文件 | 开发 | 可以审计、跟踪 | 项目大时,配置文件多,操作麻烦 |
声明式对象配置 | 目录 | 开发 | 支持目录操作 | 意外情况下难以调试 |
4.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,以json格式展示结果
kubectl get pod pod_name -o json
·操作·
Kubernetes运行对资源进行多种操作,可以通过kubectl --help
来查看详细的操作命令
查用操作如下:
① 基本命令
命令 | 翻译 | 命令作用 |
---|---|---|
create | 创建 | 创建一个资源 |
edit | 编辑 | 编辑一个资源 |
get | 获取 | 获取一个资源 |
patch | 更新 | 更新一个资源 |
delete | 删除 | 删除一个资源 |
explain | 解释 | 展示资源文档 |
② 运行和调试
命令 | 翻译 | 命令作用 |
---|---|---|
run | 运行 | 在集群中运行一个指定的镜像 |
expose | 暴露 | 暴露资源为Service |
describe | 描述 | 显示资源内部信息 |
logs | 日志 | 输出容器在Pod中的日志 |
attach | 缠绕 | 进入运行中的容器 |
exec | 执行 | 执行容器中的一个命令 |
cp | 复制 | 在Pod内外复制文件 |
rollout | 首次展示 | 管理资源的发布 |
scale | 规模 | 扩(缩)容Pod的数量 |
autoscale | 自动调整 | 自动调整Pod的数量 |
③ 高级命令
命令 | 翻译 | 命令作用 |
---|---|---|
apply | 应用 | 通过文件对资源进行配置 |
label | 标签 | 更新资源上的标签 |
④ 其它命令
命令 | 翻译 | 命令作用 |
---|---|---|
apply | 应用 | 通过文件对资源进行配置 |
label | 标签 | 更新资源上的标签 |
·资源类型·
Kubernetes中所有内容都抽象为资源,可以通过下面的命令进行查看:
kubectl api-resources
常用资源如下:
① 集群级别资源
资源名称 | 缩写 | 资源作用 |
---|---|---|
nodes | no | 集群组成部分 |
namespaces | ns | 隔离Pod |
② Pod资源
资源名称 | 缩写 | 资源作用 |
---|---|---|
Pods | po | 装载容器 |
③ Pod资源控制器
资源名称 | 缩写 | 资源作用 |
---|---|---|
replicationcontrollers | rc | 控制Pod资源 |
replicasets | rs | 控制Pod资源 |
deployments | deploy | 控制Pod资源 |
daemonsets | ds | 控制Pod资源 |
jobs | 控制Pod资源 | |
cronjobs | cj | 控制Pod资源 |
horizontalpodautoscalers | hpa | 控制Pod资源 |
statefulsets | sts | 控制Pod资源 |
④ 服务发现资源
资源名称 | 缩写 | 资源作用 |
---|---|---|
services | svc | 统一Pod对外接口 |
ingress | ing | 统一Pod对外接口 |
⑤ 存储资源
volumeattachments | 存储 | |
---|---|---|
persistentvolumes | pv | 存储 |
persistentvolumeclaims | pvc | 存储 |
⑥ 配置资源
资源名称 | 缩写 | 资源作用 |
---|---|---|
configmaps | cm | 配置 |
secrets | 配置 |
下面以一个namespace / pod的创建和删除简单演示下命令的使用:
# 创建一个namespace, namespace可以缩写为ns
[root@master ~]# kubectl create namespace dev
namespace/dev created
# 查看namespace
[root@master ~]# kubectl get ns
NAME STATUS AGE
default Active 20h
dev Active 40s
kube-node-lease Active 20h
kube-public Active 20h
kube-system Active 20h
# 在此namespace下创建并运行一个nginx的pod,-n 用来指定命名空间
[root@master ~]# kubectl run pod --image=nginx:1.17.1 -n dev
pod/pod created
# 查看新创建的pod
[root@master ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pod 1/1 Running 0 36s
# 删除指定的pod
[root@master ~]# kubectl delete pod pod -n dev
pod "pod" deleted
# 删除指定的namespace
[root@master ~]# kubectl delete ns dev
namespace "dev" deleted
4.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@master k8s]# kubectl create -f nginxpod.yaml
namespace/dev created
pod/nginxpod created
此时发现创建了两个资源对象,分别是namespace和pod
3)执行get命令查看资源:
[root@master k8s]# kubectl get -f nginxpod.yaml
NAME STATUS AGE
namespace/dev Active 93s
NAME READY STATUS RESTARTS AGE
pod/nginxpod 1/1 Running 0 93s
这样就显示了两个资源对象的信息
4)执行delete命令,删除资源
[root@master k8s]# kubectl delete -f nginxpod.yaml
namespace "dev" deleted
pod "nginxpod" deleted
此时发现两个资源对象被删除了
总结:
命令式对象配置的操作资源,可以简单的认为:命令 + yaml配置文件(里面是命令需要的各种参数)
4.3.3 声明式对象配置
声明式对象配置根命令式对象配置很相似,但是它只有一个命令apply
# 首先执行一次kubectl apply -f nginxpod.yaml文件,发现创建了资源
[root@master k8s]# kubectl apply -f nginxpod.yaml
namespace/dev created
pod/nginxpod created
# 再次执行,发现资源没有变动
[root@master k8s]# kubectl apply -f nginxpod.yaml
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 ~/.kube/ node1:~/
使用推荐:三种方式应该怎么用?
创建/更新资源: 使用声明式对象配置 kubectl apply -f xxx.yaml
删除资源: 使用命令式对象配置kubectl delete -f xxx.yaml
查询资源: 使用命令式对象管理 kubectl get / describe 资源名称
5、实战入门
本章节将介绍如何在Kubernetes集群中部署一个nginx服务,并且能够对其访问。
5.1 Namespace
Namespace是Kubernetes系统中的一种非常重要的资源,它的主要作用是用来实现多套环境的资源隔离
或者多租户的资源隔离
。
默认情况下,Kubernetes集群中所有的Pod都是可以互相访问的。但在实际中,可能不想让两个Pode之间互相访问,那么此时就可以将两个Pod划分到不同的Namespace中。Kubernetes通过将集群内部的资源分配到不同的Namespace中,可以形成逻辑上,以方便不同组的资源进行隔离使用和管理。
可以通过Kubernetes的授权机制,将不同的Namespace交给不同的租户进行管理,这样就实现了多租户的资源隔离。此时还能结合Kubernetes的资源配额机制,限定不同租户能占用的资源,例如CPU的使用量、内存使用量等等,来实现租户可用资源的管理。
Kubernetes在集群启动后,会默认创建几个Namespace
# namespace 也可以缩写为 ns
[root@master k8s]# kubectl get namespace
NAME STATUS AGE
default Active 46h
kube-node-lease Active 46h
kube-public Active 46h
kube-system Active 46h
下面来看Namespace资源的具体操作:
查看:
# 1.查看所有的namespace
[root@master k8s]# kubectl get ns
NAME STATUS AGE
default Active 46h
dev Active 41m
kube-node-lease Active 46h
kube-public Active 46h
kube-system Active 46h
# 2.查看指定的ns
[root@master k8s]# kubectl get ns kube-system
NAME STATUS AGE
kube-system Active 46h
# 3.指定输出格式 命令:kubectl get ns ns名称 -o 格式参数
# 格式参数有很多,比较常见的是 wide、json、yaml
[root@master k8s]# kubectl get ns default -o json
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"creationTimestamp": "2022-01-06T13:14:23Z",
"labels": {
"kubernetes.io/metadata.name": "default"
},
"name": "default",
"resourceVersion": "207",
"uid": "5a0e80e3-24de-4e83-8748-7b5e777dd4c7"
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
# 4.查看ns详情 命令: kubectl describe ns ns名称
[root@master k8s]# kubectl describe ns default
Name: default
Labels: kubernetes.io/metadata.name=default
Annotations: <none>
Status: Active
No resource quota.
No LimitRange resource.
创建:
# 创建namespace
[root@master k8s]# kubectl create ns test
namespace/test created
删除:
# 删除namespace
[root@master k8s]# kubectl delete ns test
namespace "test" deleted
配置方式:
首先准备一个yaml文件:ns-dev.yaml
apiVersion: v1
kind: Namespace
metadata:
name: dev
然后就可以执行对应的创建和删除命令了:
创建 kubectl create -f ns-dev.yaml
删除 kubectl delete -f ns-dev.yaml
5.2 Pod
Pod是Kubernetes集群进行管理的最小单元,程序要运行必须部署在容器中,而容器必须存在于Pod中。
Pod可以认为是容器的封装,一个Pod中可以存在一个或多个容器。
Kubernetes在集群启动之后,集群中的各个组件也都是以Pod的方式运行的。可以通过下面的命令查看:
[root@master k8s]# kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-64897985d-hgmnd 1/1 Running 0 24h
coredns-64897985d-wrqs4 1/1 Running 0 24h
etcd-master 1/1 Running 0 47h
kube-apiserver-master 1/1 Running 0 47h
kube-controller-manager-master 1/1 Running 2 (41h ago) 47h
kube-flannel-ds-amd64-dkh9p 1/1 Running 7 (46h ago) 46h
kube-flannel-ds-amd64-hx9v4 1/1 Running 7 (46h ago) 46h
kube-flannel-ds-amd64-m684w 1/1 Running 7 (46h ago) 46h
kube-flannel-ds-b9xc9 1/1 Running 0 46h
kube-flannel-ds-pft6v 1/1 Running 0 46h
kube-flannel-ds-t22gs 1/1 Running 0 46h
kube-proxy-4kg5z 1/1 Running 0 46h
kube-proxy-c4xqw 1/1 Running 0 46h
kube-proxy-rkdf5 1/1 Running 0 47h
kube-scheduler-master 1/1 Running 2 (41h ago) 47h
创建并运行:
Kubernetes没有提供单独允许Pod的命令,都是通过Pod控制器来实现的
# 命令格式: kubectl run (pod控制器名称) [参数]
# --image 指定Pod的jingx
# --port 指定端口
# --namespace 指定命名空间
[root@master k8s]# kubectl run nginx-test --image=nginx:1.17.1 --port=80 --namespace=dev
pod/nginx-test created
查看Pod信息:
# 查看pod基本信息
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx-6fdfb7f689-ht222 1/1 Running 0 40m
nginx-6fdfb7f689-q2wjx 1/1 Running 0 40m
nginx-6fdfb7f689-xklcw 1/1 Running 0 40m
nginx-test 1/1 Running 0 54s
# 查看详细信息
[root@master k8s]# kubectl get pod -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-6fdfb7f689-ht222 1/1 Running 0 41m 10.244.1.15 node1 <none> <none>
nginx-6fdfb7f689-q2wjx 1/1 Running 0 41m 10.244.2.14 node2 <none> <none>
nginx-6fdfb7f689-xklcw 1/1 Running 0 41m 10.244.1.14 node1 <none> <none>
nginx-test 1/1 Running 0 76s 10.244.1.16 node1 <none> <none>
# 查看某个pod的更详细信息,最下面记录了Pod的启动日志,可以用此来排错
[root@master k8s]# kubectl describe pod nginx-test -n dev
Name: nginx-test
Namespace: dev
Priority: 0
Node: node1/192.168.226.169
Start Time: Sat, 08 Jan 2022 20:23:54 +0800
Labels: run=nginx-test
Annotations: <none>
Status: Running
IP: 10.244.1.16
IPs:
IP: 10.244.1.16
Containers:
nginx-test:
Container ID: docker://d8419946e6348a3f5297ec3b30675e9aa27639c853d68b5a4e9bd334ef709924
Image: nginx:1.17.1
Image ID: docker-pullable://nginx@sha256:b4b9b3eee194703fc2fa8afa5b7510c77ae70cfba567af1376a573a967c03dbb
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Sat, 08 Jan 2022 20:23:55 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-fnf59 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-fnf59:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m20s default-scheduler Successfully assigned dev/nginx-test to node1
Normal Pulled 2m20s kubelet Container image "nginx:1.17.1" already present on machine
Normal Created 2m20s kubelet Created container nginx-test
Normal Started 2m20s kubelet Started container nginx-test
删除指定的Pod:
# 删除指定pod
[root@master k8s]# kubectl delete pod nginx-test -n dev
pod "nginx-test" deleted
# 此时,显示删除成功,但是再次查询,发现可能新产生了一个
# 如果是旧版本,可以会产生一个新的,新版本则不会产生。
# 说明,新版本使用run命令创建的pod没有Pod控制器。
# 在旧版本中,使用run命令会产生一个Pod控制器,该控制器会监控Pod的状态,一旦Pod被删除,Pod控制器
# 就会创建一个新的Pod
# 查看Pod控制器
kubectl get deploy -n dev
配置操作:
创建一个pod-nginx.yaml,内容如下
apiVersion: v1
kind: Pod
metadata:
name: nginx-test
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
5.3 Label
Label是Kubernetes系统中的一个重要概念。它的作用就是在资源上添加标识,用来对他们进行区分和选择。
Label的特定:
- 一个Label会以key/value键值对的形式附加到各种对象上,如Node、Pod、Service等
- 一个资源对象可以定义任意数量的Label,同一个Lable也可以被添加到任意数量的资源对象上去。
- 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 int (frontend)
:选择所有包含Label中的key="env"且value不等于"frontend"的对象
标签的选择条件可以使用多个,此时将多个Label Selector进行组合,使用逗号分隔即可,例如:
name=slave, env!=production
name not in (frontend), env!=production
命令格式:
# 为pod资源打标签
[root@master k8s]# kubectl label pod nginx-test version=1.0 -n dev
pod/nginx-test labeled
# 为pod资源更新标签
[root@master k8s]# kubectl label pod nginx-test version=2.0 -n dev --overwrite
pod/nginx-test labeled
# 查看标签
[root@master k8s]# kubectl get pod nginx-test -n dev --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-test 1/1 Running 0 14m version=2.0
# 筛选标签
[root@master k8s]# kubectl get pod -n dev -l version=2.0 --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-test 1/1 Running 0 15m version=2.0
[root@master k8s]# kubectl get pod -n dev -l version!=2.0 --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-6fdfb7f689-ht222 1/1 Running 0 70m pod-template-hash=6fdfb7f689,run=nginx
# 删除标签
[root@master k8s]# kubectl label pod nginx-test -n dev version-
pod/nginx-test unlabele
配置方式:
apiVersion: v1
kind: Pod
metadata:
name: nginx-test
namespace: dev
labels:
version: "3.0"
env: "test"
spec:
containers:
- image: nginx:1.17.1
imagePullPolicy: IfNotPresent
name: pod
ports:
- name: nginx-port
containerPort: 80
protocol: TCP
然后就可以执行对应的更新命令了: kubectl apply -f pod-labeled-nginx.yml
5.4 Deployment
在Kubernetes中,Pod是最小的控制单元,但是Kubernetes很少直接控制Pod,一般都是通过Pod控制器来完成的。Pod控制器用于Pod的管理,确保Pod资源符合预期的状态,当Pod的资源出现故障时,会尝试进行重启或重建Pod。
在Kubernetes中Pod控制器的种类有很多种,本章节只介绍一种:Deployment。
命令操作:
# 命令格式: kubectl create deployment deployment名称 [参数]
# --image 指定pod的镜像
# --port 指定端口
# --replicas 指定副本数量
# --namespace 指定namespace
[root@master k8s]# kubectl create deploy nginx --image=nginx:1.17.1 --port=80 --replicas=3 --namespace=dev
deployment.apps/nginx created
# 查看创建的pod
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx-78bc4f47cf-6tdr7 1/1 Running 0 24s
nginx-78bc4f47cf-bxwp2 1/1 Running 0 24s
nginx-78bc4f47cf-srm7m 1/1 Running 0 24s
# 查看deployment
[root@master k8s]# kubectl get deploy -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 3/3 3 3 70s
# UP-TO-DATE:成功升级的副本数量
# AVAILABLE:可以副本数量
[root@master k8s]# kubectl get deploy -n dev -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx 3/3 3 3 2m16s nginx nginx:1.17.1 app=nginx
# 查看deployment的详细信息
[root@master k8s]# kubectl describe deploy nginx -n dev
Name: nginx
Namespace: dev
CreationTimestamp: Sat, 08 Jan 2022 21:04:09 +0800
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=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: app=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-78bc4f47cf (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 2m45s deployment-controller Scaled up replica set nginx-78bc4f47cf to 3
# 删除deployment
[root@master k8s]# kubectl delete deploy 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.17.1
name: nginx
ports:
- containerPort: 80
protocol: TCP
然后使用create或apply命令来创建,delete命令来更新即可
5.5 Service
通过上节课的学习,已经能够利用Deployment来创建一组Pod来提供高可用性的服务。
虽然每个Pod都会分配一个单独的IP,然而却存在如下两问题:
- Pod IP会随着Pod的重建发生变化
- Pod IP仅仅是集群内可见的虚拟IP,外部无法访问
这样对于访问这个服务带来了难度。因此,Kubernetes设计了Service来解决这个问题。
Service可以看作是一组同类Pod对外访问的接口。借助Service,应用可以方便地实现负载均衡和服务发现。
操作一:创建集群内部可访问的Service
# 暴露Service,service可以缩写为svc
[root@master k8s]# kubectl expose deploy nginx --name=svc-nginx --type=ClusterIP --port=80 --target-port=80 -n dev
service/svc-nginx exposed
# 查看Service
[root@master k8s]# kubectl get svc -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc-nginx ClusterIP 10.111.174.153 <none> 80/TCP 22s
# 这里产生了一个CLUSTER-IP,这就是Service的IP,在Service的生命周期中,这个地址是不会变的
# 可以通过这个IP访问当前Service对应的Pod
[root@master k8s]# curl 10.111.174.153:80
<!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>
操作二:创建集群外部也可访问的Service
# 上面创建的Service的type类型为ClusterIP,这个IP地址只能集群内部访问
# 如果需要创建外部也可访问的Service,需要修改type为NodePort
[root@master k8s]# kubectl expose deploy nginx --name=svc-nginx1 --type=NodePort --port=80 --target-port=80 -n dev
service/svc-nginx1 exposed
# 此时查看,会发现出现了NodePort类型的Service,而且有一对port(80:30248)
[root@master k8s]# kubectl get svc -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc-nginx ClusterIP 10.111.174.153 <none> 80/TCP 4m58s
svc-nginx1 NodePort 10.99.100.168 <none> 80:30248/TCP 20s
# 接下来就可以通过集群外的主机访问节点IP:30248服务了
# 可以通过任意一个节点的IP:30248来访问
配置方式:
创建一个svc-nginx.yaml的文件,内容如下:
apiVersion: v1
kind: Service
metadata:
name: svc-nginx
namespace: dev
spec:
clusterIP: 10.97.241.231
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
run: nginx
type: ClusterIP
然后就可以执行对应的创建和删除命令了:
创建 kubectl create -f svc-nginx.yaml
删除 kubectl delete -f svc-nginx.yaml
小结:至此,已经学习了Namespace、Pod、Deployment和Service资源的基本操作,有了这些操作,就可以在Kubernetes集群中实现一个简单的服务部署和访问了,但是如果想要更好地使用Kubernetes,就需要深入学习这几种资源地细节和原理。
6、Pod详解
本章节将详细介绍Pod资源的各种配置和原理。
6.1 Pod介绍
6.1.1 Pod结构
每个Pod中都可以包含一个或多个容器,这些容器可以分为两类:
- 用户程序所在的容器,数量可多可少
- Pause容器,这是每个Pod都会有的一个根容器,它的作用有两个:
- 可以以它为凭据,评估整个Pod的健康状态。
- 可以在根容器上设置Ip地址,其它容器使用此IP,以实现Pod内部的网络通信
这里是Pod内部的通讯,Pod之间的通讯采用虚拟二层网络技术来实现,我们当前环境用的是Flannel。
6.1.2 Pod定义
下面是Pod的资源清单:
apiVersion: v1 # 必选,版本号,例如v1
kind: Pod # 必选,资源类型
metadata: # 必选,元数据
name: <string> # 必选,pod名称
namespace: <string> # pod所属命名空间,默认为"default"
labels: # 自定义标签列表
- name: <string>
spec: # 必选,pod中容器的详细定义
containers: # 必选,pod中容器列表
- name: <string> # 必选,容器名称
image: <string> # 必选,容器的镜像名称
imagePullPolicy: [Always|Never|IfNotPresent] # 获取镜像的策略
commange: [<string>] # 容器的启动命令列表,如不指定,使用打包时使用的启动命令
args: [<string>] # 容器的启动命令参数列表
workingDir: <string> # 容器的工作目录
volumeMounts: # 挂载到容器内部的存储卷配置
- name: <string> # 引用pod定义的共享存储卷的名称,需用volumes[]部分定义的卷名
mountPath: <string> # 存储卷在容器内mount的绝对路径,应少于512字符
readOnly: <bool> # 是否为只读模式
ports: # 需要暴露的端口号列表
- name: <string> # 端口的名称
containerPort: # 容器需要监听的端口号
hostPort: <int> # 容器所在主机需要监听的端口号,也就是映射端口,默认与容器相同
protocol: <string> # 端口协议,支持TCP和UDP,默认TCP
env: # 容器运行前需设置的环境变量列表
- name: <string> # key
value: <string> # value
resources: # 资源限制和请求的限制
limits: # 资源限制的设置
cpu: <string> # cpu的限制,单位为core数量,将用于docker run --cpu-shares参数
memory: <string> # 内存限制,单位为M,G,Mi或Gi,将用于docker run --memory参数
requests: # 资源请求的设置
cpu: <string> # cpu请求,容器启动的初始可用数量
memory: <string> # 内存请求,容器启动的初始可用大小
lifecycle: # 生命周期钩子
postStart: # 容器启动后立即执行此钩子,如果执行失败会根据重启策略进行重启
preStop: # 容器终止前执行此钩子,无论结果如何,容器都会终止
livenessProbe: # 对pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器
exec: # 对pod容器内检查方式设置为exec
command: [<string>] # exec方式需要指定的命令或脚本
httpGet: # 对pod内各个容器的健康检查方式设置为httpGet
path: <string>
port: <int>
host: <string>
scheme: <string>
HttpHeaders:
- name: <string>
value: <string>
tcpSocket: # tcpSocket方式
port: <int>
initialDelaySecondes: 0 # 容器启动完成后首次探测的时间,单位s
timeoutSeconds: 0 # 对容器健康检查探测等待响应的超时时间,单位s,默认1s
periodSeconds: 0 # 对容器监控检查的定期探测时间设置,单位s,默认10s一次
successThreshold: 0
failureThreshold: 0
restartPolicy: [Always|Never|OnFailure] # pod的重启策略
nodeName: <string> # 设置nodeName表示将该pod调度到指定名称的node节点上
nodeSelector: <object> # 设置NodeSelector表示将该pod调度到包含这个label的node上
imagePullSecrets: # 拉取镜像时使用的secret名称,以key:secretKey格式指定
- name: <string>
hostNetwork: <bool> # 是否采用主机模式,默认为false,如果设置为true,表示使用宿主机网络
volumes: # 在该pod上定义共享存储卷列表
- name: <string> # 共享存储卷名称
emptyDir: {} # 类型为emptyDir的存储卷,与Pod同生命周期的一个临时目录,为空值。
hostPath: <string> # 类型为hostPath的存储卷,表示挂载pod所在宿主机的目录
path: <string> # pod所在宿主机目录
secret: # 类型为secret的存储卷,挂载集群与定义的secret对象到容器内部
secretName:
items:
- key: <string>
path: <string>
configMap: # 类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
name: <string>
items:
- key: <string>
path: <string>
# 小提示
# 在这里,可以通过一个命令来查看每种资源的可配置项
# kubectl explain 资源类型 查看某种资源可以配置的一级属性
# kubectl explain 资源类型.属性 查看属性的子属性
[root@master k8s]# 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:
[root@master k8s]# kubectl explain pod.metadata
KIND: Pod
VERSION: v1
RESOURCE: metadata <Object>
DESCRIPTION:
Standard object's metadata. More info:
ObjectMeta is metadata that all persisted resources must have, which
includes all objects users must create.
FIELDS:
annotations <map[string]string>
clusterName <string>
creationTimestamp <string>
deletionGracePeriodSeconds <integer>
deletionTimestamp <string>
finalizers <[]string>
generateName <string>
generation <integer>
managedFields <[]Object>
name <string>
namespace <string>
......
在Kubernetes中基本所有的资源的一级属性都是一样的,主要包括5部分:
- apiVersion <string> 版本,由k8s内部定义,版本号可用kubectl api-version 查询到
- kind <string> 类型,由k8s内部定义,版本号可用kubectl api-resources 查询到
- metadata <object> 元数据,主要是资源标识和说明,常用的有name、namespace、labels等
- spec <object> 描述,这是配置中最重要的一部分,里面是对各种资源配置的详细描述
- status <object> 状态信息,里面的内容不需要定义,由k8s自己生成
在上面的属性中,spec是接下来研究的重点,继续看下它的常见子属性:
containers \<[]object\>
容器列表,用于定义容器的详细信息
nodeName \<map\>
根据nodeName的值将pod调度到指定的node节点上
nodeSelector \<string\>
根据nodeSelector中定义的信息选择将该pod调度到包含这些label的node上
hostNetwork \<bool\>
是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
volumes \<[]object\>
存储卷,用于定义pod上面挂载的存储信息
restartPolicy \<string\>
重启策略,表示pod在遇到故障时候的处理策略
6.2 Pod配置
本小节主要来研究pod.spec.containers属性,这也是pod配置中最为关键的一项配置。
[root@master k8s]# 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>
envFrom <[]Object>
image <string>
imagePullPolicy <string>
lifecycle <Object>
livenessProbe <Object>
name <string> -required-
ports <[]Object>
readinessProbe <Object>
resources <Object>
securityContext <Object>
startupProbe <Object>
stdin <boolean>
stdinOnce <boolean>
terminationMessagePath <string>
terminationMessagePolicy <string>
tty <boolean>
volumeDevices <[]Object>
volumeMounts <[]Object>
workingDir <string>
6.2.1 基本配置
创建pod-base.yaml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-base
namespace: dev
labels:
user: mage
spec:
containers:
- name: nginx
image: nginx:1.17.1
- name: busybox
image: busybox:1.30
上面定义了一个比较简单pod的配置,里面有两个容器:
- nginx::1.17.1版本的镜像构建
- busybos:1.30版本的镜像构建,busybox是一个小巧的linux命令集合
# 创建pod
[root@master k8s]# kubectl create -f pod-base.yml
pod/pod-base created
# 查看pod状态
# READY 1/2 : 表示当前pod中有两个容器,其中一个准备就绪,一个未就绪
# RESTARTS : 重启次数,因为有一个容器故障了,pod一直在重启试图恢复它
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pod-base 1/2 CrashLoopBackOff 4 (26s ago) 2m3s
# 可用通过describe查看详情
# 此时已经运行起来了一个基本pod,虽然它暂时有问题
[root@master k8s]# kubectl describe pod pod-base -n dev
6.2.2 镜像拉取
imagePullPolicy用于设置镜像拉取策略,k8s支持三种拉取策略:
- Always:总是从远程仓库拉取镜像
- IfNotPresent:本地有则使用本地的,本地没有就拉取远程仓库的
- Never:只使用本地镜像,从不去远程仓库拉取,本地没有就报错
默认值说明:
如果镜像tag为具体版本号,默认策略是:IfNotPresent 如果镜像tag为:lastest,默认策略是Always
6.2.3 启动命令
在前面的案例中,有一个问题没有解决,就是busybox容器一直没有启动成功,那么到底是什么原因呢?
对Docker比较了解的就会知道,busybox容器在启动时,容器内没有进程在运行,因此Docker就会停止该容器,解决方案就是让其一直运行一个程序,这就可以用command来配置。
创建pod-command.yaml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-command
namespace: dev
labels:
user: mage
spec:
containers:
- name: nginx
image: nginx:1.17.1
- name: busybox
image: busybox:1.30
command: ["/bin/sh", "-c", "touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt;sleep3;done;"]
command,用于在pod中的容器初始化完毕后运行一个命令。
# 创建pod
[root@master k8s]# kubectl create -f pod-command.yml
pod/pod-command created
# 查看pod状态
# 此时发现pod中的两个容器都在运行
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pod-command 2/2 Running 0 17s
# 进入pod中的busybox容器,查看文件内容
# kubectl exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh 在容器内部执行命令
# 使用这个命令就可以进入某个容器的内部,然后进行相关操作了
[root@master k8s]# kubectl exec pod-command -n dev -it -c busybox /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # tail -f /tmp/hello.txt
12:41:34
12:41:34
特别说明:
通过上面发现command已经可以完成启动命令和传递参数的功能,为什么还要提供一个args选项,用于传递参数?其实和Docker有点关系,kubernetes中的command和args两个参数其实是为了实现覆盖Dockerfile中的ENTRYPOINT的功能:
-
如果command和args均没有写,那么用Dockerfile的配置。
-
如果command写了,但是args没有写,那么Dockerfile默认的配置会被忽略,执行注入的command。
-
如果command没有写,但是args写了,那么Dockerfile中配置的ENTRYPOINT命令会被执行,使用当前args的参数。
-
如果command和args都写了,那么Dockerfile中的配置会被忽略,执行command并追加上args参数。
6.2.4 环境变量
创建pod-evn.yaml文件,内容如下
apiVersion: v1
kind: Pod
metadata:
name: pod-env
namespace: dev
labels:
user: xudaxian
spec:
containers:
- name: nginx # 容器名称
image: nginx:1.17.1 # 容器需要的镜像地址
imagePullPolicy: IfNotPresent # 设置镜像拉取策略
- name: busybox # 容器名称
image: busybox:1.30 # 容器需要的镜像地址
command: ["/bin/sh","-c","touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt;sleep 3;done;"]
env:
- name: "username"
value: "admin"
- name: "password"
value: "123456"
env:环境变量,用于在Pod中的容器设置环境变量。
6.2.5 端口设置
创建pod-ports.yaml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-ports
namespace: dev
labels:
user: xudaxian
spec:
containers:
- name: nginx # 容器名称
image: nginx:1.17.1 # 容器需要的镜像地址
imagePullPolicy: IfNotPresent # 设置镜像拉取策略
ports:
- name: nginx-port # 端口名称,如果执行,必须保证name在Pod中是唯一的
containerPort: 80 # 容器要监听的端口 (0~65536)
protocol: TCP # 端口协议
6.2.6 资源配额
-
容器中的程序要运行,肯定会占用一定的资源,比如CPU和内存等,如果不对某个容器的资源做限制,那么它就可能吃掉大量的资源,导致其他的容器无法运行。针对这种情况,kubernetes提供了对内存和CPU的资源进行配额的机制,这种机制主要通过resources选项实现,它有两个子选项:
-
- limits:用于限制运行的容器的最大占用资源,当容器占用资源超过limits时会被终止,并进行重启。
-
- requests:用于设置容器需要的最小资源,如果环境资源不够,容器将无法启动。
-
可以通过上面的两个选项设置资源的上下限。
-
创建pod-resoures.yaml文件,内容如下
apiVersion: v1
kind: Pod
metadata:
name: pod-resoures
namespace: dev
labels:
user: xudaxian
spec:
containers:
- name: nginx # 容器名称
image: nginx:1.17.1 # 容器需要的镜像地址
imagePullPolicy: IfNotPresent # 设置镜像拉取策略
ports: # 端口设置
- name: nginx-port # 端口名称,如果执行,必须保证name在Pod中是唯一的
containerPort: 80 # 容器要监听的端口 (0~65536)
protocol: TCP # 端口协议
resources: # 资源配额
limits: # 限制资源的上限
cpu: "2" # CPU限制,单位是core数
memory: "10Gi" # 内存限制
requests: # 限制资源的下限
cpu: "1" # CPU限制,单位是core数
memory: "10Mi" # 内存限制
- cpu:core数,可以为整数或小数。
- memory:内存大小,可以使用Gi、Mi、G、M等形式。
6.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值的退出状态
- 未知(Unkonwn):apiServer无法正常获取到pod对象的状态信息,通常由网络通信失败导致
6.3.1 创建和终止
pod的创建过程:
- 用户通过kubectl或其它api客户端提交需要创建的pod信息给apiServer
- apiServer开始生成pod对象的信息,并将信息存入etcd,然后返回确认信息至客户端
- apiServer开始反映etcd中的pod对象的变化,其它组件使用watch机制来跟踪检查apiServer上的变动
- schedule发现有新的pod对象需要创建,开始为pod分配主机并将结果信息更新至apiServer
- node节点上的kubelet发现有pod调度过来,尝试调用docker启动容器,并将结果送至apiServer
- apiServer将收到的pod状态信息存入etcd中
pod的终止过程:
- 用户向apiServer发送删除pod对象的命令
- apiServer中的pod对象信息会随着时间的推移而更新,在宽限期内(默认30s),pod被视为dead
- 将pod标记为terminating状态
- kubelet在监控到pod对象转为terminating状态的同时启动pod关闭过程
- 端点控制器监控到pod对象的关闭行为时将其从所有匹配到此端点的service资源的端点列表中移除
- 如果当前pod对象定义了preStop钩子处理器,则在其标记为terminating后即会以同步的方式启动执行
- pod对象中的容器进程收到停止信号
- 宽限期结束后,若pod中还存在仍在运行的进程,那么pod对象会收到立即终止的信号
- kubelet请求apiServer将此pod资源的宽限期设置为0从而完成删除操作,此时pod对于用户已不可见
6.3.2初始化容器
初始化容器是在pod的主容器启动之前要运行的容器,主要做一些容器的前置工作,它具有两大特征:
- 初始化容器必须运行完成直至结束,若初始化容器运行失败,那么k8s需要重启它直到成功完成
- 初始化容器必须按照定义的顺序执行,当且仅当一个成功后,后面的一个才能运行
初始化容器有很多的应用场景,下面列出的两个是最常见的几个:
- 提供主容器镜像中不具备的工具程序或自定义代码
- 初始化容器要先于应用容器串行启动并运行完成,因此可用于延后应用容器的启动直至依赖的条件得到满足
接下来做一个案例,模拟下面这个需求:
假设以主容器运行nginx,但是要求在运行nginx之前要先能够连接上mysql和redis所在服务器
为了简化测试,事先规定好mysql(192.168.226.201)和redis(192.168.226.202)服务器的地址
创建pod-initcontainer.yaml,内容如下:
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.226.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.226.202 -c 1; do echo waiting for redis...;sleep 2;done;']
# 创建pod
[root@master k8s]# kubectl create -f pod-initcontainer.yml
pod/pod-initcontainer created
# 查看pod状态
# 发现pod卡在启动第一个初始化容器过程中,后面的容器不会运行
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pod-initcontainer 0/1 Init:0/2 0 73s
[root@master k8s]# kubectl describe pod pod-initcontainer -n dev
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 99s default-scheduler Successfully assigned dev/pod-initcontainer to node2
Normal Pulled 97s kubelet Container image "busybox:1.30" already present on machine
Normal Created 97s kubelet Created container test-mysql
Normal Started 97s kubelet Started container test-mysql
# 动态查看pod
[root@master k8s]# kubectl get pod pod-initcontainer -n dev -w
NAME READY STATUS RESTARTS AGE
pod-initcontainer 0/1 Init:0/2 0 2m36s
pod-initcontainer 0/1 Init:1/2 0 5m28s
pod-initcontainer 0/1 PodInitializing 0 5m29s
pod-initcontainer 1/1 Running 0 5m30s
# 接下来新开一个shell,为当前服务器新增两个ip,观察pod的变化
[root@master ~]# ifconfig ens33:1 192.168.226.201 netmask 255.255.255.0 up
[root@master ~]# ifconfig ens33:2 192.168.226.202 netmask 255.255.255.0 up
6.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: / # URL地址
port: 80 # 端口号
host: 192.168.226.168 # 主机地址
scheme: HTTP # 支持的协议HTTP或HTTPS
......
接下来以exec的方式为例,演示下钩子函数的使用,创建pod-hook-exec.yaml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-hook-exec
namespace: dev
spec:
containers:
- name: main-container
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
lifecycle:
postStart: # 在容器启动后执行一个命令,修改nginx的默认首页内容
exec:
command: ["/bin/sh", "-c", "echo postStart... > /usr/share/nginx/html/index.html"]
preStop: # 在容器停止前停止nginx服务
exec:
command: ["/user/sbin/nginx", "-s", "quit"]
# 创建pod
[root@master k8s]# kubectl create -f pod-hook-exec.yml
pod/pod-hook-exec created
# 查看pod
[root@master k8s]# 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 57s 10.244.1.26 node1 <none> <none>
# 访问nginx服务
[root@master k8s]# curl 10.244.1.26:80
postStart...
6.3.4 容器探测
容器探测用于检测容器中的实例是否正常工作,是保障业务可用性的一种传统机制。如果经过探测,实例的状态不符合预期,那么k8s就会把问题实例“摘除”,不承担业务流量。k8s提供了两种探针来实现容器探测,分别是:
- 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: / # URL地址
port: 80 # 端口号
host: 192.168.226.168 # 主机地址
scheme: HTTP # 支持的协议HTTP或HTTPS
......
下面以livenessProbe为例,做几个演示:
方式一:Exec
创建pod-liveness-exec.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-liveness-exec
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
exec:
command: ["/bin/cat", "/tmp/hello.txt"] # 执行一个查看文件的命令
# 创建pod
[root@master k8s]# kubectl create -f pod-liveness-exec.yml
pod/pod-liveness-exec created
# 查看pod详情
[root@master k8s]# kubectl describe pod pod-liveness-exec -n dev
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 36s default-scheduler Successfully assigned dev/pod-liveness-exec to node1
Normal Pulled 7s (x2 over 36s) kubelet Container image "nginx:1.17.1" already present on machine
Normal Created 7s (x2 over 36s) kubelet Created container nginx
Normal Started 7s (x2 over 36s) kubelet Started container nginx
Warning Unhealthy 7s (x3 over 27s) kubelet Liveness probe failed: /bin/cat: /tmp/hello.txt: No such file or directory
Normal Killing 7s kubelet Container nginx failed liveness probe, will be restarted
# 观察上面的信息就会发现nginx容器启动之后就进行了健康检查
# 检查失败后,容器被kill掉,然后尝试重启容器(这是重启策略,后面详解)
# 稍等一会后,在观察pod信息,就可以看到RESTARTS不再是0,而是一直增长
[root@master k8s]# kubectl get pod pod-liveness-exec -n dev
NAME READY STATUS RESTARTS AGE
pod-liveness-exec 0/1 CrashLoopBackOff 4 (10s ago) 2m40s
方式二:TCPSocket
创建pod-liveness-tcpsocket.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-liveness-tcpsocket
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
tcpSocket:
port: 8080 # 尝试访问8080端口
# 创建pod
[root@master k8s]# kubectl create -f pod-liveness-tcpsocket.yml
pod/pod-liveness-tcpsocket created
# 查看pod详情
[root@master k8s]# kubectl describe pod pod-liveness-tcpsocket -n dev
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 32s default-scheduler Successfully assigned dev/pod-liveness-tcpsocket to node2
Normal Pulled 3s (x2 over 31s) kubelet Container image "nginx:1.17.1" already present on machine
Normal Created 3s (x2 over 31s) kubelet Created container nginx
Normal Started 3s (x2 over 31s) kubelet Started container nginx
Warning Unhealthy 3s (x3 over 23s) kubelet Liveness probe failed: dial tcp 10.244.2.22:8080: connect: connection refused
Normal Killing 3s kubelet Container nginx failed liveness probe, will be restarted
# 查看pod信息,可以看到已经重启了两次
[root@master k8s]# kubectl get pod pod-liveness-tcpsocket -n dev
NAME READY STATUS RESTARTS AGE
pod-liveness-tcpsocket 1/1 Running 2 (14s ago) 74s
方式三:HTTPGet
创建pod-liveness-httpget.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-liveness-httpget
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 801
livenessProbe:
httpGet:
scheme: HTTP
port: 80
path: /hello
# 创建pod
[root@master k8s]# kubectl create -f pod-liveness-httpget.yml
pod/pod-liveness-httpget created
# 查看pod详情,访问返回的状态码为404
[root@master k8s]# kubectl describe pod pod-liveness-httpget -n dev
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 24s default-scheduler Successfully assigned dev/pod-liveness-httpget to node2
Normal Pulled 23s kubelet Container image "nginx:1.17.1" already present on machine
Normal Created 23s kubelet Created container nginx
Normal Started 23s kubelet Started container nginx
Warning Unhealthy 5s (x2 over 15s) kubelet Liveness probe failed: HTTP probe failed with statuscode: 404
# 查看pod信息
[root@master k8s]# kubectl get pod pod-liveness-httpget -n dev
NAME READY STATUS RESTARTS AGE
pod-liveness-httpget 1/1 Running 2 (7s ago) 67s
至此,已经使用livenessProbe演示了三种探测方式,但是查看livenessProbe的子属性,会发现除了这三种方式,还有一些其它的配置,在这里解释一下:
[root@master k8s]# kubectl explain pod.spec.containers.livenessProbe
FIELDS:
exec <Object>
grpc <Object>
httpGet <Object>
initialDelaySeconds <integer> # 容器启动后等待多少秒执行第一次探测
timeoutSeconds <integer> # 探测超时时间,默认1s,最小1s
periodSeconds <integer> # 执行探测的频率,默认10s,最小1s
failureThreshold <integer> # 连续探测失败多少次才被认定失败。默认3,最小值1
successThreshold <integer> # 连续探测成功多少次才被认定为成功。默认1
tcpSocket <Object>
terminationGracePeriodSeconds <integer>
6.3.5 重启策略
在上一节中,一旦容器探测出现了问题,k8s就会对容器所在的pod进行重启,其实这是由pod的重启策略决定的,pod的重启策略有三种,分别如下:
- Always:容器失效时,自动重启该容器,这也是默认值
- OnFailure:容器终止运行且退出码不为0时重启
- Never:不论状态如何,都不重启该容器
重启策略适用于pod对象中所有的容器,首次需要重启的容器,将在需要时立即重启,随后再次需要重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长为10s、20s、40s、80s、160s和300s,300s是最大延迟时长。
创建pod-restartPolicy.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-restartpolicy
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
httpGet:
path: /hello
scheme: HTTP
port: 80
restartPolicy: Never # 设置重启策略为Never
# 创建pod
[root@master k8s]# kubectl create -f pod-restartPolicy.yml
pod/pod-restartpolicy create
# 查看pod详情
[root@master k8s]# kubectl describe pod pod-restartpolicy -n dev
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 25s default-scheduler Successfully assigned dev/pod-restartpolicy to node1
Normal Pulled 25s kubelet Container image "nginx:1.17.1" already present on machine
Normal Created 25s kubelet Created container nginx
Normal Started 25s kubelet Started container nginx
Warning Unhealthy 6s (x2 over 16s) kubelet Liveness probe failed: HTTP probe failed with statuscode: 404
# 查看pod信息,发现STATUS为Completed,多次查看发现RESTARTS也一直是0
[root@master k8s]# kubectl get pod pod-restartpolicy -n dev
NAME READY STATUS RESTARTS AGE
pod-restartpolicy 0/1 Completed 0 43s
6.4 Pod调度
在默认情况下,一个Pod在哪个节点上运行,是由Scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的。但是在实际使用中,这并不能满足需求,因为很多情况下,我们想控制某些Pod到某些节点上,那么应该怎么做呢?这就要了解K8S对Pod的调度规则,K8S提供了四大调度方式:
- 自动调度:运行在哪个节点上完全由Scheduler经过一系列的算法计算得出
- 定向调度:NodeName、NodeSelector
- 亲和性调度:NodeAffinity、PodAffinity、PodAntiAffinity
- 污点(容忍)调度:Taints、Toleration
6.4.1 定向调度
定向调度,指定是利用在pod上声明NodeName或者NodeSelector,以此将Pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使要调度的目标node不存在,也会向上面进行调度,只不过pod运行失败而已。
NodeName
NodeName用于强制约束将Pod调度到指定Name的Node节点上。这种方式,其实是直接跳过Scheduler的调度逻辑,直接将Pod调度到指定名称的节点上。
接下来,实验一些,创建pod-nodename.yaml文件:
apiVersion: v1
kind: Pod
metadata:
name: pod-nodename
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- name: nginx-port
containerPort: 80
nodeName: node1
# 创建pod
[root@master k8s]# kubectl create -f pod-nodename.yml
pod/pod-nodename created
# 查看pod信息,确实调度到了node1节点上
[root@master k8s]# 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 53s 10.244.1.29 node1 <none> <none>
# 接下来删除pod,然后将yml中的nodeName改为node3(并没有node3节点)
# 发现pod被调度到了node3,但是由于没有node3,所以pod是挂起状态
kubectl delete -f pod-nodename.yml
kubectl create -f pod-nodename.yml
[root@master k8s]# 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 17s <none> node3 <none> <none>
NodeSelector
NodeSelector用于将pod调度到添加了指定标签的node节点上。它是通过K8S的label-selector机制实现的,也就是说,在pod创建之前,会由Scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后将node调度到目标节点,该匹配规则是强制约束。
接下来实验一下:
1.首先分别为node节点添加标签
kubectl label node node1 nodeenv=pro
kubectl label node node2 nodeenv=test
2.创建pod-nodeselector.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeselector
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeSelector:
nodeenv: pro # 指定pod调度到具有nodeenv=pro标签的节点上
# 创建pod
[root@master k8s]# kubectl create -f pod-nodeselector.yml
pod/pod-nodeselector created
# 查看pod信息,pod被调度到了node1上
[root@master k8s]# 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 26s 10.244.1.30 node1 <none> <none>
6.4.2 亲和性调度
上一节,介绍了两种定向调度的方式,使用起来非常方便,但是也有一定的问题,那就是如果没有满足条件的Node,那么Pod将不会被运行,即使在集群中还有可用Node列表也不行,这就限制了它的使用场景。
基于上面的问题,K8S还提供了一种亲和性调度(Affinity)。它在NodeSelector的基础之上进行了扩展,可用通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足条件的节点上,使调度更加灵活。
Affinity注意分为三类:
- nodeAffinity(node亲和性):以node为目标,解决pod可以调度到哪些node的问题
- podAffinity(pod亲和性):以pod为目标,解决pod可以和哪些已存在的pod部署在同一个node中的问题。
- podAntiAffinity(pod反亲和性):以pod为目标,解决pod不能和哪些已存在pod部署在同一个node中的问题。
关于亲和性和反亲和性使用场景的说明:
亲和性:如果两个应用频繁交互,那就有必要利用亲和性让两个应用尽可能的靠近,这样可以减少因网络通信而带来的性能损耗。
反亲和性:当应用采用多副本部署时,有必要采用反亲和性让各个应用实例打散分布在各个node上,这样可以提高服务的高可用性。
NodeAffinity
首先来看一下NodeAffinity的可配置项:
pod.spec.affinity.nodeAffinity
requiredDuringSchedulingIgnoredDuringExecution: # Node节点必须满足指定的所有规则才可以,相当于硬限制
nodeSelectorTerms: # 节点选择列表
matchFields: # 按节点字段列出的节点选择器要求列表
matchExpressions: # 按节点标签列出的节点选择器要求列表(推荐)
key: # 键
values: # 值
operator: # 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
preferredDuringSchedulingIgnoredDuringExecution: # 优先调度到满足指定的规则的Node,相当于软限制 (倾向)
preference: # 一个节点选择器项,与相应的权重相关联
matchFields: # 按节点字段列出的节点选择器要求列表
matchExpressions: # 按节点标签列出的节点选择器要求列表(推荐)
key: # 键
values: # 值
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.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity: # 亲和性设置
nodeAffinity: # node亲和性设置
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions: # 配置env的值在["xxx", "yyy"]中的标签
- key: nodeenv
operator: In
values: ["xxx", "yyy"]
# 创建pod
[root@master k8s]# kubectl create -f pod-nodeaffinity-required.yaml
pod/pod-nodeaffinity created
# 查看pod状态(运行失败)
[root@master k8s]# 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 5s <none> <none> <none> <none>
# 查看pod详情
[root@master k8s]# kubectl describe pod pod-nodeaffinity-required -n dev
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 40s default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match Pod's node affinity/selector.
# 接下来删除该pod
# 将配置文件中的values改为["pro", "yyy"],再次运行
kubectl delete -f pod-nodeaffinity-required.yaml
kubectl create -f pod-nodeaffinity-required.yaml
# 然后查看pod状态,发现在node1上运行
[root@master k8s]# 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 30s 10.244.1.31 node1 <none> <none>
接下来在演示一下preferredDuringSchedulingIgnoredDuringExecution
创建pod-nodeaffinity-preferred.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity-preferred
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity: # 亲和性设置
nodeAffinity: # 设置node亲和性
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: nodeenv
operator: In
values: ["xxx", "yyy"]
# 创建pod
[root@master k8s]# kubectl create -f pod-nodeaffinity-preferred.yaml
pod/pod-nodeaffinity-preferred created
# 查看pod状态(运行成功)
[root@master k8s]# 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 31s 10.244.2.24 node2 <none> <none>
NodeAffinity规则设置的注意事项:
- 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能运行在指定的Node上
- 如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可
- 如果一个nodeSelectorTerms中有多个matchExpression,则一个节点必须满足所有的才能匹配成功
- 如果一个pod所在的node在pod运行期间其标签发生了改变,不再符合该pod节点亲和性需求,则系统将忽略此变化
PodAffinity
PodAffinity主要实现以一个运行的Pod为参照,让新创建的Pod跟参照pod在一个拓扑域中的功能。
首先来看一下PodAffinity的可配置项:
pod.spec.affinity.podAffinity
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
namespaces: # 指定参照pod的namespace
topologyKey: # 指定调度作用域
labelSelector: # 标签选择器
matchExpressions: # 按节点标签列出的节点选择器要求列表(推荐)
key: # 键
values: # 值
operator: # 关系符 支持In, NotIn, Exists, DoesNotExist.
matchLabels: # 指多个matchExpressions映射的内容
preferredDuringSchedulingIgnoredDuringExecution: # 软限制
podAffinityTerm: # 选项
namespaces:
topologyKey:
labelSelector:
matchExpressions:
key: # 键
values: # 值
operator:
matchLabels:
weight: # 倾向权重,在范围1-1
topologyKey用于指定调度时作用域,例如:
如果指定kubernetes.io/hostname,那就是以Node节点为区分范围
如果指定beta.kubernetes.io/os,则是以Node节点的操作系统类型来区分
接下来演示一下requiredDuringSchedulingIgnoredDuringExecution
1)首先创建一个参照Pod,pod-podaffinity-target.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-podaffinity-target
namespace: dev
labels:
podenv: pro # 设置标签
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeName: node1 # 将目标pod明确指定到node1上
# 启动目标pod
[root@master k8s]# kubectl create -f pod-podaffinity-target.yaml
pod/pod-podaffinity-target created
# 查看pod状况
[root@master k8s]# 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 19s 10.244.1.32 node1 <none> <none>
2)创建pod-podaffinity-required.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-podaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: podenv
operator: In
values: ["xxx", "yyy"]
topologyKey: kubernetes.io/hostname
上面配置表达的意思是:新pod必须要与拥有标签podenv=xxx或podenv=yyy的pod在 同一node上,显然现在没有这样的pod,接下来运行测试一下:
# 创建pod
[root@master k8s]# kubectl create -f pod-podaffinity-required.yaml
pod/pod-podaffinity-required created
# 查看pod状态,发现未运行
[root@master k8s]# kubectl get pod pod-podaffinity-required -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-podaffinity-required 0/1 Pending 0 24s <none> <none> <none> <none>
# 查看详细信息
[root@master k8s]# kubectl describe pod pod-podaffinity-required -n dev
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 76s default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match pod affinity rules.
Warning FailedScheduling 10s default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match pod affinity rules.
# 接下来删除该pod将values改为["pro", "yyy"],再次运行
kubectl delete -f pod-podaffinity-required.yaml
kubectl create -f pod-podaffinity-required.yaml
# 查看pod状态,发现运行在node1上
[root@master k8s]# kubectl get pod pod-podaffinity-required -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-podaffinity-required 1/1 Running 0 22s 10.244.1.33 node1 <none> <none>
PodAntiAffinity
PodAntiAffinity主要实现以运行的pod为参照,让新创建的pod跟参照pod不在一个区域的功能。
它的配置方式和选项与PodAffinity是一样的,在这里不做详细解释,直接做一个测试案例:
1)继续使用上个案例中的目标pod
2)创建pod-podantiaffinity-required.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-podantiaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: podenv
operator: In
values: ["pro"]
topologyKey: kubernetes.io/hostname
上面配置表达的意思是:新建pod必须要与拥有标签podenv=pro的pod不在同一个node上。
# 创建pod
[root@master k8s]# kubectl create -f pod-podantiaffinity-required.yaml
pod/pod-podantiaffinity-required created
# 查看pod状态,发现pod运行在node2上
[root@master k8s]# kubectl get pod pod-podantiaffinity-required -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-podantiaffinity-required 1/1 Running 0 27s 10.244.2.25 node2 <none> <none>
6.4.3 污点和容忍
污点(Taints)
前面的调度方式都是站在pod的角度上,通过在pod上添加属性,来确定pod是否要调度到指定的node上,其实我们也可以站在node的角度上,通过在node上添加`污点`属性,来决定是否允许pod调度过来。
node被设置上污点之后就和pod之间存在了一种相斥的关系,进而拒绝pod调度进来,甚至可以将已存在的pod驱逐出去。
污点的格式为:key=value:effect
,key和value是污点的标签,effect描述污点的作用,支持下面三个选项:
- PreferNoSchedule:k8s将尽量避免把pod调度到具有该污点的node上,除非没有其它节点可调度
- NoSchedule:k8s将不会把pod调度到具有该污点的node上,但不会影响当前node上已存在的pod
- NoExecute:k8s将不会把pod调度到具有该污点的node上,同时也会将node上已存在的pod驱离
使用kubectl设置和去除污点的命令如下:
# 设置污点
kubectl taint node node1 key=value:effect
# 去除污点
kubectl taint node node1 key:effect-
# 去除所有污点
kubectl taint node node1 key-
接下来演示一下污点的效果:
- 准备节点node1(为了演示效果更加明显,暂时停止node2节点)
- 为node1节点设置一个污点:tag=wudian:PreferNoSchedule,然后创建pod1
- 修改node1节点设置一个污点:tag=wudian:NoSchedule,然后创建pod2
- 修改node1节点设置一个污点:tag=wudian:NoExecute,然后创建pod3
# 关闭node2虚拟机
# 查看集群节点
[root@master k8s]# kubectl get node
NAME STATUS ROLES AGE VERSION
master Ready control-plane,master 3d22h v1.23.1
node1 Ready <none> 3d22h v1.23.1
node2 NotReady <none> 3d22h v1.23.1
# 给node1设置污点
[root@master k8s]# kubectl taint node node1 tag=wudian:PreferNoSchedule
node/node1 tainted
# 创建pod1
[root@master k8s]# kubectl run nginx --image=nginx -n dev
pod/nginx created
# 查看pod状态
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 31s
# 为node1修改污点为NoSchedule
[root@master k8s]# kubectl taint node node1 tag:PreferNoSchedule-
node/node1 untainted
[root@master k8s]# kubectl taint node node1 tag=wudian:NoSchedule
node/node1 tainted
# 创建pod2
[root@master k8s]# kubectl run nginx2 --image=nginx:1.17.1 -n dev
pod/nginx2 created
# 查看pod状态,发现nginx2是挂起状态
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 2m34s
nginx2 0/1 Pending 0 11s
# 查看pod2的详细信息
[root@master k8s]# kubectl describe pod nginx2 -n dev
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 45s default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 1 node(s) had taint {node.kubernetes.io/unreachable: }, that the pod didn't tolerate, 1 node(s) had taint {tag: wudian}, that the pod didn't tolerate.
# 为node1修改污点为NoExecute
[root@master k8s]# kubectl taint node node1 tag:NoSchedule-
node/node1 untainted
[root@master k8s]# kubectl taint node node1 tag=wudian:NoExecute
node/node1 tainted
# 启动pod3
[root@master k8s]# kubectl run nginx3 --image=nginx:1.17.1 -n dev
pod/nginx3 created
# 查看pod状态,三个pod都没有运行
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx3 0/1 Pending 0 75s
小提示:
使用kubeadm搭建的集群,默认就会为master节点添加一个污点标记,所以pod就不会调度到该节点上
容忍(Toleration)
上面介绍了污点的作用,我们可以在node上添加污点用于拒绝pod调度上来,但是如果就是想将一个pod调度到一个有污点的node上去,这时候应该怎么做呢?这就要用到`容忍`。
污点就是拒绝,容忍就是忽略,Node通过污点拒绝pod调度上去,pod通过容忍忽略拒绝
下面先通过一个案例看下效果:
- 上一节已经在node1节点上打上了NoExecute的污点,此时pod是调度不上去的
- 本小节,可以通过给pod添加容忍,然后将其调度上去
创建pod-toleration.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-toleration
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
tolerations: # 添加容忍
- key: "tag" # 要添加的污点的key
operator: "Equal" # 操作符
value: "wudian" # 容忍的污点的value
effect: "NoExecute" # 添加容忍的规则,这里必须和标记的污点规则相同
# 创建pod
[root@master k8s]# kubectl create -f pod-toleration.yaml
pod/pod-toleration created
# 查看pod状态
[root@master k8s]# 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 23s 10.244.1.36 node1 <none> <none>
下面看一下容忍的详细配置:
[root@master k8s]# kubectl explain pod.spec.tolerations
FIELDS:
effect <string> # 对应污点的effect,空值意味着匹配所有影响
key <string> # 对应要容忍的污点的键,空意味着匹配所有的键
operator <string> # key-value的运算符,支持Equal和Exists(默认)
tolerationSeconds <integer> # 容忍时间,当effect为NoExecute时生效,表示pod在node上的停留时间
value <string> # 对应要容忍的污点的值
7、Pod控制器
本章节主要介绍各种Pod控制器的详细使用。
7.1 Pod控制器介绍
在Kubernetes中,按照Pod的创建方式可以将其分为两类:
- 自主式Pod:K8S直接创建出来的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
:管理有状态应用
7.2 ReplicaSet(RS)
ReplicaSet的主要作用是**保证一定数量的Pod能够正常运行**,它会持续监听这些Pod的运行状态,一旦Pod发生故障,就会重启或重建。同时它还支持对**Pod数量的扩缩容**和**镜像版本的升级**。
ReplicaSet的资源清单:
apiVersion: apps/v1 # 版本号
kind: ReplicaSet # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: # 标签
controller: rs
spec:
replicas: 3 # Pod副本数量
selector: # 选择器,通过它指定该控制器管理哪些Pod
matchLabels: # Labels匹配规则
app: nginx-pod
matchExpressions:
- {key: app, operator: In, values: [nginx-pod]}
template: # 模板,当副本数量不足时会根据下面的模板创建Pod副本
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
prots:
- containerPort: 80
在这里面,需要了解的配置项就是spec下面几个选项:
- replicas:指定副本数量,其实就是当前rs创建出来的Pod的数量,默认为1
- selector:选择器,它的作用是建立Pod控制器和Pod之间的关联关系,采用的是Label Selector机制,在Pod模板上 定义Label,在控制器上定义选择器,就可以表明当前控制器能管理哪些Pod了
- template:模板,就是当前控制器创建Pod使用的模板,里面其实就是前一章学过的Pod的定义
创建ReplicaSet
创建pc-replicaset.yaml:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: pc-replicaset
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
# 创建rs
[root@master k8s]# kubectl create -f pc-replicaset.yaml
replicaset.apps/pc-replicaset created
# 查看rs
# DESIRED:期望副本数量
# CURRENT:当前副本数量
# READY:已经准备好提供服务的副本数量
[root@master k8s]# kubectl get rs pc-replicaset -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-replicaset 3 3 3 26s nginx nginx:1.17.1 app=nginx-pod
# 查看当前控制器创建的Pod
# 这里发现控制器创建出来的Pod的名称是在控制器名称后面拼接了-xxxxx随机码
[root@master k8s]# kubectl get pod -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pc-replicaset-h72ms 1/1 Running 0 2m27s 10.244.2.29 node2 <none> <none>
pc-replicaset-w7vdw 1/1 Running 0 2m27s 10.244.2.27 node2 <none> <none>
pc-replicaset-xgc2w 1/1 Running 0 2m27s 10.244.2.28 node2 <none> <none>
扩缩容
# 1.编辑rs的副本数量,修改spec:replicas:6,然后wq保存即可
[root@master k8s]# kubectl edit rs pc-replicaset -n dev
replicaset.apps/pc-replicaset edited
# 查看rs
[root@master k8s]# kubectl get rs pc-replicaset -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-replicaset 6 6 6 4m23s nginx nginx:1.17.1 app=nginx-pod
# 2.通过命令实现
# 使用scale命令实现扩缩容,后面--replicas=n直接指定目标数量即可
[root@master k8s]# kubectl scale rs pc-replicaset -n dev --replicas=2
replicaset.apps/pc-replicaset scaled
# 查看rs
[root@master k8s]# kubectl get rs pc-replicaset -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-replicaset 2 2 2 6m nginx nginx:1.17.1 app=nginx-pod
镜像升级
# 1.编辑rs的容器镜像为nginx:1.17.2
[root@master k8s]# kubectl edit rs pc-replicaset -n dev
replicaset.apps/pc-replicaset edited
# 查看rs
[root@master k8s]# kubectl get rs pc-replicaset -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-replicaset 2 2 2 7m46s nginx nginx:1.17.2 app=nginx-pod
# 2.使用命令来修改镜像
# kubectl set image rs rs名称 容器=镜像版本 -n namespace
[root@master k8s]# kubectl set image rs pc-replicaset nginx=nginx:1.17.1 -n dev
replicaset.apps/pc-replicaset image updated
# 查看rs,发现镜像变为了1.17.1
[root@master k8s]# kubectl get rs pc-replicaset -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-replicaset 2 2 2 9m14s nginx nginx:1.17.1 app=nginx-pod
删除ReplicaSet
# 使用kubectl delete命令会删除此rs以及它管理的Pod
# 在kubernetes删除rs前,会将rs的replicas调整为0,等待所有的pod被删除后,再执行rs对象的删除
[root@master k8s]# kubectl delete rs pc-replicaset -n dev
replicaset.apps "pc-replicaset" deleted
# 如果希望仅仅删除rs对象(保留pod),可以使用kubectl delete 命令时添加--cascade=false选项(不推荐使用)
kubectl delete rs pc-replicaset -n dev -cascade=false
# 也可以使用yaml直接删除
kubectl delelte -f pc-replicaset.yaml
7.3 Deployment(Deploy)
为了更好的解决服务编排问题,kubernetes在v1.2版本开始,引入了Deployment控制器。值得一提的是,这种控制器并不直接管理Pod,而是通过管理ReplicaSet来间接管理Pod,即:Deployment管理ReplicaSet,ReplicaSet管理Pod。所以Deployment比ReplicaSet功能更加强大。
Deployment主要功能有下面几个:
- 支持ReplicaSet的所有功能
- 支持发布的停止、继续
- 支持版本滚动更新和版本回退
Deployment的资源清单:
apiVersion: apps/v1 # 版本号
kind: Deployment # 类型
metadata: # 元数据
name: # rs名称
namespace: # rs所属命名空间
labels: # 标签
controller: deploy
spec:
replicas: 3 # 副本数量
revisionHistoryLimit: 3 # 保留历史版本数量,默认10
paused: false # 暂停部署,默认false
progressDeadlineSeconds: 600 # 部署时间(s),默认600
strategy: # 策略
type: RollingUpdata # 滚动更新策略
rollingUpdate: # 滚动更新
maxSurge: 30% # 最大额外可以存在的副本数,可以为百分比或整数
maxUnavailable: 30% # 最大不可用状态的Pod的最大值,可以为百分比或整数
selector: # 选择器
matchLabels: # labels匹配规则
app: nginx-pod
matchExpressions:
- {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
创建Deployment
创建pc-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pc-deployment
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
# 创建deployment
# --record=true 记录每次版本的变化,在新版本被废弃了
[root@master k8s]# kubectl create -f pc-deployment.yaml --record=true
Flag --record has been deprecated, --record will be removed in the future
deployment.apps/pc-deployment created
# 查看deployment
# UP-TO-DATE:最新版本的pod的数量
# AVAILABLE:当前可用的pod数量
[root@master k8s]# kubectl get deploy pc-deployment -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
pc-deployment 3/3 3 3 2m26s
# 查看rs
# 发现rs的名称是在deployment的名字后面添加了一个10位数的随机串
[root@master k8s]# kubectl get rs -n dev
NAME DESIRED CURRENT READY AGE
pc-deployment-6f7f65b46d 3 3 3 3m19s
# 查看pod
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-deployment-6f7f65b46d-4sgxp 1/1 Running 0 3m37s
pc-deployment-6f7f65b46d-qvrh8 1/1 Running 0 3m37s
pc-deployment-6f7f65b46d-rb8xh 1/1 Running 0 3m38s
扩缩容
# 变更副本数量为5个
[root@master k8s]# kubectl scale deploy pc-deployment --replicas=5 -n dev
deployment.apps/pc-deployment scaled
# 查看deployment
[root@master k8s]# kubectl get deploy pc-deployment -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
pc-deployment 5/5 5 5 4m56s
# 查看pod
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-deployment-6f7f65b46d-4sgxp 1/1 Running 0 5m13s
pc-deployment-6f7f65b46d-6crdw 1/1 Running 0 40s
pc-deployment-6f7f65b46d-7lghr 1/1 Running 0 40s
pc-deployment-6f7f65b46d-qvrh8 1/1 Running 0 5m13s
pc-deployment-6f7f65b46d-rb8xh 1/1 Running 0 5m14s
# 通过编辑改变pod副本数量为3个
[root@master k8s]# kubectl edit deploy pc-deployment -n dev
deployment.apps/pc-deployment edited
# 查看pod
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-deployment-6f7f65b46d-4sgxp 1/1 Running 0 6m16s
pc-deployment-6f7f65b46d-qvrh8 1/1 Running 0 6m16s
pc-deployment-6f7f65b46d-rb8xh 1/1 Running 0 6m17s
镜像更新
Deployment支持两种镜像更新策略:重建更新
和滚动更新(默认)
,可用通过strategy进行配置。
strategy: # 指定新的Pod替换旧的Pod的策略,支持两个属性:
type: # 指定策略类型,支持两种策略
Recreate: # 重建更新,在创建出新的Pod之前会先杀掉所有已存在的Pod
RollingUpdate: # 滚动更新,就是杀死一部分,就启动一部分,在更新过程中,存在两个版本的Pod
rollingUpdate: # 当type为RollingUpdate时生效,用于为RollingUpdate设置参数,支持两个属性
maxUnavailable: # 用来指定在升级过程中不可用Pod的最大数量,默认为25%
maxSurge: # 用来指定在升级过程中可用超过期望的Pod的最大数量,默认为25%
重建更新:
1)编辑pc-deployment.yaml,在spec节点下添加更新策略
spec:
strategy:
type: Recreate
2)创建deploy进行验证
# 应用新的yaml
[root@master k8s]# kubectl apply -f pc-deployment.yaml
deployment.apps/pc-deployment configured
# 先打开另一个shell窗口,动态观察更新过程
kubectl get pod -n dev -w
# 变更镜像
[root@master k8s]# kubectl set image deploy pc-deployment nginx=nginx:1.17.2 -n dev
deployment.apps/pc-deployment image updated
# 查看pod的更新过程,先是将三个Pod全部停止,然后再启动新的Pod
[root@master ~]# kubectl get pod -n dev -w
NAME READY STATUS RESTARTS AGE
pc-deployment-6f7f65b46d-4sgxp 1/1 Running 0 17m
pc-deployment-6f7f65b46d-qvrh8 1/1 Running 0 17m
pc-deployment-6f7f65b46d-rb8xh 1/1 Running 0 17m
pc-deployment-6f7f65b46d-rb8xh 1/1 Terminating 0 17m
pc-deployment-6f7f65b46d-qvrh8 1/1 Terminating 0 17m
pc-deployment-6f7f65b46d-4sgxp 1/1 Terminating 0 17m
pc-deployment-6f7f65b46d-rb8xh 0/1 Terminating 0 17m
pc-deployment-6f7f65b46d-rb8xh 0/1 Terminating 0 17m
pc-deployment-6f7f65b46d-rb8xh 0/1 Terminating 0 17m
pc-deployment-86f4996797-5ltvh 0/1 Pending 0 0s
pc-deployment-86f4996797-5ltvh 0/1 Pending 0 0s
pc-deployment-86f4996797-p8kch 0/1 Pending 0 0s
pc-deployment-86f4996797-5ltvh 0/1 ContainerCreating 0 1s
pc-deployment-86f4996797-xtfhp 0/1 ContainerCreating 0 2s
pc-deployment-86f4996797-p8kch 0/1 ContainerCreating 0 2s
pc-deployment-86f4996797-5ltvh 1/1 Running 0 22s
pc-deployment-86f4996797-p8kch 1/1 Running 0 23s
pc-deployment-86f4996797-xtfhp 1/1 Running 0 38s
滚动更新:
1)编辑pc-deployment.yaml,修改spec节点下的属性:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
2)创建deploy进行验证
# 应用修改后的yaml
[root@master k8s]# kubectl apply -f pc-deployment.yaml
deployment.apps/pc-deployment configured
# 先打开另一个shell窗口,动态观察更新过程
kubectl get pod -n dev -w
# 变更镜像
[root@master k8s]# kubectl set image deploy pc-deployment nginx=nginx:1.17.3 -n dev
deployment.apps/pc-deployment image updated
# 查看pod的更新过程,可以看到,先启动了新的Pod,然后杀死一个Pod,然后再这样,直到所有的Pod更新完成
[root@master ~]# kubectl get pod -n dev -w
NAME READY STATUS RESTARTS AGE
pc-deployment-6f7f65b46d-2hqs5 1/1 Running 0 46s
pc-deployment-6f7f65b46d-6kpzr 1/1 Running 0 45s
pc-deployment-6f7f65b46d-9nvbj 1/1 Running 0 48s
pc-deployment-79f7d88458-r64zr 0/1 Pending 0 0s
pc-deployment-79f7d88458-r64zr 0/1 ContainerCreating 0 0s
pc-deployment-79f7d88458-r64zr 1/1 Running 0 24s
pc-deployment-6f7f65b46d-9nvbj 1/1 Terminating 0 79s
pc-deployment-79f7d88458-b2zd9 0/1 Pending 0 0s
pc-deployment-79f7d88458-b2zd9 0/1 ContainerCreating 0 0s
pc-deployment-6f7f65b46d-9nvbj 0/1 Terminating 0 80s
pc-deployment-79f7d88458-b2zd9 1/1 Running 0 1s
pc-deployment-6f7f65b46d-2hqs5 1/1 Terminating 0 78s
pc-deployment-79f7d88458-n2sx6 0/1 Pending 0 0s
pc-deployment-79f7d88458-n2sx6 0/1 ContainerCreating 0 0s
pc-deployment-79f7d88458-n2sx6 1/1 Running 0 1s
pc-deployment-6f7f65b46d-2hqs5 0/1 Terminating 0 79s
pc-deployment-6f7f65b46d-6kpzr 1/1 Terminating 0 78s
滚动更新的过程:
镜像更新中rs的变化:
# 查看rs,发现原来的rs依旧存在,只是Pod的数量变为了0,而后又产生了一个新的rs,Pod数量为3
# 其实这就是deployment能够进行版本回退的奥妙所在,后面会详细解释
[root@master k8s]# kubectl get rs -n dev
NAME DESIRED CURRENT READY AGE
pc-deployment-6f7f65b46d 0 0 0 30m
pc-deployment-79f7d88458 3 3 3 7m16s
pc-deployment-86f4996797 0 0 0 13m
版本回退
deployment支持版本升级过程中的暂停、继续功能以及版本回退等诸多功能,下面具体来看:
kubectl rollout:版本升级相关功能,支持下面的选项:
- status 显示当前升级状态
- history 显示升级历史记录
- pause 暂停版本升级过程
- resume 继续已经暂停的版本升级过程
- restart 重启版本升级过程
- undo 回滚到上一版本(可以使用–to-revision回滚到指定版本)
# 查看当前升级状态
[root@master k8s]# kubectl rollout status deploy pc-deployment -n dev
deployment "pc-deployment" successfully rolled out
# 查看升级历史记录
[root@master k8s]# kubectl rollout history deploy pc-deployment -n dev
deployment.apps/pc-deployment
REVISION CHANGE-CAUSE
2 kubectl create --filename=pc-deployment.yaml --record=true
3 kubectl create --filename=pc-deployment.yaml --record=true
4 kubectl create --filename=pc-deployment.yaml --record=true
# 可以发现有三次版本记录,说明完成升级过两次
# 版本回滚
# 这里直接使用--to-revision回滚到版本2,如果省略这个选项,就是回退到上个版本
# 先查看一下nginx版本
[root@master k8s]# kubectl get deploy pc-deployment -n dev -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
pc-deployment 3/3 3 3 36m nginx nginx:1.17.3 app=nginx-pod
# 回滚
[root@master k8s]# kubectl rollout undo deploy pc-deployment --to-revision=2 -n dev
deployment.apps/pc-deployment rolled back
# 再次查看版本
[root@master k8s]# kubectl get deploy pc-deployment -n dev -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
pc-deployment 3/3 3 3 37m nginx nginx:1.17.2 app=nginx-pod
# 查看rs,发现第三个rs中有三个Pod运行
# 其实deployment之所以可以实现版本的回滚,就是通过记录下rs来实现的
# 一旦想回滚到哪个版本,只需要将当前版本pod数量降为0,然后将回滚版本的Pod提升为目标数量就可以了
[root@master k8s]# kubectl get rs -n dev
NAME DESIRED CURRENT READY AGE
pc-deployment-6f7f65b46d 0 0 0 38m
pc-deployment-79f7d88458 0 0 0 15m
pc-deployment-86f4996797 3 3 3 20m
金丝雀发布
Deployment支持更新过程中的控制,如“暂停”或“继续”更新操作。
比如有一批新的Pod资源创建完成后立即暂停更新过程,此时,仅存在一部分新版本的应用,主体部分还是旧版本。然后再筛选一小部分的用户请求路由到新版本的Pod应用,继续观察能否稳定地按期望的方式运行。确定没问题后再继续完成余下的Pod资源滚动更新,否则立即回滚更新操作。这就是所谓的金丝雀发布。
# 更新deployment的版本,并配置暂停deployment
[root@master k8s]# kubectl set image deploy pc-deployment nginx=nginx:1.17.4 -n dev && kubectl rollout pause deploy pc-deployment -n dev
deployment.apps/pc-deployment image updated
deployment.apps/pc-deployment paused
# 观察更新状态
[root@master k8s]# kubectl rollout status deploy pc-deployment -n dev
Waiting for deployment "pc-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
# 监控更新的过程,可以看到已经新增了一个资源,但是并未按照预期的状态去删除一个旧的资源,就是因为使用了pause暂停命令
[root@master k8s]# kubectl get rs -n dev
NAME DESIRED CURRENT READY AGE
pc-deployment-6f7f65b46d 0 0 0 46m
pc-deployment-79f7d88458 0 0 0 22m
pc-deployment-86f4996797 3 3 3 28m
pc-deployment-cf7c57879 1 1 1 103s
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-deployment-86f4996797-64m7h 1/1 Running 0 9m
pc-deployment-86f4996797-bgq8z 1/1 Running 0 9m3s
pc-deployment-86f4996797-swghv 1/1 Running 0 9m1s
pc-deployment-cf7c57879-w5m8v 1/1 Running 0 2m5s
# 确保更新的pod没问题了,继续更新
[root@master k8s]# kubectl rollout resume deploy pc-deployment -n dev
deployment.apps/pc-deployment resumed
# 查看最后的更新情况
[root@master k8s]# kubectl get rs -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-deployment-6f7f65b46d 0 0 0 48m nginx nginx:1.17.1 app=nginx-pod,pod-template-hash=6f7f65b46d
pc-deployment-79f7d88458 0 0 0 24m nginx nginx:1.17.3 app=nginx-pod,pod-template-hash=79f7d88458
pc-deployment-86f4996797 0 0 0 30m nginx nginx:1.17.2 app=nginx-pod,pod-template-hash=86f4996797
pc-deployment-cf7c57879 3 3 3 3m42s nginx nginx:1.17.4 app=nginx-pod,pod-template-hash=cf7c57879
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-deployment-cf7c57879-f77gw 1/1 Running 0 87s
pc-deployment-cf7c57879-r7x2f 1/1 Running 0 89s
pc-deployment-cf7c57879-w5m8v 1/1 Running 0 4m14s
删除Deployment
# 删除deployment,其下的rs和pos也将被删除
[root@master k8s]# kubectl delete -f pc-deployment.yaml
deployment.apps "pc-deployment" deleted
7.4 Horizontal Pod Autoscaler(HPA)
在前面的课程中,我们可以通过手动执行`kubectl scale`命令实现Pod扩缩容,但是这显然不符合K8S的定位目标--自动化、智能化。K8S期望可以通过检测Pod的使用情况,实现Pod数量的自动调整,于是就产生了HPA这种控制器。
HPA可以获取每个Pod的资源利用率,然后和HPA中定义的指标进行对比,同时计算出需要伸缩的具体值,最后实现Pod的数量的调整。其实HPA与之前的Deployment一样,也属于一种K8S资源对象,它通过追踪分析目标Pod的负载变化情况,来确定是否需要针对性地调整目标Pod的副本数。
1.安装metrics-server
metrics-server可以用来收集集群中的资源使用情况
# 安装git
yum install -y git
# 获取metrics-server
git clone -b v0.3.6 https://github.com/kubernetes-incubator/metrics-server
git clone -b v0.5.1 https://github.com/kubernetes-incubator/metrics-server
# 修改deployment,注意修改的是镜像和初始化参数
cd /root/metrics-server/deploy/1.8+/
vim metrics-server-deployment.yaml
# 按照图中添加或修改下面选项
hostNetwork: true
image: registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server-amd64:v0.3.6
- --kubelet-insecure-tls
- --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP
# 修改完成后,可能还是会出现错误,需要修改其中配置文件中的一个版本配置项,执行下面命令
sed -i 's#apiregistration.k8s.io/v1beta1#apiregistration.k8s.io/v1#' metrics-apiservice.yaml
# 安装metrics-server
[root@master 1.8+]# kubectl apply -f ./
# 查看pod运行情况
[root@master 1.8+]# kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
metrics-server-6b7d7c4c4f-wm48k 1/1 Running 0 17m
# 使用kubectl top node 查看资源使用情况
[root@master 1.8+]# kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
master 219m 10% 1645Mi 44%
node1 39m 1% 505Mi 29%
node2 49m 2% 467Mi 27%
[root@master 1.8+]# kubectl top pod -n kube-system
NAME CPU(cores) MEMORY(bytes)
coredns-64897985d-9lbdj 1m 11Mi
coredns-64897985d-jgj74 2m 10Mi
etcd-master 15m 302Mi
......
至此,metrics-server安装完成
2.准备deployment和service
创建 hpa-deployment.yaml文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: hpa-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
resources:
requests:
cpu: 100m
# 创建deployment
[root@master 1.8+]# kubectl create -f hpa-deployment.yaml
deployment.apps/hpa-deployment created
# 创建service
[root@master 1.8+]# kubectl expose deploy hpa-deployment --type=NodePort --port=80 -n dev
service/hpa-deployment exposed
# 查看
[root@master 1.8+]# kubectl get deploy,pod,svc -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hpa-deployment 3/3 3 3 81s
NAME READY STATUS RESTARTS AGE
pod/hpa-deployment-c789d6d7f-ddg6b 1/1 Running 0 81s
pod/hpa-deployment-c789d6d7f-nv9gq 1/1 Running 0 81s
pod/hpa-deployment-c789d6d7f-nw48s 1/1 Running 0 81s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/hpa-deployment NodePort 10.96.58.136 <none> 80:31343/TCP 31s
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%
scaleTargetRef: # 指定要控制的deployment信息
apiVersion: apps/v1
kind: Deployment
name: hpa-deployment
# 创建hpa
[root@master k8s]# kubectl create -f pc-hpa.yaml
horizontalpodautoscaler.autoscaling/pc-hpa created
# 查看hpa
[root@master k8s]# kubectl get hpa -n dev
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
pc-hpa Deployment/hpa-deployment <unknown>/3% 1 10 3 17s
4.使用压测工具访问nginx来查看pod、deploy、hpa的信息
在这个地方踩了很多坑,可能是k8s跟metrics-server和版本不兼容,我的k8s是1.23版本,目前还没有解决,先不管这个了。
7.5 DaemonSet(DS)
DaemonSet类型的控制器可以保证集群中的每一台(或指定)节点上都运行一个副本,一般适用于日志收集、节点监控等场景。也就是说一个Pod提供的功能是节点级别的(每个节点都需要且只需要一个),那么这类Pod就适合使用DaemonSet类型的控制器创建。
DaemonSet控制器的特定:
- 每当向集群中添加一个节点时,指定的Pod副本也将添加到该节点上
- 当节点从集群中移除时,Pod也就被当垃圾回收了
下面来看DaemonSet的资源清单:
apiVersion: apps/v1 # 版本号
kind: DaemonSet # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: # 标签
controller: daemonset
spec:
revisionHistoryLimit: 3 # 保留历史版本数
updateStrategy: # 更新策略
type: RollingUpdate # 滚动更新
rollingUpdate:
maxAvailable: 1 # 最大不可用状态的Pod的最大值,可以为百分比或整数
selector: # selector,通过它指定该控制器管理哪些pod
matchLabels: # labels匹配规则
app: nginx-pod
matchExpression:
- {key: app, operator: In, values: [nginx-pod]}
template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
创建pc-daemonset.yaml:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: pc-daemonset
namespace: dev
spec:
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
# 创建ds
[root@master k8s]# kubectl create -f pc-daemonset.yaml
daemonset.apps/pc-daemonset created
# 查看ds
[root@master k8s]# kubectl get ds -n dev
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
pc-daemonset 2 2 2 2 2 <none> 3m42s
# 查看pod
[root@master k8s]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pc-daemonset-kwprp 1/1 Running 0 4m16s
pc-daemonset-zq992 1/1 Running 0 58s
# 删除daemonset
[root@master k8s]# kubectl delete -f pc-daemonset.yaml
daemonset.apps "pc-daemonset" deleted
7.6 Job
Job,主要负责批量处理(一次要处理指定数量任务)短暂的一次性(每个任务运行一次就结束)任务。Job特点如下:
- 当Job创建的Pod执行成功时,Job将记录成功结束的Pod数量
- 当成功结束的Pod达到指定的数量时,Job将完成执行
Job的资源清单文件:
apiVersion: batch/v1 # 版本号
kind: Job # 类型
metadata:
name: # Job名称
namespace: # 所属命名空间
labels: # 标签
controller: job
spec:
completions: 1 # 指定Job需要成功运行pod的次数。默认值:1
parallelism: 1 # 指定Job在任一时刻应该并发运行Pod的数量。默认值:1
activeDeadlineSeconds: 30 # 指定Job可运行的时间期限,超时时间还未结束,系统将尝试进行终止
backoffLimit: 6 # 指定Job失败后进行重试的次数,默认:6
manualSelector: true # 是否可以使用selector选择器选择pod,默认:false
selector: # 选择器,通过它指定该控制器管理哪些Pod
matchLabels:
app: counter-pod
matchExpression:
- {key: app, operator: In, values: [counter-pod]}
template:
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never # 重启策略只能设置为Never或OnFailure
containers:
- name: counter
image: busybox:1.30
command: ["/bin/sh", "-c", "for i in {1..9};do echo $i;sleep 2;done"]
关于重启策略设置的说明:
如果指定OnFailure,则Job会在Pod出现故障时重启容器,而不是创建Pod,failed次数也不变
如果指定Never,则Job会在Pod出现故障时创建新的Pod,并且故障Pod不会消失,也不会重启,failed次数加1
如果指定为Always,就意味着一直重启,意味着Job任务会重复执行了,当然不对,所以不能设置为Always
创建pc-job.yaml:
apiVersion: batch/v1
kind: Job
metadata:
name: pc-job
namespace: dev
spec:
manualSelector: true
selector:
matchLabels:
app: counter-pod
template:
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox:1.30
command: ["/bin/sh", "-c", "for i in {9..1};do echo $i;sleep 2;done"]
# 创建job
[root@master k8s]# kubectl create -f pc-job.yaml
job.batch/pc-job created
# 动态查看job
[root@master ~]# kubectl get job -n dev -w
NAME COMPLETIONS DURATION AGE
pc-job 0/1 0s
pc-job 0/1 0s 0s
pc-job 0/1 12s 12s
pc-job 1/1 12s 12s
# 动态查看pod
[root@master ~]# kubectl get pod -n dev -w
NAME READY STATUS RESTARTS AGE
pc-job-64b76 0/1 Pending 0 0s
pc-job-64b76 0/1 ContainerCreating 0 0s
pc-job-64b76 1/1 Running 0 10s
pc-job-64b76 0/1 Completed 0 12s
# 删除job
[root@master k8s]# kubectl delete job pc-job -n dev
job.batch "pc-job" deleted
7.7 CronJob(CJ)
CronJob控制器以Job控制器资源为其管控对象,并借助它管理Pod资源对象,Job控制器定义的作业任务在其控制器资源创建后便会立即执行,但CronJob可以以类似Linux操作系统的周期性任务作业计划的方式控制其运行**时间点**和**重复运行**的方式。也就是说,CronJob可以在特定的时间点反复去运行Job任务。
CronJob的资源清单文件:
apiVersion: batch/v1 # 版本号
kind: CronJob # 类型
metadata:
name:
namespace:
labels:
controller: cronjob
spec:
schedule: # cron格式的作业调度运行时间点,用于控制任务在什么时间执行
concurrencyPolicy: # 并发执行策略,用于定义前一次作业运行尚未完成时是否以及如何运行后一次的作业
failedJobHistoryLimit: # 为失败的任务执行保留的历史记录数,默认1
successfulJobHistoryLimit: # 为成功任务执行保留的历史记录数,默认3
startingDeadlineSeconds: # 启动作业错误的超时时长
jobTemplate: # Job控制器模板,用于为cronjob控制器生成job对象
metadata:
spec:
completions: 1
parallelism: 1
activeDeadlineSeconds: 1
backoffLimit: 30
manualSelector: true
selector:
matchLabels:
app: counter-pod
matchExpression:
- {key: app, operator: In, values: [counter-pod]}
template:
metadata:
name:
namespace:
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox:1.30
command: ["/bin/sh", "-c", "for i in {9..1};do echo $i;sleep 2;done"]
需要解释的几个选项:
schedule:cron表达式,用于指定任务的执行时间
*/1 * * * *
<分钟> <小时> <日> <月份> <星期>
分钟 值从 0 到 59
小时 值从 0 到 23
日 值从 1 到 31
月 值从 1 到 12
星期 值从 0 到 6, 0表示星期日
多个时间可以用逗号隔开;范围可以用字符给出;*可以作为通配符;/表示每...
concurrencyPolicy:
Allow: 允许Job并发允许(默认)
Forbid:禁止并发运行,如果上一次运行尚未完成,则跳过下一次运行
Replace:替换,取消当前正在运行的作业并用新作业替换它
创建pc-cronjob.yaml
apiVersion: batch/v1 # 版本号
kind: CronJob # 类型
metadata:
name: pc-cronjob
namespace: dev
labels:
controller: cronjob
spec:
schedule: "*/1 * * * *" # 每分钟执行一次任务
jobTemplate:
metadata:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox:1.30
command: ["/bin/sh", "-c", "for i in {9..1};do echo $i;sleep 3;done"]
# 创建cronjob
[root@master k8s]# kubectl create -f pc-cronjob.yaml
cronjob.batch/pc-cronjob created
# 动态查看cronjob
# 可以看到每隔一分钟就会运行一个job
[root@master ~]# kubectl get cj -n dev -w
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
pc-cronjob */1 * * * * False 0 <none> 0s
pc-cronjob */1 * * * * False 1 0s 25s
pc-cronjob */1 * * * * False 0 7s 32s
pc-cronjob */1 * * * * False 0 7s 32s
pc-cronjob */1 * * * * False 1 0s 85s
pc-cronjob */1 * * * * False 0 4s 89s
# 动态查看job
[root@master ~]# kubectl get job -n dev -w
NAME COMPLETIONS DURATION AGE
pc-cronjob-27365083 0/1 0s
pc-cronjob-27365083 0/1 0s 0s
pc-cronjob-27365083 0/1 7s 7s
pc-cronjob-27365083 1/1 7s 7s
pc-cronjob-27365084 0/1 0s
pc-cronjob-27365084 0/1 0s 0s
pc-cronjob-27365084 0/1 4s 4s
pc-cronjob-27365084 1/1 4s 4s
# 动态查看pod
[root@master ~]# kubectl get pod -n dev -w
NAME READY STATUS RESTARTS AGE
pc-cronjob-27365083-t7krw 0/1 Pending 0 0s
pc-cronjob-27365083-t7krw 0/1 ContainerCreating 0 0s
pc-cronjob-27365083-t7krw 1/1 Running 0 4s
pc-cronjob-27365083-t7krw 0/1 Completed 0 7s
pc-cronjob-27365084-97rdd 0/1 Pending 0 0s
pc-cronjob-27365084-97rdd 0/1 ContainerCreating 0 0s
pc-cronjob-27365084-97rdd 1/1 Running 0 1s
pc-cronjob-27365084-97rdd 0/1 Completed 0 4s
7.8 StatefulSet
还没看
8、Service详解
本章节主要介绍Kubernetes的流量负载组件:Service和Ingress。
8.1 Service介绍
在K8S中,Pod是应用程序的载体,我们可以通过Pod的IP来访问应用程序,但是Pod的IP地址不是固定的,这也就意味着不方便直接采用Pod的IP对服务进行访问。
为了解决这个问题,K8S提供了Service资源,Service会对提供同一个服务的多个Pod进行聚合,并且提供一个统一的入口地址。通过访问Service的入口地址就能访问到后面的Pod服务。
Service在很多情况下只是一个概念,真正起作用的其实是kube-proxy服务进程,每个Node节点上都运行着一个kube-proxy服务进程。当创建Service的时候会通过apiServer向Etcd写入创建的Service信息,而kube-proxy会基于监听的机制发现这种Service的变动,然后它会将新的Service信息转换成对应的访问规则。
# 10.96.0.10:53 是service提供的访问入口
# 当访问这个入口的时候,可以发现后面有三个Pod的服务在等待调用
# kube-proxy会基于rr(轮询)的策略,将请求分发到其中一个Pod上去
# 这个规则会同时在集群内的所有节点上都生成,所以在任何一个节点上访问都可以
[root@master ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.0.10:53 rr
-> 10.244.0.2:53 Masq 1 0 0
-> 10.244.0.3:53 Masq 1 0 0
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
# 编辑cm文件,在文件中查找mode选项,改为ipvs
[root@master ~]# kubectl edit cm kube-proxy -n kube-system
# 重启kube-proxy组件
[root@master ~]# kubectl delete pod -l k8s-app=kube-proxy -n kube-system
# 查看ipvs规则
[root@master ~]# ipvsadm -Ln
8.2 Service类型
Service的资源清单文件:
apiVersion: v1
kind: Service
metadata:
name: service
namespace: dev
spec:
selector: # 标签选择器,用于确定Service代理哪些Pod
app: nginx
type: # Service类型,指定Service的访问方式
clusterIP: # 虚拟服务的IP地址
sessionAffinity: # session亲和性,支持ClientIP、None两个选项
ports: # 端口信息
- protocol: TCP
port: 3017 # Service端口
targetPort: 5003 # Pod端口
nodePort: 31122 # 主机端口
ClusterIP
:默认值,它是K8S系统自动分配的虚拟IP,只能在集群内部访问
NodePort
:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务
LoadBalancer
:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境支持
ExternalName
:把集群外部的服务引入集群内部,直接使用
8.3 Service使用
8.3.1 实验环境准备
在使用Service之前,首先利用Deployment创建出3个Pod,注意要为Pod设置app=nginx-pod
的标签
创建deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pc-deployment
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
# 创建pod
[root@master service_test]# kubectl create -f deployment.yaml
deployment.apps/pc-deployment created
# 查看pod
[root@master service_test]# kubectl get pod -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pc-deployment-6756f95949-8frqw 1/1 Running 0 3m51s 10.244.1.47 node1 <none> <none>
pc-deployment-6756f95949-cnnww 1/1 Running 0 3m51s 10.244.2.62 node2 <none> <none>
pc-deployment-6756f95949-mmpsm 1/1 Running 0 3m51s 10.244.2.63 node2 <none> <none>
# 为了方便后面的测试,修改下三台nginx的index.html页面(三台修改的ip地址不一致)
# kubectl exec -it pc-deployment-6756f95949-8frqw -n dev /bin/sh
# echo "10.244.1.47" > /usr/share/nginx/html/index.html
# 修改完成后,访问测试
[root@master service_test]# curl 10.244.1.47:80
10.244.1.47
[root@master service_test]# curl 10.244.2.62:80
10.244.2.62
[root@master service_test]# curl 10.244.2.63:80
10.244.2.63
8.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@master service_test]# kubectl create -f service-clusterip.yaml
service/service-clusterip created
# 查看Service
[root@master service_test]# kubectl get svc -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service-clusterip ClusterIP 10.97.97.97 <none> 80/TCP 10s
# 查看Service的详细信息
[root@master service_test]# kubectl get svc -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service-clusterip ClusterIP 10.97.97.97 <none> 80/TCP 10s
[root@master service_test]# kubectl describe svc service-clusterip -n dev
Name: service-clusterip
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=nginx-pod
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.97.97.97
IPs: 10.97.97.97
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.47:80,10.244.2.62:80,10.244.2.63:80
Session Affinity: None
Events: <none>
# 查看ipvs的映射规则
[root@master service_test]# 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.47:80 Masq 1 0 0
-> 10.244.2.62:80 Masq 1 0 0
-> 10.244.2.63:80 Masq 1 0 0
# 多次访问 10.97.97.97:80 观察效果,发现请求是轮询分发到每个Pod的
[root@master service_test]# curl 10.97.97.97:80
10.244.2.63
[root@master service_test]# curl 10.97.97.97:80
10.244.2.62
[root@master service_test]# curl 10.97.97.97:80
10.244.1.47
[root@master service_test]# curl 10.97.97.97:80
10.244.2.63
[root@master service_test]# curl 10.97.97.97:80
10.244.2.62
[root@master service_test]# curl 10.97.97.97:80
10.244.1.47
Endpoint
Endpoint是K8S中的一个资源对象,存储在Etcd中,用来记录一个Service对应的所有Pod的访问地址,它是根据Service配置文件中selector描述产生的。
一个Service由一组Pod组成,这些Pod通过Endpoints暴露出来,Endpoints是实现实际服务的端点集合。换句话说,Service和Pod之间的联系是通过Endpoints实现的。
[root@master service_test]# kubectl get endpoints -n dev -o wide
NAME ENDPOINTS AGE
service-clusterip 10.244.1.47:80,10.244.2.62:80,10.244.2.63:80 70m
负载分发策略
对Service的访问被分发到了后端的Pod上去,目前K8S提供了两种负载分发策略:
- 如果不定义,默认使用的kube-proxy的策略,比如随机、轮询
- 基于客户端地址的会话保持模式,即来自同一个客户端发起的所有请求都会转发到固定的一个Pod上,此模式在
spec
中添加sessionAffinity:ClientIP
选项来使用
# 查看ipvs的映射规则(rr:轮询)
[root@master service_test]# 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.47:80 Masq 1 0 0
-> 10.244.2.62:80 Masq 1 0 0
-> 10.244.2.63:80 Masq 1 0 0
# 循环访问测试
[root@master service_test]# while true;do curl 10.97.97.97:80;sleep 3;done;
10.244.2.63
10.244.2.62
10.244.1.47
10.244.2.63
10.244.2.62
10.244.1.47
# 修改分发策略 --- sessionAffinity: ClientIP
# 修改service-clusterip.yaml文件,添加sessionAffinity: ClientIP
# 应用修改后的文件
[root@master service_test]# kubectl apply -f service-clusterip.yaml
service/service-clusterip configured
# 查看ipvs规则,可以看到为 rr persistent 10800,persistent表示持久,10800为秒数
[root@master service_test]# 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.47:80 Masq 1 0 0
-> 10.244.2.62:80 Masq 1 0 0
-> 10.244.2.63:80 Masq 1 0 0
# 循环访问测试
[root@master service_test]# while true;do curl 10.97.97.97:80;sleep 3;done;
10.244.2.62
10.244.2.62
10.244.2.62
10.244.2.62
10.244.2.62
# 删除Service
[root@master service_test]# kubectl delete -f service-clusterip.yaml
service "service-clusterip" deleted
8.3.3 HeadLiness类型的Service
在某些场景中,开发人员可能不想使用Service提供的负载均衡,而希望自己来控制负载均衡策略,针对这种情况,K8S提供了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@master service_test]# kubectl create -f service-headliness.yaml
service/service-headliness created
# 查看Service,发现没有分配CLUSTER-IP
[root@master service_test]# kubectl get svc service-headliness -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service-headliness ClusterIP None <none> 80/TCP 63s app=nginx-pod
# 查看Service详情
[root@master service_test]# kubectl describe svc service-headliness -n dev
Name: service-headliness
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=nginx-pod
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: None
IPs: None
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.47:80,10.244.2.62:80,10.244.2.63:80
Session Affinity: None
Events: <none>
# 查看域名的解析情况
[root@master service_test]# kubectl exec -it pc-deployment-6756f95949-8frqw -n dev /bin/sh
# cat /etc/resolv.conf
nameserver 10.96.0.10
search dev.svc.cluster.local svc.cluster.local cluster.local
[root@master service_test]# dig @10.96.0.10 service-headliness.dev.svc.cluster.local
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.2.62
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.1.47
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.2.63
8.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
ports:
- port: 80
targetPort: 80
nodePort: 30001 # 指定绑定的node的端口(默认取值范围:30000-32767),如果不指定,会默认分配
# 创建Service
[root@master service_test]# kubectl create -f service-nodeport.yaml
service/service-nodeport created
# 查看Service
[root@master service_test]# kubectl get svc service-nodeport -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service-nodeport NodePort 10.98.208.175 <none> 80:30001/TCP 26s app=nginx-pod
# 接下来可以在浏览器中输入 主机IP:30001 来访问了
8.3.5 LoadBanancer类型的Service
LoadBanlancer和NodePort很相似,目的都是向外部暴露一个端口,区别在于LoadBanlancer会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境支持,外部服务发送到这个设备的请求,会被设备负载之后转发到集群中。
由于缺失必要设备,就没法演示了。
8.3.6 ExternalName类型的Service
创建service-externalname.yaml
apiVersion: v1
kind: Service
metadata:
name: service-externalname
namespace: dev
spec:
type: ExternalName
externalName: www.baidu.com # 改成ip地址也可以
# 创建service
[root@master service_test]# kubectl create -f service-externalname.yaml
service/service-externalname created
# 域名解析
[root@master service_test]# dig @10.96.0.10 service-externalname.dev.svc.cluster.local
www.baidu.com. 30 IN CNAME www.a.shifen.com.
www.a.shifen.com. 30 IN A 182.61.200.7
www.a.shifen.com. 30 IN A 182.61.200.6
8.4 Ingress介绍
在前面的章节中已经提到,Service对集群之外暴露服务的主要方式有两种:NodePort和LoadBalancer,但是这两种方式都有一定的缺点:
- NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多时,这个缺点就愈发明显
- LB方式的缺点是每个Service都需要一个LB,浪费、麻烦,并且需要K8S之外的设备支持
基于这种现状,K8S提供了Ingress资源对象,Ingress只需要一个NodePort或者一个LB就可以满足暴露多个Service的需求。工作机制大致如下图所示:
实际上,Ingress相当于一个7层的负载均衡器,是K8S对反向代理的一个抽象,它的工作原理类似于Nginx,可以理解成**在Ingress中建立诸多映射规则,Ingress Controller通过监听这些配置规则并转化为Nginx的反向代理配置,然后对外部提供服务**。在这里有两个核心概念:
Ingress:K8S中的一个对象,作用是定义请求如何转发到Service的规则
Ingress Controller:具体实现反向代理及负载均衡的程序,对Ingress定义的规则进行解析,根据配置的规则来实现请求转发,实现的方式有很多,比如Nginx、Contour、Haproxy等
Ingress的工作原理如下(以Nginx为例):
- 用户编写Ingress规则,说明哪个域名对应K8S集群中的哪个Service
- Ingress控制器动态感知Ingress服务规则的变化,然后生成一段对应的Nginx配置
- Ingress控制器会将生成的Nginx配置写入到一个运行着的Nginx服务中,并动态更新
- 到此为止,其实真正在工作的就是一个Nginx了,内部配置了用户定义的请求转发规则
8.5 Ingress使用
8.5.1 环境准备
搭建Ingress环境
# 创建文件夹
[root@master k8s]# mkdir ingress-controller
[root@master k8s]# cd ingress-controller/
# 粘贴下面的配置到ingress-nginx.yaml文件中
# 创建:kubectl create -f filename.yaml
# 查看pod
[root@master ingress-controller]# kubectl get pod -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-2ktm9 0/1 Completed 0 4m23s
ingress-nginx-admission-patch-sddq9 0/1 Completed 2 4m23s
ingress-nginx-controller-lhzlp 1/1 Running 0 4m23s
ingress-nginx-controller-wnscj 1/1 Running 0 4m23s
# 查看svc
[root@master ingress-controller]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller-admission ClusterIP 10.100.16.181 <none> 443/TCP 7m23s
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
---
# Source: ingress-nginx/templates/controller-serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
automountServiceAccountToken: true
---
# Source: ingress-nginx/templates/controller-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
data:
---
# Source: ingress-nginx/templates/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
name: ingress-nginx
rules:
- apiGroups:
- ''
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ''
resources:
- nodes
verbs:
- get
- apiGroups:
- ''
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- events
verbs:
- create
- patch
- apiGroups:
- networking.k8s.io
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- networking.k8s.io
resources:
- ingressclasses
verbs:
- get
- list
- watch
---
# Source: ingress-nginx/templates/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
name: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
rules:
- apiGroups:
- ''
resources:
- namespaces
verbs:
- get
- apiGroups:
- ''
resources:
- configmaps
- pods
- secrets
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- networking.k8s.io
resources:
- ingressclasses
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- configmaps
resourceNames:
- ingress-controller-leader
verbs:
- get
- update
- apiGroups:
- ''
resources:
- configmaps
verbs:
- create
- apiGroups:
- ''
resources:
- events
verbs:
- create
- patch
---
# Source: ingress-nginx/templates/controller-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-service-webhook.yaml
apiVersion: v1
kind: Service
metadata:
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller-admission
namespace: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: https
port: 443
targetPort: 443
protocol: TCP
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
---
# Source: ingress-nginx/templates/controller-deployment.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
revisionHistoryLimit: 10
minReadySeconds: 0
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
spec:
hostNetwork: true
dnsPolicy: ClusterFirst
containers:
- name: controller
image: registry.cn-beijing.aliyuncs.com/kole_chang/controller:v1.0.0
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
args:
- /nginx-ingress-controller
- --election-id=ingress-controller-leader
- --controller-class=k8s.io/ingress-nginx
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --validating-webhook=:8443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
- --watch-ingress-without-class=true
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 101
allowPrivilegeEscalation: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LD_PRELOAD
value: /usr/local/lib/libmimalloc.so
livenessProbe:
failureThreshold: 5
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
- name: webhook
containerPort: 8443
protocol: TCP
volumeMounts:
- name: webhook-cert
mountPath: /usr/local/certificates/
readOnly: true
resources:
requests:
cpu: 100m
memory: 90Mi
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
volumes:
- name: webhook-cert
secret:
secretName: ingress-nginx-admission
---
# Source: ingress-nginx/templates/controller-ingressclass.yaml
# We don't support namespaced ingressClass yet
# So a ClusterRole and a ClusterRoleBinding is required
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: nginx
namespace: ingress-nginx
spec:
controller: k8s.io/ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/validating-webhook.yaml
# before changing this value, check the required kubernetes version
# https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#prerequisites
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
name: ingress-nginx-admission
webhooks:
- name: validate.nginx.ingress.kubernetes.io
matchPolicy: Equivalent
rules:
- apiGroups:
- networking.k8s.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- ingresses
failurePolicy: Fail
sideEffects: None
admissionReviewVersions:
- v1
clientConfig:
service:
namespace: ingress-nginx
name: ingress-nginx-controller-admission
path: /networking/v1/ingresses
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ingress-nginx-admission
namespace: ingress-nginx
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
rules:
- apiGroups:
- admissionregistration.k8s.io
resources:
- validatingwebhookconfigurations
verbs:
- get
- update
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
name: ingress-nginx-admission
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ingress-nginx-admission
namespace: ingress-nginx
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
rules:
- apiGroups:
- ''
resources:
- secrets
verbs:
- get
- create
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ingress-nginx-admission
namespace: ingress-nginx
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
name: ingress-nginx-admission
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: ingress-nginx-admission-create
namespace: ingress-nginx
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
spec:
template:
metadata:
name: ingress-nginx-admission-create
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
spec:
containers:
- name: create
image: registry.cn-beijing.aliyuncs.com/kole_chang/kube-webhook-certgen:v1.0
imagePullPolicy: IfNotPresent
args:
- create
- --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc
- --namespace=$(POD_NAMESPACE)
- --secret-name=ingress-nginx-admission
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
restartPolicy: OnFailure
serviceAccountName: ingress-nginx-admission
nodeSelector:
kubernetes.io/os: linux
securityContext:
runAsNonRoot: true
runAsUser: 2000
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: ingress-nginx-admission-patch
namespace: ingress-nginx
annotations:
helm.sh/hook: post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
spec:
template:
metadata:
name: ingress-nginx-admission-patch
labels:
helm.sh/chart: ingress-nginx-4.0.1
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
spec:
containers:
- name: patch
image: registry.cn-beijing.aliyuncs.com/kole_chang/kube-webhook-certgen:v1.0
imagePullPolicy: IfNotPresent
args:
- patch
- --webhook-name=ingress-nginx-admission
- --namespace=$(POD_NAMESPACE)
- --patch-mutating=false
- --secret-name=ingress-nginx-admission
- --patch-failure-policy=Fail
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
restartPolicy: OnFailure
serviceAccountName: ingress-nginx-admission
nodeSelector:
kubernetes.io/os: linux
securityContext:
runAsNonRoot: true
runAsUser: 2000
准备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@master service_test]# kubectl create -f tomcat-nginx.yaml
deployments.apps/nginx-deployment created
deployment.apps/tomcat-deployment created
service/nginx-service created
service/tomcat-service created
# 查看deploy、pod和svc
[root@master service_test]# kubectl get deploy -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 2m50s
tomcat-deployment 3/3 3 3 2m8s
[root@master service_test]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx-deployment-6756f95949-5tmw4 1/1 Running 0 3m25s
nginx-deployment-6756f95949-jc69z 1/1 Running 0 3m25s
nginx-deployment-6756f95949-n6n8t 1/1 Running 0 3m25s
tomcat-deployment-76bccfb47c-5sph9 1/1 Running 0 2m43s
tomcat-deployment-76bccfb47c-7r2wp 1/1 Running 0 2m43s
tomcat-deployment-76bccfb47c-g95jq 1/1 Running 0 2m43s
[root@master service_test]# kubectl get svc -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP None <none> 80/TCP 2m19s
tomcat-service ClusterIP None <none> 8080/TCP 2m19s
8.5.2 Http代理
创建ingress-http.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-http
namespace: dev
spec:
rules:
- host: nginx.xmg.com
http:
paths:
- path: /
pathType: Exact
backend:
service:
name: nginx-service
port:
number: 80
- host: tomcat.xmg.com
http:
paths:
- path: /
pathType: Exact
backend:
service:
name: tomcat-service
port:
number: 8080
# 创建ingress
[root@master service_test]# kubectl create -f ingress-http.yaml
ingress.networking.k8s.io/ingress-http created
# 查看ingress
[root@master service_test]# kubectl get ing -n dev
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-http <none> nginx.xmg.com,tomcat.xmg.com 192.168.226.169,192.168.226.170 80 26s
# 查看ingress详情
[root@master service_test]# kubectl get ing -n dev
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-http <none> nginx.xmg.com,tomcat.xmg.com 192.168.226.169,192.168.226.170 80 26s
[root@master service_test]# kubectl describe ing ingress-http -n dev
Name: ingress-http
Labels: <none>
Namespace: dev
Address: 192.168.226.169,192.168.226.170
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
Host Path Backends
---- ---- --------
nginx.xmg.com
/ nginx-service:80 (10.244.1.58:80,10.244.1.59:80,10.244.2.65:80)
tomcat.xmg.com
/ tomcat-service:8080 (10.244.1.60:8080,10.244.2.66:8080,10.244.2.67:8080)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 51s (x2 over 61s) nginx-ingress-controller Scheduled for sync
Normal Sync 51s (x2 over 61s) nginx-ingress-controller Scheduled for sync
# 然后在windows的 C:\Windows\System32\drivers\etc\hosts中添加规则:
# ip和域名需要改成自己的
192.168.226.168 nginx.xmg.com
192.168.226.168 tomcat.xmg.com
# 查看svc
[root@master ingress-controller]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller-admission NodePort 10.100.16.181 <none> 80:30586/TCP,443:31694/TCP 52m
# 然后再浏览器中输入 nginx.xmg.com:30586来访问:
输入 tomcat.xmg.com来访问tomcat(不知为何,图片加载不了,但是已经成功了):
8.5.3 Https代理
创建证书
# 生成证书
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=xmg.com"
# 创建密钥
kubectl create secret tls tls-secret --key tls.key --cert tls.crt
创建ingress-https.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-https
namespace: dev
spec:
tls:
- hosts:
- nginx.xmg.com
- tomcat.xmg.com
secretName: tls-secret # 指定密钥
rules:
- host: nginx.xmg.com
http:
paths:
- path: /
pathType: Exact
backend:
service:
name: nginx-service
port:
number: 80
- host: tomcat.xmg.com
http:
paths:
- path: /
pathType: Exact
backend:
service:
name: tomcat-service
port:
number: 8080
# 创建ingress,没有成功
[root@master service_test]# kubectl create -f ingress-https.yaml
Error from server (InternalError): error when creating "ingress-https.yaml": Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io": failed to call webhook: Post "https://ingress-nginx-controller-admission.ingress-nginx.svc:443/networking/v1/ingresses?timeout=10s": x509: certificate is valid for ingress.local, not ingress-nginx-controller-admission.ingress-nginx.svc
# 网上找了解决方案
# 使用下面的命令查看 webhook
[root@master service_test]# kubectl get validatingwebhookconfigurations ingress-nginx-admission
NAME WEBHOOKS AGE
ingress-nginx-admission 1 76m
# 删除 ingress-nginx-admission
[root@master service_test]# kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission
validatingwebhookconfiguration.admissionregistration.k8s.io "ingress-nginx-admission" deleted
# 再次创建ingress(成功了)
[root@master service_test]# kubectl create -f ingress-https.yaml
ingress.networking.k8s.io/ingress-https created
# 查看ingress详情
[root@master service_test]# kubectl describe ing ingress-https -n dev
Name: ingress-https
Labels: <none>
Namespace: dev
Address: 192.168.226.169,192.168.226.170
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
tls-secret terminates nginx.xmg.com,tomcat.xmg.com
Rules:
Host Path Backends
---- ---- --------
nginx.xmg.com
/ nginx-service:80 (10.244.1.58:80,10.244.1.59:80,10.244.2.65:80)
tomcat.xmg.com
/ tomcat-service:8080 (10.244.1.60:8080,10.244.2.66:8080,10.244.2.67:8080)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 3m48s (x2 over 4m28s) nginx-ingress-controller Scheduled for sync
Normal Sync 3m48s (x2 over 4m28s) nginx-ingress-controller Scheduled for sync
# 查看svc
[root@master service_test]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller-admission NodePort 10.100.16.181 <none> 80:30586/TCP,443:31694/TCP 80m
# 使用 https://域名:31694来访问
9、数据存储
在前面已经提到,容器的生命周期可能很短,会被频繁地创建和销毁。那么容器在销毁时,保存在容器中的数据也会被清除。这种结果对用户来说,在某些情况下时不乐意看到的。为了持久化保存容器中的数据,K8S引入了Volume的概念。
Volume是Pod中能够被多个容器访问的共享目录,它被定义在Pod上,然后被Pod中的多个容器挂载到具体的文件目录下,K8S通过Volume实现同一个Pod中不同容器之间的数据共享以及数据的持久化存储。Volume的生命周期不与Pod中单个容器的生命周期相关,当容器终止或者重启时,Volume中的数据也不会丢失。
-
kubernetes的Volume支持多种类型,比较常见的有下面的几个:
-
- 简单存储:EmptyDir、HostPath、NFS。
- 高级存储:PV、PVC。
- 配置存储:ConfigMap、Secret。
9.1 基本存储
9.1.1 EmptyDir
-
EmptyDir是最基础的Volume类型,一个EmptyDir就是Host上的一个空目录。
-
EmptyDir是在Pod被分配到Node时创建的,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为kubernetes会自动分配一个目录,当Pod销毁时,EmptyDir中的数据也会被永久删除。
-
EmptyDir的用途如下:
-
- 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留。
- 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。
-
接下来,通过一个容器之间的共享案例来使用描述一个EmptyDir。
-
在一个Pod中准备两个容器nginx和busybox,然后声明一个volume分别挂载到两个容器的目录中,然后nginx容器负责向volume中写日志,busybox中通过命令将日志内容读到控制台。
创建Pod
创建volume-emptydir.yaml文件,内容如下
apiVersion: v1
kind: Pod
metadata:
name: volume-emptydir
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts: # 将logs-volume挂载到nginx容器中对应的目录,该目录为/var/log/nginx
- name: logs-volume
mountPath: /var/log/nginx
- name: busybox
image: busybox:1.30
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件
volumeMounts: # 将logs-volume挂载到busybox容器中的对应目录,该目录为/logs
- name: logs-volume
mountPath: /logs
volumes: # 声明volume,name为logs-volume,类型为emptyDir
- name: logs-volume
emptyDir: {}
创建Pod
kubectl create -f volume-emptydir.yaml
查看Pod
kubectl get pod volume-emptydir -n dev -o wide
访问Pod中的Nginx
curl 10.244.2.2
查看指定容器的标准输出
kubectl logs -f volume-emptydir -n dev -c busybox
9.1.2 HostPath
- 我们已经知道EmptyDir中的数据不会被持久化,它会随着Pod的结束而销毁,如果想要简单的将数据持久化到主机中,可以选择HostPath。
- HostPath就是将Node主机中的一个实际目录挂载到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据依旧可以保存在Node主机上。
在这里插入图片描述
创建Pod
- 创建volume-hostpath.yaml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: volume-hostpath
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts: # 将logs-volume挂载到nginx容器中对应的目录,该目录为/var/log/nginx
- name: logs-volume
mountPath: /var/log/nginx
- name: busybox
image: busybox:1.30
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件
volumeMounts: # 将logs-volume挂载到busybox容器中的对应目录,该目录为/logs
- name: logs-volume
mountPath: /logs
volumes: # 声明volume,name为logs-volume,类型为hostPath
- name: logs-volume
hostPath:
path: /root/logs
type: DirectoryOrCreate # 目录存在就使用,不存在就先创建再使用
type的值的说明:
- DirectoryOrCreate:目录存在就使用,不存在就先创建后使用。
- Directory:目录必须存在。
- FileOrCreate:文件存在就使用,不存在就先创建后使用。
- File:文件必须存在。
- Socket:unix套接字必须存在。
- CharDevice:字符设备必须存在。
- BlockDevice:块设备必须存在。
创建Pod
kubectl create -f volume-hostpath.yaml
查看Pod
kubectl get pod volume-hostpath -n dev -o wide
访问Pod中的Nginx
curl 10.244.2.3
在Node节点中找到HostPath映射的目录中的文件
进入到Pod所在节点,查看HostPath映射的目录中的文件
ls /root/logs
9.2 NFS
9.2.1 概述
- HostPath虽然可以解决数据持久化的问题,但是一旦Node节点故障了,Pod如果转移到别的Node节点上,又会出现问题,此时需要准备单独的网络存储系统,比较常用的是NFS和CIFS。
- NFS是一个网络文件存储系统,可以搭建一台NFS服务器,然后将Pod中的存储直接连接到NFS系统上,这样,无论Pod在节点上怎么转移,只要Node和NFS的对接没有问题,数据就可以成功访问。
9.2.2 搭建NFS服务器
- 首先需要准备NFS服务器,这里为了简单,直接在Master节点做NFS服务器。
- 在Master节点上安装NFS服务器
yum install -y nfs-utils rpcbind
# 准备一个共享目录:
mkdir -pv /root/data/nfs
# 将共享目录以读写权限暴露给192.168.226.0/24网段中的所有主机
vim /etc/exports
/root/data/nfs 192.168.18.0/24(rw,no_root_squash)
# 修改权限
chmod 777 -R /root/data/nfs
# 加载配置
exportfs -r
# 启动nfs服务
systemctl start rpcbind
systemctl enable rpcbind
systemctl start nfs
systemctl enable nfs
# 在Master节点上测试是否挂载成功
showmount -e 192.168.226.100
在Node节点上都安装NFS服务器,目的是为了Node节点可以驱动NFS设备
# 在Node节点上安装NFS服务,不需要启动
yum -y install nfs-utils
# 在Node节点测试是否挂载成功
showmount -e 192.168.226.100
# 高可用备份方式,在所有节点执行如下的命令:
mount -t nfs 192.168.18.100:/root/data/nfs /mnt
9.2.3 创建Pod
创建volume-nfs.yaml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: volume-nfs
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts: # 将logs-volume挂载到nginx容器中对应的目录,该目录为/var/log/nginx
- name: logs-volume
mountPath: /var/log/nginx
- name: busybox
image: busybox:1.30
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件
volumeMounts: # 将logs-volume挂载到busybox容器中的对应目录,该目录为/logs
- name: logs-volume
mountPath: /logs
volumes: # 声明volume
- name: logs-volume
nfs:
server: 192.168.18.100 # NFS服务器地址
path: /root/data/nfs # 共享文件路径
创建Pod
kubectl create -f volume-nfs.yaml
查看Pod
kubectl get pod volume-nfs -n dev
查看NFS服务器上的共享目录
ls /root/data/nfs
9.3 高级存储
9.3.1 PV和PVC概述
- 前面我们已经学习了使用NFS提供存储,此时就要求用户会搭建NFS系统,并且会在yaml配置nfs。由于kubernetes支持的存储系统有很多,要求客户全部掌握,显然不现实。为了能够屏蔽底层存储实现的细节,方便用户使用,kubernetes引入了PV和PVC两种资源对象。
- PV(Persistent Volume)是持久化卷的意思,是对底层的共享存储的一种抽象。一般情况下PV由kubernetes管理员进行创建和配置,它和底层具体的共享存储技术有关,并通过插件完成和共享存储的对接。
- PVC(Persistent Volume Claim)是持久化卷声明的意思,是用户对于存储需求的一种声明。换言之,PVC其实就是用户向kubernetes系统发出的一种资源需求申请。
在这里插入图片描述
-
使用了PV和PVC之后,工作可以得到进一步的提升:
-
- 存储:存储工程师维护。
- PV:kubernetes管理员维护。
- PVC:kubernetes用户维护。
9.3.2 PV
PV是存储资源的抽象,下面是PV的资源清单文件:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv2
spec:
nfs: # 存储类型,和底层正则的存储对应
path:
server:
capacity: # 存储能力,目前只支持存储空间的设置
storage: 2Gi
accessModes: # 访问模式
-
storageClassName: # 存储类别
persistentVolumeReclaimPolicy: # 回收策略
pv的关键配置参数说明:
-
存储类型:底层实际存储的类型,kubernetes支持多种存储类型,每种存储类型的配置有所不同。
-
存储能力(capacity):目前只支持存储空间的设置(storage=1Gi),不过未来可能会加入IOPS、吞吐量等指标的配置。
-
访问模式(accessModes):
-
- 用来描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
-
-
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载。
- ReadOnlyMany(ROX):只读权限,可以被多个节点挂载。
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载。
-
-
- 需要注意的是,底层不同的存储类型可能支持的访问模式不同。
-
回收策略( persistentVolumeReclaimPolicy):
-
- 当PV不再被使用之后,对其的处理方式,目前支持三种策略:
-
-
- Retain(保留):保留数据,需要管理员手动清理数据。
- Recycle(回收):清除PV中的数据,效果相当于
rm -rf /volume/*
。 - Delete(删除):和PV相连的后端存储完成volume的删除操作,常见于云服务器厂商的存储服务。
-
-
- 需要注意的是,底层不同的存储类型可能支持的回收策略不同。
-
存储类别(storageClassName):PV可以通过storageClassName参数指定一个存储类别。
-
- 具有特定类型的PV只能和请求了该类别的PVC进行绑定。
- 未设定类别的PV只能和不请求任何类别的PVC进行绑定。
-
状态(status):一个PV的生命周期,可能会处于4种不同的阶段。
-
- Available(可用):表示可用状态,还未被任何PVC绑定。
- Bound(已绑定):表示PV已经被PVC绑定。
- Released(已释放):表示PVC被删除,但是资源还没有被集群重新释放。
- Failed(失败):表示该PV的自动回收失败。
准备工作环境
# 创建目录
mkdir -pv /root/data/{pv1,pv2,pv3}
# 授权
chmod 777 -R /root/data
# 修改/etc/exports文件
vim /etc/exports
/root/data/pv1 192.168.18.0/24(rw,no_root_squash)
/root/data/pv2 192.168.18.0/24(rw,no_root_squash)
/root/data/pv3 192.168.18.0/24(rw,no_root_squash)
# 重启nfs服务
systemctl restart nfs
创建PV
创建pv.yaml文件,内容如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv1
spec:
nfs: # 存储类型吗,和底层正则的存储对应
path: /root/data/pv1
server: 192.168.18.100
capacity: # 存储能力,目前只支持存储空间的设置
storage: 1Gi
accessModes: # 访问模式
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain # 回收策略
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv2
spec:
nfs: # 存储类型吗,和底层正则的存储对应
path: /root/data/pv2
server: 192.168.18.100
capacity: # 存储能力,目前只支持存储空间的设置
storage: 2Gi
accessModes: # 访问模式
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain # 回收策略
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv3
spec:
nfs: # 存储类型吗,和底层正则的存储对应
path: /root/data/pv3
server: 192.168.18.100
capacity: # 存储能力,目前只支持存储空间的设置
storage: 3Gi
accessModes: # 访问模式
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain # 回收策略
创建PV
kubectl create -f pv.yaml
查看PV
kubectl get pv -o wide
9.3.3 PVC
PVC是资源的申请,用来声明对存储空间、访问模式、存储类别需求信息,下面是PVC的资源清单文件:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc
namespace: dev
spec:
accessModes: # 访客模式
-
selector: # 采用标签对PV选择
storageClassName: # 存储类别
resources: # 请求空间
requests:
storage: 5Gi
PVC的关键配置参数说明:
-
访客模式(accessModes):用于描述用户应用对存储资源的访问权限。
-
用于描述用户应用对存储资源的访问权限:
-
- 选择条件(selector):通过Label Selector的设置,可使PVC对于系统中已存在的PV进行筛选。
- 存储类别(storageClassName):PVC在定义时可以设定需要的后端存储的类别,只有设置了该class的pv才能被系统选出。
- 资源请求(resources):描述对存储资源的请求。
创建PVC
创建pvc.yaml文件,内容如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc1
namespace: dev
spec:
accessModes: # 访客模式
- ReadWriteMany
resources: # 请求空间
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc2
namespace: dev
spec:
accessModes: # 访客模式
- ReadWriteMany
resources: # 请求空间
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc3
namespace: dev
spec:
accessModes: # 访客模式
- ReadWriteMany
resources: # 请求空间
requests:
storage: 5Gi
创建PVC
kubectl create -f pvc.yaml
查看PVC
kubectl get pvc -n dev -o wide
查看PV
kubectl get pv -o wide
创建Pod使用PVC
创建pvc-pod.yaml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod1
namespace: dev
spec:
containers:
- name: busybox
image: busybox:1.30
command: ["/bin/sh","-c","while true;do echo pod1 >> /root/out.txt; sleep 10; done;"]
volumeMounts:
- name: volume
mountPath: /root/
volumes:
- name: volume
persistentVolumeClaim:
claimName: pvc1
readOnly: false
---
apiVersion: v1
kind: Pod
metadata:
name: pod2
namespace: dev
spec:
containers:
- name: busybox
image: busybox:1.30
command: ["/bin/sh","-c","while true;do echo pod1 >> /root/out.txt; sleep 10; done;"]
volumeMounts:
- name: volume
mountPath: /root/
volumes:
- name: volume
persistentVolumeClaim:
claimName: pvc2
readOnly: false
创建Pod
kubectl create -f pvc-pod.yaml
查看Pod
kubectl get pod -n dev -o wide
查看PVC
kubectl get pvc -n dev -o wide
查看PV
kubectl get pv -n dev -o wide
查看NFS中的文件存储
ls /root/data/pv1/out.txt
ls /root/data/pv2/out.txt
9.3.4 生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bJ8HSy1M-1678171945083)(F:\desktop\笔记\k8s学习笔记3.assets\image-20230307144953821.png)]
-
PVC和PV是一一对应的,PV和PVC之间的相互作用遵循如下的生命周期。
-
资源供应:管理员手动创建底层存储和PV。
-
资源绑定:
-
- 用户创建PVC,kubernetes负责根据PVC声明去寻找PV,并绑定在用户定义好PVC之后,系统将根据PVC对存储资源的请求在以存在的PV中选择一个满足条件的。
-
-
- 一旦找到,就将该PV和用户定义的PVC进行绑定,用户的应用就可以使用这个PVC了。
- 如果找不到,PVC就会无限期的处于Pending状态,直到系统管理员创建一个符合其要求的PV。
-
-
- PV一旦绑定到某个PVC上,就会被这个PVC独占,不能再和其他的PVC进行绑定了。
-
资源使用:用户可以在Pod中像volume一样使用PVC,Pod使用Volume的定义,将PVC挂载到容器内的某个路径进行使用。
-
资源释放:
-
- 用户删除PVC来释放PV。
- 当存储资源使用完毕后,用户可以删除PVC,和该PVC绑定的PV将会标记为“已释放”,但是还不能立刻和其他的PVC进行绑定。通过之前PVC写入的数据可能还留在存储设备上,只有在清除之后该PV才能再次使用。
-
资源回收:
-
- kubernetes根据PV设置的回收策略进行资源的回收。
- 对于PV,管理员可以设定回收策略,用于设置与之绑定的PVC释放资源之后如何处理遗留数据的问题。只有PV的存储空间完成回收,才能供新的PVC绑定和使用。
9.3.5 创建PVC后一直绑定不了PV的原因
- PVC的空间申请大小比PV的空间要大。
- PVC的storageClassName和PV的storageClassName不一致。
- PVC的accessModes和PV的accessModes不一致。
问题记录:
1、ns命名空间删除时卡住,一直显示Terminating的解决方案:
# 执行下面命令
kubectl get namespace <terminating-namespace> -o json > tmp.json
# 然后修改tmp.json文件,将finalizers中的kubernetes删掉
# 新增一个代理端口,代理的其实就是apiserver的非加密端口8080,运行此命令后会占用终端,需要再开一个窗口执行curl命令
kubectl proxy --port=8081
# 新开一个窗口执行下面命令
curl -k -H "Content-Type: application/json" -X PUT --data-binary @tmp.json http://127.0.0.1:8081/api/v1/namespaces/<terminating-namespace>/finalize
# 之后再查看ns已经没有了