写在前面:
K8s是什么?
k8s是一个编排容器的工具,(服务编排工具,集群化管理工具)其实也是管理应用的全生命周期的一个工具,从创建应用,应用的部署,应用提供服务,扩容缩容应用,应用更新,都非常的方便,而且可以做到故障自愈,例如一个服务器损坏,可以自动将这个服务器上的服务调度到另外一个主机上进行运行,无需进行人工干涉。k8s可以更快的更新新版本,打包应用,更新的时候可以做到不用中断服务,服务器故障不用停机,从开发环境到测试环境到生产环境的迁移极其方便,一个配置文件搞定,一次生成image,到处运行。
一, K8S--NAMESPACE
Kubernetes 支持多个虚拟集群,它们底层依赖于同一个物理集群。这些虚拟集群被称为命名空间。在一个Kubernetes集群中可以拥有多个命名空间,它们在逻辑上彼此隔离。 他们可以为您和您的团队提供组织,安全甚至性能方面的帮助!
大多数的Kubernetes中的集群默认会有一个叫default的namespace。实际上,应该是3个:
- default:你的service和app默认被创建于此。
- kube-system:kubernetes系统组件使用。
- kube-public:公共资源使用。但实际上现在并不常用。
这个默认(default)的namespace并没什么特别,但你不能删除它。这很适合刚刚开始使用kubernetes和一些小的产品系统。但不建议应用于大型生产系统。因为,这种复杂系统中,团队会非常容易意外地或者无意识地重写或者中断其他服务service。相反,请创建多个命名空间来把你的服务service分割成更容易管理的块。
接下来,我们看看 namespace的yaml文件,也就是如何创建namespace.
nginx-namespace.yaml:
#nginx-namespace.yaml:
kind: Namespace
apiVersion: v1
metadata:
name: nginx
labels:
name: nginx
创建namespace命令如下,
kubectl apply/create -f nginx-namespace.yaml
有了namespace就可以在其中创建资源了,比如pod, deplyment,service 等,这些都必须指定namespace。不指定的话默认就是 default。
二,K8S--POD
有了namespace之后我们第一个要创建的就是pod,他是k8s 的最小资源单位。也叫容器集
Pod封装了一个或多个应用程序的容器(比如nginx等),被部署在一个或多个节点上。统一容共享IP, IPC,主机名及其他资源。容器集会将网络和存储从底层容器抽象出来,这样就可以轻松移动容器了。
接下来我们看看pod的 yaml文件,也就是如何创建pod。
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: nginx
image: nginx
- name: mysql
image: mysql
可以看到这个pod容器集创建/启动了两个容器,值得注意的是,我们在pod的yaml文件上面 并没有看到端口,ip这样的信息,这里应该提出疑问,那怎么做端口映射,服务转发呢?
这个时候,我们在回过头来 看看上面说的一句话,容器集会将网络和存储从底层容器抽象出来,也就是网络与资源的最小单位pod分割开来。这样无论网络如何波动都不会影响资源(pod)的更新,分片等操作。基于这个理念(其中还涉及replication的概念),k8s 也衍生了灰度部署这种服务上线部署方式。那既然网络与资源存储分离开来了,谁做网络这部分的工作呢?请看下文
三,K8S--SERVICE
Kubernetes中一个应用服务会有一个或多个实例(Pod,Pod可以通过rs进行多复本的建立),每个实例(Pod)的IP地址由网络插件动态随机分配(Pod重启后IP地址会改变)。为屏蔽这些后端实例的动态变化和对多实例的负载均衡,引入了Service这个资源对象。
Service 做服务发现 指定 Deployment 或者特定集合 Pod 的网络层抽象。
我们先看看service 的yaml文件:
nginx-service.yaml:
#nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: nginx-service
namespace: nginx
spec:
type: NodePort
ports:
#配置NodePort,外部流量可访问k8s中的服务
- port: 80
name: nginx-service80
protocol: TCP
# 服务访问端口(外部访问)
targetPort: 80
# pod控制器中定义的端口
nodePort: 80
- port: 81
name: nginx-service81
protocol: TCP
targetPort: 81
nodePort: 81
selector:
app: nginx
3.1根据创建Service的type类型不同,可分成4种模式:
ClusterIP
: 默认方式。根据是否生成ClusterIP又可分为普通Service和Headless Service两类:
- 普通Service:通过为Kubernetes的Service分配一个集群内部可访问的
固定虚拟IP
(Cluster IP),实现集群内的访问。为最常见的方式。 - Headless Service:该服务不会分配Cluster IP,也不通过kube-proxy做反向代理和负载均衡。而是通过DNS提供稳定的络ID来访问,DNS会将headless service的后端直接解析为podIP列表。主要供StatefulSet使用。
-
NodePort
:除了使用Cluster IP之外,还通过将service的port映射到集群内每个节点的相同一个端口,实现通过nodeIP:nodePort从集群外访问服
务。 -
LoadBalancer
:和nodePort类似,不过除了使用一个Cluster IP和nodePort之外,还会向所使用的公有云申请一个负载均衡器(负载均衡器后端映射到各节点的nodePort),实现从集群外通过LB访问服务。 -
ExternalName
:是 Service 的特例。此模式主要面向运行在集群外部的服务,通过它可以将外部服务映射进k8s集群,且具备k8s内服务的一些特征(如具备namespace等属性),来为集群内部提供服务。此模式要求kube-dns的版本为1.7或以上。这种模式和前三种模式(除headless service)最大的不同是重定向依赖的是dns层次,而不是通过kube-proxy。
比如,在service定义中指定externalName的值"my.database.example.com":
3.2 Service中主要涉及三种Port:
* port
这里的port表示service暴露在clusterIP上的端口,clusterIP:Port 是提供给集群内部
访问kubernetes服务的入口。
- targetPort
containerPort,targetPort是pod上的端口,从port和nodePort上到来的数据最终经过kube-proxy流入到后端pod的targetPort上进入容器。 - nodePort
nodeIP:nodePort 是提供给从集群外部访问kubernetes服务的入口。
总的来说,port和nodePort都是service的端口,前者暴露给从集群内访问服务,后者暴露给从集群外访问服务。从这两个端口到来的数据都需要经过反向代理kube-proxy流入后端具体pod的targetPort,从而进入到pod上的容器内。
3.3 使用Service服务还会涉及到几种IP:
- ClusterIP
Pod IP 地址是实际存在于某个网卡(可以是虚拟设备)上的,但clusterIP就不一样了,没有网络设备承载这个地址。它是一个虚拟地址,由kube-proxy使用iptables规则重新定向到其本地端口,再均衡到后端Pod。当kube-proxy发现一个新的service后,它会在本地节点打开一个任意端口,创建相应的iptables规则,重定向服务的clusterIP和port到这个新建的端口,开始接受到达这个服务的连接。 - Pod IP
Pod的IP,每个Pod启动时,会自动创建一个镜像为gcr.io/google_containers/pause的容器,Pod内部其他容器的网络模式使用container模式,并指定为pause容器的ID,即:network_mode: "container:pause容器ID",使得Pod内所有容器共享pause容器的网络,与外部的通信经由此容器代理,pause容器的IP也可以称为Pod IP。 - 节点IP
Node-IP,service对象在Cluster IP range池中分配到的IP只能在内部访问,如果服务作为一个应用程序内部的层次,还是很合适的。如果这个service作为前端服务,准备为集群外的客户提供业务,我们就需要给这个服务提供公共IP了。指定service的spec.type=NodePort,这个类型的service,系统会给它在集群的各个代理节点上分配一个节点级别的端口,能访问到代理节点的客户端都能访问这个端口,从而访问到服务。
四,K8S--DEPLOYMENT
其实,基于namespace,pod,service 已经完全可以启动服务集群了,但是为什么会出现deployemnt呢?
因为在大业务量的情况下,一个pod(也可以说一个资源部署)压力会很大,在微服务的概念下,往往会出现服务分片,扩容等概念。
在早期版本使用Replication Controller对Pod副本数量进行管理,在新的版本中官方推荐使用Deployment来代替RC,Deployment相对RC有这些好处
- Deployment拥有更加灵活强大的升级、回滚功能,并且支持滚动更新
- 使用Deployment升级Pod只需要定义Pod的最终状态,k8s会为你执行必要的操作(RC要自己定义如何操作)
docker-compose 可以简单地通过 docker-compose scale 来扩容,现在用k8s扩容
在k8s中管理 Pod 的称作 Controller,我们可以使用 Deployment 这种 Controller 来为 Pod 进行扩容,当然它还可以滚动升级,回滚,金丝雀等等关于部署的事情。下面我们看看 deployment 的 yaml文件。
nginx-deployment.yaml:
#nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx-deployment
#指定域
namespace: nginx
spec:
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.14.0
name: nginx
volumeMounts:
- name: conf
mountPath: /etc/nginx/nginx.conf
- name: log
mountPath: /var/log/nginx/
- name: html
mountPath: /etc/nginx/html
#标签选择器(集群状态下,可以指定服务部署在特定node节点上,这就是标签的作用)
#nodeSelector:
#type: nginx
#设置污点可以调度到对应服务器
tolerations:
- key: "key"
operator: "Equal"
value: "nginx"
effect: "NoSchedule"
volumes:
- name: conf
hostPath:
path: /usr/local/nginx/conf/nginx.conf
- name: log
hostPath:
path: /usr/local/nginx/logs
type: Directory
- name: html
hostPath:
path: /usr/local/nginx/html
type: Directory
- spec.template: 指定要部署的 Pod
- spec.replicas: 指定要部署的个数
- spec.selector: 定位需要管理的 Pod
- spec.strategy: 可选项为Recreate,RollingUpdate。默认是 RollingUpdate,滚动发布
- spec.strategy
.rollingUpdate.maxUnavailable
是可选配置项,用来指定在升级过程中不可用Pod的最大数量。可以配置绝对值与百分比(5 / 10%)。
该值设置成30%,启动rolling update后旧的ReplicatSet将会立即缩容到期望的Pod数量的70%。新的Pod ready后,随着新的ReplicaSet的扩容,旧的ReplicaSet会进一步缩容,确保在升级的所有时刻可以用的Pod数量至少是期望Pod数量的70%。
假如replicas=100。他的历程可能如下(100旧 - > 70旧+30新 -> 49旧+21新+30新 -> ......-> 100新 )
- spec.strategy
.rollingUpdate.maxSurge
是可选配置项,用来指定可以超过期望的Pod数量的最大个数。可以配置绝对值与百分比(5 / 10%)。
该值设置成30%,启动rolling update后新的ReplicatSet将会立即扩容,新老Pod的总数不能超过期望的Pod数量的130%。旧的Pod被杀掉后,新的ReplicaSet将继续扩容,旧的ReplicaSet会进一步缩容,确保在升级的所有时刻所有的Pod数量和不会超过期望 Pod数量的130%。他的历程可能如下(100旧 - >100旧+30新 -> 70旧+30新+30新 -> ......->100新 )
可以看到deployment基本涵盖了pod的定义,但是他还包含了 replicas,strategy这样的配置,这个就起到了管理pod的作用。
4.1 deployment可以做什么
- 定义一组Pod期望数量,Controller会维持Pod数量与期望数量一致
- 配置Pod的发布方式,controller会按照给定的策略更新Pod,保证更新过程中不可用Pod维持在限定数量范围内
- 如果发布有问题支持回滚
4.2 deployment工作原理
实际开发/部署过程中,你肯定有过这样的操作。kubectl delete pod "pod名称" -n "命名空间"。删除成功之后,会有新的pod生成。你有没有想过为什么会这样,是pod自带的重启功能还是...呢?很显然这不是pod的能力范围,你也看不到这样的配置项,但是仔细想想还是可以想明白的。deployment 的配置项里面不是有个 replicas 副本数量吗,当你删除pod之后,副本数量不就是0了吗,不等于配置的1。也就是说状态发生了变化,与期望的状态并不一样。deployment检测到之后,肯定要有相应的动作喽。接着往下看。。。
- 控制器模型
在Kubernetes架构中,有一个叫做kube-controller-manager的组件。这个组件,是一系列控制器的集合。其中每一个控制器,都以独有的方式负责某种编排功能。而Deployment正是这些控制器中的一种。它们都遵循Kubernetes中一个通用的编排模式,即:控制循环
用一段go语言伪代码,描述这个控制循环
# := 为golang声明新变量的格式规范
for {
实际状态 := 获取集群中对象X的实际状态
期望状态 := 获取集群中对象X的期望状态
if 实际状态 == 期望状态 {
什么都不做
}else{
执行编排动作,将实际状态调整为期望状态
}
}
在具体实现中,实际状态往往来自于Kubernetes集群本身。比如Kubelet通过心跳汇报的容器状态和节点状态,或者监控系统中保存的应用监控数据,或者控制器主动收集的它感兴趣的信息,这些都是常见的实际状态的来源;期望状态一般来自用户提交的YAML文件,这些信息都保存在Etcd中
对于Deployment,它的控制器简单实现如下:
- Deployment Controller从Etcd中获取到所有携带 “app:nginx”标签的Pod,然后统计它们的数量,这就是实际状态
- Deployment对象的replicas的值就是期望状态
- Deployment Controller将两个状态做比较,然后根据比较结果,确定是创建Pod,还是删除已有Pod
- 滚动更新
Deployment滚动更新的实现,依赖的是Kubernetes中的ReplicaSet
Deployment控制器实际操纵的,就是Replicas对象,而不是Pod对象。对于Deployment、ReplicaSet、Pod它们的关系如下图:
ReplicaSet负责通过“控制器模式”,保证系统中Pod的个数永远等于指定的个数。这也正是Deployment只允许容器的restartPolicy=Always的主要原因:只有容器能保证自己始终是running状态的前提下,ReplicaSet调整Pod的个数才有意义。
Deployment同样通过控制器模式,操作ReplicaSet的个数和属性,进而实现“水平扩展/收缩”和“滚动更新”两个编排动作对于“水平扩展/收缩”的实现,Deployment Controller只需要修改replicas的值即可。用户执行这个操作的指令如下:
kubectl scale deployment nginx-deployment --replicas=4
#下面这种写法也支持
#kubectl scale deployment/nginx-deployment --replicas=4
如果集群支持 horizontal pod autoscaling 的话,还可以为Deployment设置自动扩展:
下面自动扩容的命令变化如下:最小10个最大15个副本,当cpu利用率高于80%的时候,会增加副本的数量
kubectl autoscale deployment nginx-deployment --min=10 --max=15 --cpu-percent=80
4.3 deployment演示
- deployment_nginx.yml文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.0
ports:
- containerPort: 80
- 创建deployment
- kubectl create -f deployment_nginx.yml
- kubectl get deployment
- kubectl get rs
- kubectl get pods
- deployment信息
可以看到这个deloyment
kubectl get deployment -o wide
- deployment的升级
针对目前的nginx1.14.0升级成latest的命令,老的下面自动移除了,全部都在新的下面。
scale 进行deployment扩容与压缩
kubectl set image deployment nginx-deployment nginx=nginx:1.13 --record=true
kubectl get deployment
kubectl get deployment -o wide
kubectl get pods
注意:--record=true 起到记录的作用,下面回滚操作查看历史的时候,可以看到change-reason
除了 kubectl set image deployment/nginx-deployment ...我们还可以使用edit命令来编辑修改 deloyment
就像vim编辑器一样 只需要保存即可更新deployment
- deployment查看历史版本
kubectl rollout history deployment nginx-deployment
- deployment 回滚到之前的版本
又变成了nginx 1.14.0
kubectl rollout undo deployment nginx-deployment -n nginx --to-revision=5
到现在,不知道你能不能领悟到 《容器集会将网络和存储从底层容器抽象出来,这样就可以轻松移动容器了》这句话的涵义。即,无论你的deployment(也就是pod)怎么变,service一点都不会变,外部访问的方式不变。看下图。
更多操作见(K8S 进阶 -2(deployment,replicateSet...))