01 系统要求
Kubernetes
系统由一组可执行程序组成,用户可以通过 Kubernetes
在GitHub
的项目网站下载编译好的二进制文件或镜像文件,或者下载源码并自行将其编译为二进制文件。
安装 Kubernetes
对软件和硬件的系统要求下图所示:
Kubernetes
需要容器运行时(Container Runtime Interface, CRI)的支持,目前官方支持的容器运行时包括:Docker、Containerd、CRI-O
和 fraktis
等。
宿主机操作系统以CentOS7
为例,使用 Systemd
系统完成对 Kubernetes
服务的配置。为了便于管理,常见的做法是将 Kubernetes
服务程序配置为 Linux
系统开机自启动的服务。
需要注意的是,CentOS7
默认启动了防火墙服务(firewalld. Service
),而 Kubernetes
的 Master
与工作Node
之间会有大量的网络通信。安全的做法是在防火墙上配置各组件需要相互通信的端口号,具体要配置的端口号下图所示:
其他组件可能还需要开通某些端口号,例如 CNI
网络插件 calico
需要 179
端口号;镜像库需要 5000
端口号等,需要根据系统要求逐个在防火墙服务上配置网络策略。
在安全的网络环境中,可以简单地关闭防火墙服务:
另外,建议在主机上禁用SELinux
(修改文件/etc/sysconfig/selinux
,将 SELINUX=enforcing
修改为 SELINUX=disabled
),让容器可以读取主机文件系统。随着 Kubernetes
对 SELinux
支持的增强,可以逐步启用 SELinux
机制,并通过 Kubernetes
设置容器的安全机制。
02 安装概述
本文不会详细地解说K8S
的安装过程,但是会简单的说明下。
K8S集群一般分为以下两种方式安装。
① 使用 kubeadm 工具快速安装 Kubernetes 集群,流程如下:
- 安装 kubeadm
- 修改 kubeadm 的默认配置
- 下载 Kubernetes 的相关镜像
- 运行 kubeadm init 命令安装 Master 节点
- 将新的 Node 加入集群
- 安装 CNI 网络插件
- 验证 Kubernetes集群是否工作正常
② 以二进制文件方式安装 Kubernetes安全高可用集群,流程如下:
- Master高可用部署架构
- 创建 CA 根证书
- 部署安全的 etcd 高可用集群
- 部署安全的 Kubernetes Masteri 高可用集群
- 部署 Node 的服务
- kube-apiserver 基于 token 的认证机制
03 私有镜像库配置与升级
3.1 私有镜像库配置
在 Kubernetes
集群中,容器应用都是基于镜像启动的,在私有云环境中建议搭建私有镜像库对镜像进行统一管理,在公有云环境中可以直接使用云服务商提供的镜像库。
私有镜像库有两种选择:
Docker
提供的Registry
镜像库 :详细说明请参考官网的说明。Harbor
镜像仓库:详细说明请参考官网的说明或者 Harbor 项目维护者及贡献者编写的《Harbor 权威指南》一书。
此外,Kubernetes
对于创建 Pod
需要使用一个名为“pause
”的镜像,tag
名为 k8s.gcr.io/pause:3.2
”,默认从镜像库k8s.gcr.io
下载,在私有云环境中可以将其上传到私有镜像库,并修改 kubelet
的启动参数-pod-infra-container--imag
e,将其设置为使用镜像库的镜像名称,例如:
3.2 升级
升级方式也分为两种。
① 二进制文件升级
在进行 Kubernetes
的版本升级之前,需要考虑不中断正在运行的业务容器的灰度升级方案。
常见的做法是:
- 先更新
Master
上Kubernetes
服务的版本,再逐个或批量更新集群中的Node
上Kubernetes
服务的版本。 - 更新
Node
的Kubernetes
服务的步骤通常包括:先隔离一个或多个Node
的业务流量,等待这些Node
上运行的Pod
将当前任务全部执行完成后,停掉业务应用(Pod
),再更新这些Node
上的kubelet
和kube-proxy
版本,更新完成后重启业务应用(Pod
),并将业务流量导入新启动的这些Node
上,再隔离剩余的Node
,逐步完成Node
的版本升级,最终完成整个集群的Kubernetes
版本升级。
同时,应该考虑高版本的 Master
对低版本的 Node
的兼容性问题。高版本的 Master
通常可以管理低版本的 Node
,但版本差异不应过大,以免某些功能或 API
版本被弃用后,低版本的 Node
无法运行。
- 通过官网获取最新版本的二进制包
kubernetes.tar.gz
,解压后提取服务的二进制文件。 - 更新
Master
的kube-apiserver
、kube-controller-manager
、kube- scheduler
服务的二进制文件和相关配置(在需要修改时更新)并重启服务。 - 逐个或批量隔离
Node
,等待其上运行的全部容器工作完成后停掉Pod
,更新kubelet
、kube-proxy
。服务文件和相关配置(在需要修改时更新),然后重启这两个服务。
② 使用 kubeadm 进行集群升级
kubeadm
提供了 upgrade
命令用于对 kubeadm
安装的 Kubernetes
集群进行升级。这一功能提供了从 1.10 到 1.11、从 1.11 到 1.12、从 1.12 到 1.13 及从 1.13 到 1.14 升级的能力,此处不再详述。
升级之前需要注意:
- 虽然
kubeadm
的升级不会触及工作负载,但还是要在升级之前做好备份; - 升级过程中可能会因为
Pod
的变化而造成容器重启。
04 CRI(容器运行时接口)详解
归根结底,Kubernetes Node (kubelet)
的主要功能就是启动和停止容器的组件,我们称之为容器运行时(Container Runtime
),其中最知名的就是 Docker
了。为了更具扩展性,Kubernetes
从 1.5 版本开始就加入了容器运行时插件 API
,即 Container Runtime Interface,简称 CRI。
4.1 CRI 概述
每个容器运行时都有特点,因此不少用户希望 Kubernetes
能够支持更多的容器运行时。Kubernetes
从 1.5 版本开始引入了 CRI
接口规范,通过插件接口模式,Kubernetes
无须重新编译就可以使用更多的容器运行时。
CRI
包含 Protocol Buffers
、gRPC API
、运行库支持及开发中的标准规范和工具。Docker
的 CRI
实现在 Kubernetes1.6
中被更新为 Beta
版本,并在 kubelet
启动时默认启动。
可替代的容器运行时支持是 Kubernetes
中的新概念。在 Kubernetes1.3
发布时,rktnetes
项目同时发布,让 rkt
容器引擎成为除 Docker
外的又一选择。
然而,不管是 Docker
还是 rkt
,都用到了 kubelet
的内部接口,同 kubelet
源码纠缠不清。这种程度的集成需要对 kubelet
的内部机制有非常深入的了解,还会给社区带来管理压力,这就给新生代容器运行时造成了难以跨越的集成壁垒。CRI
接口规范尝试用定义清晰的抽象层清除这一壁垒,让开发者能够专注于容器运行时本身。
4.2 CRI 的主要组件
kubelet
使用 gRPC
框架通过 UNIX Socket
与容器运行时(或 CRI
代理)进行通信。在这个过程中kubelet
是客户端,CRI
代理(shim
)是服务端,如图所示:
Protocol Buffers API
包含两个gRPC
服务:ImageService
和 RuntimeService
。
- ImageService: 提供了从仓库中拉取镜像、查看和移除镜像的功能。
- RuntimeService: 负责
Pod
和容器的生命周期管理,以及与容器的交互 (exec/attach/port-forward
) ,rkt
和Docker
这样的容器运行时可以使用一个Socket
同时提供两个服务,在kubelet
中可以用container-runtime-endpoint
和image-service-endpoint
参数设置这个Socket
。
4.3 Pod 和容器的生命周期管理
Pod
由一组应用容器组成,其中包含共有的环境和资源约束,在 CRI
里,这个环境被称为 PodSandbox。
Kubernetes有意为容器运行时留下一些发挥空间,它们可以根据自己的内部实现来解释 PodSandbox。
- 对于
Hypervisor
类的运行时,PodSandbox
会具体化为一个虚拟机。 - 其他例如
Docker
,会是一个Linux
命名空间。在vialpha1 API
中,kubelet
会创建Pod
级别的cgroup
传递给容器运行时,并以此运行所有进程来满足PodSandbox
对Pod
的资源保障。
在启动 Pod
之前,kubelet
调用 RuntimeService
RunPodSandbox
来创建环境。这一过程包括为 Pod
设置网络资源(分配IP
等操作)。PodSandbox
被激活之后,就可以独立地创建、启动、停止和删除不同的容器了。kubelet
会在停止和删除 PodSandbox 之前首先停止和删除其中的容器。
kubelet 的职责在于通过 RPC 管理容器的生命周期,实现容器生命周期的钩子、存活和健康监测,以及执行 Pod 的重启策略等。
RuntimeService
服务包括对 Sandboxi
和 Container
操作的方法,下面的伪代码展示了主要的 RPC
方法:
4.4 面向容器级别的设计思路
众所周知,Kubernetes 的最小调度单元是 Pod,它曾经可能采用的一个 CRI 设计就是复用 Pod 对象,使得容器运行时可以自行实现控制逻辑和状态转换,这样一来,就能极大地简化 API,让 CRI 能够更广泛地适用于多种容器运行时。但是经过深入讨论之后,Kubernetes放弃了这一想法。
首先,kubelet 有很多 Pod 级别的功能和机制(例如 crash-loop backoff 机制),如果交给容器运行时去实现,则会造成很重的负担;然后,Pod 标准还在快速演进。很多新功能(如初始化容器)都是由 kubelet完成管理的,无须交给容器运行时实现。
CRI 选择了在容器级别进行实现,使得容器运行时能够共享这些通用特性,以获得更快的开发速度。这并不意味着设计哲学的改变一 kubelet要负责、保证容器应用的实际状态和声明状态的一致性。
Kubernetes
为用户提供了与Pod
及其中的容器进行交互的功能 (kubectl exec/attach/port-forward
),kubelet目前提供了两种方式来支持这些功能:
- ①调用容器的本地方法;
- ②使用
Node
上的工具(例如nsenter
)及socat
)。
因为多数工具都假设 Pod
用 Linux namespace
做了隔离,因此使用Node
上的工具并不是一种容易移植的方案。在 CRI
中显式定义了这些调用方法,让容器运行时进行具体实现。下面的伪代码显示了 Exec、Attach、PortForward
:这几个调用需要实现的 RuntimeService
方法:
目前还有一个潜在的问题是,kubelet处理所有的请求连接,使其有成为 Node
通信瓶颈的可能。在设计 CRI
时,要让容器运行时能够跳过中间过程。容器运行时可以启动一个单独的流式服务来处理请求(还能对 Pod
的资源使用情况进行记录),并将服务地址返回给 kubelet
。这样 kubelet
就能反馈信息给 API Server
,使之可以直接连接到容器运行时提供的服务,并连接到客户端。
4.5 尝试使用新的 Docker-CRI 来创建容器
要尝试新的 kubelet-CRI-Docker
集成,只需为 kubelet
启动参数加上enable- cri=true
开关来启动 CRI
。这个选项从 Kubernetes1.6
开始已经作为 kubelet
的默认选项了。如果不希望使用 CRI
,则可以设置enable-cri=false
来关闭这个功能。
查看 kubelet
的日志,可以看到启用CRI
和创建 gRPC Server
的日志:
创建一个Deployment
:
查看 Pod
的详细信息,可以看到将会创建沙箱(Sandbox
)的 Event
:
这表明 kubelet
使用了 CRI
接口来创建容器。
4.6 CRI 的进展
目前已经有多款开源 CRI
项目可用于 Kubernetes、Docker、CRI-O、Containerd、frakti
(基于 Hypervisor
的容器运行时),各 CRI
运行时的安装手册可参考官网的说明。