k8s快速入门
本文为The kubernetes book的笔记. 作者Nigel Poulton, 译者刘康https://github.com/get-set
K8s是一个应用编排器, 可以部署应用, 动态扩缩容, 故障自愈, 不停机回滚, 同时还是一个支撑应用运行的集群, 集群由一个或多个主节点和若干个工作节点构成. 集群内部的 DNS服务还提供了服务发现与负载均衡的能力.
k8s与容器技术是互补的技术, 容器用于应用的开发. k8s用于应用编排.
节点
集群由一个或多个主节点和若干个工作节点构成. 其中主节点负责管理整个集群, 做调度决策, 监控集群, 响应事件的工作. 工作节点负责运行应用服务, 每个工作节点都有自己的主节点.
主节点
k8s的主节点(或者说控制平面, 官方文档是这么称呼的)通常有一个或多个系统服务组成. 他为集群提供了故障转移和高可用性.
-
API Server
k8s中所有的组件之间都通过API Server进行通信. API Server默认是一组RESTful风格的接口.
API Server位于控制平面的最前端, 所有的指令与通信都需要通过API Server来完成.
-
集群存储
k8s默认使用etcd存储着整个集群的配置和状态, etcd更注重一致性.
-
Controller Manager
Controller用于确保集群的当前状态与预期状态相匹配. Controller有很多个, 比如终端Controller, 工作节点Controller, 副本Controller(一般指Pod副本). 每个Controller都在后台启动独立的 循环监听功能.
::: tip 循环监听逻辑
-
获取期望状态 -
观察当前状态 -
对比状态 -
调整差异使当前状态去尽量符合期望状态 :::
Controller Manager负责创建Controller并监控他们的执行.
-
Scheduler
调度器监听API Server来启动新的工作任务, 并将其分配到合适的节点中, 合适与否由调度器判定. 判定条件有:节点是否存活, 任务依赖端口在节点中是否可用, 节点是否还有足够资源等等.
-
Cloud Controller Manager
如果集群运行在公有云平台, 控制平面会启动一个Cloud Controller Manager来负责集成底层的公有云服务. 如实例, 负载均衡等.
工作节点
工作节点托管作为应用负载的Pod.
-
kubelet
kubelet是工作节点的核心, 新的工作节点加入集群后, kubelet就会被部署到新节点上.
kubelet负责向集群汇报当前节点的资源状态, 如CPU, 内存等.
kubelet还会监听API Server分配的新任务, 每监听到一个就去执行这个任务, 并且与主节点维护一个通信频道, 当任务结束后返回执行结果.
-
Container Runtime
容器运行时供kubelet使用, 用于执行依赖容器的任务. 容器运行时可以由docker提供, 也可以由containerd或其他容器技术提供.
containerd是docker的精简版, 在某些时候更适合作为k8s的容器运行时.
-
kube-proxy
kube-proxy与kubelet和Container Runtime一样在每个工作节点中都存在, 它是维护节点上的网络规则, 允许从集群内部或外部与Pod进行网络通信.
DNS/网络
K8s有自己的DNS, 他有一个静态IP, 并且被硬编码在每个节点中. 他让我们可以使用一致的DNS名称而非IP地址来访问服务. 集群中的每个Service都会有一个DNS名称.
声明式模型
当我们要将一个应用运行在k8s上时, 通常需要先将应用打包为镜像, 然后封装到pod中去运行. 我们可以通过定义manifest文件(yml)的方式去部署.
我们在yml中告知k8s集群我们希望的应用运行状态. 也就是期望状态. 比如运行多少个副本. 然后提交到k8s中. k8s会确保应用的运行符合我们的期望状态.
整个流程如下:
-
在mainfest文件中声明一个应用的期望达到的状态 -
将manifest文件发送到API服务 -
k8s将manifest存储到集群存储中, 并作为应用的期望状态 -
k8s在集群中实现期望状态 -
k8s启动循环监听, 保证当前状态符合期望状态
循环监听会有很多个, 有监听副本数量的, 有监听存储卷挂载的, 下面会有讲解.
Pod
pod是k8s调度的原子单位, 就像容器是docker调度的原子单位一样.
k8s是无法直接运行容器的, 它使用pod来对容器进行一层简单的封装, 以允许容器运行在k8s中. pod就是为用户在宿主机系统中划出一部分区域, 构建一个网络栈, 创建一组内核命名空间, 并且在其中运行一个或多个容器.
pod中可以包含一个或多个容器, 或者说pod是被一个或多个容器共享的执行环境. 比如我们可以一个pod包含一个微服务, 也可以在一个pod中同时包含微服务与其依赖的基础设施服务(打比方, 通常不会这么做). 注意是包含不是运行
pod的部署是原子操作, 只有pod中的容器都启动成功运行后, pod提供的服务才被认为可用.
由于pod运行中可能会出现意外销毁, 而k8s的自愈功能会启动一个新pod来取到原pod(pod本身没有这个能力, Deployment才有). 新的pod会有新的id与新的ip, 所以我们切记不要在程序中去 依赖某个特定的pod
manifest
在声明式模型中提到我们可以使用manifest文件的方式去告诉k8s部署一个什么样的应用, 下面简单配置一个manifest.
apiVersion: v1
kind: Pod
metadata:
name: hello-pod
labels:
zone: prod
version: v1
spec:
containers:
- name: hello-ctr
image: nigelpoulton/k8sbook:latest
ports:
- containerPort: 8080
-
apiVersion: 创建部署对象的API组合API版本. 值得格式应该是<api-group>-<version>. 在k8s的manifest文件中, apiGroup可以省略不写, 默认是core. 也就是 apiVersion:V1
等价于apiVersion:core/v1
-
kind: 通知k8s要部署的对象类型. kind的value是一个枚举, 可选的值有 Namespace
,Service
,Pod
,Deployment
,PersistentVolume
等等. 这里写Pod
, 表示要部署一个Pod对象. -
metadata: metadata中定义的信息用于在集群中识别被部署的对象. 这里定义Pod对象的名称为hello-pod. 同时为其赋予了两个标签 zone: prod
,version: v1
. 以便与Service建立关联, 后续会讲到. -
spec: spec=special, spec中定义Pod对象中要运行的容器以及相关配置, 如挂载卷, 启动副本数量等等. 这里指定运行一个image为 nigelpoulton/k8sbook:latest
的容器, 暴露8080端口, 并取名为hello-ctr
deploy
在编写完manifest文件后. 我们需要将这个文件提交给k8s去让他运行把Pod副本到可以工作的节点上去. 下面是会涉及到的一些kubectl与API Server交互的一些命令.
# POST manifest文件到API Serve
F:学习k8sTheK8sBookpods>kubectl apply -f pod.yml
pod/hello-pod created
# 查看部署的pod
F:学习k8sTheK8sBookpods>kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-pod 1/1 Running 0 66s
当看到pod的状态为Running时, 代表pod已经在k8s中运行了. 我们可以使用kubectl exec
命令进入pod操作, 也可以使用kubectl logs
命令查看pod的日志. 这一点与docker大同小异. 不再赘述.
上面用到的kubectl apply -f
命令是通用的k8s的manifest文件部署启动命令, 除了启动Pod以外, 启动后面会讲到的所有的k8s对象的manifest文件也都是通过这个命令来部署.
还有kubectl get
命令也是通用. kubectl describe
命令则可以更详细的查看k8s对象的信息.
Deployment
Pod对象本身是没有故障自愈, 滚动升级等能力的. 所以Pod的部署一般是通过更上层的Deployment来完成的. 它是对Pod的更进一步的封装, 提供了扩缩容管理, 不停机更新和版本控制功能.
一个Deployment对象只能管理一个Pod模版, 如果有多个Pod对象, 那么每个Pod对象都应该有自己的Deployment
Deployment的内部使用ReplicaSet对象来完成Pod的自愈, 滚动升级等, 他是声明式模型(期望状态)实现的关键, 它实现了监视循环.
当滚动升级的时候, Deployment会保留原来的ReplicaSet对象的情况下, 启动一个新的ReplicaSet对象来启动新的Pod副本, 如果需要回滚, 只需要停止新的ReplicaSet, 然后启动旧ReplicaSet就可以了.
manifest
下面定义一个deploy.yml文件来通过Deployment对象启动一组pod对象
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-deploy
spec:
replicas: 10
selector:
matchLabels:
app: hello-world
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: hello-pod
image: nigelpoulton/k8sbook:latest
ports:
- containerPort: 8080
apiVersion与kind和metadata属性不再赘述. 都是k8s对象manifest的通用属性. 这里只解释spec中的信息含义
-
replicas: 表示要部署多少pod副本 -
selector: 表示这个Deployment对象下所管理的Pod副本需要具备哪些标签. -
minReadySeconds: 这个属性表示在逐个更新Pod时, 每个Pod更新间隔的时间是多少, 定义这个属性可以避免一次性全部更新Pod时出现问题而不易追踪问题 -
strategy: 表示使用滚动更新的策略, 下面的 maxUnavailable
和maxSurge
参数表示滚动更新Pod时, 不能出现比期望状态多出一个以上或少一个以上pod的情况. 结合replicas: 10
来说就是滚动更新时运行中的pod不能少于9个也不能多于11个. -
template: 这里定义的是Deployment对象所管理的Pod的具体属性, 参考Pod.yml
deploy
使用如下命令来提交一个Deployment:
# POST manifest文件到API Server
F:学习k8sTheK8sBookdeployments>kubectl apply -f deploy.yml
deployment.apps/hello-deploy created
# READY列中为已启动数量/预期启动数量
F:学习k8sTheK8sBookdeployments>kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
hello-deploy 4/10 10 0 11s
# 所有pod副本启动完毕
F:学习k8sTheK8sBookdeployments>kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-deploy-65cbc9474c-2qmkz 1/1 Running 0 58s
hello-deploy-65cbc9474c-669rg 1/1 Running 0 58s
hello-deploy-65cbc9474c-9nzp7 1/1 Running 0 58s
hello-deploy-65cbc9474c-f9hhd 1/1 Running 0 58s
hello-deploy-65cbc9474c-gknps 1/1 Running 0 58s
hello-deploy-65cbc9474c-lq9cz 1/1 Running 0 58s
hello-deploy-65cbc9474c-m6r2l 1/1 Running 0 58s
hello-deploy-65cbc9474c-mfgqx 1/1 Running 0 58s
hello-deploy-65cbc9474c-pc7tg 1/1 Running 0 58s
hello-deploy-65cbc9474c-r8ztb 1/1 Running 0 58s
RollingUpdate
当修改manifest文件后再次执行apply命令提交. 然后使用kubectl rollout status
命令查看更新过程
F:学习k8sTheK8sBookdeployments>kubectl rollout status deployment hello-deploy
Waiting for deployment "hello-deploy" rollout to finish: 4 of 10 updated replicas are available...
Waiting for deployment "hello-deploy" rollout to finish: 9 of 10 updated replicas are available...
Rollback
执行回滚操作需要一个deploy的版本号. 这个版本号可以在执行apply命令时通过--record
参数记录下来. 然后通过一下命令查看历史版本号
F:学习k8sTheK8sBookdeployments>kubectl rollout history deployment hello-deploy
deployment.apps/hello-deploy
REVISION CHANGE-CAUSE
1 <none>
2 kubectl apply --filename=deploy.yml --record=true
因为我们已经执行过了一次滚动升级, 所以hello-deploy对象中现在应该有两个ReplicaSet对象
F:学习k8sTheK8sBookdeployments>kubectl get rs
NAME DESIRED CURRENT READY AGE
hello-deploy-65cbc9474c 0 0 0 16m
hello-deploy-6f797c4b74 10 10 10 3m15s
可以看到更早创建的hello-deploy-65cbc9474c中Pod副本数量已经为零, 新创建的hello-deploy-6f797c4b74中Pod数量为10.
现在通过undo命令回滚版本
F:学习k8sTheK8sBookdeployments>kubectl rollout undo deployment hello-deploy --to-revision=1
deployment.apps/hello-deploy rolled back
再次查看ReplicaSet对象, 旧的ReplicaSet中的Pod对象已经开始逐个启动, 新的ReplicaSet中的Pod对象开始逐个销毁
F:学习k8sTheK8sBookdeployments>kubectl get rs
NAME DESIRED CURRENT READY AGE
hello-deploy-65cbc9474c 3 3 2 20m
hello-deploy-6f797c4b74 8 8 8 6m59s
Service
由于Pod可能会出现扩缩容, 故障时替换的情况, 导致了Pod的IP地址变化. 因此Pod时不可靠的, 我们不能直接去依赖Pod. Service解决了这个问题, 它提供了稳定的网络.
我们可以把Service对象想象为具备前后两端. 前端有自己的DNS名称, IP和端口号; 后端有对Pod的动态负载均衡机制, 并且实现了自我监控, 可以自动更新.
Endpoint
Service在创建后都会得到一个关联的Endpoint对象, 这个对象是一个动态的列表, 包含了所有匹配Service Selector的健康Pod.
当Service有流量需要转发时, 通过Endpoint对象中的Pod列表来查找Pod
启动会Service后可以查看对应的Endpint对象
F:学习k8sTheK8sBookservices>kubectl get ep hello-svc
NAME ENDPOINTS AGE
hello-svc 10.1.0.37:8080,10.1.0.38:8080,10.1.0.39:8080 + 7 more... 9m21s
type
Service有多种类型, 不同类型的Service对应不同的外部访问策略
-
ClusterIP
ClusterIP类型的Service有固定的IP和端口号, 只能从集群的内部访问.
ClusterIP与Service名称一起被注册到集群内部的DNS服务中. 因为所有Pod都知道集群的DNS服务, 所以所有的Pod都能够解析Service名称. 之所以是内部访问是因为要访问集群的DNS才能够找到对应的IP, 所以ClusterIP类型的Service只对Pod和集群中的其他对象奏效.
-
NodePort
NodePort在ClusterIP的基础上增加了外部访问的能力.
NodePort在集群的人与节点上都是相同的, 也就是从集群外部访问任一节点上的NodePort指定的端口号都可以找到Service, 即使这个节点上并没有Service及其选择器对应的Pod
集群内部的Pod找到Service要通过名称, 而外部的则是通过NodePort.
NodePort的端口范围在30000-32767之间
-
LoadBalancer
LoadBalancer同样能够在外部访问, 同时他还能够与云服务上提供的负载均衡服务集成. 他是基于NodePort实现的.
-
ExternalName
ExternalName能够将流量路由到k8s之外的系统
manifest
前面的实例中已经通过Deployment部署了10个监听8080端口的Pod. 但是因为没有配置网络, 所以我们通过宿主机IP+8080并不能访问到内部的Pod. 正好可以通过NodePort Service的方式去接收流量.
apiVersion: v1
kind: Service
metadata:
name: hello-svc
labels:
app: hello-world
spec:
type: NodePort
ports:
- port: 8080
nodePort: 30001
targetPort: 8080
protocol: TCP
selector:
app: hello-world
metadata中的labels并不是用来筛选Pod的, 而是用来匹配Service的.
spec中定义信息解释:
-
type: 表明service类型为NodePort, 可被集群外访问 -
ports: -
port: 指的是Service暴露在ClusterIP上的端口号. 这是提供给集群内部来使用的. -
nodePort: 对外监听30001端口, 也就是集群外部通过<集群任意节点IP>:30001就可以访问到这个Service对象 -
targetPort: 当收到请求后, 将流量路由到Pod的8080端口上 -
protocol: 使用TCP协议, 这也是默认的 -
selector: 上面ports中配置的流量路由策略会作用到标签含有app=hello-world的pod上
通过apply提交后访问http://127.0.0.1:30001
使用logs命令可以查看这次流量被路由到了哪个节点
F:学习k8sTheK8sBookservices>kubectl logs -f service/hello-svc
Found 10 pods, using pod/hello-deploy-65cbc9474c-77xx9
服务注册与服务发现
填坑之前微服务专栏中提到的虚拟IP实现服务发现部分
服务注册
k8s内部使用一个DNS服务作为服务注册中心, 他实际上是运行在kube-system命名空间中的一个名为coredns的由Deployment管理的一组Pod. 是k8s的原生应用.
F:学习k8sTheK8sBookservices>kubectl get pods -n kube-system -l k8s-app=kube-dns
NAME READY STATUS RESTARTS AGE
coredns-f9fd979d6-9sg5x 1/1 Running 0 3h3m
coredns-f9fd979d6-bpkhw 1/1 Running 0 3h3m
-
POST Service的manifest文件到API Server -
Service被分配ClusterIP -
配置持久化到集群存储 -
Endpoint对象被创建 -
集群的DNS发现新的Service
DNS内部有一个Controller会监听API Server
-
创建新的DNS记录 -
每个节点上的kube proxy拉取Service配置 -
创建IPVS规则
每个节点的kubelet进程都会监视Endpoint对象的创建
服务发现
k8s会自动配置所有容器, 让他们能够找到集群的DNS, 并用来将Service名字解析为ClusterIP. 实际操作是k8s为每个容器都注入了一个/etc/resolv.conf.
-
通过DNS解析到Service对应的ClusterIP -
将流量发送到ClusterIP -
但是没有路由, 将流量发送到容器的默认网关
因为这个ClusterIP是在一个特殊的名为servicenetwork的网络上, 是没有路由可达的, 所以容器不知道该把流量发到哪, 只能发送到默认网关.
-
转发到集群节点 -
集群节点也没有路由, 在转发到节点的默认网关
由于主机节点同样没有servicenetwork的路由, 所以只能再发往自身的默认网关.
-
流量被节点内核处理 -
流量被IPVS规则捕获 -
将流量发往的目标IP重写为Pod的IP
故障排查
由于k8s使用集群内置DNS服务作为服务的注册中心使用, 所以如果有服务注册发现相关的问题应该先排查这里.
集群的DNS是一组运行在kube-system命名空间中的pod和一个用来提供稳定网络入库的Service对象构成. 他主要包括三个组件:
-
pods: 由coredns Deployment管理 -
Service: 一个名为kube-dns的ClusterIP Service, 监听TCP/UDP 53端口 -
Endpoint: 也叫kube-dns
所有与集群DNS相关的对象都有一个k8s-app=kube-dns的标签. 这个需要记住. 方便命令排查.
针对DNS的排查过程如下:
-
先确定coredns Deployment及其管理的pods运行状态. -
查看coredns的每个pod的日志 -
查看Service是否在运行中, 且监听端口是53, 且有ClusterIP -
查看Endpoint对象是否正在运行且拥有所有coredns pod的IP地址
当DNS排查没有问题后, 可以再通过启动一个单例的名为dnsutils的pod来排查. 这个pod中内置了一些常用的网络工具如ping, curl, nslookup.
存储
k8s的存储与docker的存储不一样, 它要复杂的多, pod将资源挂载到外部存储需要很多个步骤, 不过也正因如此, k8s对存储的控制也更灵活多变.
k8s的存储分为三部分: 持久化卷子系统, 插件, 存储提供者.
pod对象通过持久化卷子系统来完成数据与外部存储的挂载. 而k8s的持久化卷子系统通过插件层访问存储提供者.
持久化卷中有三个主要的组件: PV: 持久化卷, PVC: 持久化卷的访问许可, CS: 存储类.
Volume
k8s为不同的部署环境, 使用需求提供了支持非常多的卷类型. 主要分为两类: 节点宿主机存储与节点外存储.
节点外存储有awsElasticBlockStore(亚马逊ES卷存储)
, azureDisk(微软Azure)
等等等等.
节点宿主机存储如hostPath
, local
则是将节点宿主机的文件系统上的文件或目录挂载到使用这个卷的Pod中.
卷不能挂载到其他卷之上, 也不能与其他卷有硬连接. Pod配置中的每个容器都必须独立指定各个卷的挂在位置.
manifest
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: busybox
name: test-container
command: ["sleep", "3000"]
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
# 宿主上目录位置
path: /data
在这个声明Pod的模版中的spec部分一并声明了一个hostPath的卷, 指定挂载到主机的/data目录下
Persistent Volume
k8s中的PV对象不像docker, 它可以绑定本地磁盘以及其他外部存储, 他定义了集群的存储采用什么介质.
一个外部存储只能被一个PV使用, 一个PV可以被多个POD访问, 但是要定义规则来确保正常访问, 同时Pod不能直接访问PV, 必须经过PVC.PV对象可以通过PVC对Pod实现共享.
PV针对于PVC有三种访问模式:
-
ReadWriteOnce: 限制一个PV只能以读写方式绑定到一个PVC上, 如果尝试绑定到多个PVC将会失败. -
ReadWriteMany: 允许一个PV以读写方式挂载到多个PVC上, 这种模式只支持NFS这样的文件或对象存储. -
ReadOnlyMany: 允许以只读方式挂载到多个PVC上
PV的核心是一个目录, 其中可能存有数据, Pod中的容器可以访问这个目录中的数据, 所采用的特定的卷类型将决定目录如何形成, 使用何种介质保存数据以及目录中存放的内容.
PV可以事先通过manifest文件提供, 也可以通过SC动态创建. 他的生命周期独立于使用它的Pod.
manifest
apiVersion: v1
kind: PersistentVolume
metadata:
name: test-pv
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data
这个文件的声明含义是挂载主机的/data目录上, 并且只占用10G存储空间; 除此之外, 对PVC允许的访问模式是ReadWriteOnce. storageClassName指定为manual. 后面的SC部分会将.
PVC
Persistent Volume Claim表达的是Pod对PV的使用请求. 概念与Pod相似. Pod会占用节点的资源, 而PVC申领会占用PV的资源. Pod可以请求特定数量的资源如CPU和内存. PVC申领也可以请求特定的大小和访问模式.
当pod通过PVC访问PV时, 可以选择将PV中的某个目录挂载到pod中的容器目录上.
当不再使用PV时, 我们可以通知API Server将PVC删除, 来回收再利用资源. PVC有三种回收策略来告诉集群, 当PV从申领中释放时应该如何处理这个数据卷.
-
Retain: 保留PV对象以及外部存储中的资源, 但是这回导致其他PVC无法继续使用这个PV. 如果要重复使用这个PV占用的存储, 可以手动删除PV对象. PV对象绑定的外部存储中的数据是会保留的. -
Delete: 删除PVC时, 同时将PV以及PV在外部存储中的资源删除. 这是默认的.
manifest
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
这个文件的含义是PVC申领storageClassName为manual的PV对象大小为3G的存储空间. 这里spec.storageClassName要与PV的manifest中的spec.storageClassName相对应.
在执行apply提交后, 控制平面将会查找有相同storageClassName的且满足申领要求的PV对象, 如果找的到, 则绑定到那个PV对象上.
F:学习k8sTheK8sBookstorage>kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-pv-claim Bound test-pv 10Gi RWO manual 2s
Pod使用PVC:
apiVersion: v1
kind: Pod
metadata:
name: task-pv-pod
spec:
volumes:
- name: test-pv-storage
persistentVolumeClaim:
claimName: test-pv-claim
containers:
- name: test-pv-container
image: busybox
command: ["sleep","3000"]
volumeMounts:
- mountPath: "/test-vol"
name: test-pv-storage
spec.volumes中不再使用hostPath. 使用persistentVolumeClaim去指定要使用的PVC.
SC
在大规模集群中, 手动创建大量PV和PVC是非常烦琐的. 这时可以使用StorageClass来动态分配. SC可以让我们不用手动创建PV, 只需要创建一个SC对象, 然后使用一个插件与 具体的某个存储后端联系起来.
SC创建后会观察API Server上是否有新的被关联的PVC对象, 如果匹配的PVC出现, SC会自动在后端存储系统上创建所需要的卷,以及在k8s上创建PV.
manifest
还是在本地测试一下
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
在定义SC的manifest时注意apiVersion为storage.k8s.io/v1
-
provisioner: 表示不使用外部存储, 采用本地存储 -
volumeBindingMode: 表示延迟绑定. PVC与PV的绑定被延迟到Pod的第一次调度时.
SC对象是不可变得, 在部署之后不可以再修改.
定义一个PVC对象供Pod使用
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pv-ticket
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 1Gi
部署之后查看PVC状态处于Pending, 也就是等待绑定的状态.
再启动一个Pod的使用PVC
apiVersion: v1
kind: Pod
metadata:
name: class-pod
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: pv-ticket
containers:
- name: ubuntu-ctr
image: ubuntu:latest
command:
- /bin/bash
- "-c"
- "sleep 60m"
volumeMounts:
- mountPath: /data
name: data
发现启动报错
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 10m default-scheduler 0/1 nodes are available: 1 node(s) didn't find available persistent volumes to bind.
Warning FailedScheduling 10m default-scheduler 0/1 nodes are available: 1 node(s) didn't find available persistent volumes to bind.
这是因为本地卷还不支持动态制备. 所以我们要先提供一个PV.
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-storage-pv
labels:
type: local
spec:
storageClassName: local-storage
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp
再次查看Pod状态已经运行成功.
更多的StorageClass https://kubernetes.io/zh/docs/concepts/storage/storage-classes/
ConfigMap
ConfigMap对象将配置数据从Pod中剥离出来. 并可以动态的在Pod运行时注入数据.
创建方式
-
命令式:
F:学习k8sTheK8sBookstorage>kubectl create configmap profile --from-literal name=杨同港 --from-literal wx=ytg2097
configmap/profile created
-
声明式:
F:学习k8sTheK8sBookstorage>kubectl create cm profile-json --from-file=profile.json
configmap/profile-json created
{
"姓名": "杨同港",
"微信号": "ytg2097"
}
注入方式
-
环境变量:
apiVersion: v1
kind: Pod
metadata:
labels:
chapter: configmaps
name: envpod
spec:
restartPolicy: OnFailure
containers:
- name: ctr1
image: busybox
command: [ "sleep", "300" ]
env:
- name: WX
valueFrom:
configMapKeyRef:
name: profile
key: wx
- name: NAME
valueFrom:
configMapKeyRef:
name: profile
key: name
查看环境变量
F:学习k8sTheK8sBookconfigmaps>kubectl exec -it envpod sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # echo name:$NAME wx: $WX
name:杨同港 wx: ytg2097
-
容器启动命令参数:
apiVersion: v1
kind: Pod
metadata:
labels:
chapter: configmaps
name: envpod
spec:
restartPolicy: OnFailure
containers:
- name: ctr1
image: busybox
command: [ "/bin/sh", "-c", "echo 博客作者: $(NAME) 微信号 $(WX) 济南的爷就是爷, 除了吃就是玩, 没别哒!" ]
env:
- name: WX
valueFrom:
configMapKeyRef:
name: profile
key: wx
- name: NAME
valueFrom:
configMapKeyRef:
name: profile
key: name
F:学习k8sTheK8sBookconfigmaps>kubectl apply -f envpod.yml
pod/envpod created
F:学习k8sTheK8sBookconfigmaps>kubectl logs -f envpod
博客作者: 杨同港 微信号 ytg2097 济南的爷就是爷, 除了吃就是玩, 没别哒!
-
某个卷上的文件: 这个很好理解. 我们使用卷挂载方式访问数据无非是将应用运行时产生数据保存. 和应用运行时读取两种用途.
apiVersion: v1
kind: Pod
metadata:
name: cmvol
spec:
volumes:
- name: volmap
configMap:
name: profile
containers:
- name: ctr
image: nginx
volumeMounts:
- name: volmap
mountPath: /etc/name
F:学习k8sTheK8sBookconfigmaps>kubectl exec -it cmvol ls /etc/name
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
name wx
StatefulSet
StatefulSet也是k8s中的一等公民, 与Deployment的具备相同的能力, 可以管理Pod, 进行动态扩缩容, 故障自愈, 滚动升级.
不同点
-
Pod名字可预知且保持不变原因是StatefulSet启动Pod是有顺序的, Pod的命名是<StatefulSet名称>-<1>,<StatefulSet名称>-<2>,<StatefulSet名称>-<n> -
DNS主机名可预知保持不变DNS的主机名就是StatefulSet的名称 -
卷绑定可预知保持不变
以上三个特性在哪些要求Pod保持不变的应用中非常有用.
headlessService
由StatefulSet部署的Pod是可预知的. 所以我们的一些应用可能会直接连接到某个Pod上去. 为了实现这一功能, k8s提供了一个特殊的Service对象headlessService来为StatefulSet使用headlessService来为StatefulSet中的每个Pod副本创建一个可预知的DNS主机名.
headlessService是一个将spec.clusterIP设置为None的Service对象. 当这个headlessService设置为StatefulSet的spec.service.Name
时就成为了StatefulSet的governingService.
volumeClaimTemplate
在使用StatefulSet时, 如果没有Pod有独立存储的需求时, 可以使用volumeClaimTemplates.
volumeClaimTemplate在每次创建一个新的Pod副本时, 会自动创建一个PVC, 还会自动为PVC命名, 用来准确关联Pod, PVC名为<volumeClaimTemplate名称>-<Pod名>
更多内容见官方文档 https://kubernetes.io/zh/docs/concepts/workloads/controllers/statefulset/
注意 在删除StatefulSet之前最好先缩容Pod副本数量到0来保证本地缓存安全落库
原文始发于微信公众号(者行花):K8S快速入门
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/47696.html