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

相关推荐
Demisse9 分钟前
[华为eNSP] OSPF综合实验
网络·华为
工控小楠15 分钟前
DeviceNet转Modbus TCP网关的远程遥控接收端连接研究
网络·网络协议·devicenet·profient
叶落闲庭17 分钟前
【k8s】k8s集群搭建
云原生·容器·kubernetes
搬码临时工18 分钟前
电脑同时连接内网和外网的方法,附外网连接局域网的操作设置
运维·服务器·网络
藥瓿亭20 分钟前
K8S认证|CKS题库+答案| 3. 默认网络策略
运维·ubuntu·docker·云原生·容器·kubernetes·cks
xyhshen24 分钟前
k8s下离线搭建elasticsearch
elasticsearch·容器·kubernetes
安全系统学习1 小时前
【网络安全】Qt免杀样本分析
java·网络·安全·web安全·系统安全
椰汁菠萝1 小时前
k8s集群安装坑点汇总
云原生·容器·kubernetes
背太阳的牧羊人2 小时前
sudo docker exec -it backend bash 以交互方式(interactive)进入正在运行的 Docker 容器的命令行环境
docker·容器·bash
逃逸线LOF2 小时前
Spring Boot论文翻译防丢失 From船长&cap
网络