目录
前言
名称 | 特点 |
---|---|
Deployment | 无状态服务,pod运行节点随时漂移,一节点多副本 |
DaemonSet | 随节点启动而创建&&销毁而删除,一节点一副本 |
StatefulSet | 每个pod都有一个持久的网络id标识符,持久化的存储,有序创建销毁 |
Job | 一次性运行的任务,有完成状态 |
CronJob | 定时任务 |
pod控制器分类
1、ReplicationController
2、ReplicaSet
3、Deployment
4、StatefulSet
5、DaemonSet
6、Job,Cronjob
7、HPA
pod控制器:一般包括3部分
1、标签选择器
2、期望的副本数(DaemonSet控制器不需要)
3、pod模板
deploy控制器构建于rs控制器之上,新特性包括:
1、事件和状态查看
2、回滚
3、版本记录
4、暂停和启动
5、支持两种自动更新方案
6、Recreate删除重建
7、RollingUpdate回滚升级(默认方式)
DaemonSet
通过该控制器的名称我们可以看出它的用法:Daemon,就是用来部署守护进程的,DaemonSet
用于在每个Kubernetes
节点中将守护进程的副本作为后台进程运行,说白了就是在每个节点部署一个Pod
副本,当节点加入到Kubernetes
集群中,Pod
会被调度到该节点上运行,当节点从集群只能够被移除后,该节点上的这个Pod
也会被移除,当然,如果我们删除DaemonSet
,所有和这个对象相关的Pods
都会被删除。
在哪种情况下我们会需要用到这种业务场景呢?其实这种场景还是比较普通的,比如:
- 集群存储守护程序,如
glusterd
、ceph
要部署在每个节点上以提供持久性存储; - 节点监视守护进程,如
Prometheus
监控集群,可以在每个节点上运行一个node-exporter
进程来收集监控节点的信息; - 日志收集守护程序,如
fluentd
或logstash
,在每个节点上运行以收集容器的日志
这里需要特别说明的一个就是关于DaemonSet
运行的Pod
的调度问题,正常情况下,Pod
运行在哪个节点上是由Kubernetes
的调度器策略来决定的,然而,由DaemonSet
控制器创建的Pod
实际上提前已经确定了在哪个节点上了(Pod
创建时指定了.spec.nodeName
),所以:
DaemonSet
并不关心一个节点的unshedulable
字段,这个我们会在后面的调度章节和大家讲解的。DaemonSet
可以创建Pod
,即使调度器还没有启动,这点非常重要。
下面我们直接使用一个示例来演示下,在每个节点上部署一个Nginx Pod
:(nginx-ds.yaml)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
kind: DaemonSet apiVersion: extensions/v1beta1 metadata: name: nginx-ds labels: k8s-app: nginx spec: template: metadata: labels: k8s-app: nginx spec: containers: - image: nginx:1.7.9 name: nginx ports: - name: http containerPort: 80 |
然后直接创建即可:
1 |
kubectl create -f nginx-ds.yaml |
然后我们可以观察下Pod
是否被分布到了每个节点上:
1 2 |
kubectl get nodes kubectl get pods -o wide |
StatefulSet 的使用
在学习StatefulSet
这种控制器之前,我们就得先弄明白一个概念:什么是有状态服务?什么是无状态服务?
- 无状态服务(Stateless Service):该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的,比如前面我们讲解的
WordPress
实例,我们是不是可以同时启动多个实例,但是我们访问任意一个实例得到的结果都是一样的吧?因为他唯一需要持久化的数据是存储在MySQL
数据库中的,所以我们可以说WordPress
这个应用是无状态服务,但是MySQL
数据库就不是了,因为他需要把数据持久化到本地。 - 有状态服务(Stateful Service):就和上面的概念是对立的了,该服务运行的实例需要在本地存储持久化数据,比如上面的
MySQL
数据库,你现在运行在节点A,那么他的数据就存储在节点A上面的,如果这个时候你把该服务迁移到节点B去的话,那么就没有之前的数据了,因为他需要去对应的数据目录里面恢复数据,而此时没有任何数据。
现在大家对有状态和无状态有一定的认识了吧,比如我们常见的 WEB 应用,是通过session
来保持用户的登录状态的,如果我们将session
持久化到节点上,那么该应用就是一个有状态的服务了,因为我现在登录进来你把我的session
持久化到节点A上了,下次我登录的时候可能会将请求路由到节点B上去了,但是节点B上根本就没有我当前的session
数据,就会被认为是未登录状态了,这样就导致我前后两次请求得到的结果不一致了。所以一般为了横向扩展,我们都会把这类 WEB 应用改成无状态的服务,怎么改?将session
数据存入一个公共的地方,比如redis
里面,是不是就可以了,对于一些客户端请求API
的情况,我们就不使用session
来保持用户状态,改成用token
也是可以的。
无状态服务利用我们前面的Deployment
或者RC
都可以很好的控制,对应有状态服务,需要考虑的细节就要多很多了,容器化应用程序最困难的任务之一,就是设计有状态分布式组件的部署体系结构。由于无状态组件可能没有预定义的启动顺序、集群要求、点对点 TCP 连接、唯一的网络标识符、正常的启动和终止要求等,因此可以很容易地进行容器化。诸如数据库,大数据分析系统,分布式 key/value 存储和 message brokers 可能有复杂的分布式体系结构,都可能会用到上述功能。为此,Kubernetes
引入了StatefulSet
资源来支持这种复杂的需求。
StatefulSet
类似于ReplicaSet
,但是它可以处理Pod
的启动顺序,为保留每个Pod
的状态设置唯一标识,同时具有以下功能:
- 稳定的、唯一的网络标识符
- 稳定的、持久化的存储
- 有序的、优雅的部署和缩放
- 有序的、优雅的删除和终止
- 有序的、自动滚动更新
创建StatefulSet
接下来我们来给大家演示下StatefulSet
对象的使用方法,在开始之前,我们先准备两个1G的存储卷(PV),在后面的课程中我们也会和大家详细讲解 PV 和 PVC 的使用方法的,这里我们先不深究:(pv001.yaml)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
apiVersion: v1 kind: PersistentVolume metadata: name: pv001 labels: release: stable spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle hostPath: path: /tmp/data |
另外一个只需要把 name 改成 pv002即可,然后创建:
1 2 3 4 5 6 7 8 |
$ kubectl create -f pv001.yaml && kubectl create -f pv002.yaml persistentvolume "pv001" created persistentvolume "pv002" created $ kubectl get pv kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv001 1Gi RWO Recycle Available 12s pv002 1Gi RWO Recycle Available 11s |
可以看到成功创建了两个 PV对象,状态是:Available。
然后我们使用StatefulSet来创建一个 Nginx 的 Pod,对于这种类型的资源,我们一般是通过创建一个Headless Service
类型的服务来暴露服务,将clusterIP设置为None就是一个无头的服务:(statefulset-demo.yaml)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
apiVersion: v1 kind: Service metadata: name: nginx spec: ports: - port: 80 name: web clusterIP: None # 无头服务 selector: app: nginx role: stateful --- apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 2 template: metadata: labels: app: nginx role: stateful spec: containers: - name: nginx image: cnych/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi |
注意上面的 YAML 文件中和volumeMounts
进行关联的是一个新的属性:volumeClaimTemplates,该属性会自动声明一个 pvc 对象和 pv 进行管理:
然后这里我们开启两个终端窗口。在第一个终端中,使用 kubectl get 来查看 StatefulSet 的 Pods 的创建情况。
1 |
kubectl get pods -w -l role=stateful |
在另一个终端中,使用 kubectl create 来创建定义在 statefulset-demo.yaml 中的 Headless Service 和 StatefulSet。
1 2 3 |
$ kubectl create -f statefulset-demo.yaml service "nginx" created statefulset.apps "web" created |
检查 Pod 的顺序索引
对于一个拥有 N 个副本的 StatefulSet,Pod 被部署时是按照 {0..N-1}的序号顺序创建的。在第一个终端中我们可以看到如下的一些信息:
1 2 3 4 5 6 7 8 9 10 |
$ kubectl get pods -w -l role=stateful NAME READY STATUS RESTARTS AGE web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 19s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 18s |
请注意在 web-0 Pod 处于 Running 和 Ready 状态后 web-1 Pod 才会被启动。
如同 StatefulSets 概念中所提到的, StatefulSet 中的 Pod 拥有一个具有稳定的、独一无二的身份标志。这个标志基于 StatefulSet 控制器分配给每个 Pod 的唯一顺序索引。 Pod 的名称的形式为<statefulset name>-<ordinal index>
。web StatefulSet 拥有两个副本,所以它创建了两个 Pod:web-0 和 web-1。
上面的命令创建了两个 Pod,每个都运行了一个 NGINX web 服务器。获取 nginx Service 和 web StatefulSet 来验证是否成功的创建了它们。
1 2 3 4 5 6 7 |
$ kubectl get service nginx NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx None <none> 80/TCP 12s $ kubectl get statefulset web NAME DESIRED CURRENT AGE web 2 1 20s |
使用稳定的网络身份标识
每个 Pod 都拥有一个基于其顺序索引的稳定的主机名。使用 kubectl exec 在每个 Pod 中执行 hostname 。
1 2 3 |
$ for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done web-0 web-1 |
然后我们使用 kubectl run 运行一个提供 nslookup 命令的容器。通过对 Pod 的主机名执行 nslookup,你可以检查他们在集群内部的 DNS 地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh nslookup web-0.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 10.244.1.6 nslookup web-1.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 10.244.2.6 |
headless service
的 CNAME 指向 SRV 记录(记录每个 Running 和 Ready 状态的 Pod)。SRV 记录指向一个包含 Pod IP 地址的记录表项。
然后我们再来看下删除 StatefulSet 下面的 Pod:
在一个终端中查看 StatefulSet 的 Pod:
1 |
kubectl get pod -w -l role=stateful |
在另一个终端中使用 kubectl delete 删除 StatefulSet 中所有的 Pod。
1 2 3 |
$ kubectl delete pod -l role=stateful pod "web-0" deleted pod "web-1" deleted |
等待 StatefulSet 重启它们,并且两个 Pod 都变成 Running 和 Ready 状态。
1 2 3 4 5 6 7 8 |
$ kubectl get pod -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 2s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 34s |
然后再次使用 kubectl exec 和 kubectl run 查看 Pod 的主机名和集群内部的 DNS 表项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done web-0 web-1 $ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh nslookup web-0.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 10.244.1.7 nslookup web-1.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 10.244.2.8 |
我们可以看到Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能会发生改变。所以说这就是为什么不要在其他应用中使用 StatefulSet 中的 Pod 的 IP 地址进行连接,这点很重要。一般情况下我们直接通过 SRV 记录连接就行:web-0.nginx、web-1.nginx,因为他们是稳定的,并且当你的 Pod 的状态变为 Running 和 Ready 时,你的应用就能够发现它们的地址。
同样我们可以查看 PV、PVC的最终绑定情况:
1 2 3 4 5 6 7 8 9 |
$ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv001 1Gi RWO Recycle Bound default/www-web-0 1h pv002 1Gi RWO Recycle Bound default/www-web-1 1h $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-web-0 Bound pv001 1Gi RWO 22m www-web-1 Bound pv002 1Gi RWO 22m |
当然 StatefulSet 还拥有其他特性,在实际的项目中,我们还是很少回去直接通过 StatefulSet 来部署我们的有状态服务的,除非你自己能够完全能够 hold 住,对于一些特定的服务,我们可能会使用更加高级的 Operator 来部署,比如 etcd-operator、prometheus-operator 等等,这些应用都能够很好的来管理有状态的服务,而不是单纯的使用一个 StatefulSet 来部署一个 Pod就行,因为对于有状态的应用最重要的还是数据恢复、故障转移等等。
Job
我们用Job这个资源对象来创建一个任务,我们定一个Job来执行一个倒计时的任务,定义YAML文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
apiVersion: batch/v1 kind: Job metadata: name: job-demo spec: template: metadata: name: job-demo spec: restartPolicy: Never containers: - name: counter image: busybox command: - "bin/sh" - "-c" - "for i in 9 8 7 6 5 4 3 2 1; do echo $i; done" |
注意Job的RestartPolicy仅支持Never和OnFailure两种,不支持Always,我们知道Job就相当于来执行一个批处理任务,执行完就结束了,如果支持Always的话是不是就陷入了死循环了?
然后来创建该Job,保存为job-demo.yaml:
1 2 |
$ kubectl create -f ./job.yaml job "job-demo" created |
然后我们可以查看当前的Job资源对象:
1 |
kubectl get jobs |
注意查看我们的Pod的状态,同样我们可以通过kubectl logs来查看当前任务的执行结果。
CronJob
CronJob
其实就是在Job
的基础上加上了时间调度,我们可以:在给定的时间点运行一个任务,也可以周期性地在给定时间点运行。这个实际上和我们Linux
中的crontab
就非常类似了。
一个CronJob
对象其实就对应中crontab
文件中的一行,它根据配置的时间格式周期性地运行一个Job
,格式和crontab
也是一样的。
crontab
的格式如下:
分 时 日 月 星期 要运行的命令 第1列分钟0~59 第2列小时0~23) 第3列日1~31 第4列月1~12 第5列星期0~7(0和7表示星期天) 第6列要运行的命令
现在,我们用CronJob
来管理我们上面的Job
任务,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
apiVersion: batch/v2alpha1 kind: CronJob metadata: name: cronjob-demo spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: hello image: busybox args: - "bin/sh" - "-c" - "for i in 9 8 7 6 5 4 3 2 1; do echo $i; done" |
我们这里的Kind
是CronJob
了,要注意的是.spec.schedule
字段是必须填写的,用来指定任务运行的周期,格式就和crontab
一样,另外一个字段是.spec.jobTemplate
, 用来指定需要运行的任务,格式当然和Job
是一致的。还有一些值得我们关注的字段.spec.successfulJobsHistoryLimit
和.spec.failedJobsHistoryLimit
,表示历史限制,是可选的字段。它们指定了可以保留多少完成和失败的Job
,默认没有限制,所有成功和失败的Job
都会被保留。然而,当运行一个Cron Job
时,Job
可以很快就堆积很多,所以一般推荐设置这两个字段的值。如果设置限制的值为 0,那么相关类型的Job
完成后将不会被保留。
接下来我们来创建这个cronjob
1 2 |
$ kubectl create -f cronjob-demo.yaml cronjob "cronjob-demo" created |
当然,也可以用kubectl run来创建一个CronJob:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ kubectl run hello --schedule="*/1 * * * *" --restart=OnFailure --image=busybox -- /bin/sh -c "date; echo Hello from the Kubernetes cluster" $ kubectl get cronjob NAME SCHEDULE SUSPEND ACTIVE LAST-SCHEDULE hello */1 * * * * False 0 <none> $ kubectl get jobs NAME DESIRED SUCCESSFUL AGE hello-1202039034 1 1 49s $ pods=$(kubectl get pods --selector=job-name=hello-1202039034 --output=jsonpath={.items..metadata.name} -a) $ kubectl logs $pods Mon Aug 29 21:34:09 UTC 2016 Hello from the Kubernetes cluster $ kubectl delete cronjob hello cronjob "hello" deleted |
一旦不再需要 Cron Job,简单地可以使用 kubectl 命令删除它:
1 2 |
$ kubectl delete cronjob hello cronjob "hello" deleted |
这将会终止正在创建的 Job。然而,运行中的 Job 将不会被终止,不会删除 Job 或 它们的 Pod。为了清理那些 Job 和 Pod,需要列出该 Cron Job 创建的全部 Job,然后删除它们:
1 2 3 4 |
$ kubectl get jobs NAME DESIRED SUCCESSFUL AGE hello-1201907962 1 1 11m hello-1202039034 1 1 8m |
1 2 3 |
[root@k8s-master01 jobs]# kubectl get cronjobs.batch NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE cronjob-demo */1 * * * * False 1 9s 4m |
1 2 3 |
$ kubectl delete jobs hello-1201907962 hello-1202039034 ... job "hello-1201907962" deleted job "hello-1202039034" deleted |
一旦 Job 被删除,由 Job 创建的 Pod 也会被删除。注意,所有由名称为 “hello” 的 Cron Job 创建的 Job 会以前缀字符串 “hello-” 进行命名。如果想要删除当前 Namespace 中的所有 Job,可以通过命令 kubectl delete jobs –all 立刻删除它们。
可用参数的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
apiVersion: batch/v1beta1 kind: CronJob metadata: labels: run: hello name: hello namespace: default spec: concurrencyPolicy: Allow # 并发策略 failedJobsHistoryLimit: 1 # 要保留的失败的完成作业数(默认为1) schedule: '*/1 * * * *' # 作业时间表。在此示例中,作业将每分钟运行一次 successfulJobsHistoryLimit: 3 # 要保留的成功完成的作业数(默认为3) terminationGracePeriodSeconds: 30 # job存活时间 默认不设置为永久 jobTemplate: # 作业模板。这类似于工作示例 metadata: creationTimestamp: null spec: template: metadata: creationTimestamp: null labels: run: hello spec: containers: - args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster image: busybox imagePullPolicy: Always name: hello resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: OnFailure schedulerName: default-scheduler securityContext: {} |
参考
DaemonSet和StatefulSet
k8s Job、Cronjob 的使用