一、Docker
Docker 是基于操作系统的沙盒技术,使得用户更简单和完整的去打包自己的应用。docker 底层是基于 Linux 的操作系统级别的虚拟化技术 LXC 实现;LXC 是通过 CGroup 实现了虚拟化资源管理,用来保证应用资源的隔离和应用系统资源的限制;服务器运行了多个服务,这些服务之间是可以互相影响的,其中的一个服务可以查看另外一个服务,这些是我们不愿意看到的,我们更希望同一台机器运行的服务能够完全隔离,互不影响就像运行在多台机器上一样。而 Linux 为我们提供了 NameSpaces 为我们提供了分离进程树、网络接口、资源挂载点的方法,docker 正是利用了 Linux 的 NameSpaces 技术实现了不同容器间资源的隔离;如果我们进入 docker 命令进入容器内部会发现只能看到当前容器的目录而不能看到原系统的目录,而 Linux 的 chroot 又称(change root)具有改变当前系统的根目录功能。docker 正是利用 chroot 的功能而实现了容器内部目录与原系统目录隔离的效果。通过 NameSpaces 文件系统、网络并与宿主机器之间的进程相互隔离,CGroup 实现了 CPU、内存等物理资源的隔离,docker 镜像本质上是一个基于 Linux 底层文件系统的压缩包,虽然 Docker 是最近几年流行起来但是 Docker 的核心技术其实已经有很多年的历史了,Linux Namespaces、CGroup 和 UnionFS 三大技术支撑了目前 Docker 的实现,也是 Docker 能够出现的最重要原因。
二、Docker-compose
Docker-compose 是一个单节点编排技术。如果把 docker 比喻成一堆杂乱无章的集装箱,而 compose 能够对这些集装箱整理归类,作为一个整体启动运行,docker-compose 是以 docker 为核心进行构建的,本身只支持单节点编排,在复杂多变的生产环境是无法投入使用的。docker-compose 适合于需要多个容器相互配合来完成服务的运行,当我们在开发工作中遇到一个项目运行,需要多个服务配合甚至数据库、负载均衡等这个时候我们可以考虑使用 Compose 编排管理,提高部署效率。
三、Kubernetes
工业级的编排平台主要提供服务的部署、弹性和管理;Kubernetes是希腊语,翻译中文是“舵手、飞行员”的意思。k8s,省略中间8个ubernete替换为8,而得来k8s。如果说 docke 把应用打包成镜像,那么 Kubernetes 保证容器化应用简单高效运行。他跟 docker-swarm、moby 项目不同,它不在以 docker 为核心,而是把 docker 作为一个运行时组件,更多是提供应用部署,规划,更新,维护,在复杂多变的生产环境中,这些往往是我们更加需要的。
3.1、Kubernetes 核心功能
1、服务发现和负载均衡:主要通过 Service 资源对象其底层是基于 iptables 实现。
2、服务自动装箱:主要是通过调度 组件 Scheduler 实现,它能够自动给帮助我们把容器调度到某几台机器上自动启动运行。
3、容器存储编排:Kubernetes 有跟 compose 类似的编排 yml 文件,让存储的生命周期和容器的生命周期有一个链接。
4、容器故障恢复:在集群环境中经常会因为系统原因、以及宿主机问题导致容器不可用,Kubernetes 会帮助我们把不可用的容器进行恢复或者转移到正常节点上面去。
5、自动发布和回滚:Kubernetes 能够对我们的应用进行自动的发布和回滚,并且根据不同应用场景提供了不同发布和回滚策略。
6、配置和密钥存储:Kubernetes 提供了 ConfigMap 解决了集群环境中配置文件的存储问题,其底层是基于数据卷实现,原应用不用修改任何代码即可无缝对接。
7、服务水平伸缩:Kubernetes 为了让集群更具有弹性提供了水平伸缩功能,如果线上有某种大流量活动,我们可以直接水平扩展应用部署应用的数量,当活动结束后,再减少应用部署的数量,从而高效应对高并发场景。
8、批量执行以及守护进程任务:Kubernentes 可以对 Job 类型的任务,进行批量的执行,比如数据同步、备份等;如果我们想要集群环境中每个节点都运行一份守护进程进行节点任务执行,我们可以使用 Kubernetes DeamonSet 资源类型进行任务执行。
9、探针:Kubernetes 主要提供了存活和就绪两种探针,支持 http、tcp、socket 或者脚本的形式进行检测服务是否正常,对原有服务架构没有任何侵入性。
3.2、图文演示 Kubernetes 部分特性
- Kubernetes 的调度器 Scheduler 可以把用户提交的容器,根据其规格大小调度到其中的一个节点上。如下动图所示:
- Kubernetes 平台有健康检查的功能,当集群中的某个节点或者应用出现故障时,能够自动转移到健康节点上。如下动图所示:
- Kubernetes 具备 HPA 自动扩容的能力,目前只支持按照 CPU 指标和用户自定义(比如 TPS 或 QPS)达到某个数量级触发自动扩容,当请求高峰过去之后,pod 可以恢复到原来的水平。如下图所示检测到白色节点负载过高,自动把服务复制两份,分发到其它节点运行:
3.3、架构介绍
Kubernetes 基于两层架构设计,主要包含主节点 master 和计算节点 node,master 包含 web UI 界面和 cli 命令行,支持多个 master 高可用部署,master 主要下发命令到 node,node 主要用于容器任务执行。
3.4、master
master 主要包含 APIServer、Scheduler、Controller、Etcd 等组件。
3.4.1、API Server
APIServer 提供了资源的增、删、改、查等操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制,集群内部组件与组件之间不能直接调用,调用过程都要经过 ApiServer,其中 Apiserver 支持高可用配置。
3.4.2、Scheduler
Scheduler 负责资源的调度策略,能够按照预设的策略把 pod 调度到相应的节点上,支持热备。
3.4.3、Controller
Controller 负责维护集群的状态,资源对象的自动化控制中心,比如故障检测、自动扩展、滚动更新、服务帐户和令牌控制器等功能都是由 Kubernetes Controller 完成,支持热备。
3.4.4、etcd
etcd 主要功能保存整个集群的状态;etcd 本身是一个独立与 Kubernetes 集群之外的分布式存储系统。支持高可用配置。
3.5、node
Kubernetes 的业务是在 node上运行,而业务都是以最小单元 pod 进行运行的,而 pod 中可以运行一个或者多个容器,pod 本身在 kubelet 组件上运行,它通过跟 apiserver 进行交互获得 pod 的状态。node 主要包含 Kubelet、kube-proxy、Container Runtime、存储插件、网路插件等。
3.5.1、kubelet
kubelet 主要负责 pod 的创建、启动停止等任务,与 master 节点密切交互完成集群管理和运行的基本功能。
3.5.2、kube-proxy
kube-proxy 主要用于通过 Service 提供集群内部的服务发现和负载均衡。
3.5.3、Container Runtime
Container Runtime 主要负责镜像管理以及 pod 内容器运行环境配置。
3.6、Kubernetes 资源对象
Kubernetes 提供了多种资源对象、配置对象、存储对象、策略对象,可以根据需求选择使用,如下所示:
1、资源对象:Pod、ReplicaSet、ReplicationController、Deployment、StatefulSet、DaemonSet、Job、CronJob、HorizontalPodAutoscaling。
2、配置对象:Node、Namespace、Service、Secret、ConfigMap、Ingress、Label、ThirdPartyResource、 ServiceAccount。
3、存储对象:Volume、Persistent Volume及三方插件(NFS、ceph、gfs)。
4、策略对象:SecurityContext、ResourceQuota、LimitRange。
3.7、Kubernetes 集群平台中 pod 执行过程
用户可以通过命令行提交一个 pod 到 API Server,API Server 把 pod 当前信息存储到 etcd 数据库中,Scheduler 调度器根据 pod 规格资源配置调度某个节点上,通知 API Server 把调度的节点信息和 pod 存储到 etcd 中,API Server 会通知相应节点的 kubelet 执行启动,kubelet 首先调用 Container RunTime 配置容器以及运行环境,然后调度存储插件配置存储,网络插件配置网络,从而完成容器的运行。
3.8、pod
- 最小调度以及资源单位,这里我们思考下为什么 pod 是最小调度单位?这里我们举个例子:我们有个多进程应用(比如 rsyslog 就是多进程应用),其中包含三个进程 p1,p2,p3,这三个进程必须运行在一台机器上,每个进程需要占用 0.5GB 内存,现在我们有三台机器,node1(2G)node2(1G)node3(1G);假设 pod 不是最小调度单位,p1 调度到 node2 上,这完全是有可能的,因为 node2 资源足够 p1 使用,紧接着 p2 也被调度到 node2 上,那么问题来了 p3 呢?当然我们可以通过加锁的方式解决,但是如何加锁呢,都是问题,但是 Kubernetes 通过把 pod 作为最小调度单位从而解决了此问题;
- 包含一个或者多个容器,如果说我们服务之间存在大量 rpc 调用,这时我们可以把应用放在一个 pod 中运行,共享同一个网络环境,直接本地调用,而 pod 和 pod 之间是网络隔离,可以通过 Service 访问;
- 定义容器运行时方式(命令和环境变量);
- 提供给容器共享的运行环境(网络和进程空间)。
3.8.1、pod 结构
- pod 相当于一个容器,pod 有独立的 ip 地址,也有自己的 hostname,利用 namespace 进行资源隔离,相当于一个独立沙箱环境。
- pod 内部封装的是容器,可以封装一个,或者多个容器(通常是一组相关的容器)
3.8.2、pod 网络
- pod 有自己独立的 IP 地址
- pod 内部的容器之间是通过 localhost 进行访问
3.8.3、pod 如何对外提供访问
首先pod 有自己的 IP 和 hostname,但 pod 是虚拟的资源对象 (在计算机中表现为进程),没有对应实体 (物理机,物理网卡) 与之对应,所以是无法直接对外提供服务访问的。因此如果 pod 想对外提供服务,必须绑定物理机端口 (即在物理机上开启端口,让这个端口和 pod 的端口进行映射),这样就可以通过物理机进行数据包的转发。下面以一台 Linux 系统的机器为例子( logstash 是做日志收集用的)。
3.8.4、pod 的负载均衡
很关键的一个问题:一组相关的 pod 副本,如何实现访问负载均衡?就如当请求达到,请求转发给哪个 pod 比较好?一个想法就是用 pod 再部署一个 Nginx。举例:如下图,注意下图右边的 Node 里面有两个是 支付 服务,与订单服务的是不同类型的 pod。如果一个请求订单的服务发来上面那个 Nginx,那这个 pod 可以有 4 条转发路线,可以想到用 hash 呀什么的把不同请求映射到不同的 pod 去转发。但能不能这么做呢?
思考:pod 是一个进程,是有生命周期的,一旦宕机、版本更新都会创建新的 pod( IP 地址会变化,hostname 会变化),此时再使用 Nginx 做负载均衡不太合适,因为它不知道 pod 发生了改变,那请求就不能被接受了。所以服务发生了变化它根本不知道,Nginx 无法发现服务,不能用 Nginx 做负载均衡。那该如何实现呢?使用 Service 资源对象。
3.9、Deployment
- 定义 pod 副本数量、版本等;
- 通过 ReplicaSet 控制 pod 数量(自动重启失败的 pod);
- 按照指定策略控制版本(版本升级、回滚、重新生成);
Deployment 是一个控制器,能够用来控制 pod 数量跟期望数量一致,配置 pod 的发布方式,Deployment 会按照给定策略进行发布 pod,保证在更新过程中不可用数量在限定范围内。看了上面的介绍感觉像是 Deployment 直接控制 pod,其实不然,Deployment 控制 ReplicateSet ReplicateSet 控制 pod 副本的数量,pod 所属于 replicaset,同一个 replicaset 下的 pod 版本都是一样的。
3.10、Volume
- Pod中一个或者多个容器可以访问的目录
- 支持多种存储的抽象 本地存储、分布式存储、云存储
在docker 中 volume 就是对应磁盘或者其它容器中的目录,docker 对它的管理比较松散,没有生命周期管理,而 Kubernetes 中的 volume 的生命周期和 pod 的生命周期相同。相比与 pod 中的容器来说,存储数据可能比容器生命周期更长,并且在容器重新启动后保留存储信息。在 Kubernetes 支持多种类型的卷,而 Pod 可以同时使用各种类型和任意数量的存储卷。
3.11、Service
提供访问多个pod的稳定访问方式 (IP、域名、环境变量)。说到Service不得不介绍kubernetes网络模型和通信方式
- 网络模型:一个完整的 Kubernetes 集群应该包含三层网络,首先第一层是 mater 和 node 节点之间的网络,这个网络需要在部署 kubernetes 集群之前配置完成。第二层网络是 pod 的网络通过 kubenet 或者 cni 插件实现,用于 pod 之间或者内部的通信,集群中的所有 pod 均处在同一个网络平面空间内,可以直接通信。第三层网络是 Service 资源的网络,是一个虚拟网络,用于为 Kubernetes 集群配置 IP 地址,但此地址并不配置于任何主机或者容器的网络接口之上,而是通过 kubeproxy 配置为 iptables 规则,将发往该地址的所有流量调度至后端的 pod 之上。
- 通信方式:同一个 pod 的内部通信;各个 pod 彼此通信;pod 和 service 的通信;集群外部流向 service 的通信。
- 端口介绍:containerPort是一个信息性数据,只是为集群提供一个可以快速了解相关 pod 可以访问端口的途径,而且显式指定容器端口,无论你是否指定都不影响其他节点上的客户端 pod 对其进行访问;port 是服务提供端口,用于 kubernetes 集群内部服务访问;targetPort 是 pod 目标端口,如果不设置使用默认 port 端口,port 和 nodePort 的数据通过这个端口进入到 Pod 内部,Pod 里面的 container 的端口映射到这个端口,提供服务;nodePort 是外部用户访问端口。
3.11.1、Service 资源对象是什么?
- POD IP:pod 的 IP 地址
- NODE IP:物理机的 IP 地址
- cluster IP:虚拟 IP,是由 kubernetes 抽象出的 service 对象,这个 service 对象就是一个 VIP (virtual IP, VIP) 的资源对象
3.11.2、service 如何实现负载均衡
例如现在要负载均衡地访问一组相同的服务副本——订单,这时就要去做一个 service,对外表现出是一个进程或资源对象,有虚拟的 IP (VIP) 和端口。请求会访问 service,然后 service 自己会 负载均衡 地发送给相应服务的 POD,也就是下图中 4 个相同的 pod。
3.11.3、深入 service VIP
- service 和 pod 都是一个进程,都是虚拟的,因此实际上 service 也不能对外网提供服务
- service 和 pod 之间可以直接进行通信,它们的通信属于局域网通信
- 负载策略:把请求交给 service 后,service 使用 iptables,ipvs 来实现数据包的分发
而要对外网提供服务,首先需要和之前一样 在物理机上也绑定一个端口 来接受访问请求,然后把请求转发给 service,service 再把数据包分发给相应的 POD。访问流程如下图所示:
那service 对象是如何和 pod 进行关联的呢?它们之间的关联利用的 还是标签选择器 selector。且service 只能对 一组相同的副本 提供服务,不能跨组提供服务。如果有另一组,需要再创建一个 service。因此不同的业务会有不同的 service。举例:service 和 一组 pod 副本是通过标签选择器进行关联的,相同的副本的标签是一样的。selector:app = x 选择一组订单的服务的 pod,创建一个 service;app = y 选择了一组支付的服务的 pod。通过一个 endpoints 属性存储这组 pod 的 IP 地址,这样就有了映射关系了 (关联起来)。
pod 宕机或发布新版本了,service 是如何发现 pod 已经发生变化的?通过 k8s 中的一个组件 —— kube-proxy 每个 NODE 里都运行着这个服务。它需要做的工作如下图右侧:
service 实现服务的发现:kube-proxy 监控 pod,一旦发现 pod 服务变化,将会把新的 ip 地址更新到 service。注意:endpoints 那些都是存储在 etcd 里的,所以 kube-proxy 更新的存储在 etcd 里的映射关系。
3.12、API 基础知识
通过命令行提交一个pod到时候,其提交的内容是yml,yml是一种特殊资源配置文件,主要包含 apiversion、kind、metadata、spec几部分组成。
apiVersion:用来描述当前操作的资源对象;
kind:是指资源类型,比如 Pod、Department 等;
metadata:是写上当前 pod 的名称,比如 nginx。刚刚介绍的 Deployment,它可能是代表一组的 Pod,它是一组 Pod 的抽象,一组 Pod 就是通过 label selector 来表达的,Service 通过选择一组 pod 统一进行访问。
spec:描述了pod预期达到状态,比如内部需要哪些container运行,需要哪些镜像,暴露什么端口等等信息,需要在这里定义。
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2 # tells deployment to run 2 pods matching the template
template: # create pods using pod definition in this template
metadata:
# unlike pod-nginx.yaml, the name is not included in the meta data as a unique name is
# # generated from the deployment name
labels:
app: nginx
spec:
containers:
- name: nginx
image: docker.hub.com/ops/openresty:1.15.8.2-6
ports:
- containerPort: 80
3.13、操作演示
查看集群当前状态
[root@k8s-master ~]# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 161d v1.14.2
k8s-node1 Ready <none> 161d v1.14.2
k8s-node2 Ready <none> 29d v1.14.2
查看deployments资源
[root@k8s-master src]# kubectl get deployments
No resources found.
执行
[root@k8s-master src]# kubectl apply -f nginx_deploy.yaml
deployment.apps/nginx-deployment created
查看执行状态
[root@k8s-master src]# kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 2/2 2 2 28s
[root@k8s-master src]# kubectl describe deployment nginx-deployment
Name: nginx-deployment
Namespace: default
CreationTimestamp: Sun, 02 Feb 2020 09:55:48 +0800
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision: 1
kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"apps/v1beta1","kind":"Deployment","metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},"spec":{"re...
Selector: app=nginx
Replicas: 2 desired | 2 updated | 2 total | 2 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: docker.hub.com/ops/openresty:1.15.8.2-6
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-66db8ddc49 (2/2 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 58s deployment-controller Scaled up replica set nginx-deployment-66db8ddc49 to 2
修改 nginx_deploy.yaml 文件,把副本数量修改为 4
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 4 # tells deployment to run 2 pods matching the template
template: # create pods using pod definition in this template
metadata:
# unlike pod-nginx.yaml, the name is not included in the meta data as a unique name is
# # generated from the deployment name
labels:
app: nginx
spec:
containers:
- name: nginx
image: docker.hub.com/ops/openresty:1.15.8.2-6
ports:
- containerPort: 80
扩容 nginx deployment,发现副本数量已经从 2 变成 4 个
[root@k8s-master src]# kubectl apply -f nginx_deploy.yaml
deployment.apps/nginx-deployment configured
[root@k8s-master src]# kubectl describe deployment nginx-deployment
Name: nginx-deployment
Namespace: default
CreationTimestamp: Sun, 02 Feb 2020 09:55:48 +0800
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision: 1
kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"apps/v1beta1","kind":"Deployment","metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},"spec":{"re...
Selector: app=nginx
Replicas: 4 desired | 4 updated | 4 total | 4 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: docker.hub.com/ops/openresty:1.15.8.2-6
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Progressing True NewReplicaSetAvailable
Available True MinimumReplicasAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-66db8ddc49 (4/4 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 9m15s deployment-controller Scaled up replica set nginx-deployment-66db8ddc49 to 2
Normal ScalingReplicaSet 8s deployment-controller Scaled up replica set nginx-deployment-66db8ddc49 to 4
如上所述主要演示了 deployment 对象的启动和扩容过程,当然我们也可以执行升级,回退等操作。
参考 https://k8s.io/examples/application/deployment-update.yaml