K8s是轻量级的,因为是用Go开发的,Go效率跟C看齐,但是在语言级别支持进程管理,K8s还有个特点就是弹性伸缩,负载均衡采用IPVS(LVS是一种服务器,IPVS是它的负载均衡实现技术)
K8s中所有的内容都被抽象成资源,资源实例化后叫做对象
总体章节:介绍、基础概念、K8s集群构建、资源清单、Pod控制器、服务发现
服务的分类:有状态(例:DBMS:数据管理)、无状态(调度LVS)
存储有很多:configMap存储配置文件、Secret存储重要数据(用户密码)、Volume存储基本的数据(网页)、PV(动态的创建过程)
调度器:根据要求把Pod定义到想要的节点
HELM:类比于yum,只不过yum安装rpm,HELM安装集群
RC负责当Pod副本不满足期望值时,则进行修改
Etcd存储k8s的参数信息,是天生支持集群化 K-V结构,etc v2存到内存,v3引入本地磁盘,要是v2版的话就是要进行数据备份,k8s v1.11之后才支持v3
raft是存储信息 WAL是日志,防止raft出错,snapshot会对WAL进行定时备份,store就是本地化磁盘,进行持久化
kubelet会对docker进行交互,操作docker创建对应的容器,kubelet来维持pod的声明周期,可以这样理解:kubelet将K8s的命令进行解析,解释成容器引擎能听懂的指令,让容器引擎进行解析;kube proxy是实现pod之间的交互以及负载均衡,实际是操作防火墙来实现pod的映射,负责写入规则到IPTABLES、IPVS实现服务的映射
API Server是所有访问的入口,包括各个Node的kubelet和kube proxy
Pod分为两种:一种是自主式Pod(没人管)、一种是控制器管理的Pod
只要是有Pod,则必有一个pause容器,Pod中的容器会公用pause中的网络栈和存储卷,Pod内部的容器相互访问只需要直接写端口就行,但是端口不能冲突,形式上是同一个IP下的不同端口
以下这几个都是控制器,也叫工作负载
第一类细分还是包括三种Pod,不过有重合性,RC就是会保持pod在期望数值,新版本k8s中建议用ReplicaSet取代RC,区别就是RS支持集合式的selector(就是通过Pod的标签对Pod进行操作),但是还是建议用Deployment,因为Deployment支持滚动更新,但ReplicaSet不支持 ;
注意:Deployment也是通过创建RS来创建Pod
HPA(Horizontal Pod AutoScaling)平滑扩展,只使用于Deployment和ReplicaSet
StatefulSet有状态服务,MySQL MangoDB ,稳定的持久化存储、稳定的网络标识(Pod重新调度后其PodName和HostName不变)、有序部署(下一个Pod运行之前所有之前的Pod都必须是Running和Ready),基于init container实现,例先启MySQL,再启Apache,再启Nginx
DeamonSet确保全部(或者一些,因为打了污点的Node就不会被创建)Node上运行有且只有一个Pod的副本,当有新Node加入集群时,则会新加入一个Pod的副本,Node出Pod时,也会被移除,例如日志、监控类的Pod可以创建成此种形式
Job负责批处理,且是仅执行一次的任务,保证批处理任务的一个或者多个Pod成功结束
每个Sevice都有IP和端口,通过RR(轮循)算法来找各个Pod,Service暴露的方式有Nodeport、Igress等
网络模型:假定都在一个直接连通的扁平化网络中,即Pod中可以通过对方IP“直接”访问另一个Pod,至少表面上看起来是这样的,估计实际上也跟docker一样是要经过别的docker或者pod的路由
同一个Pod中的容器相互访问,直接通过losthost访问,都通过pause这个Pod;各个Pod之间的通讯:Overlay Network(全覆盖网路);Pod与Service之间通讯,各节点的Iptables规则、IOS转发
Flannel是K8s的网络服务,目的是让集群中不同节点的主机 所创建的docker具有全集群唯一的虚拟IP,,并且在这些IP间建立一个覆盖网络
Web app1、Web app2…都是一个个的Pod。Flanneld是个守护进程,会监听一个端口,这端口就是后期转发数据包、接入数据包的服务端口;Flannel这个进程一旦启动后,就会开启一个网桥叫Flannel0,其专门收集Docker0转发出来的数据包,且其上还有存储在etcd中的路由控制表(注意Flannel0是网桥,Flanneld是一个进程);Docker0会分配自己的IP到对应的Pod上;若是同一台物理机上不同的Pod相互访问,数据包走的是本机上的Docker0网桥;而跨网段中,则是按照对应流程一步步走,注意到了Flanneld后会对数据进行封装(注意看右上角的封装结构,outerIP的目标地址应该是12,使用的是UDP的协议转发的)。总结一下实际上就是一个虚拟路由
ETCD给Flannel提提供:存储管理Flannel可分配的IP地址资源;监控每个Pod的实际地址,并在内存中建立Pod的节点路由表
总结:
其中ipitables现在基本上不用了,而是转为LVS(==IPVS)
这其中有三层网络,但是只有节点网络是真实的,Service、Pod网络都是虚拟网络,也可以理解成是内部网络
是K8s集群中的虚拟集群(群中群),一个K8s集群中可以有多个命名空间,他们在逻辑上彼此隔离,个人感觉类似于安全组
大多数的K8s集群有三个命名空间:default(业务service和app都默认创建在这里)、kube-system(kubernetes系统组件都创建在这里)、kubu-public(公共资源,实际上并不怎么使用)
default的命名空间没什么特别,但是不能删除,小的系统可以用这样,但不建议在大系统中用,因为大的系统中,可能会无意重写或者中断服务,所以要用命名空间把服务的service切割成容易管理的块
通过kubectl进行操作:步骤有创建namespace、在namespace中创建资源、管理激活当前的命名空间(使用kubens + 命名空间名 则可以切换命名空间)、跨命名空间通信
所有kubectl 都是在某个命名空间下执行的
命名空间不是相互隔绝的,一个namespace中的service可以和另一个namespace中的service进行通信,且保持service在各自的namespace下
当应用app要访问Kubernetes的service,可以使用内置的DNS服务发现并把app指到Service的名称。然而,可以在多个namespace中创建同名的service。解决这个问题,就用到DNS地址的扩展形式。一般情况下,只需要service的名称,DNS会自动解析到它的全地址。然而,如果要访问其他namespace中的service,那么就需要同时使用service名称和namespace名称。例如,想访问test中的“database”服务,可以使用下面的地址:
database.test
创建多少个namespace比较好呢?运作5-10的微服务,则将这些放在一个default空间中;如果是多个子团队协作,各自负责各自的微服务,则要针对生产环境和开发环境使用多个集群或者命名空间了,每个团队拥有各自的命名空间,这样更容易进行管理。
为什么要用yaml文件呢?因为最基本的是在命令行中用kubectl create 加一堆 参数进行创建,但是由于参数太多了,因此就用建立一个yaml文件,将参数写到yaml文件中,直接在命令行中通过yaml文件来创建
可以在一个yaml文件中编写多个资源,即可以有多个kind,则最后执行创建后就能一次性创建多个资源
集群资源分类:
名称空间级别:default kube-system,又分为
1*工作负载型(Pod ,ReplicaSet(也叫调度器、控制器管理Pod的创建,通过标签的选择来控制副本的数目),Deployment(通过控制RS的创建来创建Pod),StatefulSet(有状态服务的管理器)、DaemonSet(在每个节点都运行一个组件,用于日志、监控)
、Job(工作,为了批处理)、CronJob(轮询工作,为了批处理))注:ReplicationController在v1.11版本被废弃
2*服务发现及负载均衡型资源:Service、Ingress,这两者都是为了将服务暴露出去
3*配置与存储型资源:Volume(存储卷)、CSI(容器存储接口,可以扩充各种第三方存储卷,即只要存储符合此接口的规范,则集群就能调用此资源)
4*特殊类型的存储卷:ConfigMap(当配置中心使用的资源类型,存储配置文件,达到热更新的状态)、Secret(运用加密的方案存储数据,保护敏感数据,例如密码文件、密钥)、DownwardAPI(和CSI比较像,也是一个接口,通过接口调用外部环境的数据和信息)
集群级别:不管在什么空间下定义,在其他命名空间下都能看到,在定义时候根本没有指定命名空间
Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding
元数据型:通过指标进行操作,例如:HPA,通过CPU的利用率进行平滑扩展
HPA、PodTemplate(Pod模板)、LimitRange(资源限制)
YAML是一种可读性高,用来表示数据序列的格式,其仍然是一种标记语言,但是是以数据为中心而非以标记语言为中心(这是和json最大的区别);不能用Tab,只能用空格;相同层级元素左对齐;用#进行左对齐;支持对象(键值对,有行内表示法)、数组(有行内表示法)、对象写法和数组写法可以合在一起形成复合类型、纯量(是基本的不可再分的)
YAML必须存在的属性
对于version,每一个pod或者svc每一种类型调度的组和版本都不一样,查看完整的组和版本就用kubectl api-versions
查询
主要对象(可选的属性),不写的话系统有默认的属性
想看全的就用kubectl explain +命令名.描述(pod.spec)
Pod还可以分为:自主式Pod(没人管,死了也不会被重启)、控制器管理的Pod(在控制器的生命周期里,始终维持Pod的副本数量)
RS(原来叫RC,而RS通过label了Selector):只实现最基本的功能,即维持Pod的最基本的副本数
Deployment:(apiService:extensions/v1beta1)为Pod和RS提供了一个声明式定义方法(PS:命令式编程和声明式编程)、通过控制RS的数目和版本进行滚动更新和回滚(==只有Deployment才能做到滚动更新和回滚,而RS做不到);可以进行扩容,用kubectl scale就行,但是是只增加了模板数,不会更新rs控制器,即不会版本回退;只有进行了版本回滚,才会有rs的变化,使用rollout;还能进行镜像更新,使用set image,更新镜像也会引起创建新的rs
扩容、镜像更新、回滚也非常简单
同一个rs下扩容副本,pod加了,但rs没变化;只有回滚和更新版本时才会有新的rs被创建出来
DaemonSet:(apiVersion:app/v1)一般用于守护进程方案,确保全部或一些(管理员可以用调度策略通过标签选择和污点进行改变)Node上运行一个(注意只能是一个,要有多个就建立多个DaemonSet)Pod的副本
Job:(apiVersion:batch/v1)
关于有状态应用:除了有无状态应用的特点外,还有:
每个Pod都是独立的,保持启动顺序和唯一性(通过网络标识符,持久存储),是个Headless Service,其Service的ClusterIP 为None,即Service不是通过IP进行标识,而是生成一个特定域名进行相关操作。注意定义无头Service的yaml时要将ClusterIP设置为None,定义工作负载时kind要写成StatefulSet
Horizontal Pod Autoscaling(HPA)不是控制器,而是控制器的附属品,是通过控制Deployment等这些控制器来简介控制
所有的Pod都不会在主节点上运行,跟污点有关,主节点不会参加到调度中
作用:一是进行服务发现,即防止后端Pod在被调用时,由于自身升级等原因导致IP变化,则通过IP调用此Pod的前端服务会和此Pod失联;二是进行对Pod的负载均衡
Service和Pod建立关系的方式和工作负载(控制器)与Pod建立关系的方式相同,都是通过labels和selector
Service本身是也有IP的,叫VIP(虚拟IP)
进行服务发现,根据标签来匹配到后端不同的pod上,如果Pod有变化的化,更新信息会被同步到svc中,则nginx在反向代理svc的话,svc会自动更新信息,相当于就是封装,但是svc默认只能实现四层的负载均衡(只能基于IP和端口进行转发),不能实现七层(域名、主机名),但是可以后面通过添加ingress实现七层转发
service类型的不同体现在service配置文件中的type字段,默认为Cluster IPClusterIp就是集群内部的访问机制,只有同一k8s集群内部的其他服务、pod、node能通过此ip来访问此服务,集群外部不能访问,但集权内部的即使是其他Node上也能访问
Nodeport是在每台机器上绑定的端口,因此可以被外部访问到,这也是经常使用的对外暴露服务的方法;同时对每台物理机都会暴露一个端口(例30001,则客户访问的不管通过nginx访问哪个Node上的30001端口,后端都会有相同的服务),注意访问的IP地址是物理机上的IP,即xshell上的IP加kubectl get svc得到的端口,而不是kubectl get svc的CLUSTER-IP得到的IP,因为这个IP是内部访问IP
注意,NodePort的Service会在集群上的每个节点都开启一个port(但是每个节点主机上的端口号都是一致的),则外部通过任意一个节点的IP+端口号都能访问到服务的Pod,则每个节点上的每个端口只能对应一个应用,不能复用,这是缺点,改进的方法就是用Ingress
第三种loadbalancer和第二种比较像,只不过nginx换成了云供应商提供的接口,详见第九条
Client访问svc的时候是访问iptables,通过iptables来实现,而iptables规则是通过kube-proxy来写入的,apiserver通过监控kube-proxy来实现服务和端点信息的发现,kube-proxy通过标签来判断端点信息是否写入了svc的端点信息里去
反向代理:
以下的几种反向代理,都是服务层级上的代理,即同一个服务通过服务的反向代理来将访问分发到各个Pod中,此处的负载均衡不是nginx(nginx是此处再往前端的负载均衡)
OS要提前安装ipvs模块并开启rr、lc等算法之后才支持ipvs,否则会退化成iptables模式;ipvs和iptables来说,除了内核防火墙的模块不一致外其他都是一样的
前端Api将信息写到etcd中,kube-proxy来监控etcd,若etcd变化了,则kube-proxy将变化写入本地iptables(ipvs)规则中
一个例子:
Deployment:其中metadata中的name为此Deployment控制器的名字,selector中的matchLabels为只有当Pod中app字段和release和matchLables相同时,这个Deployment才会认这个Pod并管理;templete字段为具体创建Pod的内容,labels为实际创建Pod时的标签,这里和Deployment的matchLabels对应字段一定要相同,Deployment会不认这个Pod从而重复创建
在Pod基础上增加的Service:
Pod中上selector需要和之前创建的Pod上的标签一样,则才会识别Pod,Pod是前端的端口,而targetPort是后端各个服务Pod的端口,由于类型是Cluster IP,因此不能暴露给集群外,只能在集群内部访问
以上3种服务点上的反向代理都是叫Service IP,但有时不需要或者不想要负载均衡,则指定Cluster IP为None来创建Headless Service,这类Service不会分配Cluster IP,kube-proxy不会处理它们,平台也不会为他们进行负载均衡和路由
一组Pod可以对应多个svc,只要Pod的标签和svc的标签相同就行
第四种Service类型:ExternalName:
即作用是引入外部流量至服务的内部,即访问my-service.default.svc…这个DNS后则会转发到externalName字段定义的名字,这个服务在kubectl get svc后在EXTERNAL-IP中就有值
以上的Service转发都是四层代理,从k8s1.0后可以用ingress实现七层代理
Nginx最常见的方案还是NodePort,这样Nginx就以内部的服务暴露给外部的用户,Nginx的配置文件不需要自己写,而是会自动添加
Ingress也是一个组件,但不是k8s内置的。是一种全局的、为了代理不同后端 Service 而设置的负载均衡服务,其由两部分组成:Ingress Controller和Ingress服务。Ingress Controller有很多种,其中一种是官方维护的Nginx Controller
使用Ingress方式:第一步通过yaml文件部署Ingress Controller,第二步创建Ingress规则(也是用yaml文件,kind为ingress)
Ingress Controller:根据定义的 Ingress 对象,提供对应的代理能力。业界常用的各种反向代理项目,比如 Nginx、HAProxy、Envoy、Traefik 等,都已经为Kubernetes 专门维护了对应的 Ingress Controller
Ingress规则:即要通过配置Ingress 规则来找到需要访问端口、Pod等
Ingress 控制器不同于Deployment 控制器的是,Ingress控制器不直接运行为kube-controller-manager的一部分,它仅仅是Kubernetes集群的一个附件,类似于CoreDNS,需要在集群上单独部署
Ingress和Pod通过service进行关联,Ingress作为统一入口,由Service关联一组Pod;Service是个抽象概念,是为了通过标签发现一组Pod而已
都是通过域名访问Ingress的
使用Ingress:第一步,部署Ingress Controller ,创建Ingress规则
作用:Secret中存放的是加密的数据,而Config中存不加密的数据,==也是存放到etcd中!==让Pod以变量或者Volume挂载到容器中
创建过程:编写原始配置文件(yaml、xml、json、property),里面放着IP 端口 用户名 密码等;然后通过原始配置文件,创建ConfigMap(kind=ConfigMap),具体操作见第三条;最后通过ConfigMap创建Pod(kind=Pod)。
ConfigMap创建:用目录创建(同一目录下的所有配置文件创建成同一个配置文件kubectl create configmap 创建后的文件名 --from-litreal=[目录])、使用单一文件创建(kubectl create configmap 创建后的文件名 --from-litreal=文件名)、通过命令行中指定参数(kubectl create configmap 创建后的文件名 --from-litreal=…)。其实以上三种都是一样的
许多应用程序(比如MySQL)会从配置文件、命令行参数或者环境变量中读取配置信息,ConfigMap API给我们提供了向容器中注入配置信息的机制,其可以用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制大对象
缩写为cm 例:kubectl get cm
一些服务想提供服务的话,需要向配置文件注册中心索要自己的配置信息,注册中心会给索要配置的一些服务匹配一些配置信息,比如主机名、或者IP地址的范围从而分配对应服务的配置文件,当然不同的集群会索要到不同的配置文件,当需要修改配置文件时,则只需要在注册中心修改对应的配置文件即可,则会触发每个服务进程的自己修改自己的配置文件。==从而使得保存和维护服务进程更简单,这就是配置文件注册中心的意义!==更倾向于把配置文件保存成一个文件分发到下方
ConfigMap也是这样的思想
ConfigMap用途:代替环境变量、设置命令行参数、在数据卷中使用,有不同的选项,最基本的就是将文件填入数据卷,在这个文件中,键就是文件名,键值就是文件内容
. volume是kubernetes Pod中多个容器访问的共享目录。volume被定义在pod上,被这个pod的多个容器挂载到相同或不同的路径下。volume的生命周期与pod的生命周期相同,pod内的容器停止和重启时一般不会影响volume中的数据。所以一般volume被用于持久化pod产生的数据
和docker 还不一样,docker重启不会清除磁盘上的文件。在K8s中,即使Pod中的某个容器挂了,数据依然保存在卷中,但是当Pod整体挂了,则卷也不存在了。
K8s中Volume有多种类型,包括(empthDir、hostPath、nfs、PVC、secret等)
emptyDir的生命周期与所属的pod相同。pod删除时,其emptyDir中的数据也会被删除。emptyDir类型的volume在pod分配到node上时被创建,kubernetes会在node上自动分配 一个目录,因此无需指定宿主机node上对应的目录文件。emptyDir Volume主要用于某些应用程序无需永久保存的临时目录,多个容器的共享目录等。
pod删除或者是调度到另外一个Node,原先Node上的存储卷还在。
hostPath Volume为pod挂载宿主机上的目录或文件,使得容器可以使用宿主机的文件系统进行存储。缺点是,在k8s中,pod都是动态在各node节点上调度。当一个pod在当前node节点上启动并通过hostPath存储了文件到本地以后,下次调度到另一个节点上启动时,就无法使用在之前节点上存储的文件。
什么是挂载?在Linux下,mount挂载的作用,就是将一个设备(通常是存储设备)挂接到一个已存在的目录上。访问这个目录就是访问该存储设备。
每个Pod启动时会启动一个pause容器,这是进行网络和卷的共享,因为Pod被定义,就是因为Pod中的容器能共享网络和卷。一旦挂载了Volume,则这个Pod中的所有docker都能在本容器内的固定位置访问到这个卷,但是注意每个容器内的挂载位置可以不同,但若在yaml中指定了后就不能再改了 (即Volumn可以共享容器的相同路劲和不同路径)
nfs:网络存储,专门一个节点(不是master,也不是ndoe)进行持久化存储,其他节点来用这个节点上的数据。具体步骤:找一台服务器安装nfs服务端(yum install -y nfs-utils)、设置挂载路径(到/etc/exports中设置对外挂载的路径,但是这个路径要提前设置出来)、在集群内其他节点上安装nfs,安装好后就能自动挂载nfs、在nfs服务端启动nfs(systemctl start nfs)、在Node或者master中创建资源时,要在yaml中的volume中写成nfs,且要配置nfs服务器的地址
但是由于nfs的弊端:客户节点的配置文件中需要配置nfs服务节点的IP,所以要用到PV、PVC:PV(持久化存储,是进行数据存储的生产者):对存储资源进行抽象,对外提供可以调用的地方;PVC(持久化存储调用,是进行数据调用的消费者):用于调用,不需要关心内部实现细节
PV PVC本质上跟nfs类似,只是多了一层,通过PVC绑定PV,通过在用户的yaml中绑定PVC,而PVC的yaml文件中绑定PV,而PV是由专业人士提供的,我们只用写用户的yaml和PVC的yaml;而PVC匹配PV是通过accessModes(读写模式)、storage(容量)来匹配的,而且匹配时自动匹配
创建Pod的时候会附带一个PVC请求,PVC会去寻找一个合适的PV(因为每个PV后端真正的实现是不同的存储比如nfs、mfs、lscsl等,而且容量和读写速度不同),所以PVC代表Pod,是甲方,有容量和读写等其他的性能要求,而PV代表实际的存储,是乙方,提供固定的容量和读写等其他性能,所以PVC来选择合适的PV
PV是独立于Pod的生命周期的,即若Pod被删除了,但是PV依旧存在,若PVC和PV绑定了,则PVC依然会存在。类似于hostPath;PV也是资源,即集群中有一堆PV,可以直接挂载(通过yaml文件中的定义),也可以绑定PVC后再通过PVC挂载;PVC和PV是一对一的映射
因为PV的具体实现还是实际的存储,还是nfs、RDB、AzureFile、HostPath等
只有NFS和hostPath支持进行基本擦除(rm -rf /volome挂载目录/)
hostPath
卷能将主机节点文件系统上的文件或目录挂载到您的 Pod 中。 虽然这不是大多数 Pod 需要的,但是它为一些应用程序提供了强大的逃生舱。hostPath是将宿主机的文件系统中的文件或目录挂载到集群中
hostPath需要注意的是,容器内的挂载路径和主机内的路径的映射是固定的,因此如果Pod漂移到别的主机上,而漂移后的主机没有相应的挂载目录,则挂载失败,因此要确保集群内的各个主机的挂载路径都存在且一致
==实际搭建nfs、pv、pvc、mysql新得:通过mysql只是一个容器化的应用,通过工作负载很简单地就能创建,但是由于其是在Pod中的,因此当Pod停止后(包括意外死亡),数据就清除了,因此需要将数据进行持久化,即将卷(有很多种形式)挂载到Pod中(实际是在pause这个docker中,操作上是从Pod的控制器中进行操作)
用rancher还是后台命令行?
A:现在一般用UI界面还是命令行创建集群,都有但是常用就是在后台输入kubectl命令行,因为比较快
用kubelet命令行具体怎么创建集群?之前看到命令都是创建Pod呀?
A:用kubectl命令行创建集群是很难的,一般都是用rancher或者公司的平台来创建的,用kubelet命令只是用在创建服务的时候
刘老师说是配置文件位置不定,但是为什么rancher上的配置文件还要指定放到./kube/config
selector是什么?
A:就是service或Controller进行选择Pod管理时需要,label匹配了才管理
NodePort ClusterIP
要看懂配置文件的每个字段含义
通过YAML文件创建的是Pod吧,那为什么YAML中有container字段呢?
A:YAML文件中是分层定义的,每个spec字段下都是更深一层的定义,因此Pod更深一层就是container
我们一般会用yaml创建pod吗
A:一般都用yaml创建服务
Pod中是更新镜像,这个镜像是哪个镜像
哪种资源是命令型、哪种是编程型
标签是管理哪个层级上的?
job和cronjob用的多吗,批处理是啥
A:job是一次性任务、cronjob是定时任务
需要用ipvs吗?要安装?
A;平时不需要管,这都是底层原理,不需要了解这么多
为什么叫代理
kubectl apply和kubectl create有什么区别
虚拟IP(VIP)是给谁的
Cluster IP NodePort
kubectl version 后的client(kubectl版本信息)和server(master节点的k8s版本信息)分别指的什么
kubectl create是通过yaml创建RC或者Service,而kubectl是修改yaml后将新的配置进行应用
现在公司都用的是Deployment,将数据单独保存在主机上,不在集群中,所有的服务都是无状态的,
可以将nfs直接挂载在Pod上吗?
LVS和Nginx什么关系
./文件名
ml后将新的配置进行应用
现在公司都用的是Deployment,将数据单独保存在主机上,不在集群中,所有的服务都是无状态的,
可以将nfs直接挂载在Pod上吗?
LVS和Nginx什么关系
./文件名