k8s实践(七):存储卷和数据持久化(Volumes and Persistent Storage)

环境说明:

主机名 操作系统版本 ip docker version kubelet version 配置 备注
master Centos 7.6.1810 172.27.9.131 Docker 18.09.6 V1.14.2 2C2G master主机
node01 Centos 7.6.1810 172.27.9.135 Docker 18.09.6 V1.14.2 2C2G node节点
node02 Centos 7.6.1810 172.27.9.136 Docker 18.09.6 V1.14.2 2C2G node节点
centos7 Centos 7.3.1611 172.27.9.181 × × 1C1G nfs服务器

?

k8s集群部署详见:Centos7.6部署k8s(v1.14.2)集群
k8s学习资料详见:基本概念、kubectl命令和资料分享

?

一、Volume

1. 概念

??Kubernetes的卷是pod的一个组成部分,因此像容器一样在pod的规范中就定义了。它们不是独立的Kubernetes对象,也不能单独创建或删除。pod中的所有容器都可以使用卷,但必须先将它挂载在每个需要访问它的容器中。在每个容器中,都可以在其文件系统的任意位置挂载卷。

2. 为什么需要Volume

??容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。首先,当容器崩溃时,kubelet会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重新启动。其次,在 Pod 中同时运行多个容器时,这些容器之间通常需要共享文件。Kubernetes 中的 Volume 抽象就很好的解决了这些问题。

3. Volume类型

目前,Kubernetes支持以下Volume 类型:

本文将对emptyDir,hostPath,共享存储NFS,PV及PVC分别进行测试实践。

二、emptyDir

1. emptyDir概念

??emptyDir是最基础的Volume类型,用于存储临时数据的简单空目录。如果Pod设置了emptyDir类型Volume,Pod被分配到Node上时候,会创建emptyDir,只要Pod运行在Node上,emptyDir都会存在(容器挂掉不会导致emptyDir丢失数据),但是如果Pod从Node上被删除(Pod被删除,或者Pod发生迁移),emptyDir也会被删除,并且永久丢失。

?

??下面将用emptyDir卷实现在同一pod中两个容器之间的文件共享

2. 创建pod emptyDir-fortune

[[email protected] ~]# more emptyDir-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: prod                           #pod标签
  name: emptydir-fortune
spec:
  containers:
  - image: loong576/fortune
    name: html-generator
    volumeMounts:                       #名为html的卷挂载至容器的/var/htdocs目录
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:                       #挂载相同的卷至容器/usr/share/nginx/html目录且设置为只读
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html                          #卷名为html的emptyDir卷同时挂载至以上两个容器
    emptyDir: {}
[[email protected] ~]# kubectl apply -f emptyDir-pod.yaml
pod/emptydir-fortune created
[[email protected] ~]# kubectl get po -o wide
NAME               READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
emptydir-fortune   2/2     Running   0          9s    10.244.2.140   node02   <none>           <none>

创建pod emptydir-fortune,该pod有两个容器,同时挂载emptyDir卷,容器html-generator向卷中写入随机内容,通过访问容器web-server验证是否实现文件的共享。

2.1 loong576/fortune镜像

[email protected] ~]# more Dockerfile

[[email protected] ~]# more fortune/Dockerfile
FROM ubuntu:latest

RUN apt-get update ; apt-get -y install fortune
ADD fortuneloop.sh /bin/fortuneloop.sh

E*TRYPOINT /bin/fortuneloop.sh

该镜像的base镜像为ubuntu,镜像启动时会执行fortuneloop.sh脚本
?

fortuneloop.sh脚本:

[[email protected] ~]# more fortuneloop.sh
#!/bin/bash
trap "exit" SIGINT
mkdir /var/htdocs

while :
do
  echo $(date) Writing fortune to /var/htdocs/index.html
  /usr/games/fortune > /var/htdocs/index.html
  sleep 10
done

该脚本主要是每10秒钟输出随机短语至index.html文件中。

3. 访问nginx

3.1 创建service

[[email protected] ~]# more service-fortune.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service           #service名
spec:
  type: NodePort
  selector:
    app: prod                #pod标签,由此定位到pod emptydir-fortune
  ports:
  - protocol: TCP
    nodePort: 30002          #节点监听端口,暴露静态端口30002对外提供服务
    port: 8881               #ClusterIP监听的端口
    targetPort: 80           #容器端口
  sessionAffinity: ClientIP  #是否支持Session,同一个客户端的访问请求都转发到同一个后端Pod
[[email protected] ~]# kubectl apply -f service-fortune.yaml
service/my-service created
[[email protected] ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP          3d17h
my-service   NodePort    10.102.191.57   <none>        8881:30002/TCP   9s

3.2 nginx访问

[[email protected] ~]# curl 10.102.191.57:8881
Writing is easy; all you do is sit staring at the blank sheet of paper until
drops of blood form on your forehead.
                -- Gene Fowler
[[email protected] ~]# curl 172.27.9.135:30002
Don‘t Worry, Be Happy.
                -- Meher Baba

结论:

  • 容器nginx成功的读取到了容器fortune写入存储的内容,emptyDir卷可以实现容器间的文件共享。
  • emptyDir卷的生存周期与pod的生存周期相关联,所以当删除pod时,卷的内容就会丢失

三、hostPath

1. 概念

??hostPath允许挂载Node上的文件系统到Pod里面去。如果Pod需要使用Node上的文件,可以使用hostPath。在同一个节点上运行并在其hostPath卷中使用相同路径的pod可以看到相同的文件。

2. 创建pod hostpath-nginx

2.1 创建挂载目录

在node节点上创建挂载目录,master和各node上分别执行如下操作

[[email protected] ~]# mkdir /data && cd /data && echo `hostname` > index.html

2.2 创建pod

[[email protected] ~]# more hostPath-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: prod
  name: hostpath-nginx
spec:
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
    - mountPath: /usr/share/nginx/html   #容器挂载点
      name: nginx-volume                 #挂载卷nginx-volume
  volumes:
  - name: nginx-volume                   #卷名
    hostPath:
      path: /data                        #准备挂载的node上的文件系统
[[email protected] ~]# kubectl apply -f hostPath-pod.yaml
pod/hostpath-nginx created
[[email protected] ~]# kubectl get po -o wide
NAME               READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
emptydir-fortune   2/2     Running   0          40m   10.244.2.140   node02   <none>           <none>
hostpath-nginx     1/1     Running   0          16s   10.244.1.140   node01   <none>           <none>

3. 访问pod hostpath-nginx

[[email protected] ~]# curl 10.244.1.140
node01

结论:

  • pod运行在node01上,访问的内容为‘node01‘,为挂载的文件系统/data下index.html内容,容器成功读取到挂载的节点文件系统里的内容。
  • 仅当需要在节点上读取或写入系统文件时才使用hostPath , 切勿使用它们来持久化跨pod的数据。
  • hostPath可以实现持久存储,但是在node节点故障时,也会导致数据的丢失。

四、NFS共享存储

1. 概念

??NFS是Network File System的缩写,即网络文件系统。Kubernetes中通过简单地配置就可以挂载NFS到Pod中,而NFS中的数据是可以永久保存的,同时NFS支持同时写操作。

??emptyDir可以提供不同容器间的文件共享,但不能存储;hostPath可以为不同容器提供文件的共享并可以存储,但受制于节点限制,不能跨节点共享;这时需要网络存储 (NAS),即既可以方便存储容器又可以从任何集群节点访问,本文以NFS为例做测试。

2. nfs搭建及配置

nfs搭建详见:Centos7下NFS服务器搭建及客户端连接配置

?

完成nfs服务器搭建和客户端nfs软件安装安装后,可在master和各node节点检查nfs服务是否正常

[[email protected] ~]# showmount -e 172.27.9.181
Export list for 172.27.9.181:
/backup 172.27.9.0/24

master和node01、node02节点都执行showmount命令,用于验证nfs服务是否正常,/backup为nfs服务器对外提供的共享目录。

本文测试的NFS内容:

3. 新建pod mongodb-nfs

[[email protected] ~]# more mongodb-pod-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mongodb-nfs
spec:
  containers:
  - image: mongo
    name: mongodb
    volumeMounts:
    - name: nfs-data               #挂载的卷名,与上面的mongodb-data保持一致
      mountPath: /data/db          #MongoDB数据存放的路径
    ports:
    - containerPort: 27017
      protocol: TCP
  volumes:
  - name: nfs-data                 #卷名
    nfs:
      server: 172.27.9.181         #nfs服务器ip
      path: /backup                #nfs服务器对外提供的共享目录
[[email protected] ~]# kubectl apply -f mongodb-pod-nfs.yaml
pod/mongodb-nfs created
[[email protected] ~]# kubectl get po -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
mongodb-nfs   1/1     Running   0          23s   10.244.2.142   node02   <none>           <none>

注意此时pod的ip为10.244.2.142

4. nfs共享存储测试

4.1 向MongoDB写入数据

[[email protected] ~]# kubectl exec -it mongodb-nfs mongo
> use loong
switched to db loong
> db.foo.insert({name:‘loong576‘})
WriteResult({ "nInserted" : 1 })

切换至db loong,插入JSON文档(name:‘loong576‘)

4.2 查看写入的数据

> db.foo.find()
{ "_id" : ObjectId("5d6e17b018651a21e0063641"), "name" : "loong576" }

4.3 删除pod并重建

[[email protected] ~]# kubectl delete pod mongodb-nfs
pod "mongodb-nfs" deleted
[[email protected] ~]# kubectl apply -f mongodb-pod-nfs.yaml
pod/mongodb-nfs created
[[email protected] ~]# kubectl get po -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
mongodb-nfs   1/1     Running   0          22s   10.244.2.143   node02   <none>           <none>

删除pod mongodb-nfs并重建,此时podip变为10.244.2.143,再次访问MongoDB验证之前写入的文档是否还存在。

4.4 新pod读取共享存储数据

[[email protected] ~]# kubectl exec  -it mongodb-nfs  mongo
> use loong
switched to db loong
> db.foo.find()
{ "_id" : ObjectId("5d6e17b018651a21e0063641"), "name" : "loong576" }

即使pod被删除重建仍然能访问共享数据。

结论:

  • NFS共享存储可持久化数据
  • NFS共享存储可跨节点提供数据共享

五、PV and PVC

1. 概念

?? PersistentVolume (持久卷, 简称 PV)和Persistent VolumeClaim(持久卷声明,简称 PVC)使得K8s集群具备了存储的逻辑抽象能力,使得在配置Pod的逻辑里可以忽略对实际后台存储技术的配置,而把这项配置的工作交给PV的配置者,即集群的管理者。存储的PV和PVC的这种关系,跟计算的Node和Pod的关系是非常类似的;PV和Node是资源的提供者,根据集群的基础设施变化而变化,由K8s集群管理员配置;而PVC和Pod是资源的使用者,根据业务服务的需求变化而变化,由K8s集群的使用者即服务的管理员来配置。

?

??当集群用户需要在其pod中使用持久化存储时,他们首先创建PVC清单,指定所需要的最低容量要求和访问模式,然后用户将待久卷声明清单提交给Kubernetes API服务器,Kubernetes将找到可匹配的PV并将其绑定到PVC。PVC可以当作pod中的一个卷来使用,其他用户不能使用相同的PV,除非先通过删除PVC绑定来释放。

2. 创建PV

2.1 nfs配置

nfs服务器共享目录配置:

[[email protected] ~]# exportfs
/backup/v1      172.27.9.0/24
/backup/v2      172.27.9.0/24
/backup/v3      172.27.9.0/24

master和各node节点检查nfs配置:

[[email protected] ~]# showmount -e 172.27.9.181
Export list for 172.27.9.181:
/backup/v3 172.27.9.0/24
/backup/v2 172.27.9.0/24
/backup/v1 172.27.9.0/24

2.2 PV创建

[[email protected] ~]# more pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
spec:
  capacity:
    storage: 2Gi                             #指定PV容量为2G
  volumeMode: Filesystem                     #卷模式,默认为Filesystem,也可设置为‘Block‘表示支持原始块设备
  accessModes:
    - ReadWriteOnce                          #访问模式,该卷可以被单个节点以读/写模式挂载
  persistentVolumeReclaimPolicy: Retain      #回收策略,Retain(保留),表示手动回收
  storageClassName: nfs                      #类名,PV可以具有一个类,一个特定类别的PV只能绑定到请求该类别的PVC
  nfs:                                       #指定NFS共享目录和IP信息
    path: /backup/v1
    server: 172.27.9.181
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv002
spec:
  capacity:
    storage: 2Gi                             #指定PV容量为2G
  volumeMode: Filesystem                     #卷模式,默认为Filesystem,也可设置为‘Block‘表示支持原始块设备
  accessModes:
    - ReadOnlyMany                           #访问模式,该卷可以被多个节点以只读模式挂载
  persistentVolumeReclaimPolicy: Retain      #回收策略,Retain(保留),表示手动回收
  storageClassName: nfs                      #类名,PV可以具有一个类,一个特定类别的PV只能绑定到请求该类别的PVC
  nfs:                                       #指定NFS共享目录和IP信息
    path: /backup/v2
    server: 172.27.9.181
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv003
spec:
  capacity:
    storage: 1Gi                             #指定PV容量为1G
  volumeMode: Filesystem                     #卷模式,默认为Filesystem,也可设置为‘Block‘表示支持原始块设备
  accessModes:
    - ReadWriteOnce                          #访问模式,该卷可以被单个节点以读/写模式挂载
  persistentVolumeReclaimPolicy: Retain      #回收策略,Retain(保留),表示手动回收
  storageClassName: nfs                      #类名,PV可以具有一个类,一个特定类别的PV只能绑定到请求该类别的PVC
  nfs:                                       #指定NFS共享目录和IP信息
    path: /backup/v3
    server: 172.27.9.181
[[email protected] ~]# kubectl apply -f pv-nfs.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
[[email protected] ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLA*S   REASON   AGE
pv001   2Gi        RWO            Retain           Available           nfs                     26s
pv002   2Gi        ROX            Retain           Available           nfs                     26s
pv003   1Gi        RWO            Retain           Available           nfs                     26s

创建pv001、pv002、pv003,分别对应nfs的共享目录/backup/v1、/backup/v2、/backup/v2。

卷可以处于以下的某种状态:

  • Available(可用),一块空闲资源还没有被任何声明绑定
  • Bound(已绑定),卷已经被声明绑定
  • Released(已释放),声明被删除,但是资源还未被集群重新声明
  • Failed(失败),该卷的自动回收失败

?

PV的访问模式有三种:

  • 第一种,ReadWriteOnce:是最基本的方式,可读可写,但只支持被单个Pod挂载。
  • 第二种,ReadOnlyMany:可以以只读的方式被多个Pod挂载。
  • 第三种,ReadWriteMany:这种存储可以以读写的方式被多个Pod共享。不是每一种存储都支持这三种方式,像共享方式,目前支持的还比较少,比较常用的是NFS。

PV不属于任何命名空间, 它跟节点一样是集群层面的资源,区别于pod和PVC。

3. 创建PVC

3.1 PVC创建

[[email protected] ~]# more pvc-nfs.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: mypvc                       #声明的名称,当做pod的卷使用时会用到
spec:
  accessModes:
    - ReadWriteOnce                 #访问卷模式,筛选PV条件之一
  volumeMode: Filesystem            #卷模式,与PV保持一致,指示将卷作为文件系统或块设备使用
  resources:                        #声明可以请求特定数量的资源,筛选PV条件之一
    requests:
      storage: 2Gi
  storageClassName: nfs             #请求特定的类,与PV保持一致,否则无法完成绑定
[[email protected] ~]# kubectl apply -f pvc-nfs.yaml
persistentvolumeclaim/mypvc created
[[email protected] ~]# kubectl get pvc
NAME    STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc   Bound    pv001    2Gi        RWO            nfs            22s

创建PVC mypvc,访问卷模式为ReadWriteOnce,大小为2G;WO、ROX、RWX、RWO表示可以同时使用卷的工作节点的数量而并非pod的数量。

3.2 查看选中的PV

PVC筛选条件:

PV accessModes storage
pv001
pv002 ×
pv003 ×

PV查看:

[[email protected] ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM           STORAGECLA*S   REASON   AGE
pv001   2Gi        RWO            Retain           Bound       default/mypvc   nfs                     12m
pv002   2Gi        ROX            Retain           Available                   nfs                     12m
pv003   1Gi        RWO            Retain           Available                   nfs                     12m

pv001被选中,符合PVC定义,pv002访问模式不匹配,pv003大小不匹配。

4. pod中使用PVC

[[email protected] ~]# more mongodb-pod-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mongodb-pvc
spec:
  containers:
  - image: mongo
    name: mongodb
    volumeMounts:
    - name: pvc-data
      mountPath: /data/db
    ports:
    - containerPort: 27017
      protocol: TCP
  volumes:
  - name: pvc-data
    persistentVolumeClaim:
      claimName: mypvc          #与pvc中声明的name保持一致
[[email protected] ~]# kubectl apply -f mongodb-pod-pvc.yaml
pod/mongodb-pvc created
[[email protected] ~]# kubectl get po -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
mongodb-pvc   1/1     Running   0          16s   10.244.2.144   node02   <none>           <none>

创建pod mongodb-pvc,使用PVC mypvc,测试同四-4中的nfs共享存储测试,不再赘述。

?
?

本文所有脚本和配置文件已上传:k8s实践(七):存储卷和数据持久化(Volumes and Persistent Storage)

原文地址:https://blog.51cto.com/3241766/2435182

时间: 2024-11-05 12:53:17

k8s实践(七):存储卷和数据持久化(Volumes and Persistent Storage)的相关文章

k8s实践(九):Helm and Kubeapps UI

环境说明: 主机名 操作系统版本 ip docker version kubelet version helm version 配置 备注 master Centos 7.6.1810 172.27.9.131 Docker 18.09.6 V1.14.2 v2.14.3 2C2G master主机 node01 Centos 7.6.1810 172.27.9.135 Docker 18.09.6 V1.14.2 v2.14.3 2C2G node节点 node02 Centos 7.6.18

K8s之MySQL实现数据持久化

这个是一个只写配置及验证的博文...... 博文大纲:1.搭建nfs存储2.创建PV3.创建PVC4.确认pv及pvc的状态5.创建pod+svc(service)6.进入MySQL数据库,添加测试数据7.手动删除节点上的容器,验证数据库内的数据是否还存在8.模拟MySQL容器所在的节点宕机,验证数据是否会丢失9.client端访问MySQL数据库 k8s集群环境如下: master节点IP:192.168.20.6 node01节点IP:192.168.20.7 node02节点IP:192.

Kubernetes之(十二)存储卷

目录 Kubernetes之(十二)存储卷 简介 emptyDir存储卷 hostPath存储卷 nfs共享存储卷 PV和PVC NFS使用PV和PVC 配置NFS存储 定义PV 定义PVC 查看验证 测试访问 StorageClass Kubernetes之(十二)存储卷 简介 为了保证数据的持久性,必须保证数据在外部存储在docker容器中,为了实现数据的持久性存储,在宿主机和容器内做映射,可以保证在容器的生命周期结束,数据依旧可以实现持久性存储.但是在k8s中,由于pod分布在各个不同的节

Kubernetes学习之路(十六)之存储卷

一.存储卷的概念和类型 为了保证数据的持久性,必须保证数据在外部存储在docker容器中,为了实现数据的持久性存储,在宿主机和容器内做映射,可以保证在容器的生命周期结束,数据依旧可以实现持久性存储.但是在k8s中,由于pod分布在各个不同的节点之上,并不能实现不同节点之间持久性数据的共享,并且,在节点故障时,可能会导致数据的永久性丢失.为此,k8s就引入了外部存储卷的功能. k8s的存储卷类型: [[email protected] ~]# kubectl explain pod.spec.vo

Kubernetes 学习12 kubernetes 存储卷

一.概述 1.我们此前讲过根据应用本身是否需要持久存储数据以及某一次请求和之前的请求是否有联系,可以分为四类应用 a.有状态,要存储 b.有状态,无持久存储 c.无状态,要存储 d.无状态,无持久存储 其中,大多数和数据存储服务相关的应用和有状态应用几乎都是需要持久存储数据的.在docker中说过,容器有生命周期,为了使容器将来终结以后可以把其删除,甚至是编排到其它节点上去运行,意味着我们数据不能放在容器本地,不能放在容器自己的名称空间中.注意这是两套逻辑,以k8s为例,pod运行时应该是运行在

12.存储卷

重点: pv pvc configMap secret 1.emptyDir 只在节点本地使用,pod一删除,存储卷也就删除,临时目录,可以当缓存空间使用,无持久性,生命周期与pod一样 存储卷是可以在一个pod中的各个容器中共享. 2.gitRepo 本质上也是一个emptyDir 不会去实时同步git仓库更新,但是也可以通过一个辅助的容器去定时同步 3.hostPath 存储在宿主机上,对于集群来讲它也无持久性,如果pod被删除,新建的pod如果是在其他的node上创建,那么会访问不到之前的

7.存储卷(Volume)

一.存储卷 Pod自己是有生命周期的,如果将数据放到容器内的自有名称空间当中,则Pod的生命周期结束,数据也就消失了. 节点上提供存储集是不能解决K8S的数据存储问题,因为集群是调度的.并且节点挂掉,数据也就丢了. 集群应该使用脱离节点而存在的共享存储设备.K8S提供各种类型的存储卷来使用.对K8S来说,存储卷属于Pod,而不是容器. 持久存储卷(PersistentVolume -- pv) 持久存储卷请求(PersistentVolumeClaim -- pvc) Pv的动态供给,需要定义好

k8s存储数据持久化,emptyDir,hostPath,基于Nfs服务的PV,PVC

在docker和K8S中都存在容器是有生命周期的,因此数据卷可以实现数据持久化. 数据卷解决的主要问题: 1.数据持久性:当我们写入数据时,文件都是暂时性的存在,当容器崩溃后,host就会将这个容器杀死,然后重新从镜像创建容器,数据就会丢失. 2.数据共享:在同一个Pod中运行容器,会存在共享文件的需求. 数据卷的类型: 1.emptyDiremptyDir数据卷类似于docker数据持久化的docker manager volume,该数据卷初分配时,是一个空目录,同一个Pod中的容器可以对该

[Xcode10 实际操作]七、文件与数据-(14)数据持久化存储框架CoreData的使用:删除CoreData中的数据

本文将演示如何删除数据持久化对象. 在项目导航区,打开视图控制器的代码文件[ViewController.swift] 1 import UIKit 2 //引入数据持久化存储框架[CoreData] 3 import CoreData 4 5 class ViewController: UIViewController { 6 7 override func viewDidLoad() { 8 super.viewDidLoad() 9 // Do any additional setup a