k8s入门教程(集群部署、使用,镜像拉取失败网络问题排查)

文章目录

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集群中的容器可能被调度到任意节点,配置文件需要能在集群任意节点上访问、分发、更新。

  1. ConfigMap用来在键值对数据库(etcd)中存储非加密数据。
  2. ConfigMap可以用作环境变量、命令行参数或者数据存储卷。
  3. ConfigMap将环境配置信息与容器镜像解耦,便于配置修改。
  4. 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
示例
  1. 将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

相关推荐
大鹏说大话19 小时前
SSL证书自动化的未来:ACME协议与Let’s Encrypt实践
网络·安全
被摘下的星星20 小时前
网际协议(IP协议)
网络·tcp/ip
爱学习的小囧21 小时前
ESXi VMkernel 端口 MTU 最佳设置详解
运维·服务器·网络·php·虚拟化
eEKI DAND1 天前
对Docker部署的MySQL中的数据进行备份恢复
mysql·docker·容器
TechubNews1 天前
Base 发布首个独立 OP Stack 框架的网络升级 Azul,将是 L2 自主迭代的开端?
大数据·网络·人工智能·区块链·能源
多年小白1 天前
中科院 Ouroboros 晶圆级存算一体芯片深度解析
大数据·网络·人工智能·科技·ai
发光小北1 天前
IEC104 转 Modbus TCP 网关如何应用?
网络·网络协议·tcp/ip
山栀shanzhi1 天前
在做直播时,I帧的间隔(GOP)一般是多少?
网络·c++·面试·ffmpeg
SPC的存折1 天前
Cisco Packet Tracer 静态路由全网互通实验及详细教学文档,包括基础常识、实验信息、IP 地址规划和分步操作流程
网络·tcp/ip·智能路由器
东北甜妹1 天前
网络服务-
网络·智能路由器