背景
前面我们介绍了kubernetes集群部署、service应用部署。
此前的部署案例是无需存储数据的,不需要记录状态,可以随意扩充副本,每个副本都是一样且可替代的;针对数据库、Redis等需要存储数据、记录状态的应用,则不能随意扩充副本。
针对这种应用场景,K8S为我们提供了StatefulSet。
实现
StatefulSet特性:
- Service的
CLUSTER-IP
是空的,Pod名字也是固定的。
- Pod的创建和销毁是有序的,创建是顺序的,销毁是逆序的。
- Pod创建不会改变名字,但IP是会改变的,所以不能用IP直连。访问时,如果直接使用Service名字连接,会随机转发请求。
部署StatefulSet应用
删除已有部署
删除已有部署命令:
kubectl delete all --all
创建yaml文件
创建文件命令:
vim mongo.yaml
文件内容:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongodb
spec:
serviceName: mongodb
replicas: 3
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongo
image: mongo:4.4
# IfNotPresent 仅本地没有镜像时才远程拉,Always 永远都是从远程拉,Never 永远只用本地镜像,本地没有则报错
imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
name: mongodb
spec:
selector:
app: mongodb
type: ClusterIP
# HeadLess
clusterIP: None
ports:
- port: 27017
targetPort: 27017
部署yaml文件
部署yaml文件命令:
# 部署yaml文件
kubectl apply -f mongo.yaml
# 查看pod
kubectl get pod
# 查看service
kubectl get service
查看statefulset副本:
kubectl get statefulset
访问指定Pod
访问StatefulSet
应用时,如果直接用Service名字访问,则会随机转发到不同的Pod。
在minikube中可通过
pod-name.service-name
的方式来访问指定的Pod。但在集群中经过博主多次测试,无法实现这种方式访问指定的Pod。或许博主水平有限,大家如有高明的见解可在评论区留言。
那么我们怎样来访问指定的Pod呢?
例如我们要访问mongodb-0
这个Pod,则可以通过如下命令进入Pod内部:
# 注意双横杠与bash之间有一个空格
kubectl exec -it mongodb-0 -- bash
# 接下来输入mongo,访问mongodb数据库
mongo
记录一个坑
问题
当我们创建一个临时的mongodb的Pod,创建命令如下:
kubectl run mongodb-client --rm --tty -i --restart='Never' --image docker.io/bitnami/mongodb:4.4.10-debian-10-r20 --command -- bash
# mongodb-client:Pod的名称
# --rm:临时Pod,退出即会删除
连接mongodb数据库:
mongo
此时会出现如下报错:
MongoDB shell version v4.4.10
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Error: couldn't connect to server 127.0.0.1:27017, connection attempt failed: SocketException: Error connecting to 127.0.0.1:27017 :: caused by :: Connection refused :
connect@src/mongo/shell/mongo.js:374:17
@(connect):2:6
exception: connect failed
exiting with code 1
原因
这是因为未启动数据库服务就去连接数据库。
解决
启动数据库服务即可。
# 直接启动
mongod
或者
# 更改指定运行路径启动。推荐使用!
mongod --dbpath '/usr/data/db'
创建目录:
# 进入usr目录
cd usr/
# 创建目录
# -p:递归创建目录
mkdir -p data/db/
连接mongodb数据库:
注:
- 当前shell窗口需要保持开启mongodb服务,因此连接mongodb需要在一个新的shell窗口进行连接。
数据持久化
kubernetes集群不会处理数据的存储,因此当Pod重建时,数据就会丢失。
我们可以为数据库挂载一个磁盘来确保数据的安全,例如可以通过云存储、本地磁盘、NFS等方式。
- 云存储:不限定节点,不受集群影响,安全稳定。需要云服务商提供,裸机集群是没有的。
- 本地磁盘:可以挂载在某个节点上的目录,但是需要限定Pod运行在这个节点上。
- NFS:不限定节点,不受集群影响。
方式一:hostPath挂载
hostPath挂载,把节点上的一个目录挂载在Pod。仅供minikube单节点测试使用,不适用于多节点集群。
此方式不推荐使用,因此这里不再赘述。
方式二:挂载磁盘(推荐)
理论部分
通过多层抽象来实现磁盘的挂载,继而实现数据的持久化保存。
多层抽象的作用:
- 更好地分工。运维人员负责提供好存储,开发人员不需要关注磁盘的细节,只需要写一个申请单即可。
- 方便云服务商提供不同类型的配置,配置细节不需要开发者关注,主要一个申请单。
- 动态创建,开发人员写好申请单后,供应商根据需求自动创建所需存储卷。
各抽象层的作用:
Storage Class(SC):
将存储卷划分为不同的种类,例如:SSD,普通磁盘,本地磁盘,按需使用。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/aws-ebs
parameters:
type: io1
iopsPerGB: "10"
fsType: ext4
Persistent Volume(PV):
描述卷的具体信息,例如磁盘大小、访问模式。
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongodata
spec:
capacity:
storage: 2Gi
volumeMode: Filesystem # Filesystem(文件系统) Block(块)
accessModes:
- ReadWriteOnce # 卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /root/data
nodeAffinity:
required:
# 通过 hostname 限定在某个节点创建存储卷
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node2
Persistent Volume Claim(PVC):
对存储需求的一个申明,可以理解为一个申请单,系统根据这个申请单去找一个合适的PV。还可以根据PVC自动创建PV。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodata
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "local-storage"
resources:
requests:
storage: 2Gi
创建示例
云盘
使用云盘存储,需要同步使用云服务商的kubernetes集群。本文中的案例是本地裸机搭建,因此该部分暂时略过。
本地磁盘
不支持动态创建,需要提前创建好。
注:
- 数据卷挂载的节点以及节点中的路径要确保存在,否则挂载会失败的。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongodb
spec:
serviceName: mongodb
replicas: 1
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- image: mongo:5.0
imagePullPolicy: IfNotPresent
name: mongo
volumeMounts:
- mountPath: /data/db
name: mongo-data
volumes:
- name: mongo-data
persistentVolumeClaim:
claimName: mongodata
---
apiVersion: v1
kind: Service
metadata:
name: mongodb
spec:
clusterIP: None
ports:
- port: 27017
protocol: TCP
targetPort: 27017
selector:
app: mongodb
type: ClusterIP
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongodata
spec:
capacity:
storage: 2Gi
volumeMode: Filesystem # Filesystem(文件系统) Block(块)
accessModes:
- ReadWriteOnce # 卷可以被一个节点以读写方式挂载
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /root/data
nodeAffinity:
required:
# 通过 hostname 限定在某个节点创建存储卷
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodata
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "local-storage"
resources:
requests:
storage: 2Gi
注:
- vim编辑器中,可通过依次输入:
Esc -- 冒号 -- set paste
,进行粘贴。 - vim编辑器中,可通过依次输入:
Esc -- 冒号 -- %d
,清空文件内容。
ConfigMap和Secret
之前数据库的地址以及账号密码都是固定在代码中的,接下来我们将要通过ConfigMap
和Secret
提取出配置文件,更好地贴合实际开发。
ConfigMap
configmap.yaml文件内容:
apiVersion: v1
kind: ConfigMap
metadata:
name: mongo-config
data:
mongoHost: mongodb-0.mongodb
部署以及查看:
# 应用
kubectl apply -f configmap.yaml
# 查看
kubectl get configmap mongo-config -o yaml
Secret
一些重要数据,例如密码、TOKEN,我们可以放到secret中。
注:
- 保存的数据需要用Base64编码进行编码。
secret.yaml文件内容:
apiVersion: v1
kind: Secret
metadata:
name: mongo-secret
# Opaque 用户定义的任意数据,更多类型介绍 https://kubernetes.io/zh/docs/concepts/configuration/secret/#secret-types
type: Opaque
data:
# 数据要 base64。https://tools.fun/base64.html
mongo-username: bW9uZ291c2Vy
mongo-password: bW9uZ29wYXNz
部署以及查看:
# 应用
kubectl apply -f secret.yaml
# 查看
kubectl get secret mongo-secret -o yaml
使用方法
作为环境变量使用
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongodb
spec:
serviceName: mongodb
replicas: 3
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongo
image: mongo:4.4
# IfNotPresent 仅本地没有镜像时才远程拉,Always 永远都是从远程拉,Never 永远只用本地镜像,本地没有则报错
imagePullPolicy: IfNotPresent
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: mongo-secret
key: mongo-username
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mongo-secret
key: mongo-password
# Secret 的所有数据定义为容器的环境变量,Secret 中的键名称为 Pod 中的环境变量名称
# envFrom:
# - secretRef:
# name: mongo-secret
挂载为文件(更适合证书文件)
挂载后,会在容器中对应路径生成文件,一个key一个文件,内容就是value。
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
secret:
secretName: mysecret
总结
本文介绍了如何“优雅”地部署数据库等需记录状态的服务。
后续我们将继续介绍如何更快速地部署这些常规应用,如何通过类似于DockerHub拉取镜像的操作直接部署服务。
kubernetes为我们提供了Helm的操作方式。