Docker和K8S架构和实践

1. Kubernetes的由来

Kubernetes的名字来自希腊,意思是“舵手”或“领航员”。简称K8S,是用8代替名字中间的8个字符“ubernete”而成的缩写。所以,我们说K8S也就是说Kubernetes。在行业内,我们更习惯说K8S,而不是Kubernetes。
Google公司在10多年前开始采用的容器化基础架构——borg。随着Docker的大规模应用,Google采用Go语言对Borg框架进行了重写,开发出了Kubernetes。Google 于 2014 年开源了 Kubernetes 项目。
2015年4月,Borg论文《Large-scale cluster management at Google with Borg》伴随Kubernetes的高调宣传被Google首次公开,人们终于有缘得窥其全貌。
在Google内部,Kubernetes的原始代号曾经是Serven of Nine,即星际迷航中友好的“Borg”角色,它标识中的舵轮有七个轮辐就是对该项目代号的致意,如图所示。

Kubernetes v1.0于2015年7月21日发布,紧随其后,Google与Linux基金会合作组建了Cloud Native Computing Foundation(云原生计算基金会,简称为CNCF),并将Kubernetes作为种子技术予以提供。这之后,Kubernetes进入了版本快速迭代期,从此不断地融入着新功能,如Federation、Network Policy API、RBAC、CRD和CSI,等等,并增加了对Windows系统的支持。
那K8S到底是做什么的呢?在说它之前我们少不了要对Docker进行一番概述。

2. 什么是Docker


Docker 公司的前身是 DotCloud,2010年成立,2013年更名 Docker,同年发布了 Docker-compose 组件提供容器的编排工具。2014年 Docker 发布1.0版本,2015年Docker 提供 Docker-machine,支持 windows 平台。
Docker基于Go 语言开发并遵从 Apache2.0 协议开源。Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版)
那到底什么是Docker?
Docker是一个C/S架构的系统,Docker守护进程运行在主机上, 然后通过Socket连接从客户端访问Docker守护进程。Docker守护进程从客户端接受命令,并按照命令,管理运行在主机上的容器
一个Docker是一个运行时的环境,简单理解为进程运行的集装箱。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。Docker容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。

2.1 Docker VS KVM

Docker有着比虚拟机更少的抽象层。Docker利用的是宿主机的内核,VM需要的是Guest OS,分层架构如下:

二者的架构不同:

  • VM在宿主机操作系统的基础上创建虚拟层、虚拟化的操作系统和虚拟化的仓库,然后再安装应用;
  • Container(Docker容器),在宿主机的操作系统上创建Docker引擎,在引擎的基础上再安装应用。

作为一种轻量级的虚拟化方式,Docker在运行应用上跟传统的虚拟机方式相比具有显著优势:

  • Docker 容器很快,启动和停止可以在秒级实现。
  • Docker 容器对系统资源需求很少,一台主机上可以同时运行数千个Docker容器。
  • Docker 通过类似Git的操作来方便用户获取、分发和更新应用镜像。
  • Docker 通过Dockerfile配置文件来支持灵活的自动化创建和部署机制,提高工作效率。
  • Docker 容器除了运行其中的应用之外,基本不消耗额外的系统资源,保证应用性能的同时,尽量减小系统开销。
  • Docker 利用Linux系统上的多种防护机制实现了严格可靠的隔离。从1.3版本开始,Docker引入了安全选项和镜像签名机制,极大地提高了使用Docker的安全性。
    特性 容器 虚拟机
    启动速度 秒级 分钟级
    硬盘使用 一般为MB 一般为GB
    性能 接近原生 弱于原生
    系统支持量 单机支持上千个容器 一般几十个

2.2 Docker 架构

Docker 包括三个基本概念:

  • 镜像(Image):Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
  • 容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
  • 仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。

3. Kubernetes架构

Docker不具备自动扩容、负载均衡以及分布式集群部署的能力。Kubernetes是容器集群的编排系统,是一个开源的平台,可以实现容器集群的自动化部署、自动扩缩容、维护等功能。它是基于容器(通常是Docker)之上的。
K8S是一个编排容器的工具,其实也是管理应用的全生命周期的一个工具,从创建应用,应用的部署,应用提供服务,扩容缩容应用,应用更新,都非常的方便,而且可以做到故障自愈。
我们从整体架构来入手,来看看K8s架构。

3.1 组件

一个K8S集群包含一个master节点和一群node节点

  • Mater节点负责管理和控制

  • Node节点是工作负载节点,里面是具体的容器,容器中部署的是具体的服务。

    3.1.1 Master

    用于控制 Kubernetes 节点的计算机。所有任务分配都来自于此。Mater节点包括API Server、Scheduler、Controller Manager、etcd。

  • API Server:整个集群的对外接口,供客户端和其他组件调用。

  • Scheduler:负责集群内部资源调度

  • Controller Manager:负责管理控制器。

  • etcd:用于保存集群中所有网络配置和对象状态信息。

下面详细介绍下master组件
1、API Server
API Server是整个集群的统一入口,各组件的协调者,以HTTP的API形式对外提供服务,所有对象资源的增删改查和监听操作都交给API Server处理后再提交给etcd进行存储。
2、Scheduler
Scheduler负责集群内部资源调度,根据调度算法为新创建的pod选择一个node节点,Scheduler在整个集群中起到了承上启下的重要功能,承上是指负责接收Controller Manager创建的新的pod,为其安排一个node节点,启下是指当为pod选定node节点后,目标Node上的kubelet服务进程会接管该pod。

简单描述下创建Pod的流程:

  • 通过kubectl发送创建pod的请求,命令会先被apiserver拦截,再把创建的pod存储到etcd的podQueue中
  • Scheduler发起调用请求,命令被apiserver拦截,获取etcd中podQueue.NodeList,使用调度算法(调度算法:预选调度、优选策略)选择一个合适的node节点
  • 把确定了的合适的pod、node存储到etcd中
  • node节点上的Kubelet进程发送请求获取pod、node对应创建资源
  • node发现pod是本node需要创建的,kubelet就开始创建pod

3、Controller Manager
每个资源都对应一个控制器(Kubernets Controller),用来处理集群中常规的后台任务,而Controller Manager是用来负责管理控制器的。
K8S集群有以下控制器:

  • Replication/ReplicationSet Controller:保证RC中定义的副本数量与实际运行的pod数量一致。
  • Node Controller:管理维护Node,定期检查Node节点的健康状态,标识出失效和未失效的Node节点。
  • Namespace Controller:管理维护Namespace,定期清理无效的Namespace,包括Namespace下的API对象,例如pod和service等
  • Service Controller:管理维护Service,提供负载以及服务代理。
  • Endpoints Controller:管理维护Endpoints,即维护关联service和pod的对应关系,其对应关系通过Label来进行关联的
  • Service Account Controller:管理维护Service Account,为每个Namespace创建默认的Service Account,同时为Service Account创建Service Account Secret。
  • Persistent Volume Controller:持久化数据控制器,用来部署有状态服务
  • Deamon Set Controller:让每一个Node节点都运行相同的服务
  • Deployment Controller:无状态服务部署控制器
  • StateFulSet Controller:有状态服务部署控制器
  • Job Controller:管理维护Job,为Job创建一次性任务Pod,保证完成Job指定完成的任务数目。
  • Pod Autoscaler Controller:实现pod的自动伸缩,定时获取监控数据,进行策略匹配,当满足条件时执行pod的伸缩动作。

4、etcd
etcd是一个第三方的服务,提供分布式键值对存储,用于保存网络配置、集群状态等信息。K8S中有两个服务需要用到etcd来协调和存储数据:网络插件flannel和K8S本身状态,其中flannel使用etcd存储网络配置信息,K8S本身使用etcd存储各种对象的状态和元信息配置。

3.1.2 Node

node节点包括Docker、kubelet、kube-proxy、Fluentd、kube-dns(可选)、pod

1、kubelet
kubelet是Mater在Node节点上的代理,每个Node节点都会启动一个kubelet进程,用来处理Mater节点下发到Node节点的任务,管理本机运行容器的生命周期,比如创建容器、Pod挂载数据卷、下载secret、获取容器和节点的状态等工作,kubelet将每个pod转换成一组容器。
kubelet默认监听四个端口:4194、10250、10255、10248

  • 4194端口:kubelet通过该端口可以获取到该节点的环境信息以及node上运行的容器状态等内容,访问 http://localhost:4194 可以看到 cAdvisor 的管理界面,通过 kubelet 的启动参 数 –cadvisor-port 可以指定启动的端口。
  • 10250端口:kubelet API的端口,也就是kubelet server与api server的通讯端口,定期请求apiserver获取自己所应当处理的任务,通过该端口可以访问和获取node资源及状态。
  • 10248端口:健康检查的端口,通过访问该端口可以判断kubelet是否正常工作,可以通过 kubelet 的启动 参数 –healthz-port 和 –healthz-bind-address 来指定监听的地址和端口
  • 10255端口:提供了pod和node的信息,接口以只读形式暴露出去,访问该端口不需要认证和鉴权。

2、kube-proxy
在Node节点上实现Pod网络代理,维护网络规则和四层负载均衡工作,kube-proxy本质上类似于一个反向代理,我们可以把每个节点上运行的kube-proxy看作是service的透明代理兼LB。
kube-proxy监听apiserver中service与endpoints的信息,配置iptables规则,请求通过iptables直接转发给pod。

3、docker
运行容器的引擎,pod内部运行的都是容器,这个容器是由Docker引擎创建的,Docker引擎是node节点的基础服务。
4、pod
pod是最小的部署单元,一个pod由一个或多个容器组成,pod中共享存储和网络,在同一个Docker主机上运行。pod内部可以运行一个或多个容器,一般情况下,为了便于管理,一个pod下只运行一个容器。常用于sidercar服务部署。

3.1.3 Pod

pod:负责执行请求和所分配任务的计算机。由 Kubernetes 主机负责对节点进行控制,被部署在单个节点上的,且包含一个或多个容器的容器组。同一容器集中的所有容器共享同一个 IP 地址、IPC、主机名称及其它资源。容器集会将网络和存储从底层容器中抽象出来。
pod是一个大的容器,由K8S创建,pod内部的是docker容器,由Docker引擎创建。K8S不会直接管理容器,而是管理Pod。pod内部封装了docker容器,同时拥有自己的ip地址,也有用自己的HostName,Pod就像一个物理机一样,实际上Pod就是一个虚拟化的容器(进程),pod中运行的是一个或者多个容器。

pod的作用是管理线上运行的应用程序,在通常情况下,在服务上线部署的时候,pod通常被用来部署一组相关的服务。而一个调用链上的服务就叫做一组相关的服务。但是实际生产上一般是一个Pod对应一个服务,不会在一个Pod上部署太多的服务。
K8S中的pause容器主要为每个业务容器提供以下功能,从而对各个Pod进行了隔离:

  • PID命名空间隔离:pod中不同的应用程序可以看到其他应用程序的进程ID
  • 网络命名空间隔离:Pod中多个容器能够访问同一个IP和端口范围
  • IPC命名空间隔离:Pod中多个容器能够使用System VIPC或POSIX消息队列进行通信
  • UTS命名空间隔离:pod中多个容器共享一个主机名和挂在卷
  • Pod中各个容器可以访问在Pod级别定义的Volumes

一个Pod创建的过程:首先kubelet会先创建一个pod,然后立马会创建一个pause容器,pause容器是默认创建的,然后再创建内部其他的业务容器。

3.1.4复制控制器(Replication controller)

用于控制应在集群某处运行的完全相同的容器集副本数量。

3.1.5 服务(Service)

将工作内容与容器集分离。Kubernetes 服务代理会自动将服务请求分发到正确的容器集——无论这个容器集会移到集群中的哪个位置,甚至可以被替换掉。

3.1.6 Kubelet

运行在节点上的服务,可读取容器清单(container manifest),确保指定的容器启动并运行。

3.1.7 kubectl

Kubernetes 的命令行配置工具。

3.2 控制器实例

kubernetes中建立了很多的controller(控制器),这相当于一个控制机,来管理pod的状态和行为。
Pod分为自主式Pod和控制器管理的Pod。类型:

  • ReplicationController和ReplicaSet(无状态服务RS-Deployment)
  • Deployment 无状态负载
  • DaemonSet 守护进程集(以Node为节点部署)
  • StateFulSet 有状态负载(有状态服务)
  • Job/cronJob 普通任务/计划任务(批处理任务部署)
  • Horizontal Pod Autoscaling 自动扩展(根据利用率平滑扩,可以理解为并不是一个控制器,而是一个控制器的附属品,以其他控制器作为模板)

3.2.1 ReplicationController(RC)

ReplicationController用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的Pod来替代;而如果异常多出来的容器也会自动回收。在新版本的Kubernetes 中建议使用ReplicaSet来取代ReplicationControlle,如果集群的资源不够,会动态的显示可用的pod。
ReplicaSet(RS)
ReplicaSet跟ReplicationController只是名字不一样,并且ReplicaSet支持集合式的selector,通过标签(matchLabels)来管理 Pod。虽然ReplicaSet 可以独立使用,但一般还是建议使用Deployment 来自动管理ReplicaSet,避免不兼容问题(比如ReplicaSet不支持rolling update但Deployment 支持)。
基本的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
apiVersion: apps/v1  #api版本定义
kind: ReplicaSet #定义资源类型为ReplicaSet
metadata: #元数据定义
name: test
namespace: default
spec: # ReplicaSet的规格定义
replicas: 3 #定义副本数量为3个
selector: #标签选择器,定义匹配Pod的标签
matchLabels: # RS通过labels来确定某个Pod是否归该RS管,即RS通过标签来监控Pod
tier: test
template: #Pod的模板定义,与上面Pod的定义一致
metadata: #Pod的元数据定义
name: app-pod #自定义Pod的名称
labels: #定义Pod的标签,需要和上面的标签选择器内匹配规则中定义的标签一致,可以多出其他标签
tier: test
spec: #Pod的规格定义
containers: #容器定义
- name: nginx #容器名称
image: hub.org/library/nginx:v1 #容器镜像
imagePullPolicy: IfNotPresent #拉取镜像的规则
env:
- name: GET_HOSTS_FROM
value: dns
ports: #暴露端口
- name: http #端口名称
containerPort: 80

创建并查看RS:

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
# 创建 RS
[root@k8s-master01 yaml]# kubectl create -f rs.yaml

# 查看创建的 rs
[root@k8s-master01 yaml]# kubectl get rs
NAME DESIRED CURRENT READY AGE
frontend 3 3 3 25m

# 查看rs创建的 3 个 Pod,
[root@k8s-master01 yaml]# kubectl get pod
NAME READY STATUS RESTARTS AGE
frontend-cbhjc 1/1 Running 0 25m
frontend-dffcd 1/1 Running 0 25m
frontend-qmugq 1/1 Running 0 25m

# 删除 Pod后,RS会自动重建 Pod,维持设置的副本数(3),通过 Pod 后的随机值可以看出,Pod是重新创建的
[root@k8s-master01 yaml]# kubectl delete pod --all
pod "frontend-cbhjc" deleted
pod "frontend-dffcd" deleted
pod "frontend-qmugq" deleted
[root@k8s-master01 yaml]# kubectl get pod
NAME READY STATUS RESTARTS AGE
frontend-5lgj9 1/1 Running 0 19s
frontend-gxhrv 1/1 Running 0 19s
frontend-rwhc5 1/1 Running 0 19s

修改pod的labels:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19