本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net
前言
本文参考自《通过搭建 MySQL 掌握 k8s(Kubernetes)重要概念(上):网络与持久卷》
由于原文博主是使用 windows 的虚拟机 Vagrant 的 minikube,和 mac OS 有所不同。另外原博主的方法下载 mysql:5.7 的镜像下载不了,导致后面配置文件也不一样,所以有了这篇文章。
docker mysql:5.7 镜像拉取参考自《docker 拉取 mysql 镜像遇到到问题及解决》
侵删。
准备工作
- mac OS 系统版本 10.15.4,
- 安装 docker desktop 和 k8s,可参考阿里云的 https://github.com/AliyunContainerService/k8s-for-docker-desktop/tree/v1.16.5
- docker 版本 19.03.8,k8s 版本
1
2
3
|
➜ kubectl version
Client Version: version.Info{Major:"1", Minor:"16+", GitVersion:"v1.16.6-beta.0", GitCommit:"e7f962ba86f4ce7033828210ca3556393c377bcc", GitTreeState:"clean", BuildDate:"2020-01-15T08:26:26Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"16+", GitVersion:"v1.16.6-beta.0", GitCommit:"e7f962ba86f4ce7033828210ca3556393c377bcc", GitTreeState:"clean", BuildDate:"2020-01-15T08:18:29Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"linux/amd64"}
|
1
|
应用程序分成两种,无状态和有状态的。一般的前段和后端程序都是无状态的,而数据库是有状态的,他需要把数据存储起来,这样即使断电,数据也不会丢失。要创建有状态的程序,还需要引入另外一些k8s概念。它们虽然不是核心,但也很重要,共有三个,持久卷,网络和参数配置。掌握了这些之后,基本概念就已经做到了全覆盖,k8s就已经入门了。我们通过搭建MySQL来熟悉这些k8s概念。容器本身是无状态的,一旦出现问题它会被随时销毁,它存储的数据也就丢失了。MySQL需要一个能保存数据的持久层,在容器被销毁之后仍然存在,k8s叫它持久卷。
|
1、创建和验证 MySQL 镜像
通过国内镜像 daocloud.io/library 库下载,下载速度快到感人!
1
|
docker pull daocloud.io/library/mysql:5.7
|
查看下载好的镜像:
1
2
3
|
docker images|grep mysql
REPOSITORY TAG IMAGE ID CREATED SIZE
daocloud.io/library/mysql 5.7 718a6da099d8 2 weeks ago 448MB
|
需要注意的是:mysql 镜像名的前缀不能忘记,否则报错!
完整的名字: daocloud.io/library/mysql:5.7
运行:
1
|
docker run --name test-mysql -p 3306:33060 -e MYSQL_ROOT_PASSWORD=root -d -it --net host daocloud.io/library/mysql:5.7
|
“root”是根(root)用户的 password,这里是在创建 MySQL 容器时指定 “root” 用户的 password。“test-MySQL”是容器的名字。“daocloud.io/library/mysql:5.7”用的是 daocloud 镜像库里的“MySQL”5.7 版本。这次没有用最新的 8.0 版,因为新版跟以前的客户端不兼容,需要修改很多东西。所用的镜像是全版的 Linux,因而文件比较大,有 400M。
容器建好了之后,键入 “docker logs test-mysql”,查看日志。“docker ps”, 查看容器状态。
1
2
|
docker ps -a|grep mysql
3b6ec96e49d0 daocloud.io/library/mysql:5.7 "docker-entrypoint.s…" 3 minutes ago Up 3 minutes test-mysql
|
进入 docker 内部,并登录 mysql 查看:
1
2
|
docker exec -it 3b6ec96e49d0 bash
mysql -u root -p
|
输入密码 “root”,即可进入正常的 mysql 控制台。操作完毕后,键入两个“exit” 退出到 os 的 terminal。
2、在 k8s 上安装 MySQL
在 k8s 上安装 MySQL 分成三个部分,创建部署文件,创建服务文件和安装测试。
2.1 部署 (Deployment) 文件
下面是部署配置文件。在上一篇文章中已经详细讲解了文件格式,所有的 k8s 的配置文件格式都是相同的。“template”之上是部署配置,从 “template” 向下是 Pod 配置。从 “containers” 开始是 Pod 里面的容器配置。“env:”是环境变量,这里通过环境变量来设置数据库的用户名和口令,后面还会详细讲解。MySQL 的端口是“3306”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
apiVersion: apps/v1
kind: Deployment # 类型是部署
metadata:
name: mysql-deployment # 对象的名字
spec:
selector:
matchLabels:
app: mysql #用来绑定label是“mysql”的Pod
strategy:
type: Recreate
template: # 开始定义Pod
metadata:
labels:
app: mysql #Pod的Label,用来标识Pod
spec:
containers: # 开始定义Pod里面的容器
- image: daocloud.io/library/mysql:5.7 # image这块一定要写成daocloud的
name: mysql-con
imagePullPolicy: Never
env: # 定义环境变量
- name: MYSQL_ROOT_PASSWORD # 环境变量名
value: root # 环境变量值
- name: MYSQL_USER
value: dbuser
- name: MYSQL_PASSWORD
value: dbuser
args: ["--default-authentication-plugin=mysql_native_password"]
ports:
- containerPort: 3306 # mysql端口
name: mysql
|
2.2 服务(Service)文件
下面是服务配置文件,这个与上一篇讲的配置基本相同,这里就不解释了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
apiVersion: v1
kind: Service
metadata:
name: mysql-service
labels:
app: mysql
spec:
type: NodePort
selector:
app: mysql
ports:
- protocol : TCP
nodePort: 30306
port: 3306
targetPort: 3306
|
2.3 安装测试:
有了配置文件后,下面就开始创建 MySQL。在创建时要按照顺序,依次进行,先从最底层的对象开始创建。
创建部署和服务:
1
2
|
kubectl apply -f mysql-deployment.yaml
kubectl apply -f mysql-service.yaml
|
查看部署:
查看服务:
“mysql-service”的端口(PORT(S))有两个,“3306”是 k8s 内部端口,“30306”是外部端口。由于 “NodePort” 已经打开了对外端口,这时就可以在 os 控制台上通过 “30306” 端口访问 MySQL。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
➜ test mysql -h localhost -P 30306 --protocol=tcp -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.31 MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
|
下一步就可以用图形客户端来访问 MySQL 了。就用 30306 端口来访问 MySQL。
3、网络
这里的网络有两层含义,一层是 k8s 网络,就是让 k8s 内部服务之间可以互相访问,并且从 k8s 集群外部可以访问它内部的服务。另一层是宿主机(笔记本)和虚机之间的网路,就是在宿主机上可以访问虚机。这两层都通了之后,就可以在宿主机直接访问 k8s 集群里面的 MySQL。
3.1 k8s 网络:
k8s 的网络也有两层含义,一个是集群内部的,k8s 有内部 DNS,可以通过服务名进行寻址。另一个是从集群外部访问集群内部服务,一共有四种方式,详情请见 “Kubernetes NodePort vs LoadBalancer vs Ingress? When should I use what?”
3.1.1 LoadBalancer
3.1.2 NodePort:
这种方法可以在每个 Node 上开放一个对外端口,每一个指向这个端口的请求都被转发给一个服务。它的好处是你可以指定一个固定的端口(端口的取值范围只能是 30000–32767),这样我在笔记本上访问 MySQL 时就不用更换端口了。 如果你不指定,系统会随机分配一个。它的缺点是每个端口只能有一个服务,而且端口取值受限制,因此不适合生产环境。
3.1.3 ClusterIP
这个只能在 k8s 集群内部寻址。
3.1.4 Ingress
这是推荐的方法,一般在生产环境中使用。Load balancer 的问题是每一个服务都要有一个 Load balancer,服务多了之后会很麻烦,这时就会用 Ingress,它的缺点是配置起来比较复杂。Minikube 自带了一个基于 Nginx 的 Ingress 控制器,只需运行 “minikube addons enable ingress”,就行了。但 Ingress 的设置较复杂,因此这里没有用它。
3.2 虚拟机网络(略)
4 创建持久卷(PersistentVolume)
k8s 卷的概念包括卷和持久卷。
4.0.1 卷(volume):
卷是 k8s 的存储概念,它依附于 Pod,不能单独存在。但它不是在容器层。因此如果容器被重新启动,卷仍然在。但如果 Pod 重新启动,卷就丢失了。如果一个 Pod 里有多个容器,那么这些容器共享 Pod 的卷。你可以把卷看成是一个目录,里面可以存储各种文件。k8s 支持各种类型的卷,例如本地文件系统和各种云存储。
4.0.2 持久卷(PersistentVolume):
是对卷的一个封装,目的是为了更好地管理卷。它的生命周期不需要与 Pod 绑定,它可以独立于 Pod 存在。
4.0.3 持久卷申请(PersistentVolumeClaim):
是对持久卷资源的一个申请,你可以申请特定的存储容量的大小和访问模式,例如读写模式或只读模式。k8s 会根据持久卷申请分配适合的持久卷,如果没有合适的,系统会自动创建一个。持久卷申请是对持久卷的一个抽象,就像编程里的接口(Interface), 它可以有不同的具体实现(持久卷)。例如,阿里云和华为云支持的存储系统不同,它生成的持久卷也不相同。持久卷是与特定的存储实现绑定的。那你要把程序从阿里云移植到华为云,怎么保证配置文件的兼容性呢?你就用持久卷申请来做这个接口,它只规定存储容量大小和访问模式,而由阿里云和华为云自动生成各自云里满足这个接口需求的持久卷. 不过,它还有一个限制条件,那就是持久卷申请和持久卷的 StorageClass 需要匹配,这使它没有接口灵活。后面会详细讲解。
4.0.4 动态持久卷:
在这种情况下,你只需创建持久卷申请(不需要单独创建持久卷),然后把持久卷申请与部署绑定。系统会按照持久卷申请自动创建持久卷。下面是持久卷申请配置文件。其中 “storage:1Gi”,是指申请的空间大小是 1G。
4.1 持久卷申请配置文件:
mysql-pvc.yaml :
1
2
3
4
5
6
7
8
9
10
11
|
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
|
4.2 挂载持久卷申请的部署:
下面是挂载了持久卷申请的部署配置文件 mysql-deployment-pvc.yaml。它通过把持久卷申请当做持久卷来使用,与 Pod 进行绑定。请阅读文件里有关持久卷的注释。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
apiVersion: apps/v1
kind: Deployment # 类型是部署
metadata:
name: mysql-deployment # 对象的名字
spec:
selector:
matchLabels:
app: mysql #用来绑定label是“mysql”的Pod
strategy:
type: Recreate
template: # 开始定义Pod
metadata:
labels:
app: mysql #Pod的Label,用来标识Pod
spec:
containers: # 开始定义Pod里面的容器
- image: daocloud.io/library/mysql:5.7
name: mysql-con
imagePullPolicy: Never
env: # 定义环境变量
- name: MYSQL_ROOT_PASSWORD # 环境变量名
value: root # 环境变量值
- name: MYSQL_USER
value: dbuser
- name: MYSQL_PASSWORD
value: dbuser
args: ["--default-authentication-plugin=mysql_native_password"]
ports:
- containerPort: 3306 # mysql端口
name: mysql
volumeMounts: #挂载Pod上的卷到容器
- name: mysql-persistent-storage #Pod上卷的名字,与“volumes”名字匹配
mountPath: /var/lib/mysql #挂载的Pod的目录
volumes: #挂载持久卷到Pod
- name: mysql-persistent-storage #持久卷名字,与“volumeMounts”名字匹配
persistentVolumeClaim:
claimName: mysql-pvc #持久卷申请名字
|
这里只指定了 Pod 的挂载目录,并没有指定虚拟机(宿主机)的目录,后面会讲到如何找到虚拟机的目录(系统自动分配挂载目录)。
4.3 运行部署:
键入 “kubectl apply -f mysql-pvc.yaml” 创建持久卷申请,在创建它的同时,系统自动创建持久卷。
查看持久卷申请
1
2
3
|
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-pvc Pending manual 13s
|
- 此时发现系统 mysql-pvc 的 status 是 Pending,为什么呢?原来是系统并未如我们所想的自动创建了持久卷 pv,我们需要自己创建。新建文件 mysql-pv.yaml,输入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 1Gi # 大小1G
accessModes:
- ReadWriteOnce
hostPath:
path: "/tmp/kube" # 文件实际存放位置
|
键入 “kubectl apply -f mysql-pv.yaml” 创建持久卷,查看发现俩个都有了:
1
2
3
4
5
6
|
➜ test kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mysql-pv 1Gi RWO Retain Bound default/mysql-pvc manual 13s
➜ test kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-pvc Bound mysql-pv 1Gi RWO manual 4m50s
|
查看持久卷申请详细信息
1
2
|
kubectl describe pv mysql-pv
kubectl describe pvc mysql-pvc
|
新开一个窗口,查看 MySQL 目录信息
4.4 持久卷的回收模式:
当持久卷和持久卷申请被删除后,它有三种回收模式。
保持(Retain) :当持久卷申请被删除后,持久卷仍在。你可以手动回收持久卷里的数据。
删除(Delete) :持久卷申请和持久卷都被删除,底层存储的数据也会被删除。当使用动态持久卷时,缺省的模式是 Delete。当然,你可以在持久卷被创建之后修改它的回收模式。
回收(Recycle) :这种方式已经不推荐使用了,建议用 Retain 代替。
4.5 静态持久卷:
动态持久卷的一个问题是它的缺省回收模式是 “删除”,这样当虚机重新启动后,持久卷会被删除。当你重新运行部署时,k8s 会创建一个新的 MySQL,这样原来 MySQL 里的新建信息就会丢失,这是我们不愿意看到的。虽然你可以手动修改回收方式为 “保持”,但还是要手动回收原来持久卷里的数据。
一个解决办法是把持久卷建在宿主机上,这样即使虚机出了问题被重新启动,MySQL 里的新建信息依然不会丢失。如果是在云上,就会有专门的的存储层,如果是本地,大致有三种方式:
Local: 把存储从宿主机挂载到 k8s 集群上. 详情请参见:“Volumes”.
HostPath:也是把存储从宿主机挂载到 k8s 集群上,但它有许多限制,例如只支持单节点(Node),而且只支持 “ReadWriteOnce” 模式。详情请参见: “hostPath as volume in kubernetes”.
NFS:网络文件系统,这种是最灵活的,但需要安装 NFS 服务器。详情请参见:“Kubernetes Volumes Guide”.
我选择了比较简单的 “Local” 方式。在这种方式下,必须单独创建持久卷,不能 只创建持久卷申请而让系统自动创建持久卷。
下面是使用 “Local” 方式的配置文件,它把持久卷和持久卷申请写在了一个文件里。当用 “Local” 方式时,需要设置 “nodeAffinity” 部分,其中 “values:- minikube” 的“Minikube” 是 k8s 集群 Node 的名字,“Minikube”只支持一个 Node,既是“Master Node”,又是“Worker Node”。
新增文件 mysql-pv-pvc.yaml,持久卷和申请的配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv-local
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: standard #持久卷存储类型,它需要与持久卷申请的类型相匹配
local:
path: /tmp/kube/mysql-local #宿主机的目录
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- docker-desktop # Node的名字
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim-local
labels:
app: mysql
spec:
accessModes:
- ReadWriteOnce
# storageClassName: # 这里的存储类型注释掉了
resources:
requests:
storage: 1Gi #1 GB
|
如果不知道 Node 名字,可用如下命令查看:
1
2
3
|
kubectl get node
NAME STATUS ROLES AGE VERSION
docker-desktop Ready master 9d v1.16.6-beta.0
|
改用静态持久卷之后,只有持久卷配置文件发生了变化,部署和服务的配置文件没有变。重新运行持久卷和部署,成功之后,即使重启虚拟机,MySQL 里面的新建内容也没有丢失。
注意这里 storageClassName 的用法。k8s 规定持久卷和持久卷申请的 storageClassName 必须匹配,这时才会把持久卷分配给持久卷申请。我们这里的持久卷申请没有指定 storageClassName,这时系统会使用缺省的 storageClass。
查看是否安装了缺省的 storageClass
1
2
3
|
kubectl get sc
NAME PROVISIONER AGE
hostpath (default) docker.io/hostpath 9d
|
查看缺省的 storageClass 详细信息
1
2
3
4
5
6
7
8
9
10
11
12
|
kubectl describe sc
Name: hostpath
IsDefaultClass: Yes
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"},"name":"hostpath"},"provisioner":"docker.io/hostpath","reclaimPolicy":"Delete","volumeBindingMode":"Immediate"}
,storageclass.kubernetes.io/is-default-class=true
Provisioner: docker.io/hostpath
Parameters: <none>
AllowVolumeExpansion: <unset>
MountOptions: <none>
ReclaimPolicy: Delete
VolumeBindingMode: Immediate
Events: <none>
|
从这里可以看出,安装了缺省的 storageClass,它的名字是 “hostpath”。上面的持久卷申请里没有指定 storageClass,因此系统使用缺省的 storageClass 与之匹配,而上面的持久卷的 storageClassName 是 “hostpath”,正好能配上。详情请见 “Dynamic Provisioning and Storage Classes in Kubernetes”
5 后记
5.1 可登入 k8s 的管理后台查看我们刚才创建的 mysql
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/overview?namespace=default
显示出我们刚才创建的 deployment、pods 和 pv、pvc 等。
5.2 创建重名 PV 或 PVC
当原来的 PV 或 PVC 还在,而你又创建了一个新的 PV, 并与原来的重名,则会得到如下错误:
The persistentvolumeclaim “mysql-pv-claim” is invalid: spec: forbidden: is immutable after creation except resources.requests for bound claims
这时,你需要将原来的 PV 或 PVC 删掉,再重新创建新的。
5.3 彻底删除
1
2
3
4
|
kubectl delete service mysql-service
kubectl delete deployment mysql-deployment
kubectl delete pvc mysql-pvc
kubectl delete pv mysql-pv
|