关于 Kubernetes中一些基本概念和术语笔记(二)
写在前面
- 学习K8s,所以整理记忆
- 大部分部分内容来源于
《Kubernetes权威指南:从Docker到Kubernetes实践全接触》
第一章,感兴趣小伙伴可以支持作者一波
一个不欣赏自己的人,是难以快乐的。-------三毛
7、 Horizontal Pod Autoscaler
HPA与之前的RC、 Deployment一样,也属于一种Kubernetes资源对象。通过 追踪分析RC控制的所有目标Pod的负载变化情况,来确定是否需要针对性地调整目标Pod的副本数,这是HPA的实现原理 。当前, HPA可以有以下两种方式作为Pod负载的度量指标。
Horizontal Pod Autoscaler |
---|
CPUUtilizationPercentage. |
应用程序自定义的度量指标,比如服务在每秒内的相应的请求数(TPS或QPS) |
apiversion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec
maxReplicas: 10
minReplicas: 1
scaleTargetRef:
kind: Deployment
name: php-apache
targetcpuutilizationPercentage: 90
CPUUtilizationPercentage
是一个算术平均值,即目标Pod
所有副本自身的CPU利用率
的平均值。一个Pod自身的CPU利用率
是该Pod当前CPU的使用量除以它的Pod Request的值,比,如我们定义一个Pod的Pod Request为0.4,而当前Pod的CPU使用量为0.2,则它的CPU使用率为50%
根据上面的定义,我们可以知道这个HPA控制的目标对象为一个名叫php-apache Deployment里的Pod副本,当这些Pod副本的CPUUtilizationPercentage的值超过90%时会触发自动动态扩容行为,扩容或缩容时必须满足的一个约束条件是Pod的副本数要介于1与10之间
。
除了可以通过直接定义yaml文件并且调用kubectrl create
的命令来创建一个HPA资源对象的方式,我们还能通过下面的简单命令行直接创建等价的HPA对象
:
kubectl autoscale deployment php-apache --cpu-percent=90--min-1 --max=10
8、 StatefulSet
在Kubernetes系统中, Pod的管理对象RC, Deployment, DaemonSet和Job都是面向无状态的服务。 但现实中有很多服务是有状态的,特别是一些复杂的中间件集群,例如MysQL集·群、MongoDB集群、ZooKeeper集群等,这些应用集群有以下一些共同点:
共同點 |
---|
每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并且通信。 |
集群的规模是比较固定的,集群规模不能随意变动。 |
集群里的每个节点都是有状态的,通常会持久化数据到永久存储中。 |
如果磁盘损坏,则集群里的某个节点无法正常运行,集群功能受损 |
如果用
RC/Deployment控制Pod副本数
的方式来实现上述有状态的集群
,则我们会发现第1点是无法满足的,因为Pod的名字是随机产生的,Pod的IP地址也是在运行期才确定且可能有变动的
,我们事先无法为每个Pod确定唯一不变的ID,
为了能够在其他节点上恢复某个失败的节点,这种集群中的Pod需要挂接某种共享存储,为了解决这个问题, Kubernetes从v1.4版本开始引入了PetSet这个新的资源对象
,并且在v1.5版本时更名为StatefulSet
, StatefulSet从本质上来说,可以看作DeploymentRC的一个特殊变种,它有如下一些特性。)
特性 |
---|
StatefulSet 里的每个Pod都有稳定、唯一的网络标识 ,可以用来发现集群内的其他成员。假设StatefulSet 的名字叫kafka,那么第1个Pod 叫 kafka-0,第2个叫kafk-1,以此类推。) |
StatefulSet控制的Pod副本的启停顺序是受控的 ,操作第n个Pod时,前n-1个Pod已经是运行且准备好的状态) |
StatefulSet里的Pod采用稳定的持久化存储卷 ,通过PV/PVC来实现,删除Pod时默认不会删除与StatefulSet相关的存储卷(为了保证数据的安全)。 |
statefulSet除了要与PV卷捆绑使用以存储Pod的状态数据
,还要与Headless Service
配合使用,即在每个StatefulSet
的定义中要声明它属于哪个Headless Service. Headless Service与普通Service的关键区别在于,它没有Cluster IP,如果解析Headless Service的DNS域名,则返回的是该Service对应的全部Pod的Endpoint列表。StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod实例创建了一个DNS域名
,这个域名的格式为:
$(podname).$(headless service name)
9、 Service (服务)
Service也是Kubernetes里的最核心的资源对象之一
, Kubernetes里的每个Service其实就是我们经常提起的微服务架构中的一个“微服务”,之前我们所说的Pod, RC等资源对象其实都是为这节所说的“服务”-Kubernetes Service作“嫁衣”的。Pod,RC与Service的逻辑关系。
Kubernetes的Service定义了一个服务的访问入口地址
,前端的应用(Pod)
通过这个入口地址访问其背后的一组由Pod副本组成的集群实例, Service与其后端Pod副本集群之间则是通过Label Selector来实现“无缝对接”的。而RC的作用实际上是保证Service的服务能力和服务质量始终处干预期的标准。
每个Pod都会被分配一个单独的IP地址,而且每个Pod都提供了一个独立的Endpoint(Pod IP+ContainerPort)
以被客户端访问,现在多个Pod副本组成了一个集群来提供服务.客户端如何来访问它们呢?一般的做法是部署一个负载均衡器(软件或硬件),
Kubernetes
中运行在每个Node
上的kube-proxy
进程其实就是一个智能的软件负载均衡器
,它负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。
Kubernetes发明了一种很巧妙又影响深远的设计:
Service不是共用一个负载均衡器的IP地址,而是每个Service
分配了一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP
,这样一来,每个服务就变成了具备唯一IP地址的“通信节点”
,服务调用就变成了最基础的TCP网络通信问题
。
我们知道, Pod的Endpoint地址会随着Pod的销毁和重新创建而发生改变,因为新Pod的IP地址与之前旧Pod的不同。而 Service一旦被创建, Kubernetes就会自动为它分配一个可用的Cluster IP,而且在Service的整个生命周期内,它的Cluster IP不会发生改变。于是,服务发现这个棘手的问题在Kubernetes的架构里也得以轻松解决:只要用Service的Name与Service的Cluster IP地址做一个DNS域名映射即可完美解决问题。
┌──[root@vms81.liruilongs.github.io]-[~/ansible/k8s-pod-create]
└─$kubectl get svc myweb -o yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2021-10-16T14:25:08Z"
name: myweb
namespace: liruilong-pod-create
resourceVersion: "339816"
uid: 695aa461-166c-4937-89ed-7b16ac49c96b
spec:
clusterIP: 10.109.233.35
clusterIPs:
- 10.109.233.35
externalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- nodePort: 30001
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: myweb
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}
Kubernetes Service支持多个Endpoint(端口),在存在多个Endpoint的情况下,要求每个Endpoint定义一个名字来区分。下面是Tomcat多端口的Service定义样例:
spec:
ports:
- port: 8080
name: service-port
- port: 8005
name: shutdown-port
多端口为什么需要给每个端口命名呢?这就涉及Kubernetes的服务发现机制了
Kubernetes 的服务发现机制
Kubernetes 的服务发现机制 |
---|
最早时Kubernetes采用了Linux环境变量的方式解决这个问题,即每个Service生成一些对应的Linux环境变量(ENV),并在每个Pod的容器在启动时,自动注入这些环境变量 |
后来Kubernetes通过Add-On增值包的方式引入了DNS系统,把服务名作为DNS域名,这样一来,程序就可以直接使用服务名来建立通信连接了。目前Kubernetes上的大部分应用都已经采用了DNS这些新兴的服务发现机制 |
外部系统访问 Service 的问题
Kubernetes里的“三种IP" | 描述 |
---|---|
Node IP | Node 节点的IP地址,Node IP是Kubernetes集群中每个节点的物理网卡的IP地址,这是一个真实存在的物理网络,所有属于这个网络的服务器之间都能通过这个网络直接通信,不管它们中是否有部分节点不属于这个Kubernetes集群。这也表明了Kubernetes集群之外的节点访问Kubernetes集群之内的某个节点或者TCP/IP服务时,必须要通过Node IP进行通信。 |
Pod IP | Pod 的 IP 地址:Pod IP是每个Pod的IP地址,它是Docker Engine 根据dockero网桥的IP地址段进行分配的,通常是一个虚拟的二层网络,前面我们说过, Kubernetes要求位于不同Node上的Pod能够彼此直接通信,所以Kubernetes里一个Pod里的容器访问另外一个Pod里的容器,就是通过Pod IP所在的虚拟二层网络进行通信的,而真实的TCP/IP流量则是通过Node IP所在的物理网卡流出的。 |
Cluster IP | Service 的IP地址,Cluster IP仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配IP地址(来源于Cluster IP地址池)。Cluster IP无法被Ping,因为没有一个“实体网络对象”来响应。Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备TCPIP通信的基础,并且它们属于Kubernetes集群这样一个封闭的空间,集群之外的节点如果要访问这个通信端口,则需要做一些额外的工作。在Kubernetes集群之内, Node IP网、Pod IP网与Cluster IP网之间的通信,采用的是Kubermetes自己设计的一种编程方式的特殊的路由规则,与我们所熟知的IP路由有很大的不同。 |
外部系统访问 Service,采用NodePort是解决上述问题的最直接、最有效、最常用的做法。具体做法如下,以tomcat-service为例,我们在Service的定义里做如下扩展即可:
...
spec:
type: NodePort
posts:
- port: 8080
nodePort: 31002
selector:
tier: frontend
...
即这里我们可以通过nodePort:31002 来访问Service,NodePort的实现方式是在Kubernetes集群里的每个Node上为需要外部访问的Service开启个对应的TCP监听端口,外部系统只要用任意一个Node的IP地址+具体的NodePort端口即可访问此服务,在任意Node上运行netstat命令,我们就可以看到有NodePort端口被监听:
Service 负载均衡问题
但NodePort
还没有完全解决外部访问Service
的所有问题,比如负载均衡
问题,假如我们的集群中有10个Node
,则此时最好有一个负载均衡器
,外部的请求只需访问此负载均衡器的IP地址
,由负载均衡器负责转发流量到后面某个Node的NodePort上。如图
NodePort的负载均衡 |
---|
Load balancer 组件独立于Kubernetes集群 之外,通常是一个硬件的负载均衡器 ,或者是以软件方式实现 的,例如HAProxy 或者Nginx 。对于每个Service,我们通常需要配置一个对应的Load balancer实例来转发流量到后端的Node上 |
Kubernetes 提供了自动化的解决方案 ,如果我们的集群运行在谷歌的GCE公有云 上,那么只要我们把Service的type-NodePort改为type-LoadBalancer ,此时Kubernetes 会自动创建一个对应的Load balancer 实例并返回它的IP地址供外部客户端使用 。 |
10、 Volume (存储卷)
Volume是Pod中能够被多个容器访问的共享目录。Kuberetes的Volume概念、用途和目的与Docker的Volume比较类似,但两者不能等价
。
Volume (存储卷) |
---|
Kubernetes中的Volume定义在Pod上 ,然后被一个Pod里的多个容器挂载到具体的文件目录下; |
Kubernetes中的Volume与Pod的生命周期相同 ,但与容器的生命周期不相关 ,当容器终止或者重启时, Volume中的数据也不会丢失。 |
Kubernetes支持多种类型的Volume ,例如GlusterFS, Ceph 等先进的分布式文件系统 。 |
Volume
的使用也比较简单,在大多数情况下,我们先在Pod
上声明一个Volume
,然后在容器里引用该Volume
并Mount
到容器里的某个目录上。举例来说,我们要给之前的Tomcat Pod
增加一个名字为datavol
的Volume
,并且Mount
到容器的/mydata-data
目录上,则只要对Pod的定义文件做如下修正即可(注意黑体字部分):
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
volumes:
- name: datavol
emptyDir: {}
containers:
- name: tomcat-demo
image: tomcat
volumeMounts:
- mountPath: /myddata-data
name: datavol
imagePullPolicy: IfNotPresent
除了可以让一个
Pod
里的多个容器共享文件、让容器的数据写到宿主机的磁盘上或者写文件到网络存储中
,Kubernetes的Volume
还扩展出了一种非常有实用价值的功能,即
容器配置文件集中化定义与管理,这是通过ConfigMap
这个新的资源对象来实现的.
Kubernetes提供了非常丰富的Volume类型
,下面逐一进行说明。
1. emptyDir
一个emptyDir Volume是在Pod分配到Node时创建的。从它的名称就可以看出,它的初始内容为空
,并且无须指定宿主机上对应的目录文件,因为这是 Kubernetes自动分配的一个目录,当Pod从Node上移除时, emptyDir中的数据也会被永久删除
。emptyDir的一些用途如下。
emptyDir的一些用途 |
---|
临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留。 |
长时间任务的中间过程CheckPoint的临时保存目录。 |
一个容器需要从另一个容器中获取数据的目录(多容器共享目录) |
2. hostPath
hostPath为在Pod上挂载宿主机上的文件或目录
,它通常可以用于以下几方面。
|容器应用程序生成的日志文件需要永久保存时,可以使用宿主机的高速文件系统进行存储。|
需要访问宿主机上Docker
引擎内部数据结构的容器应用时,可以通过定义hostPath
为宿主机/var/lib/docker
目录,使容器内部应用可以直接访问Docker
的文件系统。
在使用这种类型的Volume
时,需要注意以下几点。
在不同的Node上具有相同配置的Pod
可能会因为宿主机上的目录和文件不同而导致对Volume
上目录和文件的访问结果不一致。)
如果使用了资源配额管理,则Kubernetes无法将hostPath在宿主机上使用的资源纳入管理。在下面的例子中使用宿主机的/data目录定义了一个hostPath
类型的Volume
:
volumes:
- name: "persistent-storage"
hostPath:
path: "/data"
3. gcePersistentDisk
使用这种类型的Volume表示使用谷歌公有云提供的永久磁盘(PersistentDisk, PD)存放Volume的数据,它与emptyDir不同, PD上的内容会被永久存,当Pod被删除时, PD只是被卸载(Unmount),但不会被删除。需要注意是,你需要先创建一个永久磁盘(PD),才能使用gcePersistentDisk.
4. awsElasticBlockStore
与GCE类似,该类型的Volume使用亚马逊公有云提供的EBS Volume存储数据,需要先创建一个EBS Volume才能使用awsElasticBlockStore.
5. NFS
使用NFS网络文件系统提供的共享目录存储数据时,我们需要在系统中部署一个NFSServer,定义NES类型的Volume的示例如下
yum -y install nfs-utils
...
volumes:
- name: test-volume
nfs:
server: nfs.server.locathost
path: "/"
....
11、 Persistent Volume
Volume
是定义在Pod
上的,属于“计算资源
”的一部分,而实际上, “网络存储
”是相对独立于“计算资源
”而存在的一种实体资源
。比如在使用虚拟机
的情况下,我们通常会先定义一个网络存储,然后从中划出一个“网盘”并挂接到虚拟机
上
Persistent Volume(简称PV)
和与之相关联的Persistent Volume Claim (简称PVC)
也起到了类似的作用。PV
可以理解成 Kubernetes集群中的某个网络存储中对应的一块存储,它与Volume很类似,但有以下区别。
Persistent Volume与Volume的区别 |
---|
PV只能是网络存储,不属于任何Node,但可以在每个Node上访问。 |
PV并不是定义在Pod上的,而是独立于Pod之外定义。 |
PV目前支持的类型包括: gcePersistentDisk、 AWSElasticBlockStore, AzureFileAzureDisk, FC (Fibre Channel). Flocker, NFS, isCSI, RBD (Rados Block Device)CephFS. Cinder, GlusterFS. VsphereVolume. Quobyte Volumes, VMware Photon.PortworxVolumes, ScalelO Volumes和HostPath (仅供单机测试)。 |
apiversion: v1
kind: PersistentVolume
metadata:
name: pv0003
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
nfs:
path: /somepath
server: 172.17.0.2
PV的accessModes属性, 目前有以下类型:
- ReadWriteOnce:读写权限、并且只能被单个Node挂载。
- ReadOnlyMany:只读权限、允许被多个Node挂载。
- ReadWriteMany:读写权限、允许被多个Node挂载。
如果某个Pod想申请某种类型的PV,则首先需要定义一个PersistentVolumeClaim (PVC)对象:
kind: Persistentvolumeclaim
apiversion: v1
metadata:
name: myclaim
spec:
accessModes:
- Readwriteonce
resources:
requests:
storage: BGi
引用PVC
volumes:
- name: mypd
persistentvolumeclaim:
claimName: myclaim
PV 是有状态的对象,它有以下几种状态。 |
---|
Available :空闲状态。 |
Bound :已经绑定到某个Pvc上。 |
Released :对应的PVC已经删除,但资源还没有被集群收回。 |
Failed : PV自动回收失败。 |
12、 Namespace (命名空间)
Namespace (命名空间)是Kubernetes系统中非常重要的概念, Namespace在很多情况下用于实现 多租户的资源隔离。Namespace通过将集群内部的资源对象“分配”到不同的Namespace 中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。Kubernetes集群在启动后,会创建一个名为"default"
的Namespace
,通过kubectl
可以查看到:
不同的namespace之间互相隔离 |
---|
查看所有命名空间 |
查看当前命名空间 |
设置命名空间 |
kub-system 本身的各种 pod,是kubamd默认的空间。pod使用命名空间相互隔离
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl get namespaces
NAME STATUS AGE
default Active 13h
kube-node-lease Active 13h
kube-public Active 13h
kube-system Active 13h
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl get ns
NAME STATUS AGE
default Active 13h
kube-node-lease Active 13h
kube-public Active 13h
kube-system Active 13h
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$
命名空间基本命令
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl create ns liruilong
namespace/liruilong created
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl get ns
NAME STATUS AGE
default Active 13h
kube-node-lease Active 13h
kube-public Active 13h
kube-system Active 13h
liruilong Active 4s
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl create ns k8s-demo
namespace/k8s-demo created
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl get ns
NAME STATUS AGE
default Active 13h
k8s-demo Active 3s
kube-node-lease Active 13h
kube-public Active 13h
kube-system Active 13h
liruilong Active 20s
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl delete ns k8s-demo
namespace "k8s-demo" deleted
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl get ns
NAME STATUS AGE
default Active 13h
kube-node-lease Active 13h
kube-public Active 13h
kube-system Active 13h
liruilong Active 54s
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$
命名空间切换
┌──[root@vms81.liruilongs.github.io]-[~/.kube]
└─$vim config
┌──[root@vms81.liruilongs.github.io]-[~/.kube]
└─$kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* context1 cluster1 kubernetes-admin1
context2 cluster2 kubernetes-admin2
┌──[root@vms81.liruilongs.github.io]-[~/.kube]
└─$kubectl config set-context context2 --namespace=kube-system
Context "context2" modified.
┌──[root@vms81.liruilongs.github.io]-[~/.kube]
└─$kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* context1 cluster1 kubernetes-admin1
context2 cluster2 kubernetes-admin2 kube-system
┌──[root@vms81.liruilongs.github.io]-[~/.kube]
└─$kubectl config set-context context1 --namespace=kube-public
Context "context1" modified.
或者可以这样切换名称空间
kubectl config set-context $(kubectl config current-context) --namespace=<namespace>
kubectl config view | grep namespace
kubectl get pods
创建pod时指定命名空间
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod-static
name: pod-static
namespeace: default
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: pod-demo
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
当我们给每个租户创建一个Namespace来实现多租户的资源隔离时,还能结合Kubernetes"的资源配额管理,限定不同租户能占用的资源,例如CPU使用量、内存使用量
等。
13、 Annotation (注解)
Annotation与Label类似,也使用key/value键值
对的形式进行定义。
不同的是Label具有严格的命名规则,它定义的是Kubernetes对象的元数据(Metadata),并且用于Label Selector.
Annotation则是用户任意定义的“附加”信息,以便于外部工具进行查找, Kubernetes的模块自身会通过Annotation的方式标记资源对象的一些特殊信息。
┌──[root@vms81.liruilongs.github.io]-[~/ansible/k8s-pod-create]
└─$kubectl annotate nodes vms82.liruilongs.github.io "dest=这是一个工作节点"
node/vms82.liruilongs.github.io annotated
┌──[root@vms81.liruilongs.github.io]-[~/ansible/k8s-pod-create]
└─$kubectl describe nodes vms82.liruilongs.github.io
Name: vms82.liruilongs.github.io
Roles: worker1
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
disktype=node1
kubernetes.io/arch=amd64
kubernetes.io/hostname=vms82.liruilongs.github.io
kubernetes.io/os=linux
node-role.kubernetes.io/worker1=
Annotations: dest: 这是一个工作节点
kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
node.alpha.kubernetes.io/ttl: 0
projectcalico.org/IPv4Address: 192.168.26.82/24
projectcalico.org/IPv4IPIPTunnelAddr: 10.244.171.128
volumes.kubernetes.io/controller-managed-attach-detach: true
.....................
通常来说,用Annotation来记录的信息如下 |
---|
build信息、 release信息、Docker镜像信息等,例如时间戳、release id号、PR号、镜像hash值、 docker registry地址等。 |
日志库、监控库、分析库等资源库的地址信息。 |
程序调试工具信息,例如工具名称、版本号等。 |
团队的联系信息,例如电话号码、负责人名称、网址等。 |
- 点赞
- 收藏
- 关注作者
评论(0)