K8S介绍

什么是K8S?

​ 它是一个为 容器化 应用提供集群部署和管理的开源工具,由 Google 开发。Kubernetes 这个名字源于希腊语,意为“舵手”或“飞行员”。k8s 这个缩写是因为 k 和 s 之间有八个字符的关系。 Google 在 2014 年开源了 Kubernetes 项目。

主要特性:

  • 高可用,不宕机,自动灾难恢复
  • 灰度更新,不影响业务正常运转
  • 一键回滚到历史版本
  • 方便的伸缩扩展(应用伸缩,机器加减)、提供负载均衡
  • 有一个完善的生态

不同的应用部署方案

PixPin_2024-12-25_21-26-20

  • 传统部署方式:应用直接在物理机上部署,机器资源分配不好控制,出现Bug时,可能机器的大部分资源被某个应用占用,导致其他应用无法正常运行,无法做到应用隔离。
  • 虚拟机部署:在单个物理机上运行多个虚拟机,每个虚拟机都是完整独立的系统,性能损耗大。
  • 容器部署:所有容器共享主机的系统,轻量级的虚拟机,性能损耗小,资源隔离,CPU和内存可按需分配。

什么时候需要 Kubernetes?

  • 当你的应用只是跑在一台机器,直接一个 docker + docker-compose 就够了,方便轻松;
  • 当你的应用需要跑在 3,4 台机器上,你依旧可以每台机器单独配置运行环境 + 负载均衡器;
  • 当你应用访问数不断增加,机器逐渐增加到十几台、上百台、上千台时,每次加机器、软件更新、版本回滚,都会变得非常麻烦、痛不欲生,再也不能好好的摸鱼了,人生浪费在那些没技术含量的重复性工作上。

这时候,Kubernetes 就可以一展身手了,让你轻松管理百万千万台机器的集群。Kubernetes 可以为你提供集中式的管理集群机器和应用,加机器、版本升级、版本回滚,那都是一个命令就搞定的事,不停机的灰度更新,确保高可用、高性能、高扩展。

Kubernetes 集群架构

img

master

​ 主节点,控制平台(控制平面),不需要很高性能,不跑任务,通常一个就行了,也可以开多个主节点来提高集群可用度。

worker

​ 工作节点,可以是虚拟机或物理计算机,任务都在这里跑,机器性能需要好点;通常都有很多个,可以不断加机器扩大集群;每个工作节点由主节点管理。

Pod

​ 豆荚,K8S 调度、管理的最小单位,一个 Pod 可以包含一个或多个容器,每个 Pod 有自己的虚拟IP。一个工作节点可以有多个 pod,主节点会考量负载自动调度 pod 到哪个节点运行。

img

Kubernetes 组件

  • kube-apiserver :API 服务器,公开了 Kubernetes API
  • etcd :键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库
  • kube-scheduler: 调度 Pod 到哪个节点运行
  • kube-controller: 集群控制器
  • cloud-controller :与云服务商交互

img

安装 Kubernetes 集群

安装方式介绍

  • minikube
    • 只是一个 K8S 集群模拟器,只有一个节点的集群,只为测试用,master 和 worker 都在一起
  • 直接用云平台 Kubernetes
    • 可视化搭建,只需简单几步就可以创建好一个集群。
    • 优点:安装简单,生态齐全,负载均衡器、存储等都给你配套好,简单操作就搞定
  • 裸机安装(Bare Metal)
    • 至少需要两台机器(主节点、工作节点个一台),需要自己安装 Kubernetes 组件,配置会稍微麻烦点。
    • 可以到各云厂商按时租用服务器,费用低,用完就销毁。
    • 缺点:配置麻烦,缺少生态支持,例如负载均衡器、云存储。
  • K3S(推荐)

minikube

安装简单,支持各种平台,安装方法:https://minikube.sigs.k8s.io/docs/start/

1
2
3
4
5
6
7
8
9
10
# 启动集群
minikube start
# 查看节点。kubectl 是一个用来跟 K8S 集群进行交互的命令行工具
kubectl get node
# 停止集群
minikube stop
# 清空集群
minikube delete --all
# 安装集群可视化 Web UI 控制台
minikube dashboard

云平台搭建

  • 腾讯云 TKE(控制台搜索容器)
  • 登录阿里云控制台 - 产品搜索 Kubernetes

裸机搭建(Bare Metal)

主节点需要组件

  • docker(也可以是其他容器运行时)
  • kubectl 集群命令行交互工具
  • kubeadm 集群初始化工具

工作节点需要组件

开始安装

  • 开源项目:https://github.com/lework/kainstall,一个脚本快速搭建K8S裸机集群

  • 手动搭建:

    1
    2
    3
    4
    # 每个节点分别设置对应主机名
    hostnamectl set-hostname master
    hostnamectl set-hostname node1
    hostnamectl set-hostname node2
    1
    2
    3
    4
    5
    # 所有节点都修改 hosts
    vim /etc/hosts
    172.16.32.2 node1
    172.16.32.6 node2
    172.16.0.4 master
    1
    2
    3
    # 所有节点关闭 SELinux
    setenforce 0
    sed -i --follow-symlinks 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/sysconfig/selinux

    所有节点确保防火墙关闭
    systemctl stop firewalld
    systemctl disable firewalld

​ 添加安装源(所有节点):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 添加 k8s 安装源
cat <<EOF > 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/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
mv kubernetes.repo /etc/yum.repos.d/

# 添加 Docker 安装源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

​ 安装所需组件(所有节点):

1
yum install -y kubelet-1.22.4 kubectl-1.22.4 kubeadm-1.22.4 docker-ce

​ 启动 kubelet、docker,并设置开机启动(所有节点):

1
2
3
4
systemctl enable kubelet
systemctl start kubelet
systemctl enable docker
systemctl start docker

​ 修改 docker 配置(所有节点):

1
2
3
4
5
6
7
8
9
10
11
12
# kubernetes 官方推荐 docker 等使用 systemd 作为 cgroupdriver,否则 kubelet 启动不了
cat <<EOF > daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors": ["https://ud6340vz.mirror.aliyuncs.com"]
}
EOF
mv daemon.json /etc/docker/

# 重启生效
systemctl daemon-reload
systemctl restart docker

​ 用 kubeadm 初始化集群(仅在主节点跑):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 初始化集群控制台 Control plane
# 失败了可以用 kubeadm reset 重置
kubeadm init --image-repository=registry.aliyuncs.com/google_containers

# 记得把 kubeadm join xxx 保存起来
# 忘记了重新获取:kubeadm token create --print-join-command

# 复制授权文件,以便 kubectl 可以有权限访问集群
# 如果你其他节点需要访问集群,需要从主节点复制这个文件过去其他节点
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# 在其他机器上创建 ~/.kube/config 文件也能通过 kubectl 访问到集群

​ 把工作节点加入集群(只在工作节点跑):

1
kubeadm join 172.16.32.10:6443 --token xxx --discovery-token-ca-cert-hash xxx

​ 安装网络插件,否则 node 是 NotReady 状态(主节点跑):

1
2
3
4
5
6
7
8
9
# 很有可能国内网络访问不到这个资源,你可以网上找找国内的源安装 flannel
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# 如果上面的插件安装失败,可以选用 Weave,下面的命令二选一就可以了。
kubectl apply -f https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml
kubectl apply -f http://static.corecore.cn/weave.v2.8.1.yaml

# 更多其他网路插件查看下面介绍,自行网上找 yaml 安装
https://blog.csdn.net/ChaITSimpleLove/article/details/117809007

​ 查看节点,要在主节点查看(其他节点有安装 kubectl 也可以查看):

img

K3s快速搭建集群

提示:
1.本文档基于kubernetes V1.25版本。
2.从V1.24开始,kubernetes默认容器运行时使用containerd,不再使用docker。

为什么使用K3s

K3s 是一个轻量级的、完全兼容的 Kubernetes 发行版本。非常适合初学者。
K3s将所有 Kubernetes 控制平面组件都封装在单个二进制文件和进程中,文件大小<100M,占用资源更小,且包含了kubernetes运行所需要的部分外部依赖和本地存储提供程序。
K3s提供了离线安装包,安装起来非常方便,可以避免安装过程中遇到各种网络资源访问问题。
K3s特别适用于边缘计算、物联网、嵌入式和ARM移动端场景。

K3s完全兼容kubernetes,二者的操作是一样的,使用k3s完全满足我们学习kubernetes的要求

离线安装K3s集群

K3s集群分为k3s Server(控制平面)和k3s Agent(工作节点)。所有的组件都打包在单个二进制文件中。

image-20241228172001575

运行环境

  • 最低 内存: 512MB / CPU: 1 核心
  • K3s版本:v1.25.0+k3s1
  • 集群规划(不同机器的ip和主机名不能一样,都要进行配置)

image-20241228172131914

准备工作

需要在每台机器上执行如下命令:

  • 关闭防火墙
  • 设置selinux(需要联网)
1
systemctl disable firewalld --now
1
2
yum install -y container-selinux selinux-policy-base
yum install -y https://rpm.rancher.io/k3s/latest/common/centos/7/noarch/k3s-selinux-0.2-1.el7_8.noarch.rpm

下载安装包

执行安装脚本

  • 将k3s二进制文件移动到/usr/local/bin目录,并添加执行权限
1
2
mv k3s /usr/local/bin
chmod +x /usr/local/bin/k3s
  • 将镜像移动到/var/lib/rancher/k3s/agent/images/目录(无需解压)
1
2
mkdir -p /var/lib/rancher/k3s/agent/images/
cp ./k3s-airgap-images-amd64.tar.gz /var/lib/rancher/k3s/agent/images/
  • 在k8s-master节点执行:
1
2
3
4
5
6
7
8
9
#修改权限
chmod +x install.sh
#离线安装
INSTALL_K3S_SKIP_DOWNLOAD=true ./install.sh
#安装完成后,查看节点状态
kubectl get node
#查看token
cat /var/lib/rancher/k3s/server/node-token
#K10c4b79481685b50e4bca2513078f4e83b62d1d0b5f133a8a668b65c8f9249c53e::server:bf7b63be7f3471838cbafa12c1a1964d
  • 在k8s-worker1和k8s-worker2节点执行
1
2
3
4
INSTALL_K3S_SKIP_DOWNLOAD=true \
K3S_URL=https://192.168.56.109:6443 \
K3S_TOKEN=K1012bdc3ffe7a5d89ecb125e56c38f9fe84a9f9aed6db605f7698fa744f2f2f12f::server:fdf33f4921dd607cadf2ae3c8eaf6ad9 \
./install.sh

排查错误

如果安装或启动不成功,可能有以下几个原因:

1
2
3
4
1. 时间不统一
2. IP有冲突,请为每个主机分配不同的IP
3. 主机名(hostname)重复,请为每个主机设置不同的主机名
4. 网卡的MAC有冲突,复制虚拟机时,请为所有网卡重新生产MAC地址

K8S基础

Pod

​ Pod 是包含一个或多个容器的容器组,是 Kubernetes 中创建和管理的最小对象。Pod 有以下特点:

  • Pod是kubernetes中最小的调度单位(原子单元),Kubernetes直接管理Pod而不是容器。
  • 同一个Pod中的容器总是会被自动安排到集群中的同一节点(物理机或虚拟机)上,并且一起调度。
  • Pod可以理解为运行特定应用的“逻辑主机”,这些容器共享存储、网络和配置声明(如资源限制)。
  • 每个 Pod 有唯一的 IP 地址。 IP地址分配给Pod,在同一个 Pod 内,所有容器共享一个 IP 地址和端口空间,Pod 内的容器可以使用localhost互相通信。

例如,你可能有一个容器,为共享卷中的文件提供 Web 服务器支持,以及一个单独的 “边车 (sidercar)” 容器负责从远端更新这些文件,如下图所示:

image-20241228173712211

创建和管理Pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
kubectl run mynginx --image=nginx
# 查看Pod
kubectl get pod
# 描述
kubectl describe pod mynginx
# 查看Pod的运行日志
kubectl logs mynginx

# 显示pod的IP和运行节点信息
kubectl get pod -owide
# 使用Pod的ip+pod里面运行容器的端口
curl 10.42.1.3

#在容器中执行
kubectl exec mynginx -it -- /bin/bash

kubectl get po --watch
# -it 交互模式
# --rm 退出后删除容器,多用于执行一次性任务或使用客户端
kubectl run mynginx --image=nginx -it --rm -- /bin/bash

# 删除
kubectl delete pod mynginx
# 强制删除
kubectl delete pod mynginx --force

img

镜像加速

由于kubernetes从V1.24版本开始默认使用containerd,需要修改containerd的配置文件,才能让Pod的镜像使用镜像加速器。配置文件路径一般为/etc/containerd/config.toml,详见阿里云镜像加速。

1、在K3s中配置镜像仓库

K3s 会自动生成containerd的配置文件/var/lib/rancher/k3s/agent/etc/containerd/config.toml,不要直接修改这个文件,k3s重启后修改会丢失。
为了简化配置,K3s 通过**/etc/rancher/k3s/registries.yaml**文件来配置镜像仓库,K3s会在启动时检查这个文件是否存在。
我们需要在每个节点上新建/etc/rancher/k3s/registries.yaml文件,配置内容如下:

1
2
3
4
mirrors:
docker.io:
endpoint:
- "https://fsp2sfpr.mirror.aliyuncs.com/"

重启每个节点:

1
2
systemctl restart k3s
systemctl restart k3s-agent

查看配置是否生效:

1
cat /var/lib/rancher/k3s/agent/etc/containerd/config.toml

img

容器与镜像

1、容器运行时接口(CRI)

​ Kubelet运行在每个节点(Node)上,用于管理和维护Pod和容器的状态。
​ 容器运行时接口(CRI)是kubelet 和容器运行时之间通信的主要协议。它将 Kubelet 与容器运行时解耦,理论上,实现了CRI接口的容器引擎,都可以作为kubernetes的容器运行时。

Docker没有实现(CRI)接口,Kubernetes使用dockershim来兼容docker。
自V1.24版本起,Dockershim 已从 Kubernetes 项目中移除。

crictl是一个兼容CRI的容器运行时命令,他的用法跟docker命令一样,可以用来检查和调试底层的运行时容器。

1
2
crictl pull mysql:5.7-debian
crictl images

​ 在一些局域网环境下,我们没法通过互联网拉取镜像,可以手动的导出、导入镜像。
​ crictl命令没有导出、导入镜像的功能。
​ 需要使用ctr命令导出、导入镜像,它是containerd的命令行接口。

2、导入导出

  • 从docker导出镜像再导入到containerd中
1
2
3
4
5
docker pull alpine:3.16
docker save alpine:3.16 > alpine.tar

#kubernetes使用的镜像都在k8s.io命名空间中
ctr -n k8s.io images import alpine.tar
  • 从containerd导出、导入镜像
1
2
3
4
#导出镜像
ctr -n k8s.io images export mysql.tar docker.io/library/mysql:5.7-debian --platform linux/amd64
#导入镜像
ctr -n k8s.io images import mysql.tar

Deployment(部署)与ReplicaSet(副本集)

Deployment是对ReplicaSet和Pod更高级的抽象。它使Pod拥有多副本,自愈,扩缩容、滚动升级等能力。

ReplicaSet(副本集)是一个Pod的集合。它可以设置运行Pod的数量,确保任何时间都有指定数量的 Pod 副本在运行。通常我们不直接使用ReplicaSet,而是在Deployment中声明。

1
2
3
4
5
6
7
8
#创建deployment,部署3个运行nginx的Pod
kubectl create deployment nginx-deployment --image=nginx:1.22 --replicas=3
#查看deployment
kubectl get deploy
#查看replicaSet
kubectl get rs
#删除deployment
kubectl delete deploy nginx-deployment

缩放

  • 手动缩放
1
2
3
#将副本数量调整为5
kubectl scale deployment/nginx-deployment --replicas=5
kubectl get deploy
  • 自动缩放
    • 自动缩放通过增加和减少副本的数量,以保持所有 Pod 的平均 CPU 利用率不超过 75%。
    • 自动伸缩需要声明Pod的资源限制,同时使用 Metrics Server 服务(K3s默认已安装)。
1
2
3
4
5
6
#自动缩放
kubectl autoscale deployment/nginx-auto --min=3 --max=10 --cpu-percent=75
#查看自动缩放
kubectl get hpa
#删除自动缩放
kubectl delete hpa nginx-deployment

滚动更新

1
2
3
4
5
6
7
8
9
10
#查看版本和Pod
kubectl get deployment/nginx-deployment -owide
kubectl get pods

#更新容器镜像
kubectl set image deployment/nginx-deployment nginx=nginx:1.23
#滚动更新
kubectl rollout status deployment/nginx-deployment
#查看过程
kubectl get rs --watch

版本回滚

1
2
3
4
5
6
#查看历史版本
kubectl rollout history deployment/nginx-deployment
#查看指定版本的信息
kubectl rollout history deployment/nginx-deployment --revision=2
#回滚到历史版本
kubectl rollout undo deployment/nginx-deployment --to-revision=2

Service(服务)

Service将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。
Service为一组 Pod 提供相同的 DNS 名,并且在它们之间进行负载均衡。
Kubernetes 为 Pod 提供分配了IP 地址,但IP地址可能会发生变化。
集群内的容器可以通过service名称访问服务,而不需要担心Pod的IP发生变化。

Kubernetes Service 定义了这样一种抽象:
逻辑上的一组可以互相替换的 Pod,通常称为微服务。
Service 对应的 Pod 集合通常是通过选择算符来确定的。
举个例子,在一个Service中运行了3个nginx的副本。这些副本是可互换的,我们不需要关心它们调用了哪个nginx,也不需要关注 Pod的运行状态,只需要调用这个服务就可以了。

创建Service对象

  • ServiceType 取值

    ● ClusterIP:将服务公开在集群内部。kubernetes会给服务分配一个集群内部的 IP,集群内的所有主机都可以通过这个Cluster-IP访问服务。集群内部的Pod可以通过service名称访问服务。
    ● NodePort:通过每个节点的主机IP 和静态端口(NodePort)暴露服务。 集群的外部主机可以使用节点IP和NodePort访问服务。
    ● ExternalName:将集群外部的网络引入集群内部。
    ● LoadBalancer:使用云提供商的负载均衡器向外部暴露服务。

1
2
3
4
# port是service访问端口,target-port是Pod端口
# 二者通常是一样的
kubectl expose deployment/nginx-deployment \
--name=nginx-service --type=ClusterIP --port=80 --target-port=80
1
2
3
# 随机产生主机端口
kubectl expose deployment/nginx-deployment \
--name=nginx-service2 --type=NodePort --port=8080 --target-port=80

image

访问Service

  • 外部主机访问:192.168.56.109:32296。

    1.NodePort端口是随机的,范围为:30000-32767。
    2.集群中每一个主机节点的NodePort端口都可以访问。
    3.如果需要指定端口,不想随机产生,需要使用配置文件来声明。

img

1
2
3
4
5
6
#集群内访问
curl 10.43.65.187:80

#容器内访问
kubectl run nginx-test --image=nginx:1.22 -it --rm -- sh
curl nginx-service:80

Namespace(命名空间)

命名空间(Namespace)是一种资源隔离机制,将同一集群中的资源划分为相互隔离的组。命名空间可以在多个用户之间划分集群资源(通过资源配额)。

  • 例如我们可以设置开发、测试、生产等多个命名空间。

同一命名空间内的资源名称要唯一,但跨命名空间时没有这个要求。
命名空间作用域仅针对带有名字空间的对象,例如 Deployment、Service 等。
这种作用域对集群访问的对象不适用,例如 StorageClass、Node、PersistentVolume 等。

  • Kubernetes 会创建四个初始命名空间:

    ● default 默认的命名空间,不可删除,未指定命名空间的对象都会被分配到default中。
    ● kube-system Kubernetes 系统对象(控制平面和Node组件)所使用的命名空间。
    ● kube-public 自动创建的公共命名空间,所有用户(包括未经过身份验证的用户)都可以读取它。通常我们约定,将整个集群中公用的可见和可读的资源放在这个空间中。
    ● kube-node-lease 租约(Lease)对象使用的命名空间。每个节点都有一个关联的 lease 对象,lease 是一种轻量级资源。lease对象通过发送心跳,检测集群中的每个节点是否发生故障。

使用kubectl get lease -A查看lease对象

使用多个命名空间

● 命名空间是在多个用户之间划分集群资源的一种方法(通过资源配额)。
○ 例如我们可以设置开发、测试、生产等多个命名空间。
● 不必使用多个命名空间来分隔轻微不同的资源。
○ 例如同一软件的不同版本: 应该使用标签 来区分同一命名空间中的不同资源。
● 命名空间适用于跨多个团队或项目的场景。
○ 对于只有几到几十个用户的集群,可以不用创建命名空间。
● 命名空间不能相互嵌套,每个 Kubernetes 资源只能在一个命名空间中。

管理命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#创建命名空间
kubectl create namespace dev
#查看命名空间
kubectl get ns

#在命名空间内运行Pod
kubectl run nginx --image=nginx --namespace=dev
kubectl run my-nginx --image=nginx -n=dev

#查看命名空间内的Pod
kubectl get pods -n=dev

#查看命名空间内所有对象
kubectl get all
# 删除命名空间会删除命名空间下的所有内容
kubectl delete ns dev

切换当前命名空间

1
2
3
4
5
#查看当前上下文
kubectl config current-context

#将dev设为当前命名空间,后续所有操作都在此命名空间下执行。
kubectl config set-context $(kubectl config current-context) --namespace=dev

声明式对象配置

​ 云原生的代表技术包括:

  • 容器
  • 服务网格
  • 微服务
  • 不可变基础设施
  • 声明式API

管理对象

  • 命令行指令:例如,使用kubectl命令来创建和管理 Kubernetes 对象。命令行就好比口头传达,简单、快速、高效。但它功能有限,不适合复杂场景,操作不容易追溯,多用于开发和调试。
  • 声明式配置:kubernetes使用yaml文件来描述 Kubernetes 对象。声明式配置就好比申请表,学习难度大且配置麻烦。好处是操作留痕,适合操作复杂的对象,多用于生产。
  • 常用命令缩写

常用命令缩写

名称 缩写 Kind
namespaces ns Namespace
nodes no Node
pods po Pod
services svc Service
deployments deploy Deployment
replicasets rs ReplicaSet
statefulsets sts StatefulSet
1
2
kubectl create deploy my-deploy --image=nginx:1.22 --replicas=3
kubectl get po

YAML规范

○ 缩进代表上下级关系
○ 缩进时不允许使用Tab键,只允许使用空格,通常缩进2个空格
○ : 键值对,后面必须有空格
○ -列表,后面必须有空格
○ [ ]数组
○ #注释
○ | 多行文本块
○ —表示文档的开始,多用于分割多个资源对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
group: 
name: group-1
members:
- name: "Jack Ma"
UID: 10001
- name: "Lei Jun"
UID: 10002
words:
["I don't care money","R U OK"]
# comments
text: |
line
new line
3rd line

配置对象

在创建的 Kubernetes 对象所对应的 yaml文件中,需要配置的字段如下:
● apiVersion - Kubernetes API 的版本
● kind - 对象类别,例如Pod、Deployment、Service、ReplicaSet等
● metadata - 描述对象的元数据,包括一个 name 字符串、UID 和可选的 namespace
● spec - 对象的配置

使用yaml定义一个Pod:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: my-nginx
spec:
containers:
- name: nginx
image: nginx:1.22
ports:
- containerPort: 80

使用yaml文件管理对象:

1
2
3
4
5
6
#创建对象
kubectl apply -f my-pod.yaml
#编辑对象
kubectl edit nginx
#删除对象
kubectl delete -f my-pod.yaml

标签

标签(Labels) 是附加到对象(比如 Pod)上的键值对,用于补充对象的描述信息。
标签使用户能够以松散的方式管理对象映射,而无需客户端存储这些映射。
由于一个集群中可能管理成千上万个容器,我们可以使用标签高效的进行选择和操作容器集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: label-demo
labels: #定义Pod标签
environment: test
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.22
ports:
- containerPort: 80
1
2
kubectl get pod --show-labels
kubectl get pod -l environment=test,app=nginx

选择器

标签选择器 可以识别一组对象。标签不支持唯一性。
标签选择器最常见的用法是为Service选择一组Pod作为后端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector: #与Pod的标签一致
environment: test
app: nginx
ports:
# 默认情况下,为了方便起见,`targetPort` 被设置为与 `port` 字段相同的值。
- port: 80
targetPort: 80
# 可选字段
# 默认情况下,为了方便起见,Kubernetes 控制平面会从某个范围内分配一个端口号(默认:30000-32767)
nodePort: 30007

目前支持两种类型的选择运算:基于等值的和基于集合的。
多个选择条件使用逗号分隔,相当于And(&&)运算。

  • 等值选择
1
2
3
4
selector:
matchLabels: # component=redis && version=7.0
component: redis
version: 7.0
  • 集合选择
1
2
3
4
selector:
matchExpressions: # tier in (cache, backend) && environment not in (dev, prod)
- {key: tier, operator: In, values: [cache, backend]}
- {key: environment, operator: NotIn, values: [dev, prod]}

金丝雀发布(灰度发布)

金丝雀部署(canary deployment)也被称为灰度发布。早期,工人下矿井之前会放入一只金丝雀检测井下是否存在有毒气体。采用金丝雀部署,你可以在生产环境的基础设施中小范围的部署新的应用代码。一旦应用签署发布,只有少数用户被路由到它,最大限度的降低影响。如果没有错误发生,则将新版本逐渐推广到整个基础设施。

image (1)学习笔记/image (1).png)

部署过程

image (2)学习笔记/image (2).png)

部署第一个版本

​ 发布v1版本的应用,镜像使用nginx:1.22,数量为 3。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
apiVersion: v1
kind: Namespace
metadata:
name: dev
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-v1
namespace: dev
labels:
app: nginx-deployment-v1
spec:
replicas: 3
selector:
matchLabels: # 跟template.metadata.labels一致
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.22
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: canary-demo
namespace: dev
spec:
type: NodePort
selector: # 更Deployment中的selector一致
app: nginx
ports:
# By default and for convenience, the `targetPort` is set to the same value as the `port` field.
- port: 80
targetPort: 80
# Optional field
# By default and for convenience, the Kubernetes control plane will allocate a port from a range (default: 30000-32767)
nodePort: 30008
创建Canary Deployment

发布新版本的应用,镜像使用docker/getting-started,数量为 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-canary
namespace: dev
labels:
app: nginx-deployment-canary
spec:
replicas: 1
selector:
matchLabels: # 跟template.metadata.labels一致
app: nginx
template:
metadata:
labels:
app: nginx
track: canary
spec:
containers:
- name: new-nginx
image: docker/getting-started
ports:
- containerPort: 80
分配流量

查看服务kubectl describe svc canary-demo –namespace=dev

image (3)学习笔记/image (3).png)

  • 调整比例:待稳定运行一段时间后,扩大试用范围,将部署的v2版本数量调整为3,v1和v2的数量都是3个。
1
kubectl scale deployment/deploy-v2-canary --replicas=3 -n=dev
  • 下线旧版本:最后下线所有v1版本,所有服务升级为v2版本。
1
kubectl scale deployment/deploy-v1 --replicas=0 -n=dev
清空环境

使用namespace可以方便的清空环境:

1
kubectl delete all --all -n=dev

局限性

按照 Kubernetes 默认支持的这种方式进行金丝雀发布,有一定的局限性:

  • 不能根据用户注册时间、地区等请求中的内容属性进行流量分配
  • 同一个用户如果多次调用该 Service,有可能第一次请求到了旧版本的 Pod,第二次请求到了新版本的 Pod

在 Kubernetes 中不能解决上述局限性的原因是:Kubernetes Service 只在 TCP 层面解决负载均衡的问题,并不对请求响应的消息内容做任何解析和识别。如果想要更完善地实现金丝雀发布,可以考虑Istio灰度发布。

运行有状态应用

​ 以MySQL数据库为例,在kubernetes集群中运行一个有状态的应用。部署数据库几乎覆盖了kubernetes中常见的对象和概念:

● 配置文件–ConfigMap
● 保存密码–Secret
● 数据存储–持久卷(PV)和持久卷声明(PVC)
● 动态创建卷–存储类(StorageClass)
● 部署多个实例–StatefulSet
● 数据库访问–Headless Service
● 主从复制–初始化容器和sidecar
● 数据库调试–port-forward
● 部署Mysql集群–helm

创建MySQL数据库

  • 配置环境变量

  • 挂载卷

    • 将数据存储在容器中,一旦容器被删除,数据也会被删除。
    • 将数据存储到卷(Volume)中,删除容器时,卷不会被删除。
  • hostPath卷:hostPath 卷将主机节点上的文件或目录挂载到 Pod 中。示例

    • hostPath的type值:
    DirectoryOrCreate 目录不存在则自动创建。
    Directory 挂载已存在目录。不存在会报错。
    FileOrCreate 文件不存在则自动创建。不会自动创建文件的父目录,必须确保文件路径已经存在。
    File 挂载已存在的文件。不存在会报错。
    Socket 挂载 UNIX 套接字。例如挂载/var/run/docker.sock进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- containerPort: 3306
volumeMounts:
- mountPath: /var/lib/mysql #容器中的目录
name: data-volume
volumes:
- name: data-volume
hostPath:
# 宿主机上目录位置
path: /home/mysql/data
type: DirectoryOrCreate

注意:hostPath 仅用于在单节点集群上进行开发和测试,不适用于多节点集群;

例如,当Pod被重新创建时,可能会被调度到与原先不同的节点上,导致新的Pod没有数据。

在多节点集群使用本地存储,可以使用local卷。

ConfigMap与Secret

在Docker中,我们一般通过绑定挂载的方式将配置文件挂载到容器里。

在Kubernetes集群中,容器可能被调度到任意节点,配置文件需要能在集群任意节点上访问、分发和更新。

ConfigMap

  • ConfigMap 用来在键值对数据库(etcd)中保存非加密数据。一般用来保存配置文件。
  • ConfigMap 可以用作环境变量、命令行参数或者存储卷。
  • ConfigMap 将环境配置信息与 容器镜像 解耦,便于配置的修改。
  • ConfigMap 在设计上不是用来保存大量数据的。
  • 在 ConfigMap 中保存的数据不可超过 1 MiB。
  • 超出此限制,需要考虑挂载存储卷或者访问文件存储服务。
  • ConfigMap用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
volumeMounts:
- mountPath: /var/lib/mysql
name: data-volume
- mountPath: /etc/mysql/conf.d
name: conf-volume
readOnly: true
volumes:
- name: conf-volume
configMap:
name: mysql-config
- name: data-volume
hostPath:
# directory location on host
path: /home/mysql/data
# this field is optional
type: DirectoryOrCreate
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
mysql.cnf: |
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
init-connect='SET NAMES utf8mb4'

[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4
1
2
# 修改configMap,配置文件会被自动更新
kubectl edit cm mysql-config

Secret

Secret 用于保存机密数据的对象。一般由于保存密码、令牌或密钥等。

  • data字段用来存储 base64 编码数据。
  • stringData存储未编码的字符串。
  • Secret 意味着你不需要在应用程序代码中包含机密数据,减少机密数据(如密码)泄露的风险。Secret 可以用作环境变量、命令行参数或者存储卷文件。
  • Secret用法:
1
2
echo -n '123456' | base64
echo 'MTIzNDU2' | base64 --decode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
apiVersion: v1
kind: Secret
metadata:
name: mysql-password
type: Opaque
data:
PASSWORD: MTIzNDU2Cg==
---
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-password
key: PASSWORD
optional: false # 此值为默认值;表示secret已经存在了
volumeMounts:
- mountPath: /var/lib/mysql
name: data-volume
- mountPath: /etc/mysql/conf.d
name: conf-volume
readOnly: true
volumes:
- name: conf-volume
configMap:
name: mysql-config
- name: data-volume
hostPath:
# directory location on host
path: /home/mysql/data
# this field is optional
type: DirectoryOrCreate
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
mysql.cnf: |
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
init-connect='SET NAMES utf8mb4'

[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

卷(Volume)

将数据存储在容器中,一旦容器被删除,数据也会被删除。

卷是独立于容器之外的一块存储区域,通过挂载(Mount)的方式供Pod中的容器使用。

  • 使用场景

    • 卷可以在多个容器之间共享数据
    • 卷可以将容器数据存储在外部存储或云存储上
    • 卷更容易备份或迁移
  • 常见的卷类型

    • **临时卷(Ephemeral Volume)**:与 Pod 一起创建和删除,生命周期与 Pod 相同
      • emptyDir - 作为缓存或存储日志
      • configMap 、secret、 downwardAPI - 给Pod注入数据
    • 持久卷(Persistent Volume):删除Pod后,持久卷不会被删除
      • 本地存储 - hostPath、 local
      • 网络存储 - NFS
      • 分布式存储 - Ceph(cephfs文件存储、rbd块存储)
    • 投射卷(Projected Volumes):projected 卷可以将多个卷映射到同一个目录上

后端存储

一个集群中可以包含多种存储(如localNFSCeph或云存储)。

每种存储都对应一个存储类(StorageClass) ,存储类用来创建和管理持久卷,是集群与存储服务之间的桥梁。

管理员创建持久卷(PV)时,通过设置不同的StorageClass来创建不同类型的持久卷。

img

临时卷(EV)

  • 临时卷(Ephemeral Volume)
    • 与 Pod 一起创建和删除,生命周期与 Pod 相同
    • emptyDir - 初始内容为空的本地临时目录
    • configMap - 为Pod注入配置文件
    • secret - 为Pod注入加密数据

emptyDir

emptyDir会创建一个初始状态为空的目录,存储空间来自本地的 kubelet 根目录或内存(需要将emptyDir.medium设置为"Memory")。

通常使用本地临时存储来设置缓存、保存日志等。例如,将redis的存储目录设置为emptyDir。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: redis-pod
spec:
containers:
- name: redis
image: redis
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}

configMap卷和secret卷

注意:这里的configMap和secret代表的是卷的类型不是configMap和secret对象
删除Pod并不会删除ConfigMap对象和secret对象。

image

​ configMap卷和Secret卷是一种特殊类型的卷,kubelet引用configMap和Secret中定义的内容,在Pod所在节点上生成一个临时卷,将数据注入到Pod中。删除Pod,临时卷也会被删除。

  • 临时卷位于Pod所在节点的/var/lib/kubelet/pods目录下。

image (1)学习笔记/image (1)-1735993788608-13.png)

持久卷(PV)与持久卷声明(PVC)

持久卷(Persistent Volume):删除Pod后,卷不会被删除

  • 本地存储
    • hostPath - 节点主机上的目录或文件(仅供单节点测试使用;多节点集群请用 local 卷代替)
    • local - 节点上挂载的本地存储设备(不支持动态创建卷)
  • 网络存储
    • NFS - 网络文件系统 (NFS)
  • 分布式存储
    • Ceph(cephfs文件存储、rbd块存储)

持久卷(PV)和持久卷声明(PVC)

  • 持久卷(PersistentVolume,PV) 是集群中的一块存储。可以理解为一块虚拟硬盘。
    • 持久卷可以由管理员事先创建, 或者使用存储类(Storage Class)根据用户请求来动态创建。
    • 持久卷属于集群的公共资源,并不属于某个namespace;
  • 持久卷声明(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。
    • PVC声明好比申请单,它更贴近云服务的使用场景,使用资源先申请,便于统计和计费。
    • Pod 将 PVC 声明当做存储卷来使用,PVC 可以请求指定容量的存储空间和访问模式 。PVC对象是带有namespace的。

创建持久卷(PV)

​ 创建持久卷(PV)是服务端的行为,通常集群管理员会提前创建一些常用规格的持久卷以备使用。hostPath仅供单节点测试使用,当Pod被重新创建时,可能会被调度到与原先不同的节点上,导致新的Pod没有数据。多节点集群使用本地存储,可以使用local卷。创建local类型的持久卷,需要先创建存储(StorageClass)。

1
2
3
4
5
6
7
# 创建本地存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: Immediate

local卷不支持动态创建,必须手动创建持久卷(PV)。
创建local类型的持久卷,必须设置nodeAffinity(节点亲和性)。
调度器使用nodeAffinity信息来将使用local卷的 Pod 调度到持久卷所在的节点上,不会出现Pod被调度到别的节点上的情况。

注意:local卷也存在自身的问题,当Pod所在节点上的存储出现故障或者整个节点不可用时,Pod和卷都会失效,仍然会丢失数据,因此最安全的做法还是将数据存储到集群之外的存储或云存储上。

  • 创建PV
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-1
spec:
capacity:
storage: 4Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage #通过指定存储类来设置卷的类型
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-worker1

创建持久卷声明(PVC)

持久卷声明(PVC)是用户端的行为,用户在创建Pod时,无法知道集群中PV的状态(名称、容量、是否可用等),用户也无需关心这些内容,只需要在声明中提出申请,集群会自动匹配符合需求的持久卷(PV)。Pod使用持久卷声明(PVC)作为存储卷。

image (2)学习笔记/image (2)-1735994597912-18.png)

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-pv-claim
spec:
storageClassName: local-storage # 与PV中的storageClassName一致
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
1
kubectl apply -f pvc.yaml

使用PVC作为卷

Pod 的配置文件指定了 PersistentVolumeClaim,但没有指定 PersistentVolume。 对 Pod 而言,PersistentVolumeClaim 就是一个存储卷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- containerPort: 3306
volumeMounts:
- mountPath: /var/lib/mysql #容器中的目录
name: local-mysql-data
volumes:
- name: local-mysql-data
persistentVolumeClaim:
claimName: local-pv-claim

绑定

创建持久卷声明(PVC)之后,集群会查找满足要求的持久卷(PV),将 PVC 绑定到该 PV上。

PVC与PV之间的绑定是一对一的映射关系,绑定具有排他性,一旦绑定关系建立,该PV无法被其他PVC使用。

PVC可能会匹配到比声明容量大的持久卷,但是不会匹配比声明容量小的持久卷。

例如,即使集群上存在多个 50 G大小的 PV ,他们加起来的容量大于100G,也无法匹配100 G大小的 PVC。

找不到满足要求的 PV ,PVC会无限期地处于未绑定状态(Pending) , 直到出现了满足要求的 PV时,PVC才会被绑定。

访问模式

  • ReadWriteOnce
    • 卷可以被一个节点以读写方式挂载,并允许同一节点上的多个 Pod 访问。
  • ReadOnlyMany
    • 卷可以被多个节点以只读方式挂载。
  • ReadWriteMany
    • 卷可以被多个节点以读写方式挂载。
  • ReadWriteOncePod
    • 卷可以被单个 Pod 以读写方式挂载。 集群中只有一个 Pod 可以读取或写入该 PVC。
    • 只支持 CSI 卷以及需要 Kubernetes 1.22 以上版本。

卷的状态

  • Available(可用)– 卷是一个空闲资源,尚未绑定到任何;
  • Bound(已绑定)– 该卷已经绑定到某个持久卷声明上;
  • Released(已释放)– 所绑定的声明已被删除,但是资源尚未被集群回收;
  • Failed(失败)– 卷的自动回收操作失败。

卷模式

卷模式(volumeMode)是一个可选参数。针对 PV 持久卷,Kubernetes 支持两种卷模式(volumeModes):

  • Filesystem(文件系统):默认的卷模式。
  • Block(块):将卷作为原始块设备来使用。

存储类(StorageClass)

创建持久卷(PV)

  • 静态创建
    • 管理员预先手动创建
    • 手动创建麻烦、不够灵活(local卷不支持动态创建,必须手动创建PV)
    • 资源浪费(例如一个PVC可能匹配到比声明容量大的卷)
    • 对自动化工具不够友好
  • 动态创建
    • 根据用户请求按需创建持久卷,在用户请求时自动创建
    • 动态创建需要使用存储类(StorageClass)
    • 用户需要在持久卷声明(PVC)中指定存储类来自动创建声明中的卷。
    • 如果没有指定存储类,使用集群中默认的存储类。

存储类(StorageClass)

一个集群可以存在多个存储类(StorageClass)来创建和管理不同类型的存储。每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件创建持久卷。 该字段必须指定。

img

Local Path Provisioner

image (3)学习笔记/image (3)-1735995429450-25.png)

K3s自带了一个名为local-path的存储类(StorageClass),它支持动态创建基于hostPath或local的持久卷。

创建PVC后,会自动创建PV,不需要再去手动的创建PV。

删除PVC,PV也会被自动删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-path-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- containerPort: 3306
volumeMounts:
- mountPath: /var/lib/mysql #容器中的目录
name: local-mysql-data
volumes:
- name: local-mysql-data
persistentVolumeClaim:
claimName: local-path-pvc

卷绑定模式

volumeBindingMode用于控制什么时候动态创建卷和绑定卷

  • Immediate立即创建:创建PVC后,立即创建PV并完成绑定。
  • WaitForFirstConsumer 延迟创建:当使用该PVC的 Pod 被创建时,才会自动创建PV并完成绑定。

回收策略(Reclaim Policy)

回收策略告诉集群,当用户删除PVC 对象时, 从PVC中释放出来的PV将被如何处理:

  • 删除(Delete,为默认配置):当PVC被删除时,关联的PV 对象也会被自动删除。
  • 保留(Retain):当 PVC 对象被删除时,PV 卷仍然存在,数据卷状态变为”已释放(Released)”。