k8s-07-存储_olume.beta.kubernetes.io/storage-class-程序员宅基地

技术标签: k8s  

掌握多种存储类型的特点 并且能够在不同环境中选择合适的存储方案(有自己的见解)
一、ConfigMap
https://kubernetes.io/zh/docs/concepts/configuration/configmap/
在这里插入图片描述
1.2版本中引入,主要功能是为了解决应用程序会从配置文件、环境变量中获取配置信息,ConfigMap API为我们提供了向容器中注入配置信息的机制,ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制对象。ConfigMap 是为了方便的处理不含铭感信息的字符串,你可以将它理解为Linux系统中的/etc目录,专门用来存储配置文件的目录

注意: ConfigMap不是属性配置文件的代替品,只是作为多个properties文件的引用。

一、创建configmap

1.创建目录和配置文件
# mkdir configmap
# cd configmap/
# cat <<EOF > cm.properties
user.name=liuli
user.password=12345
EOF
1.使用目录创建
#kubectl create configmap cm-config-path --from-file /root/configmap
2.使用文件创建
#kubectl create configmap cm-config-file --from-file /root/configmap/cm.properties
3.使用字面值创建 (--from-literal)
#kubectl create configmap cm-config-kv --from-literal=user.name=liuli --from-literal=user.password=12345
4.资源清单形式创建  【 --- 多个文件放到一起】
# vim  vim configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: special-config
  namespace: default
data:
  special.level: very
  special.type: charm
---   
apiVersion: v1
kind: ConfigMap
metadata:
  name: env-config
  namespace: default
data:
  log_level: INFO
查看
# kubectl get configmap
# kubectl get configmap cm-config-kv -o yaml

二、在Pod中使用 【# kubectl explain configmap.apiVersion 版本 v1】2种方式 (env /envFrom)

# vim pod.yaml  

apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test-container
      image: nginx
      command: ["/bin/sh","-c","env"]
      env:
        - name: SPECIAL_LEVEL_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.level
        - name: SPECIAL_TYPE_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.type
      envFrom:
        - configMapRef:
            name: env-config
  restartPolicy: Never     
 # kubectl apply -f pod.yaml    
 服务启动会打印环境变量,查看日志,可以看到导入的环境变量               
 # kubectl logs configmap-pod  

在命令行中引用 :

command: ["/bin/sh","-c","echo $(SPECIAL_TYPE_KEY)"]

通过数据卷插件使用ConfigMap

# vim valume-pod.yaml  

apiVersion: v1
kind: Pod
metadata:
  name: valume-configmap-pod
spec:
  containers:
    - name: test-container
      image: nginx
      command: ["/bin/sh","-c","sleep 6000s"]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: special-config
  restartPolicy: Never

 # kubectl apply -f valume-pod.yaml  
 进入容器查看
 # kubectl exec valume-configmap-pod -it -- /bin/bash
 -- 进入挂载目录 打印数据
root@valume-configmap-pod:/etc/config# cd /etc/config
root@valume-configmap-pod:/etc/config# ls
special.level  special.type
root@valume-configmap-pod:/etc/config# cat special.level
veryroot@valume-configmap-pod:/etc/config# 

configMap热更新

修改configMap
kubectl edit configMap special-config

修改信息:
在这里插入图片描述
等一会( 大约10s 就看到环境变量 改变了)

veryroot@valume-configmap-pod:/etc/config# cat special.level
very-hotroot@valume-configmap-pod:/etc/config# 

更新ConfigMap目前并不会触发相关pod的滚动更新,可以通过修改 pod annotaions 的方式强制触发滚动更新【TODO】
1.使用该ConfigMap挂载Env不会同步更新
2.使用该ConfigMap挂载的volume中的数据需要一段时间(大概10s)才能同步更新

二、Secret
https://kubernetes.io/zh/docs/concepts/configuration/secret/
在这里插入图片描述
解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴漏到镜像或者Pod Spec中。Secret可以以Vlolume或者环境变量的但是使用。

内置类型
kubernetes.io/service-account-token 服务账号令牌
kubernetes.io/dockercfg ~/.dockercfg 文件的序列化形式
kubernetes.io/dockerconfigjson ~/.docker/config.json 文件的序列化形式
kubernetes.io/basic-auth 用于基本身份认证的凭据
kubernetes.io/ssh-auth 用于 SSH 身份认证的凭据
kubernetes.io/tls 用于 TLS 客户端或者服务器端的数据
bootstrap.kubernetes.io/token 启动引导令牌数据

Service Account:用来访问k8s api,由系统自动创建,并且自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中
在这里插入图片描述
Opaque: base64编码格式的secret,用来存储密码、密钥等 ;是一个map类型的数据,要求value是base64编码格式

# 获取base64编码
[root@k8s ~]# echo -n "admin" |base64
YWRtaW4=
[root@k8s ~]# echo -n "12345" |base64
MTIzNDU=
# vim secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: MTIzNDU=    
  
  # kubectl apply -f secret.yaml 
  # kubectl get secret 

1.将Secret挂载到volume中

# vim pod-secret.yaml

apiVersion: v1
kind: Pod
metadata:
  name: sercret-test  
  labels  
    name: sercret-test
spec:
   volumes:
    - name: secrets
     secret:
       secretName: mysecret  
  containers:
    -image:nginx 
     name: db
     volumeMounts:
     - name: secrets
       mountPath: ''
       readOnly: true    
       
# kubectl apply -f pod-secret.yaml    
# kubectl get pod  
# kubectl exec secret-test -it -- /bin/sh
查看效果 (使用时自己完成解密)
# cd /etc/secrets
# ls
password  username
# cat password
12345# cat username
admin#

2.将secret导出到环境变量

# vim pd-pod-secrets.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-deploment
spec:
   replicas: 2
   selector:
     matchLabels:
       app: pod-deploment
   template:
     metadata:
       labels:
         app: pod-deploment
     spec:
       containers:
       - name:  myapp
         image: nginx
         imagePullPolicy: IfNotPresent
         ports:
         - containerPort: 80
         env:
         - name: TEST_USER
           valueFrom:
             secretKeyRef:
               name: mysecret
               key: username
         - name: TEST_PASSWORD
           valueFrom:
             secretKeyRef:
               name: mysecret
               key: password
               
#kubectl apply -f  pd-pod-secrets.yaml       
# 查看结果    
[root@k8s secret]# kubectl get pod
NAME                            READY   STATUS    RESTARTS   AGE
pod-deploment-f69948d58-gqmlx   1/1     Running   0          6s
pod-deploment-f69948d58-vjfwh   1/1     Running   0          6s
[root@k8s secret]# kubectl exec pod-deploment-f69948d58-gqmlx -it -- bin/sh
# echo $TEST_USER
admin

kubernates.io/dockerconfigjson: 用来存储私有docker register 的认证信息

# kubectl create secret docker-registry myregistrykey \
--docker-server=DOCKER_REGISTER_SERVER \
--docker-username=DOCKER_USER \
--docker-password= DOCKER_PASSWORD \
--docker-email=DOCKER_EMAIL 

在创建pod的时候使用,通过imagePullSecrets来引用创建的 ‘myregistrykey ’

apiVersion: v1
kind: Pod
metadata:
   name: foo
spec:
  containers:
    - name: foo
      iamge: nginx
    imagePullSecrets:
     -name: myregistrykey      

basic-auth 、basic-auth (参考服务发现 ingress-nginx 应用)

三、volume
https://kubernetes.io/zh/docs/concepts/storage/
Container 中的文件在磁盘上是临时存放的,这给 Container 中运行的较重要的应用 程序带来一些问题。问题之一是当容器崩溃时文件丢失。kubelet 会重新启动容器, 但容器会以干净的状态重启。 第二个问题会在同一 Pod 中运行多个容器并共享文件时出现。 Kubernetes 卷(Volume) 这一抽象概念能够解决这两个问题。
在这里插入图片描述
olume是k8s数据卷,常见的数据卷有4种类型:EmptyDir,HostDir,NFS,Secret,gitRepo。Kubernetes支持Volume类型有:

emptyDir
hostPath
gcePersistentDisk
awsElasticBlockStore
nfs
iscsi
fc (fibre channel)
flocker
glusterfs
rbd
cephfs
gitRepo
secret
persistentVolumeClaim
downwardAPI
projected
azureFileVolume
azureDisk
vsphereVolume
Quobyte
PortworxVolume
ScaleIO
StorageOS
local

emptyDir
使用emptyDir,当Pod分配到Node上时,将会创建emptyDir,并且只要Node上的Pod一直运行,Volume就会一直存。当Pod(不管任何原因)从Node上被删除时,emptyDir也同时会删除,存储的数据也将永久删除。注:删除容器不影响emptyDir。
使用场景:

  • 暂存空间,例如用于基于磁盘的合并排序
  • 用做长时间计算崩溃恢复时的检查点
  • web服务器容器提供数据时,保存内容管理器容器提取的文件

hostPath
hostPath允许挂载Node上的文件系统到Pod里面去。如果Pod需要使用Node上的文件,可以使用hostPath。

  • 空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。
  • DirectoryOrCreate 如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。
  • Directory 在给定路径上必须存在的目录。
  • FileOrCreate 如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet
    相同的组和所有权。
  • File 在给定路径上必须存在的文件。
  • Socket 在给定路径上必须存在的 UNIX 套接字。
  • CharDevice 在给定路径上必须存在的字符设备。
  • BlockDevice 在给定路径上必须存在的块设备。

当使用这种类型的卷时要小心,因为:
1.具有相同配置(例如基于同一 PodTemplate 创建)的多个 Pod 会由于节点上文件的不同 而在不同节点上有不同的行为。
2.下层主机上创建的文件或目录只能由 root 用户写入。你需要在 特权容器 中以 root 身份运行进程,或者修改主机上的文件权限以便容器能够写入 hostPath 卷。

实验

# vim emptydir.yaml

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: nginx
    name: myapp
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {
    }
          
# kubectl apply -f  emptydir.yaml    
## 一个空目录
# kubectl exec test-pd -it  -- /bin/sh
# cd /cache

# vim hostpath.yaml

apiVersion: v1
kind: Pod
metadata:
  name: test-pod2
spec:
  containers:
  - image: nginx
    name: myapp2
    volumeMounts:
    - mountPath: /home
      name: mydir
    - mountPath: /home/a.txt
      name: myfile
  volumes:
  - name: mydir
    hostPath:
      path: /home
      type: DirectoryOrCreate
  - name: myfile
    hostPath:
      path: /home/a.txt
      type: FileOrCreate
# kubectl apply -f hostpath.yaml     
# kubectl exec test-pod2 -it -- /bin/sh  
查看挂载的文件
# cd /home
# ls

四、PV/PVC (实际中使用的较少 ,没有深入研究)
https://kubernetes.io/zh/docs/concepts/storage/persistent-volumes/
在这里插入图片描述
**持久卷(PersistentVolume,PV)**是集群中的一块存储,可以由管理员事先供应,或者 使用存储类(Storage Class)来动态供应。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样,也是使用 卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。

**持久卷申领(PersistentVolumeClaim,PVC)**表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载,参见访问模式)。

静态供应
集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息,并且对集群 用户可用(可见)。PV 卷对象存在于 Kubernetes API 中,可供用户消费(使用)。

动态供应 (实现比较繁琐,还不太有好)
如果管理员所创建的所有静态 PV 卷都无法与用户的 PersistentVolumeClaim 匹配, 集群可以尝试为该 PVC 申领动态供应一个存储卷。 这一供应操作是基于 StorageClass 来实现的:PVC 申领必须请求某个 存储类,同时集群管理员必须 已经创建并配置了该类,这样动态供应卷的动作才会发生。 如果 PVC 申领指定存储类为 “”,则相当于为自身禁止使用动态供应的卷。

绑定
PVC 申领与 PV 卷之间的绑定是一种一对一的映射

保护使用中的存储对象

确保仍被 Pod 使用的 PersistentVolumeClaim(PVC)对象及其所绑定的 PersistentVolume(PV)对象在系统中不会被删除,因为这样做可能会引起数据丢失。

访问模式

  • ReadWriteOnce – 卷可以被一个节点以读写方式挂载;
  • ReadOnlyMany – 卷可以被多个节点以只读方式挂载;
  • ReadWriteMany – 卷可以被多个节点以读写方式挂载。

在命令行接口(CLI)中,访问模式也使用以下缩写形式:

  • RWO - ReadWriteOnce
  • ROX - ReadOnlyMany
  • RWX - ReadWriteMany

重要提醒! 每个卷只能同一时刻只能以一种访问模式挂载,即使该卷能够支持 多种访问模式。
在这里插入图片描述


每个 PV 可以属于某个类(Class),通过将其 storageClassName 属性设置为某个 StorageClass 的名称来指定。 特定类的 PV 卷只能绑定到请求该类存储卷的 PVC 申领。 未设置 storageClassName 的 PV 卷没有类设定,只能绑定到那些没有指定特定 存储类的 PVC 申领。

早前,Kubernetes 使用注解 volume.beta.kubernetes.io/storage-class 而不是 storageClassName 属性。这一注解目前仍然起作用,不过在将来的 Kubernetes 发布版本中该注解会被彻底废弃。

回收策略

  • Retain – 手动回收
  • Recycle – 基本擦除 (rm -rf /thevolume/*) 【新版本已经废弃】
  • Delete – 诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除

目前,仅 NFS 和 HostPath 支持回收(Recycle)。 AWS EBS、GCE PD、Azure Disk 和 Cinder 卷都支持删除(Delete)。

阶段
每个卷会处于以下阶段(Phase)之一:

  • Available(可用)-- 卷是一个空闲资源,尚未绑定到任何申领;
  • Bound(已绑定)-- 该卷已经绑定到某申领;
  • Released(已释放)-- 所绑定的申领已被删除,但是资源尚未被集群回收;
  • Failed(失败)-- 卷的自动回收操作失败。

PVC(PersistentVolumeClaims)
每个 PVC 对象都有 spec 和 status 部分,分别对应申领的规约和状态,申领可以通过为 storageClassName 属性设置 StorageClass 的名称来请求特定的存储类。 只有所请求的类的 PV 卷,即 storageClassName 值与 PVC 设置相同的 PV 卷, 才能绑定到 PVC 申领。

1.在(192.168.44.134) 安装nfs服务器
# yum install -y nfs-common nfs-utils rpcbind
# mkdir /nfs 
# chmod 777 /nfs/
# chown nfsnobody /nfs/
# vim /etc/exports
   /nfs *(rw,no_root_squash,no_all_squash,sync)
# systemctl start rpcbind
# systemctl start nfs
# exportfs -rv      //重新刷新共享(修改配置后找不到)
2.访问节点安装客户端(所有k8s节点)
# yum install -y nfs-utils rpcbind
# vim nfspv.yaml
测试挂载 ,验证后取消挂载
# mkdir /test
# showmount -e 192.168.44.134
    Export list for 192.168.44.134:
    /nfs *
# mount -t nfs 192.168.44.134:/nfs /test/
==== 可以测试文件访问+写入
# umount /test/
3.创建pv
# vim nfspv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /tmp
    server: 192.168.44.134
    
# kubectl apply -f nfspv.yaml    
# kubectl get pv
4.创建PVC
# vim pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc001
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  storageClassName: slow
  resources: 
    requests:
      storage: 5Gi
创建      
# kubectl apply -f pvc.yaml 
查看
# kubectl get pv
# kubectl get pvc
5.使用申请PVC   【该实验没成功,有时间在研究 TODO】
# vim pod-pvc.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pvc-pod
spec:
  volumes:
    - name: pvc-storage
      persistentVolumeClaim:
        claimName: pvc001
  containers:
    - name: pvc-container
      image: nginx
      ports:
        - containerPort: 80
      volumeMounts:
        - mountPath: /var/www/html
          name: pvc-storage
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wenwang3000/article/details/113578718

智能推荐

C++ 输出二进制数_c++输出二进制-程序员宅基地

文章浏览阅读1.9w次,点赞6次,收藏25次。这几天学组成原理,碰到需要输出二进制数的情况,验证a÷(2^n)≠a>>n,我想用高级语言内在的模块实现,程序如下。bitset后面的<>中的数字,指定输出的位数#include<iostream>#include<bitset>using namespace std;int main(){ int a; cin>>a;..._c++输出二进制

[Android官方API阅读]___<System Permissions>_安卓阅读api-程序员宅基地

文章浏览阅读2.5k次。System PermissionsAndroidis a privilege-separated operatingsystem, in which each application runs with a distinct system identity (Linuxuser ID and group ID). Parts of the system are also separate_安卓阅读api

机器学习实战案例:使用随机森林/XGBoost等模型进行分类预测,提高银行营销活动效率_csdn机器学习随机森林模型训练实例-程序员宅基地

文章浏览阅读7.7k次,点赞7次,收藏118次。1. 项目背景介绍在这篇文章中,我们将使用 Python 搭建逻辑回归(Logistic Regression),随机森林(Random Forest),XGBoosting,Bagging,KNN (K-Nearest Neighbors) ,神经网络(Neural Network)等6种机器学习/深度学习模型,对某个银行的营销活动数据集进行分类预测,尝试找出那些潜在客户。银行通常会打电话给一些潜在客户来销售一些存款/投资产品,这些投资产品会使银行获得更多利润以及资金的灵活性,因此银行希望能定位那些会_csdn机器学习随机森林模型训练实例

ZZULIOJ 1068: 二进制数_二进制数 题目描述 将一个二进制数,转换为对应的十进制数。 输入描述 输入一个二-程序员宅基地

文章浏览阅读1k次。题目描述将一个二进制数,转换为对应的十进制数。输入输入一个二进制数,以回车结束。该二进制数为正数,长度不超过31。输出输出一个整数,为该二进制数对应的十进制数。样例输入00000000001样例输出2049int main(){ int d = 0; char ch; while ((ch = getchar()) != '\n') {..._二进制数 题目描述 将一个二进制数,转换为对应的十进制数。 输入描述 输入一个二

一篇文章,读懂品牌广告与效果广告的相同和不同_两个广告的相同点和不同点-程序员宅基地

文章浏览阅读2.9k次。在互联网公司工作的同学,对效果广告应该在熟悉不过了,花钱买流量,几乎是每个互联网公司都在做的事情。如果能一直这样维持下去,花多少钱,买多少流量,那也挺好的。然而,随着流量成本的急剧攀升,获客单价从几年前的几块钱,到如今的几十、上百块,在LTV依旧不变的情况下,ROI越来越低,这个时候,如何低成本的获取到流量,成为了很多互联网公司迫在眉睫需要解决的问题。有先见之明的老板,已然把目光锁定在了传统的..._两个广告的相同点和不同点

Java 读写锁_java 读写锁 trylock-程序员宅基地

文章浏览阅读303次。读写锁ReadWriteLock 读写锁 它的实现类对象为ReentrantReadWriteLock1.获取读锁:锁对象.readLock2.获取写锁:锁对象.writeLock3.读锁可以共享 写锁则互斥package com.Trylockkk;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks._java 读写锁 trylock

随便推点

炸!1024我的故事,一个写了两年博客的大厂码农!_小傅哥的码场-程序员宅基地

文章浏览阅读3k次,点赞34次,收藏37次。作者:小傅哥博客:https://bugstack.cn沉淀、分享、成长,让自己和他人都能有所收获!????一、嗯,肝了两年300篇文章、4本PDF、2个小册、1本出版图书,为自己折腾到日子让我兴奋!两年来,11前睡觉,早上6:20起床洗漱????、7:20跑步回来????,写作️或看书一小时,到了周末基本就可以全时间投入到自己到这个小世界里:编写案例、整理博客、发布文章、技术交流、同好扯皮。哈哈哈,有伙伴问傅哥,你咋这么卷!可能我自己到没觉得,因为做自己喜欢的事你会发现自己特别容易投入,_小傅哥的码场

谈谈对软件开发的理解_对软件开发的理解和认识-程序员宅基地

文章浏览阅读4k次。大学毕业已有三年整,从kai_对软件开发的理解和认识

Java基础05-接口、多态_接口要不要多態-程序员宅基地

文章浏览阅读135次。1.接口1.1什么是接口接口,是Java语言中一种引用类型,是方法的集合。接口的定义,它与定义类方式相似,但是使用interface关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。引用数据类型:数组,类,接口。接口的使用,它不能创建对象,但是可以被实现( implements ,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。1.2 定义格式publi_接口要不要多態

深入学习 gremlin_深入学习gremlin-程序员宅基地

文章浏览阅读607次。gremlin中文文档常用 1_深入学习gremlin

dlopen 与dlsym_"main_fptr = (int (*)())dlsym(rtld_default, \"main-程序员宅基地

文章浏览阅读762次。共享对象通过dlopen动态打开动态库的加载完成后,返回一个句柄,通过dlsym定位到你需要执行的函数指针然后可以在程序中使用dlopen -- open a dynamically linked librarydlsym -- get the address of a symbol in a dynamically linked library例子void*handle;_"main_fptr = (int (*)())dlsym(rtld_default, \"main\")"

Mimics 21.0 安装_mimics21-程序员宅基地

文章浏览阅读2.6w次,点赞20次,收藏91次。文章目录Mimics 21.0 安装Mimics 21.0 更新说明安装教程安装前准备主程序安装破解Mimics教程破解3-matic 教程汉化教程功能介绍**模块介绍**基础模块可选模块软件优势比利时 Materialise 公司介绍Mimics 21.0 安装Mimics 21.0 更新说明Mimics 21破解版是一款非常专业的交互式的医学影像控制系统,全称为"Materialise's interactive medical image control system",是全球领先的致力于快速成_mimics21

推荐文章

热门文章

相关标签