Kubernetes学习整理
1. 概述
Kubernetes 是一个可移植的,可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态系统,建立在Google在大规模运行生产工作负载方面拥有十几年的经验的基础上。
Kubernetes 这个名字源于希腊语,意为“舵手”或“飞行员”, 简称k8s(k和s之间有8个字符)。
2. Kubernetes 的优点
- 服务发现和负载均衡:Kubernetes 可以使用 DNS 名称或自己的 IP 地址公开容器,如果进入容器的流量很大, Kubernetes 可以负载均衡并分配网络流量,从而使部署稳定。
- 存储编排:Kubernetes 允许你自动挂载你选择的存储系统,例如本地存储、公共云提供商等。
- 自动部署和回滚:使用 Kubernetes 描述已部署容器的所需状态,它可以以受控的速率将实际状态 更改为期望状态。
- 自动完成装修计算:Kubernetes 允许指定每个容器所需 CPU 和内存(RAM)。 当容器指定了资源请求时,Kubernetes 可以做出更好的决策来管理容器的资源。
- 自我修复:Kubernetes 重新启动失败的容器、替换容器、杀死不响应用户定义的运行状况检查的容器,并且在准备好服务之前不将其通告给客户端。
- 密钥和配置管理:Kubernetes 允许存储和管理敏感信息,例如密码、OAuth 令牌和 ssh 密钥。可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置,也无需在堆栈配置中暴露密钥。
3. 部署方式演变历史
3.1 传统部署
早期,各个组织机构在物理服务器上运行应用程序。无法为物理服务器中的应用程序定义资源边界,这会导致资源分配问题。 例如,如果在物理服务器上运行多个应用程序,则可能会出现一个应用程序占用大部分资源的情况, 结果可能导致其他应用程序的性能下降。 一种解决方案是在不同的物理服务器上运行每个应用程序,但是由于资源利用不足而无法扩展, 并且维护许多物理服务器的成本很高。
3.2 虚拟化部署
作为解决方案,引入了虚拟化。虚拟化技术允许你在单个物理服务器的 CPU 上运行多个虚拟机(VM)。 虚拟化允许应用程序在 VM 之间隔离,并提供一定程度的安全,因为一个应用程序的信息 不能被另一应用程序随意访问。
虚拟化技术能够更好地利用物理服务器上的资源,并且因为可轻松地添加或更新应用程序而可以实现更好的可伸缩性,降低硬件成本等等。
每个 VM 是一台完整的计算机,在虚拟化硬件之上运行所有组件,包括其自己的操作系统。
3.3 容器部署
容器类似于 VM,但是它们具有被放宽的隔离属性,可以在应用程序之间共享操作系统(OS)。 因此,容器被认为是轻量级的。容器与 VM 类似,具有自己的文件系统、CPU、内存、进程空间等。 由于它们与基础架构分离,因此可以跨云和 OS 发行版本进行移植。
3.3.1 容器的优点
- 敏捷应用程序的创建和部署:与使用 VM 镜像相比,提高了容器镜像创建的简便性和效率。
- 持续开发、集成和部署:通过快速简单的回滚(由于镜像不可变性),支持可靠且频繁的容器镜像构建和部署。
- 关注开发与运维的分离:在构建/发布时而不是在部署时创建应用程序容器镜像, 从而将应用程序与基础架构分离。
- 可观察性:不仅可以显示操作系统级别的信息和指标,还可以显示应用程序的运行状况和其他指标信号。
- 跨开发、测试和生产的环境一致性:在便携式计算机上与在云中相同地运行。
- 跨云和操作系统发行版本的可移植性:可在 Ubuntu、RHEL、CoreOS、本地、 Google Kubernetes Engine 和其他任何地方运行。
- 以应用程序为中心的管理:提高抽象级别,从在虚拟硬件上运行 OS 到使用逻辑资源在 OS 上运行应用程序。
- 松散耦合、分布式、弹性、解放的微服务:应用程序被分解成较小的独立部分, 并且可以动态部署和管理 - 而不是在一台大型单机上整体运行。
- 资源隔离:可预测的应用程序性能。
- 资源利用:高效率和高密度。
4. Kubernetes 组件架构
4.1 Control Plane
控制平面的组件对集群做出全局决策(比如调度),以及检测和响应集群事件,例如,当不满足部署的 replicas 字段时,启动新的 pod)。可以在集群中的任何节点上运行。 然而,为了简单起见,设置脚本通常会在同一个计算机上启动所有控制平面组件, 并且不会在此计算机上运行用户容器。
4.1.1 API server
API server是 Kubernetes 控制面的前端,公开了 Kubernetes API。它的主要实现是 kube-apiserver。 kube-apiserver 设计上考虑了水平伸缩,也就是说,它可通过部署多个实例进行伸缩。可以运行 kube-apiserver 的多个实例,并在这些实例之间平衡流量。
4.1.2 Cloud controller manager
云控制器管理器是指嵌入特定云的控制逻辑的 控制平面组件。 云控制器管理器可以将集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同集群交互的组件分离开来。
4.1.3 Controller manager
用于运行控制器进程。从逻辑上讲,每个控制器都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在一个进程中运行。包括:
- 节点控制器(Node Controller): 负责在节点出现故障时进行通知和响应。
- 任务控制器(Job controller): 监测代表一次性任务的 Job 对象,然后创建 Pods 来运行这些任务直至完成。
- 端点控制器(Endpoints Controller): 填充端点(Endpoints)对象(即加入 Service 与 Pod)
- 服务帐户和令牌控制器(Service Account & Token Controllers): 为新的命名空间创建默认帐户和 API 访问令牌。
4.1.4 etcd
etcd 是兼具一致性和高可用性的键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库。
4.1.5 Scheduler
负责监视新创建的、未指定运行节点(node)的 Pods,选择节点让 Pod 在上面运行。
调度决策考虑的因素包括单个 Pod 和 Pod 集合的资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰和最后时限。
4.2 Node
节点组件在每个节点上运行,维护运行的 Pod 并提供 Kubernetes 运行环境。
4.2.1 Kubelet
一个在集群中每个节点(node)上运行的代理。 它保证容器(containers)都运行在 Pod 中。
kubelet 接收一组通过各类机制提供给它的 PodSpecs,确保这些 PodSpecs 中描述的容器处于运行状态且健康。 kubelet 只管理由 Kubernetes 创建的容器。
kubelet是唯一没有以容器形式运行的Kubernetes组件。
4.2.2 Kube-proxy
kube-proxy 是集群中每个节点上运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。
kube-proxy 维护节点上的网络规则。这些网络规则允许从集群内部或外部的网络会话与 Pod 进行网络通信。
4.3 组件之间的协作流程
# 1.运行部署httpd
kubectl run httpd-app --image=httpd --replicas=2
# 2.查看运行情况
kubectl get deployment
kubectl get pod -o wide
描述:Kubernetes部署了deployment httpd-app,有两个副本Pod,分别运行在k8s-node1和k8s-node2。
- kubectl发送部署请求到API Server。
- API Server通知Controller Manager创建一个deployment资源。
- Scheduler执行调度任务,将两个副本Pod分发到k8s-node1和k8s-node2。
- k8s-node1和k8s-node2上的kubectl在各自的节点上创建并运行Pod。
补充两点:
- 应用的配置和当前状态信息保存在etcd中,执行kubectl get pod时APIServer会从etcd中读取这些数据。
- flannel会为每个Pod都分配IP。因为没有创建service,所以目前kube-proxy还没参与进来。
5. 运行容器化应用
Kubernetes通过各种Controller来管理Pod的生命周期。为了满足不同业务场景,Kubernetes开发了Deployment、ReplicaSet、DaemonSet、StatefuleSet、Job等多种Controller。
5.1 Deployment
5.1.1 运行Deployment
5.1.1.1 运行
该命令运行一个名称为nginx-deployment,容器镜像为nginx:1.7.9,副本数为2的Deployment。
# 运行
kubectl run nginx-deployment --image=nginx:1.7.9 --replicas=2
5.1.1.2 查看Deployment运行情况
输出显示两个副本正常运行。
# 查看Deployment运行情况
kubectl get deployment
kubectl get deployment nginx-deployment
5.1.1.3 查看Deployment的详细信息
重点看最后Events日志信息,这里告诉我们创建了一个Replicaset nginx-deployment-8476d5f8cd,这里也告诉我们Deployment是通过创建Replicaset 来管理Pod的。
# 查看Deployment的详细信息
kubectl describe deployment nginx-deployment
5.1.1.4 查看Replicaset的详细信息
Controlled By表示当前Replicaset 是由Deployment/nginx-deployment创建的,Events里展示了两个副本Pod的创建日志。
# 查看所有的Replicaset和当前创建的Replicaset
kubectl get replicaset
kubectl describe replicaset nginx-deployment-8476d5f8cd
5.1.1.5 查看Pod信息
两个Pod都处于Running状态,Controlled By表示当前副本Pod由ReplicaSet/nginx-deployment-8476d5f8cd创建,Events展示了Pod的启动过程。
# 查看所有的pod和当前创建的其中一个副本pod
kubectl get pod
kubectl describe pod nginx-deployment-8476d5f8cd-9nqds0
5.1.1.6 总结
- 用户通过kubectl创建Deployment
- Deployment创建ReplicaSet
- ReplicaSet创建Pod
注:对象的命名方式为:子对象的名称=父对象的名称+随机字符。
5.1.2 创建资源的方式
5.1.2.1基于kubectl命令
比如kubectl run nginx-deployment --image=nginx:1.7.9--replicas=2
,在命令行中通过参数指定资源的属性。特点:
- 简单、直观、快捷,上手快
- 适合临时测试或实验。
5.1.2.2基于配置文件
通过配置文件和kubectl apply创建。要完成前面同样的工作,可执行命令kubectl apply -f nginx-deployment.yml
。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx-deployment
template:
metadata:
labels:
app: nginx-deployment
spec:
containers:
- name: nginx
image: nginx:1.7.9
- 配置文件描述了What,即应用最终要达到的状态。
- 配置文件提供了创建资源的模板,能够重复部署。
- 可以像管理代码一样管理部署。
- 适合正式的、跨环境的、规模化部署。
- 这种方式要求熟悉配置文件的语法,有一定难度。
5.1.3 Deployment 配置文件简介
以上述nginx的配置文件为例:
- apiVersion是当前配置格式的版本(与当前集群版本相关)。
- kind是要创建的资源类型,这里是Deployment。
- metadata是该资源的元数据,name是必需的元数据项。
- spec部分是该Deployment的规格说明。
- replicas指明副本数量,默认为1。
- selector标签选择器,能够识别一组有共同特征或属性的资源对象。
- template定义Pod的模板,这是配置文件的重要部分。
- metadata定义Pod的元数据,至少要定义一个label。label的key和value可以任意指定。
- spec描述Pod的规格,此部分定义Pod中每一个容器的属性,name和image是必需的。
如果我们要删除这些资源:执行kubectl delete deployment nginx-deployment
或者kubectl delete -f nginx-deployment.yml
5.1.4 伸缩
伸缩是指在线增加或减少Pod的副本数。
Deployment nginx-deployment初始是两个副本,k8snode1和k8snode2上各一个。
- 修改nginx-deployment.yml文件,将
spec: replicas: 2
的值设为5。 - 再次执行
kubectl apply -f nginx-deployment.yml
- 三个新副本被调度到k8snode1和k8snode2上。
- 再次修改nginx-deployment.yml文件,将
spec: replicas: 5
的值设为2。 - 再次执行
kubectl apply -f nginx-deployment.yml
- 可以看到3个副本被删除了,最终保留了2个副本。
出于安全考虑,Kubernetes不会将pod调度到master上,如果希望将k8s-master也当作Node使用,可以执行命令:
kubectl taint node k8smaster node-role.kubernetes.io/master-
;恢复命令:kubectl taint node k8smaster node-role.kubernetes.io/master="":NoSchedule
5.1.5 节点故障转移
- 关闭k8snode2节点。
- 等待一段时间,Kubernetes会检测到k8snode2不可用。
- 将k8snode2上的Pod标记为Terminating,并在k8snode1上创建一个新的Pod,维持总副本Pod数为2。
恢复k8snode2节点后,Terminating的Pod会被删除,不过已经运行的Pod不会被调度回k8snode2。
5.1.6 用label控制Pod的位置
默认配置下,Scheduler会将Pod调度到所有可用的Node。不过有些情况我们希望将Pod部署到指定的Node,比如将有大量磁盘I/O的Pod部署到配置了SSD的Node;或者Pod需要GPU,需要运行在配置了GPU的节点上。Kubernetes是通过label来实现这个功能的。
label是key-value对,各种资源都可以设置label,灵活添加各种自定义属性。
- 执行
kubectl label node k8snode1 disktype=ssd
标注k8s-node1是配置了SSD的节点。删除命令为kubectl label node k8snode1 disktype-
。 - 执行
kubectl get node --show-labels
查看节点的label。
- 修改nginx-deployment.yml文件,增加配置
spec: template: spec: nodeSelector: disktype: ssd
。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx-deployment
template:
metadata:
labels:
app: nginx-deployment
spec:
containers:
- name: nginx
image: nginx:1.7.9
nodeSelector:
disktype: ssd
- 重新部署并查看Pod
5.2 DaemonSet
DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。
DaemonSet 的一些典型用法:
- 在每个节点上运行集群守护进程。
- 在每个节点上运行日志收集守护进程。
- 在每个节点上运行监控守护进程。
一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。 一个稍微复杂的用法是为同一种守护进程部署多个 DaemonSet;每个具有不同的标志, 并且对不同硬件类型具有不同的内存、CPU 要求。
5.2.1 系统自身的DaemonSet
其实Kubernetes自己就在用DaemonSet运行系统组件。执行kubectl get Daemonset --namespace=kube-system
DaemonSet kube-flannel-ds和kube-proxy分别负责在每个节点上运行flannel和kube-proxy组件。执行命令kubectl get pod --namespace=kube-system -o wide
因为flannel和kube-proxy属于系统组件,需要在命令行中通过–namespace=kube-system指定namespace kube-system。若不指定,则只返回默认namespace default中的资源。
可以使用命令kubectl edit daemonset kube-proxy --namespace=kube-system
查看详细的yml配置信息。
5.2.2 运行自定义DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- 执行命令
vim fluentd-elasticsearch.yml
,将上述内容复制进去 - 执行命令
kubectl apply -f fluentd-elasticsearch.yml
部署 - 执行命令
kubectl get pod -o wide --namespace=kube-system
5.3 Job
容器按照持续运行的时间可分为两类:服务类容器和工作类容器。服务类容器通常持续提供服务,需要一直运行,比如HTTP Server、Daemon等。工作类容器则是一次性任务,比如批处理程序,完成后容器就退出。Kubernetes的Deployment、ReplicaSet和DaemonSet都用于管理服务类容器;对于工作类容器使用Job。
5.3.1 运行示例Job
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
template:
spec:
containers:
- name: myjob
image: perl
command: ["echo", "k8s myjob!"]
restartPolicy: Never
backoffLimit: 4
- batch/v1是当前Job的apiVersion。
- 指明当前资源的类型为Job。
- restartPolicy指定什么情况下需要重启容器。对于Job,只能设置为Never或者OnFailure。对于其他controller(比如Deployment),可以设置为Always。通过
kubectl apply -f myjob.yml
启动Job。
通过kubectl logs myjob-wn6mz
可以查看Pod的标准输出
5.3.2 Pod失败的情况
以上是Pod成功执行的情况,如果Pod失败了会怎么样呢?修改myjob.yml,故意引入一个错误。
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
template:
spec:
containers:
- name: myjob
image: perl
command: ["echo1", "k8s myjob!"]
restartPolicy: Never
backoffLimit: 4
- 先删除之前的Job
kubectl delete -f myjob.yml
- 运行新的Job并查看状态
- 可以看到有多个Pod,状态均不正常。通过kubectl describe pod查看某个Pod的启动日志
日志显示没有可执行程序,符合我们的预期。下面解释一个现象:为什么kubectl get pod会看到这么多个失败的Pod?原因是:当第一个Pod启动时,容器失败退出,根据restartPolicy: Never,此失败容器不会被重启,但Job目前COMPLETIONS为0,不满足,所以Job controller会启动新的Pod,直到COMPLETIONS为1。对于我们这个例子,COMPLETIONS永远也到不了1,所以Job controller会一直创建新的Pod。为了终止这个行为,只能删除kubectl delete -f myjob.yml
。
如果不想启动新的Pod,可以将restartPolicy设置为OnFailure。
这里只有一个Pod,不过RESTARTS为3,而且不断增加,说明OnFailure生效,容器失败后会自动重启。
5.3.2 Job的并行性
5.3.2.1 设置并行数
yml文件中增加parallelism配置:
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
parallelism: 2 #并行数
template:
spec:
containers:
- name: myjob
image: perl
command: ["echo", "k8s myjob!"]
restartPolicy: OnFailure
backoffLimit: 4
重新部署后:
Job一共启动了两个Pod,而且AGE相同,可见是并行运行的。
5.3.2.2 设置运行Pod最大数
yml文件中增加parallelism配置:
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
completions: 6 #最大Pod数
parallelism: 2 #并行数
template:
spec:
containers:
- name: myjob
image: perl
command: ["echo", "k8s myjob!"]
restartPolicy: OnFailure
backoffLimit: 4
重新部署后:
如果不指定completions和parallelism,默认值均为1。
5.3.3 定时Job
Linux中有cron程序定时执行任务,Kubernetes的CronJob提供了类似的功能,可以定时执行Job。
apiVersion: batch/v2alpha1
kind: CronJob
metadata:
name: hello
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox:1.28
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
部署后,可以看到每隔一分钟就会启动一个Job。执行kubectl logs可查看某个Job的运行日志。
6. Service
主要是提供负载均衡、服务发现和联网。
6.1 创建Service
Kubernetes Service从逻辑上代表了一组Pod,具体是哪些Pod则是由label来挑选的。Service有自己的IP,而且这个IP是不变的。客户端只需要访问Service的IP,Kubernetes则负责建立和维护Service与Pod的映射关系。无论后端Pod如何变化,对客户端不会有任何影响,因为Service没有变。
- 创建deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd
spec:
replicas: 3
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
run: httpd
spec:
containers:
- name: httpd
image: httpd
ports:
- containerPort: 80
- 启动了三个Pod,运行httpd镜像,label是run: httpd,Service将会用这个label来挑选Pod。Pod分配了各自的IP,这些IP只能被Kubernetes Cluster中的容器和节点访问。
- 创建service并执行
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
selector:
run: httpd
ports:
- protocol: TCP
port: 8080
targetPort: 80
- httpd-svc分配到一个CLUSTER-IP 10.99.229.179。可以通过该IP访问后端的httpd Pod,端口为yml文件中的配置的8080。
- 通过kubectl describe可以查看httpd-svc与Pod的对应关系
Endpoints展示了三个Pod的IP和端口,由此可见Pod的IP是在容器中配置的。
6.2 Cluster IP
Cluster IP是一个虚拟IP,外部网络无法ping通,只有kubernetes集群内部访问使用,是由Kubernetes节点上的iptables规则管理的。
使用iptables-save
命令打印当前节点的iptables规则。
- 如果Cluster内的Pod(源地址来自10.96.71.231/32)要访问httpd-svc,则允许。
- 其他源地址访问httpd-svc,跳转到规则KUBE-SVC-RL3JAE4GN7VOGDGP。
- 1/3的概率跳转到规则KUBE-SEP-C5KB52P4BBJQ35PH。
- 1/3的概率(剩下2/3的一半)跳转到规则KUBE-SEP-HGVKQQZZCF7RV4IT。
- 1/3的概率(最后剩下的概率)跳转到规则KUBE-SEP-XE25WGVXLHEIRVO5。
结论:iptables使用类似轮询的负载均衡策略将访问Service的流量转发到后端Pod,Cluster的每一个节点都配置了相同的iptables规则,这样就确保了整个Cluster都能够通过Service的Cluster IP访问Service
6.3 DNS访问Service
6.4 外网访问Service
除了Cluster内部可以访问Service,很多情况下我们也希望应用的Service能够暴露给Cluster外部。Kubernetes提供了多种类型的Service,默认是ClusterIP。
(1)ClusterIPService通过Cluster内部的IP对外提供服务,只有Cluster内的节点和Pod可访问,这是默认的Service类型,前面实验中的Service都是ClusterIP。
(2)NodePortService通过Cluster节点的静态端口对外提供服务。Cluster外部可以通过:访问Service。
(3)LoadBalancerService利用cloud provider特有的load balancer对外提供服务,cloud provider负责将load balancer的流量导向Service。目前支持的cloud provider有GCP、AWS、Azur等。
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
type: NodePort
selector:
run: httpd
ports:
- protocol: TCP
nodePort: 30000
port: 8080
targetPort: 80
Kubernetes依然会为httpd-svc分配一个ClusterIP,不同的是:
(1)EXTERNAL-IP为nodes,表示可通过Cluster每个节点自身的IP访问Service。
(2)PORT(S)为8080:32312。8080是ClusterIP监听的端口,32312则是节点上监听的端口。Kubernetes会从30000~32767中分配一个可用的端口,每个节点都会监听此端口并将请求转发给Service。可以通过
- nodePort是节点上监听的端口。
- port是ClusterIP上监听的端口。
- targetPort是Pod监听的端口。
最终,Node和ClusterIP在各自端口上接收到的请求都会通过iptables转发到Pod的targetPort。
7. 滚动更新
是一次只更新一小部分副本,成功后再更新更多的副本,最终完成所有副本的更新。最大的好处是零停机,整个更新过程始终有副本在运行,从而保证了业务的连续性。
7.1 更新示例
部署三副本应用,初始镜像为httpd:2.2.31,然后将其更新到httpd:2.2.32。
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd
spec:
replicas: 3
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: httpd
image: httpd:2.2.31
ports:
- containerPort: 80
部署后:
将yml配置文件中的镜像版本改为httpd:2.2.32后重新部署。
- 镜像升级到了2.2.32。
- 创建了新的replicaset httpd-65f5fc8b67。
- 之前的replicaset httpd-578ccb9458中没有pod。
查看httpd的部署细节:
默认每次只更新替换一个Pod(可通过参数maxSurge和maxUnavailable来修改)
7.2 回滚示例
kubectl apply每次更新应用时,Kubernetes都会记录下当前的配置,保存为一个revision(版次),这样就可以回滚到某个特定revision。
默认配置下,Kubernetes只会保留最近的几个revision,可以在Deployment配置文件中通过revisionHistoryLimit属性增加revision数量。
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd
spec:
revisionHistoryLimit: 10
replicas: 3
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: httpd
image: httpd:2.2.31
ports:
- containerPort: 80
- 创建3个配置文件httpd_v1.yml、httpd_v2.yml、httpd_v3.yml。
- 版本依次为2.2.31、2.2.32、2.2.33
- 分别执行
kubectl apply -f httpd_v1.yml --record
部署,–record的作用是将当前命令记录到revision中。 - 通过
kubectl rollout history deployment httpd
查看revision历史记录。
- 回滚则执行命令
kubectl rollout undo deployment httpd --to-revision=1
,1为REVISION的值 - 此时revision的历史记录也会发生变化。
8. Health Check
强大的自愈能力是Kubernetes这类容器编排引擎的一个重要特性。自愈的默认实现方式是自动重启发生故障的容器。还可以用Liveness和Readiness探测机制设置更精细的健康检查,进而实现以下需求:
(1)零停机部署。
(2)避免部署无效的镜像。
(3)更加安全的滚动升级。
8.1 默认检查机制
每个容器启动时都会执行一个进程,此进程由Dockerfile的CMD或ENTRYPOINT指定。如果进程退出时返回码非零,则认为容器发生故障,Kubernetes就会根据restartPolicy重启容器。
编写配置文件并部署:
apiVersion: v1
kind: Pod
metadata:
name: health-check
spec:
restartPolicy: OnFailure
containers:
- name: health-check
image: busybox
args:
- /bin/sh
- -c
- sleep 10; exit 1
Pod的restartPolicy设置为OnFailure,默认为Always。sleep 10; exit 1模拟容器启动10秒后发生故障。过几分钟后查看,当前容器已经重启4次。
在上面的例子中,容器进程返回值非零,Kubernetes则认为容器发生故障,需要重启。
但是有不少情况是容器发生了故障,但进程并不会退出。比如访问Web服务器时显示500内部错误,可能是系统超载,也可能是资源死锁,此时httpd进程并没有异常退出,在这种情况下重启容器可能是最直接、最有效的解决方案,这时就需要用到其他两种检测方式。
8.2 Liveness探测
Liveness探测让用户可以自定义判断容器是否健康的条件。如果探测失败,Kubernetes就会重启容器。
编写配置文件并部署:
apiVersion: v1
kind: Pod
metadata:
name: liveness
spec:
restartPolicy: OnFailure
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
启动进程首先创建文件/tmp/healthy,30秒后删除,在我们的设定中,如果/tmp/healthy文件存在,则认为容器处于正常状态,反之则发生故障。
(1)通过cat命令检查/tmp/healthy文件是否存在。如果命令执行成功,返回值为零,则本次Liveness探测成功;如果命令返回值非零,本次Liveness探测失败。
(2)initialDelaySeconds:容器启动后多久开始Liveness探测。
(3)periodSeconds:每隔多少秒开始Liveness探测,连续执行3次Liveness探测均失败,则会杀掉并重启容器。
8.2 Readiness探测
Liveness探测告诉Kubernetes什么时候通过重启容器实现自愈;
Readiness探测则是告诉Kubernetes什么时候可以将容器加入到Service负载均衡池中,对外提供服务。Readiness探测的配置语法与Liveness探测完全一样。
编写配置文件并部署:这个配置文件只是将前面例子中的liveness替换为了readiness
apiVersion: v1
kind: Pod
metadata:
name: readiness
spec:
restartPolicy: OnFailure
containers:
- name: readiness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
(1)刚被创建时,READY状态为不可用。
(2)第一次进行Readiness探测并成功返回,设置READY为可用。
(3)一段时间后/tmp/healthy被删除,连续3次Readiness探测均失败后,READY被设置为不可用。
通过kubectl describe pod readiness
查看详情
Liveness探测和Readiness探测对比:
(1)Liveness探测和Readiness探测是两种Health Check机制,如果不配置,Kubernetes将对两种探测采取相同的默认行为,即通过判断容器启动进程的返回值是否为零来判断探测是否成功。
(2)两种探测的配置方法完全一样,支持的配置参数也一样。不同之处在于探测失败后的行为:Liveness探测是重启容器;Readiness探测则是将容器设置为不可用,不接收Service转发的请求。
(3)Liveness探测和Readiness探测是独立执行的,二者之间没有依赖,所以可以单独使用,也可以同时使用。用Liveness探测判断容器是否需要重启以实现自愈;用Readiness探测判断容器是否已经准备好对外提供服务。
8.3 Health Check应用
8.3.1 在Scale Up中应用
对于多副本应用,当执行Scale Up操作时,新副本会作为backend被添加到Service的负载均衡中,与已有副本一起处理客户的请求。考虑到应用启动通常都需要一个准备阶段,比如加载缓存数据、连接数据库等,从容器启动到真正能够提供服务是需要一段时间的。可以通过Readiness探测判断容器是否就绪,避免将请求发送到还没有准备好的backend。
8.3.2 在滚动更新中应用
Health Check另一个重要的应用场景是Rolling Update。如果一个正常运行的多副本应用需要进行更新,Kubernetes会启动新副本,然后发生了如下事件:
(1)正常情况下新副本需要10秒钟完成准备工作,在此之前无法响应业务请求。
(2)由于人为配置错误,副本始终无法完成准备工作(比如无法连接后端数据库)。因为新副本本身没有异常退出,默认的Health Check机制会认为容器已经就绪,进而会逐步用新副本替换现有副本,其结果就是:当所有旧副本都被替换后,整个应用将无法处理请求,无法对外提供服务。如果正确配置了Health Check,新副本只有通过了Readiness探测才会被添加到Service;如果没有通过探测,现有副本不会被全部替换,业务仍然正常进行。
9. 数据存储
9.1 Volume
容器中的文件在磁盘上是临时存放的,这就会存在两个问题。
- 当容器崩溃时文件丢失,kubelet 会重新启动容器,但容器会以干净的状态重启。
- 同一 Pod 中运行多个容器并共享文件时。
Volume就是为了解决这两个问题。本质上,Kubernetes Volume是一个目录,支持多种类型,包括emptyDir、hostPath、GCEPersistent Disk、AWS Elastic Block Store、NFS、Ceph等,完整列表可参考https://kubernetes.io/zh/docs/concepts/storage/volumes/。
9.1.1 emptyDir
apiVersion: v1
kind: Pod
metadata:
name: producer-consumer
spec:
containers:
- name: producer
image: busybox
volumeMounts:
- mountPath: /producer_dir
name: shared-volume
args:
- /bin/sh
- -c
- echo "hello world" > /producer_dir/hello; sleep 30000
- name: consumer
image: busybox
volumeMounts:
- mountPath: /producer_dir
name: shared-volume
args:
- /bin/sh
- -c
- cat /producer_dir/hello; sleep 30000
volumes:
- name: shared-volume
emptyDir: {}
1.文件最底部volumes定义了一个emptyDir类型的Volume shared-volume。
2.producer容器将shared-volume mount到/producer_dir目录。
3.producer通过echo将数据写到文件hello里。
4.consumer容器将shared-volume mount到/consumer_dir目录。
5.consumer通过cat从文件hello读数据。
部署后使用kubectl logs poducer-consumer consumer
验证:
容器consumer成功读到了producer写入的数据,两个容器共享emptyDir Volume。
emptyDir是Docker Host文件系统里的目录,使用docker ps
查看运行中的容器。
使用docker inspect 容器id
查看容器的详细配置信息,发现两个容器都mount了同一个目录。
source是shared-volume在主机上的真正路径。删除pod后该路径也会被删除,适合pod中的容器需要临时共享空间的场景。
9.1.2 hostPath
hostPath Volume的作用是将Docker Host文件系统中已经存在的目录mount给Pod的容器。大部分应用都不会使用hostPath Volume,因为会增加了Pod与节点的耦合,限制Pod的使用。
适合需要访问Kubernetes或Docker内部数据(配置文件和二进制库)的应用。比如kube-apiserver和kube-controller-manager,通过kubectl edit --namespace=kube-system pod kube-apiserver-k8smaster
查看kube-apiserver Pod的配置:
可以看到这里定义了三个hostPath:volume k8s、certs和pki,分别对应Host目录/etc/ssl/certs、/etc/pki、/etc/kubernetes/pki。如果Pod被销毁了,hostPath对应的目录还是会被保留,持久性比emptyDir强。但是一旦Host崩溃,hostPath也就无法访问了。
9.1.3 外部Storage Provider
Kubernetes部署在诸如AWS、GCE、Azure等公有云上,可以直接使用云硬盘作为Volume。
要在Pod中使用EBS volume,必须先在AWS中创建,然后通过volume-id引用。
apiVersion: v1
kind: Pod
metadata:
name: test-ebs
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /test-ebs
name: test-volume
volumes:
- name: test-volume
# 此 AWS EBS 卷必须已经存在
awsElasticBlockStore:
volumeID: "<volume-id>"
fsType: ext4
Kubernetes Volume也可以使用主流的分布式存储,比如Ceph、GlusterFS等。
相对于emptyDir和hostPath,这些Volume类型的最大特点就是不依赖Kubernetes。Volume的底层基础设施由独立的存储系统管理,与Kubernetes集群是分离的。数据被持久化后,即使整个Kubernetes崩溃也不会受损。
9.2 PersistentVolume & PersistentVolumeClaim
Volume提供了非常好的数据持久化方案,不过在可管理性上还有不足。拿前面的AWS EBS例子来说,要使用Volume,Pod必须事先知道如下信息:
(1)当前Volume来自AWS EBS。
(2)EBS Volume已经提前创建,并且知道确切的volume-id。
Pod通常是由应用的开发人员维护,而Volume则通常是由存储系统的管理员维护。这样就带来一个管理上的问题:应用开发人员和系统管理员的职责耦合在一起了。如果系统规模较小或者对于开发环境,这样的情况还可以接受,当集群规模变大,特别是对于生成环境,考虑到效率和安全性,这就成了必须要解决的问题。
- PersistentVolume(PV):是外部存储系统中的一块存储空间,由管理员创建和维护。与Volume一样,PV具有持久性,生命周期独立于Pod。
- PersistentVolumeClaim (PVC):是对PV的申请(Claim)。PVC通常由普通用户创建和维护。需要为Pod分配存储资源时,用户可以创建一个PVC,指明存储资源的容量大小和访问模式(比如只读)等信息,Kubernetes会查找并提供满足条件的PV。有了PersistentVolumeClaim,用户只需要告诉Kubernetes需要什么样的存储资源,而不必关心真正的空间从哪里分配、如何访问等底层细节信息。这些StorageProvider的底层信息交给管理员来处理,只有管理员才应该关心创建PersistentVolume的细节信息。
9.2.1 阿里云OSS示例
9.2.1.1创建Secret
apiVersion: v1
kind: Secret
metadata:
name: oss-secret
namespace: default
stringData:
akId: <yourAccessKey ID>
akSecret: <yourAccessKey Secret>
9.2.1.2 创建pv
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-oss
labels:
alicloud-pvname: pv-oss
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
csi:
driver: ossplugin.csi.alibabacloud.com
volumeHandle: pv-oss
nodePublishSecretRef:
name: oss-secret
namespace: default
volumeAttributes:
bucket: "oss"
url: "oss-cn-hangzhou.aliyuncs.com"
otherOpts: "-o max_stat_cache_size=0 -o allow_other"
path: "/"
9.2.1.3 创建pvc
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-oss
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
selector:
matchLabels:
alicloud-pvname: pv-oss
9.2.1.4 创建应用
apiVersion: apps/v1
kind: Deployment
metadata:
name: oss-static
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: pvc-oss
mountPath: "/data"
- name: pvc-oss
mountPath: "/data1"
livenessProbe:
exec:
command:
- sh
- -c
- cd /data
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: pvc-oss
persistentVolumeClaim:
claimName: pvc-oss
9.2.1.5 验证OSS的持久化存储
1.查看部署oss-static应用的Pod和OSS文件:
kubectl get pod
kubectl exec oss-static-7755776c44-q7bn8 ls /data | grep tmpfile
2.执行以下命令,在/data路径下创建文件static。
kubectl exec oss-static-7755776c44-q7bn8 touch /data/tmpfile
3.执行以下命令,查看/data路径下的文件。
kubectl exec oss-static-7755776c44-q7bn8 ls /data | grep tmpfile
4.执行以下命令,删除名称为oss-static-7755776c44-q7bn8的Pod。
kubectl delete pod oss-static-7755776c44-q7bn8
5.同时在另一个窗口中,执行以下命令,查看Pod删除及重建Pod的过程。
kubectl get pod -w -l app=nginx
6.验证删除Pod后,存储空间里创建的文件是否还存在。
kubectl get pod
kubectl exec oss-static-7755776c44-q7bn8 ls /data | grep tmpfile
10 Secret & Configmap
应用启动过程中可能需要一些敏感信息,比如访问数据库的用户名、密码或者密钥。将这些信息直接保存在容器镜像中的配置文件中显然不太安全,为解决这个安全性问题,我们可以使用Secret。Secret会以密文的方式存储数据,避免了直接在配置文件中保存敏感信息。Secret会以Volume的形式被mount到Pod,容器可通过文件的方式使用Secret中的敏感数据;此外,容器也可以环境变量的方式使用这些数据。
10.1 创建Secret
四种方法创建:
- 通过–from-literal:
kubectl create secret generic mysecret --from-literal=username=admin --from-literal=password=123456
,每个–from-literal对应一个信息条目。
- 通过–from-file:每个文件内容对应一个信息条目。
echo -n admin > ./username
echo -n password > ./password
kubectl create secret generic fromfilesecret --from-file=./username --from-file=./password
- 通过–from-env-file:每个文件内容对应一个信息条目。
cat << EOF > env.txt
username=admin
password=123456
EOF
kubectl create secret generic fromenvfilesecret --from-env-file=env.txt
- 通过YAML配置文件:文件中的敏感数据必须是通过base64编码后的结果。
apiVersion: v1
kind: Secret
metadata:
name: yml-secret
namespace: default
data:
username: YWRtaW4=
password: MTIzNDU2
10.2 查看Secret
通过kubectl get secret
查看存在的secret。
通过kubectl describe secret
查看条目的Key,DATA代表数据条目。
通过kubectl edit secret mysecret
可以查看条目的Value,然后通过base64将Value反编码。
10.3 使用Secret
Pod可以通过Volume或者环境变量的方式使用Secret。
10.3.1 Volume方式
1.定义volume foo,来源为secret mysecret。
2.将foo mount到容器路径/etc/foo,可指定读写权限为readOnly。
apiVersion: v1
kind: Pod
metadata:
name: my-secret-pod
spec:
containers:
- image: busybox
name: my-secret-pod
args:
- /bin/sh
- -c
- sleep 10; touch /tmp/healthy; sleep 30000
volumeMounts:
- mountPath: "/etc/foo"
name: foo
readOnly: true
volumes:
- name: foo
secret:
secretName: mysecret
3.创建Pod并在容器中读取Secret。
Kubernetes会在指定的路径/etc/foo下为每条敏感数据创建一个文件,文件名就是数据条目的Key,这里是/etc/foo/username和/etc/foo/password,Value则以明文存放在文件中。文件名也可以自定义,只需要在配置文件的volumes项修改为:
volumes:
- name: foo
secret:
secretName: mysecret
items:
- key: username
path: my-group/my-username
- key: password
path: my-group/my-password
以Volume方式使用的Secret支持动态更新:Secret更新后,容器中的数据也会更新。
将password更新为abcdef,base64编码为YWJjZGVm,重新部署mysecret。
10.3.2 环境变量方式
通过Volume使用Secret,容器必须从文件读取数据,稍显麻烦,Kubernetes还支持通过环境变量使用Secret。
apiVersion: v1
kind: Pod
metadata:
name: my-secret-pod01
spec:
containers:
- image: busybox
name: my-secret-pod01
args:
- /bin/sh
- -c
- sleep 10; touch /tmp/healthy; sleep 30000
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: password
创建pod并读取secret:
虽然环境变量读取Secret很方便,但无法支撑Secret动态更新。
10.4 ConfigMap
Secret可以为Pod提供密码、Token、私钥等敏感数据;对于一些非敏感数据,比如应用的配置信息,则可以用ConfigMap。ConfigMap的创建和使用方式与Secret非常类似,主要的不同是数据以明文的形式存放。与Secret一样,ConfigMap也支持四种创建方式:
- 通过–from-literal:
kubectl create configmap fromliteralconfigmap --from-literal=config1=admin --from-literal=config2=123456
,每个–from-literal对应一个信息条目。 - 通过–from-file:每个文件内容对应一个信息条目。
echo -n admin > ./config1
echo -n password > ./config2
kubectl create configmap fromfileconfigmap --from-file=./config1--from-file=./config2
- 通过–from-env-file:每个文件内容对应一个信息条目。
cat << EOF > configmap.txt
config1=admin
config2=123456
EOF
kubectl create configmap fromenvfileconfigmap --from-env-file=configmap.txt
- 通过YAML配置文件:文件中的数据是明文。
apiVersion: v1
kind: ConfigMap
metadata:
name: yml-configmap
namespace: default
data:
config1: YWRtaW4=
config2: MTIzNDU2
大多数情况下,配置信息都以文件形式提供,所以在创建ConfigMap时通常采用–from-file或YAML方式,读取ConfigMap时通常采用Volume方式。
11 Helm(待更新)
Helm是Kubernetes的包管理器。
12 Kubernetes Dashboard
一个基于Web的Dashboard,用户可以用Kubernetes Dashboard部署容器化的应用、监控应用的状态、执行故障排查任务以及管理Kubernetes的各种资源。
在Kubernetes Dashboard中可以查看集群中应用的运行状态,也能够创建和修改各种Kubernetes资源,比如Deployment、Job、DaemonSet等。用户可以ScaleUp/Down Deployment、执行Rolling Update、重启某个Pod或者通过向导部署新的应用。Dashboard能显示集群中各种资源的状态以及日志信息。
12.1 安装
1.下载资源文件
wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc7/aio/deploy/recommended.yaml
2.使用vim recommended.yaml
修改service
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
type: NodeType #增加
ports:
- port: 443
targetPort: 8443
selector:
k8s-app: kubernetes-dashboard
3.部署kubectl apply -f recommended.yaml
4.使用浏览器访问:nodeIp:port
12.2 配置登录权限
Dashboard支持Kubeconfig和Token两种认证方式,通过配置文件dashboard-admin.yaml为Dashboard默认用户赋予admin权限。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard-minimal
namespace: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
获取用户token:
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep dashboard-admin | awk '{print $1}')
在登录页面选择token登录并输入token: