kuberenetes控制器(管理Pod)
kuberenetes控制器(管理Pod)
在K8S
中去管理pod
的任务是由控制器(controllers)
去做的,控制器又称为工作负载(workload)
,有了控制器才能更好的对服务进行编排,有五个比较重要的控制器,如下。
- Deployment
- StatefulSet
- DaemonSet
- Job
- CronJob
下面会分别介绍,先看看Pod
与控制器的关系。
Pod 与控制器的关系
首先控制器是在集群上管理和运行容器的对象,也就是管理Pod
的,第二控制与pod
关联是通过标签(label-selector)
进行关联,通过控制器去管理Pod
会实现更多流啤的功能,像是什么弹性伸缩升级滚动更新之类的,都是在控制器层面实现的。
Deployment部署无状态应用
使用Deployment,这个控制器创建了nginx,这种算是无状态的应用,像是nginx根本不用去考虑分配到那个节点或容器IP是多少,所以Deployment适用于部署无状态的应用,他能管理Pod和ReplicaSet
[root@k8s01 yml]# kubectl get replicasets.apps
NAME DESIRED CURRENT READY AGE
nginx-dd6b5d745 3 3 3 3d5h
[root@k8s01 yml]#
ReplicaSet这对于用于来说,这是一个隐藏的控制器,用作管理Pod
副本数的,我预期的状态是创建三个副本,可以看一下上文的配置文件,如果有一个pod
挂掉了他就会再启动一个pod
,会确保有三个在运行,deployment
是管理replicasets&pod
的,replicasets
也做版本的管理,之前我们使用kubectl rollout
回滚到上一个版本就是依靠的replicasets
。
所以总结一下就是deployment
能管理Pod
和ReplicaSet
、具有上线部署、副本设定、滚动更新、回滚等功能,下面编辑一下nginx-deployment
的配置文件
[root@k8s01 yml]# kubectl edit deployments.apps nginx
spec:
progressDeadlineSeconds: 600
replicas: 3 #设置副本数
revisionHistoryLimit: 10
selector:
matchLabels:
run: nginx #标签选择器,pod与控制器关联
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate # 滚动更新策略,默认滚动更新
查看回滚版本,也是replicasets
来做的。
[root@k8s01 yml]# kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION CHANGE-CAUSE
1 <none>
[root@k8s01 yml]#
支持声明式更新,例如想更新某一个字段,他就会更新某一个字段,例如更新image
字段,deployments
比较适合Web
服务,之前部署用的就是deployments
。
有状态和无状态服务区别
刚刚上面提到了deployment
部署的是无状态服务,还有一种服务是有状态服务,这俩服务有啥区别,下面分别看一下。
无状态服务概述
deployment
认为所有的Pod
都是一样的,也就是这些。
[root@k8s01 yml]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-dd6b5d745-6z8lx 1/1 Running 5 3d5h
nginx-dd6b5d745-cv4t8 1/1 Running 4 3d5h
nginx-dd6b5d745-tgh5c 1/1 Running 5 3d5h
[root@k8s01 yml]#
这三个副本都是一模一样的,都是使用同一个镜像去启动的,里面的数据也是完全一样的,属于是对等关系,而且他们的启动顺序也是没有任何规律,先启动哪个都无所谓,对我应用部署没有任何影响,也不用考虑pod
在哪个node
运行,更不用也不用考虑Pod:IP
是什么,所以你可以随意扩容和缩容。
有状态服务概述
举一个最简单的例子,例如我现在需要使用三个zookeeper
做一个集群,集群最低要求要有三个zookeeper
实例,每个容器都设置的一个名为ZOO_MY_ID
的全局变量,而且这个值各个容器之间都不一样,有三个容器,我设置的分别是{1..3}
,各个节点地址都是用的zoo{1..3}
,这算是唯一的网络标识符,容器挂了重启这个标识符是不变的,但是我那个zookeeper
没有挂载数据卷,其实还要有持久存储。
像是这种服务你就不能随意的扩容和缩减了,需要有序进行了,包括扩展删除终止滚动更新服务,像是这种就算有状态服务了,每个实例之间属于不对等关系,各个容器之间有些参数不一致,或是挂载的数据卷源/位置不一致,你不可以随意的扩容或缩减这些服务,否则就会有问题,在K8S
中,要实现这种服务就要使用StatefulSet
了。
headless service
想知道StatefulSet
具体是怎么实现的,要从一个服务谈起,也就是headless service
,称为无头服务,回忆一下之前的Service
,它是一组pod
的访问策略,提供负载均衡和服务发现,访问这个Service
他会帮你把请求转发到某个Pod
,也就是这个东西,这是之前创建的。
[root@k8s01 yml]# kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 10d
nginx-service NodePort 10.0.0.180 <none> 80:30010/TCP 3d21h
[root@k8s01 yml]#
类型NodePort
,无头服务也是使用了Service
,不过在定义的时候不需要CLUSTER-IP
了,直接会绑定具体Pod
的IP,上面就是一个常规的Service
,他关联了三个标签为nginx
的Pod
,且提供了一个统一的入口,IP
为10.0.0.180
,你访问这个IP:port
他会帮你把请求转发到后端Pod
,也就是这里。
[root@k8s01 yml]# kubectl get endpoints nginx-service
NAME ENDPOINTS AGE
nginx-service 10.244.0.38:80,10.244.1.100:80,10.244.1.101:80 3d22h
[root@k8s01 yml]#
再来看headless service
,和常规的稍微有点区别,他会将CLUSTER-IP
置位none
[root@k8s01 yml]# cat headless-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
和正常创建Service
的区别就是clusterip
为none
,默认clusterIP
是启用的,现在制定为none
了就是不为这个Service
分配一个clusterip
了,创建一下看效果。
[root@k8s01 yml]# kubectl create -f headless-service.yaml
service/nginx created
[root@k8s01 yml]# kubectl get services nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 6s
[root@k8s01 yml]#
可以看到CLUSTER-IP
为None
了,他没有具体的IP
,这种情况下主要用的就是DNS
访问了,他保证了唯一的网络标识符,使用IP
无法保证你这个pod
挂掉重启后永远都是使用这个IP
,所以这就是利用了DNS
保证了网络唯一标识符的,我们之前部署过了coredns
,下面开始部署有状态服务。
部署有状态应用
SatefulSet是部署有状态应用,解决Pod独立生命周期,保持Pod顺序和唯一性。稳定,唯一的网络标识符,持久存储。有序,优雅的部署和扩展,删除和终止。有序,滚动升级
[root@k8s01 yml]# kubectl delete -f headless-service.yaml
service "nginx" deleted
[root@k8s01 yml]# kubectl delete -f deployment.yaml
deployment.apps "nginx-deployment" deleted
[root@k8s01 yml]# kubectl delete -f services.yaml
service "nginx-service" deleted
[root@k8s01 yml]# cat nginx-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
name: nginx
我把Service
和StatefulSet
写到了一起,StatefulSet
和deployment
有差别的地方就是StatefulSet
有个serviceName
,他是用来关联上面无头service
的,上面无头Service
的名称就是nginx
,直接用这个文件进行部署,部署完之后看一下所创建的StatefulSet
和Service
。
[root@k8s01 yml]# kubectl create -f nginx-headless.yaml
service/nginx created
statefulset.apps/nginx created
[root@k8s01 yml]# kubectl get services nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 14s
[root@k8s01 yml]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 53s
nginx-1 1/1 Running 0 36s
nginx-2 1/1 Running 0 20s
[root@k8s01 yml]#
创建好了,现在了解一下唯一的网络标识是怎么来的,就是上图NAME
那一行的名称,这就是一个固定的DNS
名称了,而且会有有序编号,现在随便启动一个镜像去测试一下。
[root@k8s01 yml]# kubectl run -it --image=busybox:1.28.4 --rm --restart=Never sh
If you don't see a command prompt, try pressing enter.
/ #
/ # for i in 0 1 2;do nslookup nginx-$i.nginx;done
Server: 10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local
Name: nginx-0.nginx
Address 1: 10.244.1.102 nginx-0.nginx.default.svc.cluster.local
Server: 10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local
Name: nginx-1.nginx
Address 1: 10.244.0.39 nginx-1.nginx.default.svc.cluster.local
Server: 10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local
Name: nginx-2.nginx
Address 1: 10.244.1.103 nginx-2.nginx.default.svc.cluster.local
/ # pod "sh" deleted
[root@k8s01 yml]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-0 1/1 Running 0 9m19s 10.244.1.102 k8s03 <none> <none>
nginx-1 1/1 Running 0 9m2s 10.244.0.39 k8s02 <none> <none>
nginx-2 1/1 Running 0 8m46s 10.244.1.103 k8s03 <none> <none>
[root@k8s01 yml]#
没什么问题, pod
名称后需要加上Service
的名称才能解析到,这就是一个Pod
有一个唯一的域名,这是个固定的,并且解析到自身的IP
,所以,总结一下。
StatefulSet
和deployment
的主要区别就是StatefulSet
是有身份的,这个身份有三要素,分别是有自己的域名、主机名、存储(PVC)
,每一个Pod
都有一个唯一的主机名,看一下是什么就知道了。
[root@k8s01 yml]# for i in {0,1,2};do kubectl exec nginx-$i hostname;done
nginx-0
nginx-1
nginx-2
[root@k8s01 yml]#
DaemonSet
这个比较好理解,他会在你每一个Node
上运行一个Pod
,新加入的Node
也会自动运行一个Pod
,比较适合运行采集器之类的东西
[root@k8s01 yml]# cat daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat
spec:
selector:
matchLabels:
app: filebeat
template:
metadata:
labels:
app: filebeat
spec:
containers:
- name: filebeat
image: nginx:latest
volumeMounts:
- mountPath: /log
name: varlog
volumes:
- name: varlog
hostPath:
path: /var/log
type: Directory
[root@k8s01 yml]#
使用了nginx
的镜像,你就把他当成filebeat
,这东西是干嘛的应该都了解过,上面把宿主机的/var/log
目录挂在到了容器的/log
,上面提到了这个类型会在所有的Node
节点都启动一个的pod
,所以你可以理解为该Pod
可以收集宿主机日志,通过filebeat
读取挂载目录的日志推到logstash||elasticsearch
中
[root@k8s01 yml]# kubectl create -f daemonset.yaml
daemonset.apps/filebeat created
[root@k8s01 yml]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
filebeat-5sfvj 1/1 Running 0 26s 10.244.0.46 k8s02 <none> <none>
filebeat-q5f7c 1/1 Running 0 26s 10.244.2.60 k8s05 <none> <none>
filebeat-zcl56 1/1 Running 0 26s 10.244.1.104 k8s03 <none> <none>
可以看到每个节点都有一个在运行,下面去Pod
里看看宿主机的东西是否被挂载到Pod
,挂载到了就没问题
[root@k8s01 yml]# kubectl exec -it filebeat-5sfvj /bin/bash
root@filebeat-5sfvj:/# ls -l /log/
total 5468
drwxr-xr-x. 2 root root 204 May 1 05:24 anaconda
drwx------. 2 root root 23 May 1 05:24 audit
-rw-------. 1 root root 0 Jun 5 03:19 boot.log
-rw------- 1 root root 78724 Jun 2 02:50 boot.log-20200602
-rw------- 1 root root 9076 Jun 3 08:06 boot.log-20200603
-rw------- 1 root root 37533 Jun 5 03:19 boot.log-20200605
-rw-------. 1 root voice 0 May 1 05:22 btmp
drwxr-xr-x 2 root root 4096 Jun 5 08:44 containers
-rw-------. 1 root root 294625 Jun 5 08:46 cron
-rw-r--r-- 1 root root 122195 Jun 5 02:58 dmesg
-rw-r--r-- 1 root root 122185 Jun 5 02:42 dmesg.old
-rw-r--r--. 1 root root 1842 May 1 05:27 firewalld
-rw-r--r--. 1 root root 193 May 1 05:21 grubby_prune_debug
-rw-r--r--. 1 root root 292000 Jun 5 06:13 lastlog
-rw-------. 1 root root 153548 Jun 5 08:46 maillog
-rw-------. 1 root root 4517207 Jun 5 08:46 messages
drwxr-xr-x 7 root root 4096 Jun 5 08:43 pods
drwxr-xr-x. 2 root root 6 May 1 05:24 rhsm
drwxr-xr-x. 2 root root 91 Jun 5 02:42 sa
-rw-------. 1 root root 51606 Jun 5 08:24 secure
-rw-------. 1 root root 0 May 1 05:22 spooler
-rw-------. 1 root root 0 May 1 05:21 tallylog
drwxr-xr-x. 2 root root 23 May 1 05:24 tuned
-rw-r--r--. 1 root root 21390 Jun 5 02:58 vmware-vgauthsvc.log.0
-rw-r--r--. 1 root root 27822 Jun 5 06:05 vmware-vmsvc.log
-rw-rw-r--. 1 root voice 41472 Jun 5 08:24 wtmp
-rw-------. 1 root root 4262 Jun 1 09:35 yum.log
root@filebeat-5sfvj:/#
没问题,使用场景就是这样,将来使用filebeat
就可以直接采集这个目录下的日志文件了
Job与CronJob
Job
分为普通任务(Job)
和定时任务(CronJob)
,普通任务是一次性执行的,定时任务就是有计划的去执行,下面分别去了解一下。
Job
也就是普通任务,下面是一个例子,在官网扒过来的。
[root@k8s01 yml]# cat job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl","-Mbignum=bpi","-wle","print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
使用perl
镜像启动一个容器,运行一条命令去计算,这个不用深入了解,重启策略为Never
,表示容器退出码为0
的情况下从不重启,backoffLimit
设置重试的次数,如果这个任务执行失败了怎么办,现在定义了4
,如果失败了他会帮你重试4
次,为毛要有这个?上面提到了Never
表示容器退出码为0
的情况下从不重启,如果退出状态码非0
还是会重启,如果四次退出码都是0
,也就是到达了backoffLimit
的限制,他就不会再重启了,默认是6
,下面创建一下。
[root@k8s01 yml]# kubectl create -f job.yml
job.batch/pi created
[root@k8s01 yml]# kubectl get jobs.batch
NAME COMPLETIONS DURATION AGE
pi 1/1 3m17s 3m22s
[root@k8s01 yml]# kubectl get pod pi-k2zvd
NAME READY STATUS RESTARTS AGE
pi-k2zvd 0/1 Completed 0 3m25s
[root@k8s01 yml]#
可以看到pod
的状态为Completed
了,说明他执行完命令后正常关闭了,关闭后他不会被删除,自行删除吧,所以这个job
比较适合临时跑一个任务,跑完之后就退出了,不再执行了。
CronJob
定时任务,像crontab
一样,基于时间去调度,用来做备份还是蛮不错的,下面一个例子,也是在官方扒过来的,顺便改了改。
[root@k8s01 yml]# cat cronjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: opesn
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: opesn
image: centos:latest
command: ["bash","-c","date; echo opesn"]
restartPolicy: OnFailure
[root@k8s01 yml]#
schedule
为时间表达式,和crontab
的表达式写法是一样的,所以这个就是一分钟一次,重启策略为OnFailure
,如果容器异常退出就会重启.
[root@k8s01 yml]# kubectl create -f cronjob.yaml
cronjob.batch/opesn created
[root@k8s01 yml]# kubectl get cronjobs.batch opesn
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
opesn */1 * * * * False 1 17s 59s
[root@k8s01 yml]# kubectl get pod
NAME READY STATUS RESTARTS AGE
opesn-1591347900-492v4 0/1 ContainerCreating 0 17s
[root@k8s01 yml]# kubectl logs opesn-1591347900-492v4
Fri Jun 5 09:05:22 UTC 2020
opesn
[root@k8s01 yml]#
[root@k8s01 yml]# kubectl get pod
NAME READY STATUS RESTARTS AGE
opesn-1591347900-492v4 0/1 Completed 0 107s
opesn-1591347960-4fn49 0/1 Completed 0 47s
[root@k8s01 yml]# kubectl logs opesn-1591347960-4fn49
Fri Jun 5 09:06:21 UTC 2020
opesn
[root@k8s01 yml]#
[root@k8s01 yml]# kubectl get pod
NAME READY STATUS RESTARTS AGE
opesn-1591347900-492v4 0/1 Completed 0 2m36s
opesn-1591347960-4fn49 0/1 Completed 0 96s
opesn-1591348020-4fs92 0/1 Completed 0 36s
[root@k8s01 yml]# kubectl logs opesn-1591348020-4fs92
Fri Jun 5 09:07:21 UTC 2020
opesn
[root@k8s01 yml]#
就是这种效果,创建成功后每分钟执行一次,历史默认保留三次