文章目录
- K8S基础
- 运行有状态应用
-
- 创建MySQL数据库
- ConfigMap
- Secret
- 卷Volume
-
- 常见的卷类型
-
- [临时卷Ephemeral Volume](#临时卷Ephemeral Volume)
- [持久卷Persistent Volume](#持久卷Persistent Volume)
- [投射卷Projected Volumes](#投射卷Projected Volumes)
- 后端存储
- [临时卷EV(Ephemeral Volume)](#临时卷EV(Ephemeral Volume))
- 持久卷pv和持久卷声明pvc
-
- [持久卷PV(Persistent Volume)](#持久卷PV(Persistent Volume))
- [持久卷声明PVC(Persistent Volume Claim)](#持久卷声明PVC(Persistent Volume Claim))
- 创建持久卷PV
- 创建持久卷声明(PVC)
- 使用持久卷声明
- 卷的状态和绑定模式
- 存储类StorageClass
- 有状态应用集StatefulSet
- [无头服务Headless Service](#无头服务Headless Service)
- MySQL主从复制
- port-forward端口转发
- Helm的用法
- Helm部署MySQL集群
K8S基础
创建centos虚拟机
txt
注意事项:先要保障网络能和互联网互通,笔者采用桥接模式与仅主机模式。
开启桥接模式可能导致火绒安全软件6.5.0.6版本ARP防护误报,可能导致虚拟机无法访问网络。同样宿主机防火墙可能导致无法访问网络。
shell
# 网络固定IP
sudo vi /etc/sysconfig/network-scripts/ifcfg-enp0s8
# ifcfg-enp0s3 文件内容
ONBOOT=yes
IPADDR=192.168.56.107
NETMASK=255.255.255.0
GATWAY=192.168.56.100
# 这里要优先用谷歌域名解析服务,谷歌解析不了的再用内网解析。否则有可能收到神秘的DNS污染。
# 特别注意,谷歌解析放前面,否则可能导致拉取镜像的网络各种问题。如果遇到可以看本文下面的解决方案。
DNS1=8.8.8.8
DNS2=192.168.56.100
# 重启
sudo systemctl restart network
# 服务器命名
# 重命名
sudo hostnamectl set-hostname k3s-m
# 加入hosts
sudo sed -i "s/^127.0.0.1.*/127.0.0.1 localhost localhost.localdomain k3s-m/" /etc/hosts
# 验证
hostnamectl
hostname
# 重新加载bash
exec bash
# 关闭防护墙
systemctl disable firewalld --now
# 修改镜像源地址
# 忽略 GPG 检查(可选)
wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
sed -i 's/gpgcheck=1/gpgcheck=0/' /etc/yum.repos.d/CentOS-Base.repo
yum clean all && yum makecache
# 设置selinux(需要联网)
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部署
shell
#主节点 离线安装
INSTALL_K3S_SKIP_DOWNLOAD=true ./install.sh
#安装完成后,查看节点状态
kubectl get node
#查看token
cat /var/lib/rancher/k3s/server/node-token
# K106f611e85af3f3248326c5b3a9173f425489216a41c2ff88c2a2c5cb257056a92::server:4b68867878b608c9daea6bad5a82194a
#########################################################################################################
# 注意配置/etc/hosts
192.168.56.107 master
192.168.56.108 work1
# work节点加入主节点
INSTALL_K3S_SKIP_DOWNLOAD=true \
> K3S_URL=https://master:6443 \
> K3S_TOKEN=K106f611e85af3f3248326c5b3a9173f425489216a41c2ff88c2a2c5cb257056a92::server:4b68867878b608c9daea6bad5a82194a \
> ./install.sh
配置k3s容器containerd镜像

shell
cat /var/lib/rancher/k3s/agent/etc/containerd/config.toml
vi /etc/rancher/k3s/registries.yaml
# 配置文件内容
mirrors:
docker.io:
endpoint:
- "https://docker.1ms.run/"
# 重启主节点
systemctl restart k3s
# 重启从节点
systemctl restart k3s-agent
笔者在拉取镜像的时候遇到了由于高墙技术各种网络问题。
可以通过阿里云镜像仓库库来使用。
https://cr.console.aliyun.com/cn-hangzhou/instance/repositories
也可以使用本地自建的镜像仓库
shell
# 本地docker自建仓库
docker run -d -p 5000:5000 --name registry registry:2
# k3s仓库配置
mirrors:
"192.168.56.1:5000":
endpoint:
- "http://192.168.56.1:5000"
insecure: true # 信任非https协议
# 将宿主机的docker镜像推送到镜像仓库
docker pull nginx:1.9.3
docker tag nginx:1.9.3 192.168.122.1:5000/nginx:1.9.3
docker push 192.168.122.1:5000/nginx:1.9.3
# 拉取镜像
kubectl run mynginx --image=192.168.122.1:5000/nginx:1.9.3
配置镜像未生效,可能是走了IPV6
shell
# 系统禁用IPV6协议
sudo tee /etc/sysctl.d/99-disable-ipv6.conf > /dev/null <<EOF
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
# 使配置生效
sudo sysctl --system
# 查看是否生效
ip a | grep inet6
# 查看DNS服务配置,删除IPV6的配置
[root@k3s-w1 k3s]# cat /etc/resolv.conf
# Generated by NetworkManager
nameserver 192.168.10.1
nameserver 192.168.56.100
nameserver 8.8.8.8
# 重启k3s
systemctl restart k3s
2025年4月测试可用镜像源配置
/etc/rancher/k3s/registries.yaml
yaml
mirrors:
docker.io:
endpoint:
- "https://docker.m.daocloud.io"
gcr.io:
endpoint:
- "https://gcr.m.daocloud.io"
quay.io:
endpoint:
- "https://quay.m.daocloud.io"
registry.k8s.io:
endpoint:
- "https://k8s.m.daocloud.io"
"192.168.56.1:5000":
endpoint:
- "http://192.168.56.1:5000"
insecure: true
configs:
docker.io:
tls:
insecure_skip_verify: true
gcr.io:
tls:
insecure_skip_verify: true
quay.io:
tls:
insecure_skip_verify: true
registry.k8s.io:
tls:
insecure_skip_verify: true
Pod容器
是一个或多个容器组,是k8s中创建和管理的最小对象。是最小调度单位,同一个pod容器会被按跑到同一节点一起调度。每一个pod有唯一的IP地址。
shell
# 创建容器
kubectl run mynginx --image=192.168.122.1:5000/nginx:1.9.3
# 获取容器状态
kubectl get pods
# 获取容器详细信息
kubectl get pods -owide
# 查看容器日志
kubectl logs -f mynginx
# 查看容器描述信息
kubectl describe mynginx
# 删除容器
kubectl delete pod mynginx
# 进入容器
kubectl exec -it mynginx -- /bin/bash
# 退出容器
exist;
# 退出时销毁容器
kubectl run mybusybox --image=busybox -it --rm
Deployment(部署)和ReplicaSet(副本集)
Deployment是对ReplicaSet更高级的抽象。使Pod拥有多副本、自愈、扩缩容、滚动升级等能力。
ReplicaSet是一个Pod集合。可以设置Pod数量,确保任何时间都有指定数量的Pod副本在运行。通常不直接使用ReplicaSet,而是在Deployment中声明。
shell
# 创建nginx部署
kubectl create deployment nginx-deploy --image=nginx:1.21 --replicas=3
# 获取部署信息
kubectl get deploy # kubectl get deployment
# 删除部署信息
kubectl delete deployment nginx-deploy
# 获取副本信息
kubectl get replicaSet
# 手动调整副本数量
kubectl scale deploy nginx-deploy --replicas=2
# 获取副本信息(持续观察)
kubectl get replicaSet -=watch
# 自动扩缩容
kubectl autoscale deployment/nginx-auto --min=1 --max=3 --cpu-percent=70
# 查看自动扩缩容
kubectl get hpa
# 删除自动扩缩容
kubectl delete hpa nginx-deployment
# 监控副本状态
kubectl get rs --watch
# 升级副本版本
kubectl set image deploy/nginx-deploy nginx=1.23
# 部署回滚
# 查看部署历史版本
kubectl rollout history deploy/nginx-dploy
# 查看版本详情
kubectl rollout history deploy/nginx-dploy --revision=1
# 回滚版本
kubectl rollout undo deploy/nginx-dploy --to-revision=1
镜像拉取失败问题排查
shell
# 获取deploy信息,发现有一个pod未启动
[root@k3s-m ~]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-dploy 2/3 3 2 8h
# 主节点上的nginx pod拉镜像失败了
[root@k3s-m ~]# kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox1 0/1 Completed 0 8h 10.42.1.45 k3s-w1 <none> <none>
nginx-dploy-5d45cdbf87-5q9d7 1/1 Running 1 (8m53s ago) 8h 10.42.1.49 k3s-w1 <none> <none>
nginx-dploy-5d45cdbf87-4cphq 1/1 Running 1 (8m53s ago) 8h 10.42.1.50 k3s-w1 <none> <none>
mynginx 1/1 Running 2 (8m53s ago) 21h 10.42.1.51 k3s-w1 <none> <none>
nginx-dploy-5d45cdbf87-96vbx 0/1 ImagePullBackOff 0 7h41m 10.42.0.59 k3s-m <none> <none>
# 查看域名解析,主节点的域名被解析到了内网地址。应该是主机的DNS被某神秘力量污染了。
[root@k3s-m ~]# dig docker.m.daocloud.io +short
198.18.0.9
[root@k3s-w1 ~]# dig docker.m.daocloud.io +short
139.224.82.144
# 那我们直接指定DNS服务器解析域名
[root@k3s-m ~]# dig @8.8.8.8 docker.m.daocloud.io +short
139.224.82.144 # 返回的真实IP,可以用上面的命令在work节点试一下
# 好了,把域名IP映射写入hosts
[root@k3s-m ~]# echo "139.224.82.144 docker.m.daocloud.io" >> /etc/hosts
# 先ping一下上面的域名,看到返回正确的IP地址了
# 重启k3s主节点
systemctl restart k3s
# 如果是从节点出现故障,可以重启从节点
systemctl restart k3s-agent
# 验证
[root@k3s-m ~]# dig docker.m.daocloud.io +short
139.224.82.144
# 配置没有生效可以排查文件权限
chmod 644 /etc/rancher/k3s/registries.yaml
# 检查是否走了镜像加速
[root@k3s-m k3s]# k3s ctr images pull docker.m.daocloud.io/library/nginx:1.21
docker.m.daocloud.io/library/nginx:1.21: resolving |--------------------------------------|
elapsed: 3.4 s total: 0.0 B (0.0 B/s)
# 主节点不参与pod调度,临时驱逐主节点
kubectl cordon k3s-m
kubectl drain k3s-m --ignore-daemonsets --delete-emptydir-data
# 恢复主节点
kubectl uncordon k3s-m
# 检查域名映射等配置,这里不太能理解第一次未配置不能访问,第二次配置了不能访问。
[root@k3s-m ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain k3s-m
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# 139.224.82.144 docker.m.daocloud.io
# 139.224.82.144 auth.m.daocloud.io
Service服务
将运行在一组Pods上的应用程序公开为网络服务的抽象方法。为一组pod提供相同的DNS名称,并在他们之间进行负载均衡。K8s为Pod分配了IP地址,但是IP可能会变化。集群内的容器可以通过service名称访问服务,而不需要担心Pod的IP变化。
shell
# 将部署deploy公开为服务。port是服务端口,target-port是容器内端口
kubectl expose deploy/nginx-deploy --name=nginx-service --port=8080 --target-port=80
# 获取服务
kubectl get service
# 访问测试(注意是nginx-service的cluster-ip,而不是虚拟机的IP也不是本机IP)
curl CLUSTER-IP:8080
# 查看服务详情
kubectl describe service nginx-service
# 将部署deploy公开为服务。port是服务端口,target-port是容器内端口。NodePort除了内部访问端口,还会随机一个外部访问端口。外部主机可以通过任意的节点的该端口访问到服务。。
kubectl expose deploy/nginx-deploy --name=nginx-outside --type=NodePort --port=8081 --target-port=80
# 访问:访问:curl 10.43.148.62:8081 或者 curl 127.0.0.1:30135
nginx-outside NodePort 10.43.148.62 <none> 8081:30135/TCP 5s
ServiceType取值
- ClusterIP:将服务公开在集群内部。K8S会给服务分配一个集群内部的IP,集群内的所有主机都可以通过这个Cluster-IP访问服务。集群内部的Pod可以通过Service名称访问服务。
- NodePort:通过每个节点的主机IP和静态端口(NodePort)暴露服务。集群的外部主机可以使用节点IP和NodePort访问服务。
- ExternalName:将集群外部的网络引入集群内部。
- LoadBalancer:使用云提供商的负载均衡器向外部暴露服务。
NameSpace命名空间
一种资源隔离机制,将同一集群中的资源划分为相互隔离的组。
shell
# k8s创建的四个默认命名空间
[root@k3s-m k3s]# kubectl get ns
NAME STATUS AGE
# 默认命名空间,不可删除。未指定命名空间对象会被分配到default。
default Active 5d13h
# 系统对象(控制平面和Node组件)。
kube-system Active 5d13h
# 自动创建的公共命名空间,所有用户都可以读取。
kube-public Active 5d13h
# 租约(Lease)对象使用的命名空间。每个节点都有一个关联的Lease对象,lease是一种轻量级资源。Lease对象通过心跳检测集群中的每个节点是否故障。
kube-node-lease Active 5d13h
# 查看lease对象
kubectl get lease -A
shell
#创建命名空间
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
声明式对象配置YAML
k8s管理对象的两种方式
- 命令行指令:kubectl 命令来创建和管理k8s对象。多用于开发调试。
- 声明式配置:k8s使用yaml来描述k8s对象。声明式配置类似申请表,优点是操作留痕,适合操作复杂对象和生产操作。
常见命令缩写

配置对象
在创建的k8s对象所在的yaml文件中,需要配置的字段如下:
- apiVersion:k8s的api版本
- kind:对象类别,例如Pod、Deployment、Service、ReplicaSet。
- metadata:描述对象的元数据,包括一个name字符串、UID和可选的namespace。
- spec:对象的配置。
案例(使用yaml方式创建pod)
shell
vi my-pod.yaml
# 创建pod
kubectl apply -f my-pod.yaml
# 查看pod
kubectl get pods
# 删除pod
kubectl delete -f my-pod.yaml
yaml
apiVersion: v1
kind: Pod
metadata:
name: my-nginx
spec:
containers:
- name: nginx
image: nginx:1.22
ports:
- containerPort: 80
标签(Labels)
标签是附加到对象上的键值对,用于补充对象的描述信息。标签使用户额能够以松散的方式管理对象映射,而无需客户端存储这些映射。
由于一个集群中可以管理成千上万个容器,我们可以使用标签高校的进行下选择和操作容器集合。
shell
vi label-pod.yaml
# 创建pod
kubectl apply -f label-pod.yaml
# 查看pod的labels
kubectl get pod --show-labels
# 根据标签过滤
kubectl get pod -l "app=nginx"
# 多个标签用逗号分割
kubectl get pod -l "app=nginx,env=prod"
yaml
apiVersion: v1
kind: Pod
metadata:
name: label-demo
labels: #定义Pod标签
env: test
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.22
ports:
- containerPort: 80
选择器
标签选择器可以识别一组对象。标签不支持唯一性。标签选择器是最常见的用法是为Serivce选择一组pod作为后端。
shell
vi my-service.yaml
# 创建service
kubectl apply -f my-service.yaml
# 获取服务
kubectl get svc
# 查看service详情
kubectl describe svc/my-service
yaml
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
容器和镜像
容器运行时接口CRI
kubelet运行在每个节点上,用于管理和维护pod容器状态。容器运行时接口是kubelet和容器运行时之间通信的主要协议。将kubelet与容器运行时解耦。(理论上,实现了CRI接口的容器引擎,都可以作为k8s的容器运行时。docker没有实现CRI接口,k8s使用dockershim来兼容docker。但是从V1.24移除了dockershim)。
crictl是一个兼容cri的容器运行时命令,用法和docker命令一样,可以用来检查和调用底层的运行时容器。
shell
# 查看正在运行的容器
[root@k3s-m k3s]# crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
a2458a9654710 72463d8000a35 3 hours ago Running traefik 2 f689575b9f5ac traefik-7d647b7597-pkd4f
# 查看镜像
[root@k3s-m k3s]# crictl images
IMAGE TAG IMAGE ID SIZE
docker.io/library/nginx 1.21 0e901e68141fd 56.7MB
# 查看用法
[root@k3s-m k3s]# crictl
NAME:
crictl - client for CRI
USAGE:
crictl [global options] command [command options] [arguments...]
VERSION:
v1.25.0-k3s1
COMMANDS:
attach Attach to a running container
ctr命令:导入导出镜像
ctr命令是containerd的命令行接口。
docker镜像导入containerd。当无法拉取镜像的时候,导入镜像需要在每个节点都执行。
shell
# windows docker上导出镜像
docker save redis > e:\opt\redis.tar
# ctr导入镜像。-n指定命名空间,--platform指定平台。
[root@k3s-m k3s]# ctr -n k8s.io images import redis.tar --platform linux/amd64
# ctr导入镜像并指定tag版本
[root@k3s-m k3s]# ctr -n k8s.io images import redis-6-alpine.tar --platform linux/amd64
unpacking docker.io/library/redis:6-alpine (sha256:1165be66730a5c50a8d1a404030cf6ec3ade258d2c487457377695c921226dfc)...done
unpacking docker.io/library/redis:latest (sha256:068c3f0f2fc319974b18739deb2e46157167b4d89b031af2f873790efe2f5bb6)...done
# 查看镜像
[root@k3s-m k3s]# crictl images
IMAGE TAG IMAGE ID SIZE
docker.io/library/redis 6-alpine 6dd588768b9ba 31MB
docker.io/library/redis latest 6c199afc1dae9 120MB
# 导出镜像
ctr -n k8s.io images export redis.tar docker.io/library/redis:latest --platform linux/amd64
灰度发布(金丝雀发布)
小范围部署测试应用代码。确认没有错误发生再推广到整个基础设施。
部署过程
逐步部署V2版本,直到V1版本全部下线。
部署V1版本
shell
[root@k3s-m canary-demo]# vi deploy-v1.yaml
[root@k3s-m canary-demo]# kubectl apply -f deploy-v1.yaml
namespace/dev created
deployment.apps/nginx-deployment-v1 created
service/canary-demo created
[root@k3s-m canary-demo]# kubectl get all -n=dev
NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-v1-645549fcf7-6rg8h 1/1 Running 0 76s
pod/nginx-deployment-v1-645549fcf7-tv4rx 1/1 Running 0 76s
pod/nginx-deployment-v1-645549fcf7-b7wlb 1/1 Running 0 76s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/canary-demo NodePort 10.43.217.50 <none> 80:30008/TCP 76s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment-v1 3/3 3 3 76s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-v1-645549fcf7 3 3 3 76s
# 访问服务
curl 10.43.217.50:80
# 查看详情
[root@k3s-m canary-demo]# kubectl describe service/canary-demo -n=dev
Name: canary-demo
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=nginx
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.43.217.50
IPs: 10.43.217.50
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 30008/TCP
Endpoints: 10.42.0.119:80,10.42.1.123:80,10.42.1.124:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
yaml
# 创建Namespace
apiVersion: v1
kind: Namespace
metadata:
name: dev
---
# 创建Deployment
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
---
# 创建外部访问的Service
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.
拷贝配置文件,并修改。deploy-canary.yaml。
shell
[root@k3s-m canary-demo]# vi deploy-canary.yaml
[root@k3s-m canary-demo]# kubectl apply -f deploy-canary.yaml
deployment.apps/nginx-deployment-canary created
[root@k3s-m canary-demo]# kubectl get all -n=dev
NAME READY STATUS RESTARTS AGE
pod/nginx-deployment-v1-645549fcf7-6rg8h 1/1 Running 0 14m
pod/nginx-deployment-v1-645549fcf7-tv4rx 1/1 Running 0 14m
pod/nginx-deployment-v1-645549fcf7-b7wlb 1/1 Running 0 14m
pod/nginx-deployment-canary-74577469b5-w6rp6 1/1 Running 0 18s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/canary-demo NodePort 10.43.217.50 <none> 80:30008/TCP 14m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment-v1 3/3 3 3 14m
deployment.apps/nginx-deployment-canary 1/1 1 1 18s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-v1-645549fcf7 3 3 3 14m
replicaset.apps/nginx-deployment-canary-74577469b5 1 1 1 18s
[root@k3s-m canary-demo]# kubectl describe service/canary-demo -n=dev
Name: canary-demo
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=nginx
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.43.217.50
IPs: 10.43.217.50
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 30008/TCP
Endpoints: 10.42.0.119:80,10.42.1.123:80,10.42.1.124:80 + 1 more...
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
yaml
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 # 由于app=nginx标签与service中的选择器标签一致,所以该部署下的pod会自动加入到service服务。
track: canary
spec:
containers:
- name: new-nginx
image: docker/getting-started
ports:
- containerPort: 80
通过多次访问,会发现有一次看到了这个页面
调整副本数量
shell
# 增加新版本副本数量
[root@k3s-m canary-demo]# kubectl scale deploy nginx-deployment-canary --replicas=3 -n=dev
deployment.apps/nginx-deployment-canary scaled
[root@k3s-m canary-demo]# kubectl get deploy -n=dev
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment-v1 3/3 3 3 21m
nginx-deployment-canary 3/3 3 3 6m53s
# 减少旧版本副本数量
[root@k3s-m canary-demo]# kubectl scale deploy nginx-deployment-v1 --replicas=0 -n=dev
deployment.apps/nginx-deployment-v1 scaled
[root@k3s-m canary-demo]# kubectl get deploy -n=dev
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment-canary 3/3 3 3 8m34s
nginx-deployment-v1 0/0 0 0 22m
清空测试环境
shell
kubectl delete all --all -n=dev
局限性
按照k8s默认支持的这种方式进行灰度发布,存在一定局限性。
- 不能根据用户注册时间、地区等请求中的属性进行流量分配。
- 同一个用户如果多次调用该Serivice,可能多次到不同的新旧版本。
在k8s中不能解决上述问题的原因是:k8s service只在tcp层面解决了负载均衡问题,并不对请求响应的消息内容做任何解析和识别。如果要更加完善的实现灰度发布,可以考虑lstio灰度发布。
运行有状态应用
以MySQL为例,在k8s集群中运行一个有状态的应用。部署数据库几乎覆盖了k8s中常见的对象和概念:
- 配置文件--ConfigMap
- 保存密码--Secret
- 数据存储--持久卷(PV)和持久卷声明(PVC)
- 动态创建卷--存储类(StorageClass)
- 部署多个实例--StatefulSet
- 数据库访问--Headless Service
- 主从复制--初始化容器和sidecar
- 数据库调试--端口转发(port-forward)
- 部署MySQL集群--helm
创建MySQL数据库
配置环境变量
- 使用MySQL镜像创建Pod,需要使用环境变量设置MySQL初始密码。
挂在卷(Volume)
- 将数据存储在容器中,随容器使用过程中数据变多,不方便数据迁移和恢复
- 一旦容器被删除,数据也会被删除。将数据存储到卷中,删除容器时,卷不会被删除。
hostPath卷
hostPath卷将主机节点上的文件或目录挂载到Pod中。
示例
配置示例:
shell
vi mysql-pod.yaml
kubectl apply -f mysql-pod.yaml
kubectl get pod -owide
yaml
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
ConfigMap
创建Pod的时候通常要使用自己的配置文件,在Docker中可以通过绑定挂载目录的方式将配置文件挂载到容器。
但是k8s集群中的容器可能被调度到任意节点,配置文件需要能在集群任意节点上访问、分发、更新。
- ConfigMap用来在键值对数据库(etcd)中存储非加密数据。
- ConfigMap可以用作环境变量、命令行参数或者数据存储卷。
- ConfigMap将环境配置信息与容器镜像解耦,便于配置修改。
- ConfigMap在设计上不是用来保存大数据的。保存到数据不可超过1M,超出需要考虑挂在存储卷或者文件服务。
示例
shell
vi mysql-pod1.yaml
kubectl delete pod mysql-pod
kubectl apply -f mysql-pod1.yaml
# 查看configMap
kubectl describe cm mysql-config
# 进入pod容器
kubectl exec -it mysql-pod -- bash
# 连接数据库
mysql -uroot -p123456
# 查看配置的字符集
SHOW VARIABLES LIKE '%char%';
# 修改配置文件
kubectl edit cm mysql-config
# 查看配置
kubectl get cm mysql-config -o yaml
# 再次进入容器
kubectl exec -it mysql-pod -- bash
# 查看mysql配置文件
cat /etc/mysql/conf.d/mysql.cnf
yaml
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
# 第二步,在volumeMounts添加config卷绑定
- mountPath: /etc/mysql/conf.d
name: conf-volume
readOnly: true # 设置只读
volumes:
- name: conf-volume
configMap: # 第一步,在pod中使用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配置文件
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
Secret
Secret用于保存机密数据的对象。一般用于保存密码、令牌或密钥等。
data字段用来存储base64编码数据。stringData存储未编码的字符串。
Secret意味着不需要在应用程序代码中包含机密数据,减少泄露风险。
Secret可以用作环境变量、命令行参数或者存储卷文件。
Secret配置示例
shell
echo -n '123456' | base64
echo 'MTIzNDU2' | base64 --decode
vi mysql-pod2.yaml
kubectl delete pod mysql-pod
# 创建pod
[root@k3s-m 3_state_mysql]# kubectl apply -f mysql-pod2.yaml
secret/mysql-password created
pod/mysql-pod created
configmap/mysql-config configured
# 查看Secret
kubectl describe secret/mysql-password
# 在环境变量中使用secret,当secret修改后,环境变量不会被自动更新,需要重启pod容器。
yaml
apiVersion: v1
kind: Secret
metadata:
name: mysql-password # secret名称
type: Opaque
data: # 存储base64编码数据,不允许明文存储
PASSWORD: MTIzNDU2Cg==
---
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- name: mysql
image: mysql:5.7
env: # 在mysql容器中,密码是通过环境变量的方式设置的
- name: MYSQL_ROOT_PASSWORD
valueFrom: # 将secret用作环境变量
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注入数据
注意:这里的configMap、secret是卷类型,而不是对象。
持久卷Persistent Volume
删除Pod后,持久卷不会被删除。
- 本地存储:hostPath、local
- 网路存储:NFS
- 分布式存储:Ceph(cephfs文件存储、rbd块存储)
投射卷Projected Volumes
projected卷可以将多个卷映射到同一个目录上。
后端存储
一个集群中可以有多个存储,每一种存储都对应一个存储类。
存储类是集群与存储之间沟通的桥梁。
通过不同的存储类可以创建不通的持久卷。这些持久卷可以是预先创建好的也可以删动态创建的。

临时卷EV(Ephemeral Volume)
- 与pod一起创建和删除,生命周期与Pod相同
- emptyDir:初始内容为空的本地临时目录,存储空间来自本地kubelet根目录或者内存(需要将emptyDir.medium设置为"Memory")。通常使用本地临时存储来设置缓存、保存日志等。
- configMap为Pod注入配置文件、Secret为Pod注入加密数据
- configMap和Secret是一种特殊类型的卷,kubelet引用configMap和secret中定义的内容,在Pod所在节点生成一个临时卷,将数据注入到Pod中。删除Pod时临时卷也被删除。
shell
# 查看临时卷
kubectl get ev
# 查看mysql-pod运行节点
kubectl get pod -owide
# 进入kubectl运行的目录
cd /var/lib/kubectl/pods
# 列出所有pod
ls -al
# 进入pod中
cd todo
cd volumes
cd kubernetes.io\~configmap/
# ls 列出文件夹可以看到一个目录,这个目录名称就是我们配置文件中挂在卷的名称
conf-volume
# 进入文件夹,可以看到mysql.cnf
cd conf-volume
# 查看内容,就是我们在配置文件中定义的内容
cat mysql.cnf
示例
- 将redis的存储目录设置为emptyDir
yaml
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: {}
持久卷pv和持久卷声明pvc
- 本地存储
- hostPath:节点主机上的目录或文件(仅单节点测试使用,多节点集群用local代替)
- local:节点上挂载的本地存储设备(不支持动态创建卷)
- 网络存储:NFS网络文件系统
- 分布式存储:Ceph(cephfs文件存储、rbd块存储)
持久卷PV(Persistent Volume)
持久卷是集群中的一块存储。可以理解为一块虚拟硬盘。持久卷可以由管理员事先创建,或者使用存储类(StorageClass)根据用户请求来动态创建。持久卷属于集群公共资源,并不属于某个namespace。
持久卷声明PVC(Persistent Volume Claim)
表达的是用户对存储的请求。PVC声明好比申请单,更贴近云服务的使用场景,使用资源先申请,便于统计和计费。Pod将PVC声明当作存储卷来使用,PVC可以请求指定容量的存储空间和访问模式。PVC对象是带有namespace的。

创建持久卷PV
创建持久卷PV是服务端的行为,通常集群管理员会提前创建一些常用规格的持久卷备用。
可以使用local卷来创建local类型的持久卷,需要先创建存储类(StorageClass)。
local卷不支持动态创建,必须手动创建持久卷PV。
创建local类型的持久卷,必须设置nodeAffinity属性。
调度器使用nodeAffinity信息来使用local卷的pod调度到持久卷所在的节点上,不会出现Pod被调度到别的节点的情况。如果节点故障,则Pod会创建失败。
示例
shell
vi local_storage.yaml
[root@k3s-m 3_state_mysql]# kubectl apply -f local_storage.yaml
storageclass.storage.k8s.io/local-storage created # 创建了存储类对象
persistentvolume/local-pv-1 created # 创建了存储卷local-pv-1
# 查看持久卷PV
[root@k3s-m 3_state_mysql]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv-1 2Gi RWO Delete Available local-storage 46s
yaml
# 创建本地存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: Immediate
---
# 创建持久卷
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-1
spec:
capacity:
storage: 2Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage #通过指定存储类来设置卷的类型,需要和上面创建存储类的名称一样
local:
path: /mnt/disks/ssd1 # 这个目录不会自动创建,我们需要手动创建
nodeAffinity: # local类型存储卷必须指定nodeAffinity属性,该属性标识节点亲和性。
required:
nodeSelectorTerms: # 通过节点选择器,将pod和存储调度到指定的节点上,这样防止了pod和存储被调度到不同节点的情况。
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k3s-w1
创建持久卷声明(PVC)
持久卷声明是用户端的行为,用户在创建Pod时,无法知道也不关心集群中的PV状态(名称、容量、是否可用等)。只需要在声明中提出申请,集群会自动匹配符合需求的持久卷。Pod使用持久卷声明来作为持久卷。
shell
vi pvc.yaml
# 创建持久卷声明
[root@k3s-m 3_state_mysql]# kubectl apply -f pvc.yaml
persistentvolumeclaim/local-pv-claim created
# 查看持久卷声明
[root@k3s-m 3_state_mysql]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
local-pv-claim Bound local-pv-1 2Gi RWO local-storage 19s
# 再次查看持久卷,发现STATUS由Available变更为Bound,CLAIM变更为default/local-pv-claim
[root@k3s-m 3_state_mysql]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv-1 2Gi RWO Delete Bound default/local-pv-claim local-storage 7m41s
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-pv-claim
spec:
storageClassName: local-storage # 与PV中的storageClassName一致
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
使用持久卷声明
使用PVC作为卷:Pod的配置文件指定了PersistentVolumeClaim,但是没有指定PersistentVolume。对Pod而言PersistentVolumeClaim就是一个存储卷。
shell
kubectl delete pod mysql-pod
vi mysql-pod3.yaml
[root@k3s-m 3_state_mysql]# kubectl apply -f mysql-pod3.yaml
pod/mysql-pod created
[root@k3s-m 3_state_mysql]# kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mysql-pod 1/1 Running 0 9s 10.42.1.139 k3s-w1 <none> <none>
[root@k3s-m 3_state_mysql]# kubectl describe pod mysql-pod
Volumes:
local-mysql-data:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: local-pv-claim
ReadOnly: false
yaml
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 # 将local-pv-claim挂载到容器中
卷的状态和绑定模式
绑定
创建持久卷声明PVC后,集群会查找满足要求的持久卷PV,将PVC绑定到该PV上。
PVC和PV之间的绑定是一对一的映射关系,且绑定关系具有排他性。
PCV可能会匹配到比声明容量大的持久卷,但不会匹配到比声明容量小的持久卷。
当PCV找不到满足要求的PV时,PVC会无限期的处于未绑定的Pending状态,直到出现了满足要求的PV时,PVC才会被绑定转为Bound状态。
卷的四种状态
- Available:可用,卷是一个空闲资源,尚未绑定
- Bound:已绑定,该卷已绑定到某个持久卷声明上
- Released:已释放,所绑定的声明已被删除,但是资源尚未被集群会收
- Failed:失败,卷自动回收操作失败
shell
[root@k3s-m 3_state_mysql]# kubectl delete pod mysql-pod
pod "mysql-pod" deleted
# 当删除pod后,pvc和pv不会被删除
[root@k3s-m 3_state_mysql]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
local-pv-claim Bound local-pv-1 2Gi RWO local-storage 26m
# 我们需要手动的删除PVC
[root@k3s-m 3_state_mysql]# kubectl delete pvc local-pv-claim
persistentvolumeclaim "local-pv-claim" deleted
# 当删除pvc后,local卷不支持自动回收
[root@k3s-m 3_state_mysql]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv-1 2Gi RWO Delete Failed default/local-pv-claim local-storage 28m
# 手动删除持久卷
[root@k3s-m 3_state_mysql]# kubectl delete pv local-pv-1
persistentvolume "local-pv-1" deleted
卷的访问模式
- ReadWriteOnce:卷可以被一个节点以读写方式挂载,并允许同意节点上的多个Pod访问
- ReadOnlyMany:卷可以被多个节点以只读方式挂载
- ReadWriteMany:卷可以被多个节点以读写方式挂载
- ReadWriteOncePod:卷可以被单个Pod以读写方式挂载。集群中只有一个Pod可以读取或写入该PVC。只支持SCI卷一级需要k8s1.22以上版本。
卷模式
卷模式(volumeMode)是一个可选参数。
针对PV持久卷,k8s支持俩种模卷模式:
- Filesystem:文件系统。默认的卷模式
- Block:块。将卷作为原始块设备来使用。
存储类StorageClass
创建持久卷PV
静态创建
- 管理员预先手动创建
- 手动创建麻烦、不够灵活(local卷不支持动态创建,必须手动创建PV)
- 资源浪费(例如一个PVC可能匹配到比声明容量大的卷)
- 对自动化工具不友好
动态创建
- 根据用户请求按需创建持久卷,在用户请求时自动创建
- 动态创建需要使用存储类(StorageClass)
- 用户需要在持久卷声明PVC中指定存储类来自动创建声明中的卷
- 如果没有指定存储类,使用集群中默认的存储类
存储类的概念
一个集群可以存在多个存储类来创建和管理不同类型的存储。
每个存储类都有一个制备器(Provisioner),用来决定使用哪个卷插件制备PC。该字段必须指定。
一般使用静态创建和动态创建结合的方式,管理员事先手动创建一些常用规格的持久卷以备使用,集群也可以根据用户请求动态创建持久卷。
Local Path Provisioner 本地路径制备器
shell
# 查看存储类
[root@k3s-m 3_state_mysql]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
# k3s自带了一个默认的存储类,使用了rancher提供的制备器
local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 12d
# 这是我们刚才创建的存储类
local-storage kubernetes.io/no-provisioner Delete Immediate false 100m
k3s自带了一个名为local-path的存储类(StorageClass),它支持动态创建基于hostPath或local持久卷。
创建或删除PVC后,会自动创建/删除PV,不需要再手动维护PV。
shell
vi local_path_pvc.yaml
[root@k3s-m 3_state_mysql]# kubectl apply -f local_path_pvc.yaml
persistentvolumeclaim/local-path-pvc1 created
# 查看持久卷声明,可以看到处于Pending
[root@k3s-m 3_state_mysql]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
local-path-pvc1 Pending local-path 45s
# 查看持久卷可以看到为空
[root@k3s-m 3_state_mysql]# kubectl get pv
No resources found
# 这里为什么没有自动创建pv,通过获取存储类信息可以看到,我们的local-path采用的卷绑定模式为WaitForFirstConsumer延迟创建,而上面的local-storage用的是Immediate立即创建
[root@k3s-m 3_state_mysql]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 12d
local-storage kubernetes.io/no-provisioner Delete Immediate false 100m
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-path-pvc1
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 2Gi
卷绑定模式Volume Binding Mode
volumeBindingMode用于控制什么时候动态创建卷和绑定卷。
- Immediate:立即创建。创建PVC后,立即创建PV并完成绑定。
- WaitForFirstConsumer:延迟创建。当使用该PVC的Pod被创建时,才会自动创建PV并完成绑定。
shell
vi mysql-pod4.yaml
[root@k3s-m 3_state_mysql]# kubectl apply -f mysql-pod4.yaml
pod/mysql-pod created
# 再次查看持久卷声明,发现已经绑定了一个持久卷
[root@k3s-m 3_state_mysql]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
local-path-pvc1 Bound pvc-fa1c3afa-07ab-4adf-9bc4-a1a18e63b2a7 2Gi RWO local-path 9m12s
[root@k3s-m 3_state_mysql]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-fa1c3afa-07ab-4adf-9bc4-a1a18e63b2a7 2Gi RWO Delete Bound default/local-path-pvc1 local-path 62s
# 删除pod后,pvc不会被删除,pv也不会被删除
[root@k3s-m 3_state_mysql]# kubectl delete pod mysql-pod
pod "mysql-pod" deleted
[root@k3s-m 3_state_mysql]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
local-path-pvc1 Bound pvc-fa1c3afa-07ab-4adf-9bc4-a1a18e63b2a7 2Gi RWO local-path 10m
[root@k3s-m 3_state_mysql]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-fa1c3afa-07ab-4adf-9bc4-a1a18e63b2a7 2Gi RWO Delete Bound default/local-path-pvc1 local-path 107s
# 当我们删除持久卷声明之后,可以看到持久卷被释放了
[root@k3s-m 3_state_mysql]# kubectl delete pvc local-path-pvc1
persistentvolumeclaim "local-path-pvc1" deleted
[root@k3s-m 3_state_mysql]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-fa1c3afa-07ab-4adf-9bc4-a1a18e63b2a7 2Gi RWO Delete Released default/local-path-pvc1 local-path 2m34s
# 再次查看,已经被自动删除了。使用存储类动态创建存储卷,默认情况下,删除声明也会自动删除声明关联的存储卷。可以在策略中配置是否自动删除。
[root@k3s-m 3_state_mysql]# kubectl get pv
No resources found
yaml
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-pvc1
回收策略(Reclaim Policy)
回收策略高速集群,当用户删除PVC对象时,从PVC中释放的PV将会被如何处理。
- 删除Delete:默认为Deleete。当PVC被删除的时候,关联的PV对象也会被自动删除。
- 保留Retain:当PVC对象被删除时,PV卷仍然存在,数据卷状态变更为"已释放Released"。此时卷上仍保留数据,该卷不可用于其他PVC。需要手动删除PV。
有状态应用集StatefulSet
如果现需要部署多个MySQL实例,就需要用到有状态应用集StatefulSet。
StatefulSet用来管理有状态的应用。一般用于管理数据库、缓存等。
于Deployment类似,StatefulSet用来管理Pod集合的部署和扩缩容。
Deployment用来部署无状态应用。StatefulSet用来部署有状态应用。
shell
vi statefulest.yaml
# 创建pod
[root@k3s-m 3_state_mysql]# kubectl apply -f statefulest.yaml
statefulset.apps/mysql created
# 新建窗口以观察pod情况。StatefulSet的pod是依次创建的,只有当前面的创建完成后,后面的pod才会创建。删除缩容则是逆序进行。
[root@k3s-m 3_state_mysql]# kubectl get pod --watch
NAME READY STATUS RESTARTS AGE
mysql-0 0/1 Pending 0 0s
mysql-0 0/1 Pending 0 5s
mysql-0 0/1 ContainerCreating 0 5s
mysql-0 1/1 Running 0 7s
mysql-1 0/1 Pending 0 0s
mysql-1 0/1 Pending 0 6s
mysql-1 0/1 ContainerCreating 0 6s
mysql-1 1/1 Running 0 52s
mysql-2 0/1 Pending 0 0s
mysql-2 0/1 Pending 0 5s
mysql-2 0/1 ContainerCreating 0 5s
mysql-2 1/1 Running 0 6s
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql # 必须匹配 .spec.template.metadata.labels
serviceName: db
replicas: 3 # 默认值是 1
minReadySeconds: 10 # 默认值是 0
template:
metadata:
labels:
app: mysql # 必须匹配 .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- containerPort: 3306
volumeMounts:
- mountPath: /var/lib/mysql #容器中的目录
name: mysql-data
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 2Gi
无头服务Headless Service
之前我们创建了三个各自独立的数据库实例。要想让别的容器访问数据库,我们需要将它发布为Service,但是Service带有负载均衡功能,每次请求会转发给不同的数据库,我们无法保障每次都落到同一个数据库。
无头服务(Headless Service)可以为StatefulSet成员提供稳定的DNS地址。在不需要负载均衡的情况下,可以通过指定ClusterIP的值为"None"来创建无头服务。
注意:StatefulSet中的ServiceName必须要和Service中的metadata.name一致。
shell
vi statefulest2.yaml
# 新建窗口以观察pod情况。StatefulSet的pod是依次创建的,只有当前面的创建完成后,后面的pod才会创建。删除缩容则是逆序进行。
kubectl get pod --watch
# 创建pod
[root@k3s-m 3_state_mysql]# kubectl apply -f statefulest2.yaml
service/db created
statefulset.apps/mysql created
# 查看服务类型,可以看到ClusterIP=None
[root@k3s-m 3_state_mysql]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 13d
nginx-service ClusterIP 10.43.220.21 <none> 8080/TCP 7d22h
nginx-outside NodePort 10.43.148.62 <none> 8081:30135/TCP 7d22h
db ClusterIP None <none> 3306/TCP 58s
# 查看服务,可以看到创建了三个MySQL端点
[root@k3s-m 3_state_mysql]# kubectl describe svc/db
Endpoints: 10.42.0.140:3306,10.42.1.148:3306,10.42.1.149:3306
# 进入pod容器
[root@k3s-m 3_state_mysql]# kubectl exec -it mysql-0 -- bash
# 查看hosts文件
bash-4.2# cat /etc/hosts
10.42.1.148 mysql-0.db.default.svc.cluster.local mysql-0
# 可以看到dns地址中有一条由"pod名称.service名称.namespace.svc.cluster.local"构成。在其他pod中可以根据这个地址访问到该数据库。
# 启动busybox测试
kubectl run dns-test -it --image=busybox:1.28 --rm
/ # nslookup mysql-svc
Server: 10.43.0.10
Address 1: 10.43.0.10 kube-dns.kube-system.svc.cluster.local
# 可见输出,首先跳转到了k8s内置的dns服务,再跳转到了后端的三个MySQL地址
# 我们可以通过每个pod固定的dns地址来访问指定的数据库
# 访问mysql-sts-0数据库
/ # nslookup mysql-0.db
Server: 10.43.0.10
Address 1: 10.43.0.10 kube-dns.kube-system.svc.cluster.local
Name: mysql-0.db
Address 1: 10.42.1.148 mysql-0.db.default.svc.cluster.local
yaml
# 为 StatefulSet 成员提供稳定的 DNS 表项的无头服务(Headless Service)
apiVersion: v1
kind: Service
metadata:
#重要!这里的名字要跟后面StatefulSet里ServiceName一致
name: db
labels:
app: database
spec:
ports:
- name: mysql
port: 3306
# 设置Headless Service
clusterIP: None
selector:
app: mysql
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql # 必须匹配 .spec.template.metadata.labels
serviceName: db #重要!这里的名字要跟Service中metadata.name匹配
replicas: 3 # 默认值是 1
minReadySeconds: 10 # 默认值是 0
template:
metadata:
labels:
app: mysql # 必须匹配 .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- containerPort: 3306
volumeMounts:
- mountPath: /var/lib/mysql
name: mysql-data
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 2Gi
MySQL主从复制
注意:该配置复杂,仅说明原理。不可以用于生产,未设置MySQL密码。后续会使用helm自动化部署。
shell
主从复制原理
每个MySQL的Pod中都包含两类容器(初始化容器和应用容器)。初始化容器执行初始化操作,应用容器运行数据库服务,伴随运行的还有辅助容器边车。
初始化容器
初始化容器是一种特殊的容器,在Pod内的应用容器启动前运行。初始化容器未执行完毕或以错误状态退出,Pod内的应用容器不会启动。
初始化容器需要在initContainers中定义,与containers同级。
基于上面的特性,初始化容器通常用于:生成配置文件、执行初始化命令或脚本、执行健康检查(检查依赖的服务是否ready或health状态)
边车sidecar
pod中运行了俩个容器,MySQL容器和一个充当辅助工具的xtrabackup容器,我们称为边车sidecar。
Xtrabackup是一个开源的MySQL备份工具,支持在线热备份(备份时不影响数据读写),是目前各个云厂商普遍使用的MySQL备份工具。
sidecar容器负责将备份数据文件发送给下一个Pod,并在副本服务器初次启动时,使用数据文件完成数据的导入。MySQL使用bin-log同步数据,但是数据库运行一段时间后,产生了一些数据,这时如果我们扩容,创建一个新副本,有可能无法追溯到bin-log源头(可能被手动清理或过期自动删除),因此需要将现有的数据导入到副本后,再开启数据同步。sidecar只负责数据库初次启动时完成历史数据导入,后续的数据MySQL会自动同步。
port-forward端口转发
通常集群中的数据库不直接对外访问。但是有时需要通过图形化工具链接数据库调试。可以采用端口转发来访问集群中的应用。
kubectl port-forward
可以将本地端口的连接转发给容器。
此命令在前台运行,命令终止后,转发会话将会结束。
shell
# 主机端口:容器端口。如果主机有多个IP,要通过--address参数指定IP监听端口
kubectl port-forward pods/mysql-0 --address=127.0.0.1 33061:3306
Helm的用法
介绍
Helm是一个k8s应用的包管理工具,类似Ubuntu的apt和centos的yum。
Helm使用chart来封装k8s应用的yaml文件,我们只需要设置自己的参数,就可以实现自动化的快速部署应用。
三大概念
Chart代表着Helm包
包含运行应用程序需要的所有资源定义和依赖,相当于模板。
类似maven中的pom.xml、apt重点dpkb或yum中的rpm。
Repository仓库
用来存放和共享charts。不同的应用放在不同的仓库中。
Release是运行chart的实例
一个chart通常可以在同一个集群中安装多次。
每一次安装都会创建一个新的release,release name不可重复。
安装Helm
下载二进制文件,解压,将解压的文件移动到指定目录即可。
shell
# 由于helm需要使用k8s集群的配置文件,而因为k3s的配置文件没有放在默认目录下,故需要将kubeconfig环境变量设置为k3s集群配置文件所在的文件目录
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm help
Helm仓库
Helm有一个和docker hub类似的应用中心,可以在这里面找到需要部署的应用。
shell
# 安装仓库
helm repo add bitnami https://charts.bitnami.com/bitnami
helm search hub mysql
[root@k3s-m soft]# helm repo add stable https://mirror.azure.cn/kubernetes/charts/
"stable" has been added to your repositories
[root@k3s-m soft]# helm repo add bitnami https://mirror.azure.cn/kubernetes/charts/
"bitnami" has been added to your repositories
# 安装release
Helm部署MySQL集群
安装过程中有两种方式传递配置参数
-f
或-values
:使用yaml文件覆盖默认配置。可以指定多次,优先使用最右边的配置文件。--set
:通过命令行的方式对指定项进行覆盖。
如果同时使用俩种方式,则--set中的值会被合并到-f中,但--set中的值优先级更高。
yaml
# 执行命令
# helm install my-db -f values.yaml bitnami/mysql
# values.yaml配置
auth:
rootPassword: "123456"
primary:
persistence:
size: 2Gi
enabled: true
secondary:
replicaCount: 2
persistence:
size: 2Gi
enabled: true
architecture: replication
本文内容参考B站"一小时技术精讲"老师的内容。同时也给出了笔者在实践中遇到的问题解答。 在此,特别感谢老师的干货视频。附上连接:https://www.bilibili.com/video/BV1k24y197KC