kubernetes的原理和使用(详细)
一、应用程序的部署演变
物理机部署:早期一般将应用程序直接部署到物理机上,部署方式简单,但是不能为应用程序定义资源和使用边界,很难合理的分配计算资源,应用程序之间会产生影响。
虚拟机部署:在一台物理机上运行多个虚拟机,每个虚拟机都是一个独立的环境,这会导致额外的操作系统开销。
容器化部署:容器化部署与虚拟机类似,不同点是容器之间共享操作系统,每个容器之间拥有自己的文件系统、CPU、内存、进程空间,应用程序需要的资源都被容器包装,并且和底层基础架构解耦,可以实现跨服务器跨操作系统进行部署。
二、容器和容器化部署
容器通俗的意思就是用来装东西的家伙,比如“瓶子”、“箱子”、“水杯”等等。K8S中提到容器也有类似的作用,是用来 “装”我们需要部署的应用程序,由于我们的应用程序需要运行时环境,因此它内部装了运行时环境、配置文件、程序代码。
早期的部署我们可以直接把程序部署到物理机或者虚拟机中,这些机器其实也可以当作一个容器,它“装”了操作系统、运行时环境、部署程序等等,这样的部署方式有很多缺点,比如运行时环境难以维护、资源利用率低、不利于扩展、资源不隔离等等。
随着技术的发展,大佬们搞出了这样的一种技术:在同一个操作系统中,采用虚拟化方式可以隔离出多个小型的操作系统,这些操作系统底层共享物理操作系统的内核。有了这种技术,我们就可以根据程序的需要在这个小型的操作系统中安装自己的运行时环境,让程序在这个小型的虚拟操作系统中运行。这个虚拟操作系统从内部看五脏俱全,包括CPU、内存、磁盘、网络等等,只不过这些东西都是虚拟的,是物理操作系统虚拟化技术生成的,这原理跟在虚拟机软件中安装虚拟机是类似的。
以下是虚拟机和容器的区别:
-
虚拟化层次:虚拟机虚拟化的是整个物理硬件,包括操作系统,而容器虚拟化的是操作系统。这意味着虚拟机运行自己的完整操作系统,而容器则共享宿主机的操作系统内核。
-
资源占用:虚拟机因为需要运行完整的操作系统,资源占用较多,通常以GB为单位。而容器只是一个进程,只需要将应用以及相关的组件打包,比较轻量级,常以MB为单位。
-
启动速度:由于虚拟机需要先启动虚拟机的操作系统,再启动应用,一般启动速度较慢,通常需要分钟级别。而容器只是宿主机上的一个程序,一般启动速度非常快,通常是秒级。
-
隔离性:虚拟机提供系统级别的隔离,每个虚拟机都是独立的运行环境。容器虽然也提供文件系统、进程空间和网络接口的隔离,但由于共享内核,理论上存在一定的安全风险。
-
可移植性:容器具有更好的可移植性,可以在不同的操作系统和云平台上运行,这使得容器的部署和迁移非常快速和方便。
-
使用场景:虚拟机适合需要完全隔离环境的场景,如运行不同的操作系统或需要完全独立的测试环境。容器则适合微服务架构、持续集成/持续部署以及需要快速迭代和扩展的应用。
容器化部署是指将应用程序和所需要的运行时环境直接打包到容器中,以容器为单位快速部署、具有可移植性、一致性等特点。是替代虚拟机、操作系统直接部署程序的最佳方案,具有如下优势:
- 灵活性:容器可以在不同的操作系统和云平台上运行,提供了更大的灵活性和可移植性。
- 高效性:容器可以快速启动和停止,减少了资源的浪费,提高了应用程序的性能和效率。
- 可扩展性:容器化部署可以根据需求动态地扩展应用程序的实例数量,以适应不同的负载情况。
- 隔离性:容器之间相互隔离,一个容器的故障不会影响其他容器的正常运行,提高了应用程序的可靠性和安全性。
- 管理性:容器化部署可以通过容器编排工具进行集中管理和监控,简化了应用程序的部署和维护过程。
三、容器运行时
容器运行时是负责管理和执行容器的核心组件,它实现了对容器生命周期的控制,包括创建、启动、停止、删除以及资源隔离、网络配置、存储挂载等功能。
以下是目前主流的容器运行时。
- docker Engine:Docker Engine 是 Docker 容器化解决方案的核心组件,它是一个轻量级的容器运行时环境。它包括 Docker 守护进程(dockerd)、客户端工具(docker)和 REST API。Docker 守护进程负责管理容器的生命周期,包括创建、运行、销毁等操作。Docker 客户端工具通过与守护进程通信,与容器进行交互并执行各种操作。REST API 则提供了与 Docker 进行交互的标准接口,使得开发人员可以通过编程语言编写自己的工具对 Docker 进行操作。
- containerd:containerd是Docker公司的开源项目之一,于2017年捐赠给了CNCF。它提供了一个稳定、可移植的基础架构来管理容器的生命周期,包括镜像管理、容器执行和存储管理等功能。与docker Engine相比,它更加轻量级,启动和运行速度更快。
- cri-o:它是一个开源项目,专门作为k8s的CRI接口的一个实现,致力于提供一个轻量级、高效且安全的方式来运行容器。containerd是一个通用型的容器运行时,提供了更广泛的插件支持和更丰富的功能集,而cri-o是一个专用于k8s的运行时,适用于追求极致轻量化,并且完全依赖Kubernetes进行容器编排的场景。
前面介绍了3种容器运行时,如何选择使用哪一种得取决于项目的需求,以下是三者区别:
容器运行时 | 特点 |
docker Engine | 是Doker容器化部署方案的核心组件。 |
containerd | 独立于Docker的通用容器运行时,提供了更广泛的插件支持和更丰富的功能集。包括各种镜像仓库、网络和存储解决方案。 |
cri-o | 专注于k8s集成,通过精简设计来提高效率和安全性。与containerd相比,占用系统资源更少。 |
四、容器镜像
镜像是容器的模板,容器运行需要借助镜像来装载环境。镜像描述了容器所需的运行时环境,我们以Docker镜像为例来了解镜像到底是什么。
Docker镜像实际上是由一层一层的文件系统构成,这种层级的文件系统称为UnionFS。UnionFS文件系统是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。
Docker镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的镜像。一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统加载起来,这样最终的文件系统会包含所有的底层文件和目录。
UnionFS主要包含bootloader和kernel,bootloader主要是引导加载kernel,Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的linux/unix系统是一样的,包含boot加载器内核。当boot加载完之后整个内核就都在内存中了,此时内存的使用权已经由bootfs交给内核了,此时系统也会卸载bootfs。对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就行,因为底层直接用host和kernel,自己只需要提供rootfs就行。由此可见对于不同的Linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以公用bootfs。
在下载镜像的过程中我们可以看到Docker的镜像好像是在一层一层的下载,这些层其实都是文件系统,使用分层思想主要是为了实现文件系统的共享,比如多个镜像都从相同的base镜像构建而来,那么宿主机只需要在磁盘上保存一份base镜像,同时内存中也需要加载一份base镜像,就可以为所有上层文件系统提供服务了。而且镜像的每一层都可以被共享。
Docker 支持通过扩展现有镜像,创建新的镜像。实际上,Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层,其构建过程 如下:
当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下都叫“镜像层”,可写层可以改动,镜像层则为只读的,其容器的结构如下图:
镜像本身是不可描述的,Docker提供了镜像文件Dockerfile机制,Dockerfile是一个文本文件,内容包含了一条条构建镜像所需的指令和说明, 它可以用来构建镜像,我们还可以通过它了解镜像的信息和功能。因此镜像的产生有仓库拉取、commit、dockerfile构建等几种方式,如图所示:
五、容器化部署带来的问题及解决方案
如果一个容器故障了,则容器内的程序就无法提供服务了,我们需要另一个容器立刻去替代这个故障的容器。当访问量变大的时候,一个容器就服务满足并发要求,需要横向扩展容器的数量。解决前面的这些问题需要在容器本身进行管理,这称为容器编排。于是一些容器编排软件就产生了,常见的容器编排软件有Swarm、Mesos、Kubernetes。
六、Kubernetes介绍
Kubernetes是谷歌公司一款开源的容器编排管理工具,它的本质是一组服务器集群管理工具,能够在集群的每个节点上运行特定的程序,它的目的是实现资源的管理自动化,主要提供了自我修复,弹性伸缩、服务发现、负载均衡、版本回退、存储编排等功能。
- 自我修复:一个容器崩溃,会立马启动另一个新的容器来替代。
- 弹性伸缩:可以根据并发需要,自动对集群中的运行容器数量进行动态扩展。
- 服务发现:主要用于微服务部署,可以通过自动发现的形式找到它所依赖的另一个服务。
- 负载均衡:如果一个服务启动了多个容器,能够自动实现请求的负载均衡。
- 版本回退:如果发现新发布的版本有问题,可以立即回退到原来的版本。
- 存储编排:可以根据容器自身的需求自动创建存储卷。
Kubernetes集群主要由控制节点master、工作节点node构成,每个节点上都会安装不同的Kubernetes组件。控制节点主要对节点进行管理和集群决策,包含ApiServer、Scheduler、ControllerManager、Etcd组件。nodel节点主要负责提供容器的运行时环境,包含Kubelet、KubeProxy等。
- ApiServer组件:提供了资源操作的唯一入口(默认端口6443),接收用户输入的命令,提供认证授权、API注册和发现机制。
- Scheduler组件:负责集群资源调度,按照预定的调度策略将POD调度到相应的node节点上。
- ControllerManager组件:负责维护集群的状态,比如部署、故障检测、自动扩展、滚动更新等。这些控制器共同构成了Kubernetes集群管理的核心部分,确保集群资源能够按需创建、更新、调度和销毁,以维持集群整体的状态稳定性和可靠性。Kubernetes中的控制器类型主要包括以下几种:
- Deployment:用于部署长期运行的、无状态的应用。它支持ReplicaSet的所有功能,包括创建ReplicaSet和Pod、滚动升级和回滚应用、平滑地扩容和缩容、暂停和继续Deployment等。Deployment是现代Kubernetes应用中最常用的控制器类型之一,提供滚动更新、滚动回滚、暂停与恢复等功能,使得应用的升级更为平滑和可控。
- StatefulSet:适合有状态的服务部署,用于管理有序的、持久化的、具有唯一标识符和稳定的网络标识符的Pod集合。适用于需要存储卷持久化、有序启动和停止以及固定网络标识的有状态应用。
- DaemonSet:确保在每个(或满足特定条件的)Node上仅运行一个Pod副本。通常用于运行那些需要在每个节点上都存在实例的系统守护进程或者agent,例如集群存储daemon、日志收集daemon、监控daemon等。
- Job:用于执行一次性任务到完成的任务控制器,比如批处理作业。当其关联的Pod成功执行到完成时,Job认为工作已经完成。
- CronJob:类似于Linux的cron定时任务,它会按照预定的时间表定期启动Job。CronJob控制器可以自动化周期性任务的执行。
- Etcd组件:主要负责存储集群资源对象的信息。
- dns组件:自定义DNS服务,负责为整个集群提供DNS服务,从而实现服务直接的访问。
- kubelet组件:运行在每个node节点上的代理组件,它监视每个node上的pod。负责向master汇报node节点的健康状态,接受指令并在pod中创建容器,准备pod所需的数据卷,返回pod的运行状态,在node节点执行容器健康检查。
- KubeProxy组件:运行在每个节点上,监听apiserver中服务对象的变化,再通过iptables或者ipvs实现网络的转发。主要负责集群内部的网络通信。
七、以部署Nginx服务为例来说明组件的调用关系
1、首先要明确,一旦kubernetes环境启动之后,master和node都会将自身的信息存储到etcd数据库。
2、一个nginx服务的安装请求会首先被发送到master节点的apiServer组件。
3、 apiServer组件会调用scheduler组件来决定到底应该把这个服务安装到哪个node节点上。在此时,它会从etcd中读取各个node节点的信息,然后按照一定的算法进行选择,并将结果告知aplServer。
4、 apiServer调用controller-manager去调度Node节点安装nginx服务。
5、 kubelet接收到指令后,会通知docker,然后由docker来启动一个nginx的pod。pod是kubernetes的最小操作单元,容器必须跑在pod中。
6、至此一个nginx服务就运行了,如果需要访问nginx,就需要通过kube-proxy来对pod产生访问的代理,外界用户就可以访问集群中的nginx服务了。
八、Kubernetes集群搭建
kubernetes集群一般分为一主多从、多主多从两种方式,前者搭建简单,有单机故障风险,适合测试环境。后者搭建麻烦,但安全性高,适合生产环境。安装方式有kubeadm、minikube、二进制包三种。此处只简单介绍搭建集群的步骤,详细的搭建过程请参考从零开始搭建高可用的k8s集群环境。
安装步骤:
1、环境准备:准备2台以上Linux服务器,为每台服务器配置固定的内网ip,保证集群机器可以互通,选择一台作为master节点,其他服务器为node节点。进行个性化配置,比如设置固定ip、自定义主机名称、主机名解析、防火墙配置、同步服务器时间日期、禁用Selinux、禁用swap分区、配置网桥过滤和地址转发功能、配置ipvs功能。
2、安装docker:在每台服务器上安装docker,更改镜像源为国内镜像源,设置为开机自启动。添加kubernetes阿里云yum软件源,方便后续组件快速下载。
3、安装kubernetes组件:在每台服务器上配置kubernetes镜像源为国内镜像源,安装kubeadm、kubelet、kubectl组件。配置kube_proxy_model为ipvs模式、kubelet_cgroup_args为“--cgroup-driver=systemd”,设置kubelet开机自启动。
4、集群初始化:在master节点服务器上运行kubeadm init命令初始化kubeadm。初始化过程中可以携带一些参数,这些参数按需配置。初始化命令运行完成后会生成一些脚本,请根据提示运行这些脚本。
5、将node节点加入集群:加入命令在步骤4的屏幕输出中,复制命令去node节点机器上运行,就会将该节点加入master节点控制的集群中。
6、安装CNI网络插件:kubernets支持多种网络插件,比如flannel、calico、canal等,只需要在master节点运行安装网络插件的命令即可,安装完成后节点的状态将会由NotReady变为Ready。
7、若使用二进制方式安装k8s集群,则按照创建多台虚拟机、初始化操作系统、为etcd和apiserver自签证书、部署etcd集群、部署master组件的顺序完成安装。
8、部署docker应用:经过前几个步骤,集群环境已经搭建完成,只需要部署应用,暴露端口即可完成应用程序的部署。
九、Kubernetes资源管理
在kubernetes中,所有的内容都被抽象为资源,用户需要通过操作资源来管理bubernetes。用户可以在集群中部署服务,所谓服务就是在集群中运行一个个的容器,并在容器中运行程序。它的最小管理单元叫POD,容器只能放到POD中,Kubernetes通过POD控制器来管理POD。POD中运行的服务想要被外界访问就需要Service资源模块来实现,如果程序的数据需要持久化就需要kubernets的Volume存储系统来完成。 因此,kubernetes包含了pod、pod控制器、Service、Volume存储卷等概念。
1、资源管理方式:kubernetes提供了命令式对象管理、命令式对象配置、声明式对象配置等3种方式去操作资源,kubectl是kubernets集群的命令行工具,通过它能够对集群本身进行管理,并且可以在集群上进行容器化应用的安装部署。命令式对象配置和声明式对象配置方式的资源信息都包含在yaml配置文件中。
命令式对象管理举例:kubectl run nginx-pod --images=nginx:1.17.1 --port=80
命令式对象配置举例:kubectl create/patch -f nginx-pod.yaml
声明式对象配置举例:kubectl apply -f nginx-pod.yaml
2、NameSpace:它是kubernetes中的一种资源,它的主要作用是用来实现多套环境的资源隔离或多租户资源隔离。默认情况下,k8s集群的所有POD都是可以相互访问的,我们可以将POD划分到不同的Namespace中,形成逻辑上的组,不同组的POD无法进行访问,还可以将不同的namespace交给不同的用户进行管理。k8s有几个默认的命名空间default、kube-node-lease、kube-public、kube-system。
3、POD:它是kubernetes的一种资源,可以理解为对容器的一种封装,一个pod中可以存在多个容器,pod存在于NameSpace命名空间下,k8s的组件也是运行在pod中的。
4、Label:它的作用是在资源上添加标识,用来选择和区分资源。一般以键值对的形式附加到对象上,同一资源对象可以定义多个不同的标签,通过Label Selector选择器来筛选对象。
5、Deployment控制器:它是控制pod的控制器资源,它会控制pod的重启、销毁、创建,因为pod控制器的pod资源管理机制,要想删除一个pod时无法直接删除pod,因为控制器检测到一个pod被销毁后回重新创建一个pod来代替,所以只有通过删除pod控制器来连带删除pod。在pod控制器创建pod时可以通过指定pod的数量来确保同时有多个pod同时提供服务。
6、Service:可以看作是一组同类POD对外的访问接口,借助Service可以方便地实现服务发现和负载均衡。虽然每个POD都会分配一个单独的podIP,但是podIP会随着pod的重建而变化,而且podIP仅仅是集群内可见的,外部无法访问,因此Service提供了POD对外提供访问的统一接口。
十、kubectl命令行工具的使用
kubectl是k8s集群的命令行工具,它能管理集群本身,也能管理容器化应用。命令语法 “kubectl [command] [type] [name] [flags]” ,command指定要执行的操作,type指定要操作的资源的类型,name指定要操作的资源的名称,flage指定可选参数。 通过“kubectl --help ”命令可以查询命令文档,例如:
kubectl get pod pod1 //获取一个名为pod1的pod信息 kubectl get node //获取所有节点 kubectl get node node1 //获取一个名为node1的node信息
十一、k8s中的yaml资源文件与pod的结构定义
k8s集群中对资源管理和资源对象编排部署都可以通过yaml文件来解决,也就是说可以把资源对象编辑到yaml文件中,我们称这种文件为资源清单文件,通过kubectl命令直接使用资源清单文件就可以实现大量资源对象的编排部署。yaml配置应该由上而下逐层学习,资源属性较多,我们可以通过命令 kubectl explain pod命令来查阅这些属性说明文档,要求能够读懂和配置这些属性。如果要查阅二级、多级属性只需要使用 “.”加属性名来查阅,比如kubectl explain pod.metadata 。
yaml文件内容包含控制器定义和被控制对象两大部分,一般来说,我们很少手写YAML文件,因为这里面涉及到了很多内容,我们一般都会借助工具来创建:
a.使用 kubectl create deployment web --image=nginx -o yaml --dry-run > hello.yaml 命令方式输出yaml文件 。
b.使用 kubectl get deploy nginx -o=yaml --export > nginx.yaml 命令导出yaml文件。
以下是pod的yaml文件的内容举例:
apiVersion: v1 #必选,版本号,例如v1,版本号必须可以用 kubectl api-versions 查询到 .
kind: Pod #必选,Pod
metadata: #必选,元数据
name: string #必选,Pod名称
namespace: string #必选,Pod所属的命名空间,默认为"default"
labels: #自定义标签
- name: string #自定义标签名字
annotations: #自定义注释列表
- name: string
spec: #必选,Pod中容器的详细定义
containers: #必选,Pod中容器列表
- name: string #必选,容器名称,需符合RFC 1035规范
image: string #必选,容器的镜像名称
imagePullPolicy: [ Always|Never|IfNotPresent ] #获取镜像的策略 Alawys表示下载镜像 IfnotPresent表示优先使用本地镜像,否则下载镜像,Nerver表示仅使用本地镜像
command: [string] #容器的启动命令列表,如不指定,使用打包时使用的启动命令
args: [string] #容器的启动命令参数列表
workingDir: string #容器的工作目录
volumeMounts: #挂载到容器内部的存储卷配置
- name: string #引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
mountPath: string #存储卷在容器内mount的绝对路径,应少于512字符
readOnly: boolean #是否为只读模式
ports: #需要暴露的端口库号列表
- name: string #端口的名称
containerPort: int #容器需要监听的端口号
hostPort: int #容器所在主机需要监听的端口号,默认与Container相同
protocol: string #端口协议,支持TCP和UDP,默认TCP
env: #容器运行前需设置的环境变量列表
- name: string #环境变量名称
value: string #环境变量的值
resources: #资源限制和请求的设置
limits: #资源限制的设置
cpu: string #Cpu的限制,单位为core数,将用于docker run --cpu-shares参数
memory: string #内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
requests: #资源请求的设置
cpu: string #Cpu请求,容器启动的初始可用数量
memory: string #内存请求,容器启动的初始可用数量
livenessProbe: #对Pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器,检查方法有exec、httpGet和tcpSocket,对一个容器只需设置其中一种方法即可
exec: #对Pod容器内检查方式设置为exec方式
command: [string] #exec方式需要制定的命令或脚本
httpGet: #对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port
path: string
port: number
host: string
scheme: string
HttpHeaders:
- name: string
value: string
tcpSocket: #对Pod内个容器健康检查方式设置为tcpSocket方式
port: number
initialDelaySeconds: 0 #容器启动完成后首次探测的时间,单位为秒
timeoutSeconds: 0 #对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
periodSeconds: 0 #对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
restartPolicy: [Always | Never | OnFailure] #Pod的重启策略,Always表示一旦不管以何种方式终止运行,kubelet都将重启,OnFailure表示只有Pod以非0退出码退出才重启,Nerver表示不再重启该Pod
nodeSelector: obeject #设置NodeSelector表示将该Pod调度到包含这个label的node上,以key:value的格式指定
imagePullSecrets: #Pull镜像时使用的secret名称,以key:secretkey格式指定
- name: string
hostNetwork: false #是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
volumes: #在该pod上定义共享存储卷列表
- name: string #共享存储卷名称 (volumes类型有很多种)
emptyDir: {} #类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值
hostPath: string #类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
path: string #Pod所在宿主机的目录,将被用于同期中mount的目录
secret: #类型为secret的存储卷,挂载集群与定义的secre对象到容器内部
scretname: string
items:
- key: string
path: string
configMap: #类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
name: string
items:
- key: string
path: string
十二、Pod的存在和意义
pod是k8s系统中可以创建和管理的最小单元,是由用户创建或部署的最小资源对象模型,也是运行容器化应用的资源对象,其他资源对象都是来支撑或扩展pod对象的功能的,比如控制器对象是管控pod对象的、Service对象是用来暴露pod引用对象的、persistentVolume对象是用来为pod提供存储的。pod的出现主要是为了多进程运行多容器,保证这些容器内之间能够相互通信、共享网络、共享存储。 k8s不直接处理容器,而是处理pod,pod的特点如下:
◉最小部署单元。
◉具有一个特殊的pause根容器和N个业务容器。
◉同一个pod中的容器共享网络命名空间。
◉pod是短暂的,每一次启动都是一个新的pod。
十三、POD的使用
a.镜像拉取策略:pod的容器镜像拉取策略通过imagesPullPolicy属性指定,可选值有IfNotPresent(不存在时拉取)、Always(总是拉取)、Never(从不主动拉取)。
apiVersion: v1 kind: Pod metadata: name: foo namespace: awesomeapps spec: containers: - name: foo image: janedoe/awesomeapp:v1 imagePullPolicy: IfNotPresent #修改拉取策略类型
b.资源限制:一个容器要运行,通常对容器程序所需要的硬件资源有要求,我们可以通过containers.resources属性来配置,配置好所需硬件资源参数后,pod的调度程序会根据集群节点的硬件资源信息去选择将pod运行在哪一个合适的node机器上。资源限制本身时通过docker容器来实现的,而非pod本身实现。
apiVersion: v1 kind: Pod metadata: name: frontend spec: containers: - name: db image: mysql env: - name: MYSQL_ROOT_PASSWORD value: "password" resources: requests: #容器资源请求,向硬件申请的资源最低要求是多少 memory: "64Mi" cpu: "250m" limits: #容器资源限制,限制pod资源使用最大不超过多少 memory: "128Mi" cpu: "500m" - name: wp image: wordpress resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m"
c.重启机制:当pod里面的容器终止退出之后,是否需要重启容器,这通过restartPolicy属性来配置。restartPolicy可选值有三个Always(总是重启)、OnFailure(异常退出才重启)、Never(不重启)。
apiVersion: v1 kind: Pod metadata: name: pod-test labels: test: centos spec: containers: - name: hello image: centos:6 command: ["bash","-c","while true;do date;sleep 1;done"] restartPolicy: OnFailure #配置当容器终止退出后是否进行重启
d.健康检查:k8s中提供了2种容器健康检查机制,用来判断容器化应用是否运行正常,一种是livenessProbe存活检查(如果检查失败,将杀死容器,然后根据重启机制来处理杀死容器后的操作),一种是readinessProbe就绪检查(如果检查失败,则k8s会把pod从service endpoints中剔除)。
apiVersion: v1 kind: Pod metadata: labels: test: liveness-exec name: liveness-exec spec: containers: - name: liveness-demo image: busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 60; rm -rf /tmp/healthy; sleep 600 livenessProbe: #存活检查,支持httpget、exec、tcpsocket三种检查方式。httpget通过发送请求返回code来检查;exec通过执行shell命令返回bool值来检查;tcpsocket通过是否能建立正常连接来检查。 exec: command: - test - -e - /tmp/healthy initialDelaySeconds: 15 timeoutSeconds: 1
e.调度策略:apiServer收到用户输入的create pod的命令后,会进行一些列的操作创建出pod并运行容器,创建pod的创建流程如下图1所示,在此过程中会通过调度算法把pod调度到某个node节点上。影响k8s调度的属性有如下几个:
◉pod资源限制:调度算法会根据requests属性选择合适的节点。
◉节点选择器标签限制:调度算法会根据pod的nodeSelector标签选择器属性将pod调度到相应的标签节点。也就说需要先给node节点打上标签,然后通过pod的nodeSelector指定node节点。
◉节点亲和性限制:pod的nodeAffinity属性用于配置节点亲和性,其作用与nodeSelector节点相似,也是根据标签来约束pod调度的node节点的。
◉污点和污点容忍限制:节点选择器和节点亲和性是pod的属性,而污点(taint)是节点的属性(三个可选值NoSchedule、PreferNoSchedule、NoExecute),它用来排斥一类特定的pod。污点容忍(Tolerations)可以理解为污点的白名单,可以配置哪些类型的pod可以被分配,污点和污点容忍相互配合,可以用来避免 Pod 被分配到不合适的节点上。
十四、k8s的控制器Controller
Controller是用来控制和管理运行容器的组件对象,pod是通过Controller实现应用的维护和部署的,比如程序发布、弹性伸缩、滚动升级等。pod与Controller之间通过label标签建立关系。最常用的k8s控制器为deployment控制器,它可以部署无状态应用、管理pod和reolicaSet、部署和滚动升级,应用场景为web服务、微服务等,除deployment控制器以外还有StatefulSet控制器(部署有状态应用)、DaemonSet控制器(部署守护进程)、Job控制器(部署一次性任务)、Cronjob控制器(部署定时任务)。
使用deployment部署应用,它是通过label标签来建立关系的,在yaml文件中存在如下代码证明了一个关系。
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: #控制器中的selector标签 matchLabels: #label标签匹配 pod里面的label标签 app: nginx replicas: 3 template: #template是模板,里面包含pod metadata: labels: #pod里面的label标签 app: nginx spec: containers: - name: nginx image: nginx:alpine ports: - containerPort: 80
使用deployment部署应用步骤如下:
◉准备yaml文件,可通过前面提到的yaml创建方式实现。
◉运行命令" kubectl apply -f xxx.yaml "部署应用。
◉运行命令" kubectl expose deployment web --port=80 --target-port --name=webname "对外发布(暴露端口号和地址)。。
以上发布步骤在于理解发布的流程,实际开发中常将步骤三生存的pod导出为yaml,直接使用命令”kubectl apply -f xxx.yaml “完成发布动作。发布完成后可以使用命令"kubectl set image deployment web imagename:tag"格式升级镜像。可以使用命令" kubectl rollout undo deployment web deployment.apps/web rolled back "回滚到上一个发布版本,也可通过参数指定版本回滚。可以使用命令”kubectl scale deployment web --replicas=10 deployment.apps/web scaled“来设置弹性伸缩的副本数量。
什么是无状态应用和有状态应用?无状态应用的特点是认为所有pod都是一样的、无启动顺序要求、不考虑在哪个节点运行、随意进行弹性伸缩等,部署无状态应用不需要考虑这些因素,而有状态应用的部署则需要考虑这些因素,deployment控制器用于部署无状态应用,StatefulSet控制器用于部署有状态应用部署,二者的区别是后者部署的应用有唯一身份标识。以下是部署有状态应用的yaml文件举例:
apiVersion: apps/v1 kind: StatefulSet metadata: name: nginx-statefulset namespace: default spec: serviceName: nginx replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80
什么是守护进程Daemon Pod?k8s的守护进程有三个特征:
◉这个 Pod 运行在 Kubernetes 集群里的每一个节点(Node)上;
◉每个节点上只有一个这样的 Pod 实例;
◉当有新的节点加入 Kubernetes 集群后,该 Pod 会自动地在新节点上被创建出来;而当旧节点被删除后,它上面的 Pod 也相应地会被回收掉。
Daemon Pod可以运用在网络插件的Agent组件上、日志组件、监控组件等,以下是部署守护进程应用的yaml文件举例:
apiVersion: apps/v1 kind: DaemonSet metadata: name: ds-test labels: app: filebeat spec: # 标签选择器 selector: matchLabels: app: filebeat # 标签类型 template: metadata: labels: app: filebeat spec: # 配置容器 containers: - name: logs containers: - image: nginx ports: - containerPort: 80 # 挂载目录 volumeMounts: - name: varlog mountPath: /tmp/log volumes: - name: varlog hostPath: path: /var/log
除以上常用的控制器之外,还可以使用Job控制器、Cronjob控制器部署一次性和定时任务,其yaml文件举例如下:
apiVersion: batch/v1beta1 #指定当前描述文件遵循batch/v1beta1版本的KubernetesAPI kind: CronJob #我们在描述一个CronJob metadata: name: batch-job-every-fifteen-minutes #指定CronJob的名称 spec: #当没有指定pod选择器时它将根据pod模板中的标签创建 schedule: "0,15,30,45 * * * *" #这项工作应该每天在每小时0、15、30和45分钟运行 jobTemplate: #创建新pod所使用的pod模板 spec: template: #此CronJob创建Job资源会用到的模板 metadata: labels: app: periodic-batch-job spec: restartPolicy: OnFailure #Job不能使用Always作为默认的重新启动策略 containers: - name: main image: fanqisoft/batch-job ports: - containerPort: 8080
apiVersion: batch/v1 kind: Job metadata: name: pi spec: template: spec: containers: - name: pi image: perl command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] restartPolicy: Never backoffLimit: 4 # 重试次数
十五、k8s的Service网络
由于pod的每次更新重启,它的内部IP都会发生变化,因此外部无法直接通过pod的IP访问容器应用,Service抽象对象定义了一组pod的访问规则,提供了外部访问pod的统一固定接口地址,我们通过访问该接口地址就可以访问其对应的pod服务。也就是说即使pod的IP发生变化,service组件的服务发现功能也能保证外部访问pod内的应用程序,同时service定义的pod访问策略支持负载均衡功能。k8s的服务发现功能原理与目前主流的eureka服务发现机制类似,包含服务注册中心、消费者、服务提供者几大角色,而k8s的master节点就充当了服务注册中心的角色。
pod与service之间是根据label和selector标签建立关联的,这与pod与Controller关联方式一样。常见的Service网络类型有ClusterIP(集群内部使用)、NodePort(对外访问应用使用)、LoadBalancer(对外访问应用使用-公有云)。K8s的Service网络只是一个集群内部网络,集群外部是无法直接访问的。为此,想要将应用暴露出去让公网能够访问,K8S提供了两种方式:
◉NodePort:使Service通过Cluster节点的静态端口对外提供服务,外部可以通过 NodeIP:NodePort 来访问Service。(要使用该模式,只要修改type为NodePort并指定端口。外网使用nginx反向代理,手动添加访问节点到nginx即可访问,通常前端webui服务才使用该模式,后端服务之间基本上都只是在集群内部访问,使用ClusterIP模式即可)。
◉LoadBalancer:使Service利用Cloud Provider提供的Load Balancer对外提供服务,Cloud Provider负责将Load Balancer的流量导向Service。目前支持的Cloud Provider包括AWS、Azure、阿里云、腾讯云等。
ClusterIP网络用在集群内部的POD之间通信,其架构图如下:
ClusterIP网络的yaml示例代码如下,如要使用yaml文件创建service,使用命令"kubectl apply -f service.yaml"即可创建,使用"kubectl get service"可以查看网络信息。
apiVersion: v1 kind: Service #资源类型 为service metadata: labels: app: web name: web spec: type: ClusterIP # 服务类型,默认ClusterIP,可选值有None,ClusterIP、NodePort、LoadBalancer ports: - port: 80 # Service端口 protocol: TCP # 协议 targetPort: 80 # 容器端口 selector: app: web # 指定关联Pod的标签
十六、配置管理
k8s提供了Secret和ConfigMap两种类型的配置,存储在etcd中,让pod容器以挂载Volume数据卷或挂载变量的方式访问。Secret的作用是加密数据存储,应用场景为存储凭证。ConfigMap主要存储不加密的数据,一般用于存储常规配置信息。
Secret的使用步骤如下:
1、创建Secret加密数据,使用命令”kubectl create -f secret.yaml“创建,secret.yaml的格式如下:
apiVersion: v1 kind: Secret metadata: name: mysecret type: Opaque data: username: YWRtaW4= password: MWYyZDFlMmU2N2Rm
2、以变量的形式将加密数据Secret挂在到容器中:以下pod.yaml文件中的env表示环境变量,其中有两个环境变量名SECRET_USERNAME、SECRET_PASSWORD,他们的值分别来自mysecret加密数据。使用$SECRET_USERNAME、$SECRET_PASSWORD的形式调用。
apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: nginx image: nginx env: - name: SECRET_USERNAME #变量名 valueFrom: #表示以变量形式挂载 secretKeyRef: name: mysecret #指定secret的名称(与secret.yaml中的metadata.name一致) key: username #指定secret中的数据key - name: SECRET_PASSWORD valueFrom: secretKeyRef: name: mysecret key: password
3、以Volume的方式将加密数据Secret挂载到Pod容器中:以下pod.yaml文件中挂载了一个数据卷名为foo,将mysecret数据挂载到了/etc/foo目录,在容器内cd到该目录即可查看和使用挂载的数据。
apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: nginx image: nginx volumeMounts: #指定挂载到的目录 - name: foo mountPath: "/etc/foo" readOnly: true volumes: #指定要挂载的数据卷 - name: foo secret: secretName: mysecret #指定secret资源的名称(与secret.yaml文件中metadata.name一致)
CofigMap的使用步骤如下:
1、创建.properties配置文件,配置文件redis.properties的内容举例如下:
redis.host=127.0.0.1 redis.port=6379 redis.password=123456
2、创建ConfigMap,使用kubectl create configmap redis-config --from-file=redis.properties命令创建一个ConfigMap。创建完成后使用命令kubectl describe cm redis-config查看内容如下:
Name: redis-config Namespace: default Labels: <none> Annotations: <none> Data ==== redis.properties: ---- redis.host=127.0.0.1 redis.port=6379 redis.password=123456 Events: <none>
3、使用Volume方式挂载到pod容器中,其pod.yaml文件的内容格式如下:
apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: busybox image: busybox command: [ "/bin/sh","-c","cat /etc/config/redis.properties" ] volumeMounts: - name: config-volume mountPath: /etc/config #指定挂载的目录 volumes: - name: config-volume configMap: #表示卷的类型为configMap name: redis-config #指定configMap资源的名称
restartPolicy: Never
4、使用变量的方式挂载到pod容器中,首先创建ConfigMap.yaml,在其中声明变量信息然后在pod.yaml中引入,其内容格式如下:
apiVersion: v1 kind: ConfigMap metadata: name: myconfig namespace: default data: special.level: info special.type: hello
apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: busybox image: busybox command: [ "/bin/sh", "-c", "echo $(LEVEL) $(TYPE)" ] env: - name: LEVEL #环境变量名 valueFrom: configMapKeyRef: #表示变量值来自configMap name: myconfig #指定configMap的名称,与configMap.yaml中metadata.name对应 key: special.level #指定configMap的key - name: TYPE valueFrom: configMapKeyRef: name: myconfig key: special.type restartPolicy: Never
十七、k8s集群安全机制
APIServer是整个k8s集群访问的入口,访问安全机制包括认证、鉴权、准入等几种方式,Client在调用APIServer进行操作的时候需要经过以下三个步骤:
1、认证:用户身份认证——用kubectl等工具去管理k8s集群,APIserver要验证kubectl客户端的证书,同时kubectl也要验证对应APIserver的证书,这个过程叫k8s用户认证。除了通过证书方式认证客户端,还有用户名和密码认证、token机制去验证对应客户端。用户身份认证只是验证对应客户端是否是合法客户端,当有多种验证方法时,它会依次进行验证,如果对应验证方法没有明确拒绝,它会用下一个验证方法,直到有一个机制通过以后,验证结束。如果所有验证方法都没有拒绝,说明该客户端提供的认证信息在k8s上不适配,此时apiserver 就会把对应客户端归纳为匿名用户。当然此类用户虽然登陆到APIserver上,但它没有权限操作资源。
2、鉴权:基于RBAC模型(基于角色的访问控制)的鉴权操作。只有验证通过的客户端,才会有机会进行鉴权,鉴权是指验证对应客户端是否拥有对应k8s上的资源访问/操作权限。如果鉴权通过则会进入准入控制,否则APIserver就直接响应对应的客户端请求没有权限。
以下是创建角色并绑定主体的过程:
◉创建命名空间namespace。
◉在命名空间下创建pod。
◉创建角色Role,并指定角色权限。
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-reader namespace: default rules: - apiGroups: [""] # "" 指定核心 API 组 resources: ["pods"] verbs: ["get", "watch", "list"]
◉创建角色绑定(RoleBinding):将角色中定义的权限赋予一个用户或者一组用户。 它包含若干主体subjects(users、groups或 service accounts)的列表和对这些主体所获得的角色引用,RoleBinding 也可以引用 ClusterRole,这可以允许管理者在整个集群中定义一组通用的角色,然后在多个命名空间中重用它们。
apiVersion: rbac.authorization.k8s.io/v1 # 此角色绑定,使得用户 "jane" 能够读取 "default" 命名空间中的 Pods kind: RoleBinding metadata: name: read-pods namespace: default subjects: - kind: User name: jane # 名称大小写敏感 apiGroup: rbac.authorization.k8s.io roleRef: kind: Role #this must be Role or ClusterRole name: pod-reader # 这里的名称必须与你想要绑定的 Role 或 ClusterRole 名称一致 apiGroup: rbac.authorization.k8s.io
apiVersion: rbac.authorization.k8s.io/v1 # 这个集群角色绑定允许 "manager" 组中的任何用户读取任意命名空间中 "secrets"。 kind: ClusterRoleBinding metadata: name: read-secrets-global subjects: - kind: Group name: manager # 名称区分大小写 apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: secret-reader apiGroup: rbac.authorization.k8s.io
◉使用证书识别身份。
3、准入控制:准入控制是指检查对应客户端的请求是否符合对应请求/操作API规范,传递参数是否是正确,设置缺省的默认参数。只有当准入控制通过后,ApiServer才会进行操作,并将数据存入etcd中。
etcd是保存整个集群所有资源状态配置信息的kv键值存储数据库,一旦etcd宕机,k8s整个集群将无法正常工作,为此我们需要对etcd做高可用;除此之外为了保证etcd中的数据安全,k8s只允许APIserver去访问/操作etcd,这也是APIserver为什么是整个集群的访问入口的原因,也是为什么需要进行安全机制限制非法访问ApiServer的原因。以下是k8s认证授权的架构图:
十八、Ingress
Ingress是对集群中服务的外部访问进行管理的 API 对象,Ingress 公开了从集群外部到集群内 service 的 HTTP 和 HTTPS 路由,它包含两个部分:
◉Ingress:定义请求如何转发到service的规则。ingress是一个API对象,和其他对象一样,通过yaml文件来配置。ingress通过http或https暴露集群内部service,给service提供外部URL、负载均衡、SSL/TLS能力以及基于host的方向代理。ingress要依靠ingress-controller来具体实现以上功能。
◉IngressController :实现反向代理及负载均衡的程序,对ingress定义的规则进行解析,根据配置的规则来实现请求转发。ingress-controller并不是k8s自带的组件,实际上ingress-controller只是一个统称,用户可以选择不同的ingress-controller实现,目前,由k8s维护的ingress-controller只有google云的GCE与ingress-nginx两个,其他还有很多第三方维护的ingress-controller,具体可以参考官方文档。
前面提到了Service是外部访问pod的统一入口,Ingress相当于在service的外层又包裹了一层,这一层才是真正的从外部访问应用的入口,它是一个实体软件, 一般是Nginx 和 Haproxy ,在这一层可以完成架构图如下:
使用Ingress对外提供服务需要完成两步操作,一是部署IngressController控制器,二是配置Ingress规则。以下是ingressController的yaml文件和ingress规则的yaml文件:
apiVersion: v1 kind: Namespace metadata: name: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx --- kind: ConfigMap apiVersion: v1 metadata: name: nginx-configuration namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx --- kind: ConfigMap apiVersion: v1 metadata: name: tcp-services namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx --- kind: ConfigMap apiVersion: v1 metadata: name: udp-services namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx --- apiVersion: v1 kind: ServiceAccount metadata: name: nginx-ingress-serviceaccount namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: nginx-ingress-clusterrole labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx rules: - apiGroups: - "" resources: - configmaps - endpoints - nodes - pods - secrets verbs: - list - watch - apiGroups: - "" resources: - nodes verbs: - get - apiGroups: - "" resources: - services verbs: - get - list - watch - apiGroups: - "" resources: - events verbs: - create - patch - apiGroups: - "extensions" - "networking.k8s.io" resources: - ingresses verbs: - get - list - watch - apiGroups: - "extensions" - "networking.k8s.io" resources: - ingresses/status verbs: - update --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: Role metadata: name: nginx-ingress-role namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx rules: - apiGroups: - "" resources: - configmaps - pods - secrets - namespaces verbs: - get - apiGroups: - "" resources: - configmaps resourceNames: # Defaults to "<election-id>-<ingress-class>" # Here: "<ingress-controller-leader>-<nginx>" # This has to be adapted if you change either parameter # when launching the nginx-ingress-controller. - "ingress-controller-leader-nginx" verbs: - get - update - apiGroups: - "" resources: - configmaps verbs: - create - apiGroups: - "" resources: - endpoints verbs: - get --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: RoleBinding metadata: name: nginx-ingress-role-nisa-binding namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: nginx-ingress-role subjects: - kind: ServiceAccount name: nginx-ingress-serviceaccount namespace: ingress-nginx --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: nginx-ingress-clusterrole-nisa-binding labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: nginx-ingress-clusterrole subjects: - kind: ServiceAccount name: nginx-ingress-serviceaccount namespace: ingress-nginx --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-ingress-controller namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx template: metadata: labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx annotations: prometheus.io/port: "10254" prometheus.io/scrape: "true" spec: # wait up to five minutes for the drain of connections terminationGracePeriodSeconds: 300 serviceAccountName: nginx-ingress-serviceaccount nodeSelector: kubernetes.io/os: linux hostNetwork: true containers: - name: nginx-ingress-controller image: shichao01/nginx-ingress-controller:0.30.0 args: - /nginx-ingress-controller - --configmap=$(POD_NAMESPACE)/nginx-configuration - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - --udp-services-configmap=$(POD_NAMESPACE)/udp-services - --publish-service=$(POD_NAMESPACE)/ingress-nginx - --annotations-prefix=nginx.ingress.kubernetes.io securityContext: allowPrivilegeEscalation: true capabilities: drop: - ALL add: - NET_BIND_SERVICE # www-data -> 101 runAsUser: 101 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace ports: - name: http containerPort: 80 protocol: TCP - name: https containerPort: 443 protocol: TCP livenessProbe: failureThreshold: 3 httpGet: path: /healthz port: 10254 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 10 readinessProbe: failureThreshold: 3 httpGet: path: /healthz port: 10254 scheme: HTTP periodSeconds: 10 successThreshold: 1 timeoutSeconds: 10 lifecycle: preStop: exec: command: - /wait-shutdown --- apiVersion: v1 kind: LimitRange metadata: name: ingress-nginx namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx spec: limits: - min: memory: 90Mi cpu: 100m type: Container
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: abc-ingress annotations: kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/use-regex: "true" spec: tls: - hosts: - api.abc.com secretName: abc-tls rules: - host: api.abc.com http: paths: - backend: serviceName: apiserver servicePort: 80 - host: www.abc.com http: paths: - path: /image/* backend: serviceName: fileserver servicePort: 80 - host: www.abc.com http: paths: - backend: serviceName: feserver servicePort: 8080
十九、Helm
学过之前的内容,我们可以通过编写yaml,创建deploment、创建Service、创建Ingress等一系列步骤完成应用的部署,但是这些过程中会出现大量的yaml,如果部署的应用较多,则维护yaml、版本等特别复杂。使用helm可以把这些yaml作为一个整体管理、实现yaml的高效复用、应用级别的版本管理。那么helm是什么呢? 它是k8s的的包管理工具,类似于Linux系统的yum/apt等工具,可以方便地将打包好的yaml文件部署到k8s集群中。heml有三个重要概念,分别为:
◉Helm:一个命令行客户端工具,主要用于k8s应用chart的创建、打包、发布和管理。其命令语法参考帮助命令""
◉Chart:一系列用于描述k8s资源文件的集合(把yaml文件打包形成yaml集合)。
◉Release:基于Chart的部署实体,一个chart被Helm运行后将会生成对应的一个release,将在k8s中创建出真实运行的资源对象。
使用helm部署应用步骤:
◉安装helm:与Linux中安装其他软件一样,可通过下载tar.gz包解压后安装。
◉配置helm仓库:使用 "helm repo add restoryname restoryAddresss" 命令添加一个仓库restoryname,仓库地址为restoryAddresss。
◉部署应用:使用命令"helm search repo 应用名称 " 搜索应用,根据搜索的内容选择安装应用,其安装命令为" helm install 安装后应用名称 搜索到要安装的应用的名称 "。安装完成后可使用"helm list "或"helm status 安装后名称 "查看安装状态。
上面使用helm部署应用的过程中,我们搜索了应用,那这个应用是从何而来呢?其实这个应用名称就是一个chart的名称,是我们手动自行创建的chart。创建chart的过程如下:
◉创建chart:使用命令"helm create mychartname "即可创建一个名为mychartname的chart,创建完成后使用ls查看目录会发现多了一个mychartname的目录,切换到该目录会发现里面包含chart、Chart.yaml、templates、values.yaml等文件和子目录。其中chart.yaml文件包含了chart相关的信息,templates是存放k8s资源文件yaml的文件夹,values.yaml主要用于配置一些全局变量。
◉导入k8s资源文件yaml:将自己编写的yaml相关文件集合存入templates文件夹中(这就实现了yaml文件的整体管理)。
前面提到values.yaml可以定义全局变量和值,我们通过该文件传递参数,动态渲染模板可以实现yaml文件的高效复用。使用方法是把具体yaml文件中将可变部分定义到values.yaml中(key:value形式),使用"{{.values.变量名}}"的方式获取变量值。
二十、数据持久化存储
前面讲到了数据卷emptyDir存储,这种方式是本地存储,当pod重启后数据会丢失,这里的数据指的是容器运行产生的数据,而不是k8s配置数据,配置数据是存储到etcd中的,因此我们需要对数据进行持久化存储。常用的持久化方式有nfs网络存储、pv存储和pvc存储。
◉nfs网络存储:用一台服务器安装nfs(yum install -y nfs-utils),设置挂载路径(编辑/etc/exports文件,增加挂载路径),启动nfs服务;在k8s集群节点上安装nfs(安装完成后会自动挂载nfs)。完成这两部操作后,部署应用时在yaml文件里以nfs方式挂载路径即可,其yaml格式举例如下:
apiVersion: v1 kind: Pod metadata: name: test-nfs labels: app: test project: test-nfs spec: restartPolicy: Always containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent volumeMounts: - name: data000 #name与volumes要挂载的name一致 mountPath: /usr/share/nginx/html #将这个路径挂载到192.168.1.100服务器的/data/nfs路径下 --- 相当于目录映射 volumes: - name: data000 nfs: #指定挂载方式为nfs path: /data/nfs #nfs服务器的对外挂载路径 server: 192.168.1.100 #nfs服务器地址
◉PV和PVC:PV指持久化存储卷,是由管理员创建的一种存储空间(存储资源的抽象);PVC是持久卷声明的意思,用于申请和调用PV存储资源。PV和PVC是一一对应的,即一个PV只能为一个PVC服务。使用PV模式可以方便统一管理nfs服务器,以下是架构模型:
以下是创建PV和PVC的yaml文件内容举例:
apiVersion: v1 kind: PersistentVolume metadata: name: kl-test # pv 名字 namespace: klvchen labels: app: kl-test # 定义 labels ap: kl-test spec: capacity: storage: 10Mi # 定义容量 accessModes: - ReadWriteMany # 访问模式 persistentVolumeReclaimPolicy: Retain # 回收策略 storageClassName: nfs # 定义 storageClassName 只有相同名字的才能绑定在一起 nfs: path: /data2/nfs server: 192.168.1.100 --- kind: PersistentVolumeClaim apiVersion: v1 metadata: name: kl-test # pvc 名字 namespace: klvchen spec: storageClassName: nfs # 存储类名的名称,使用pv中定义的相同类名 accessModes: - ReadWriteMany resources: requests: storage: 10Mi # 存储请求数量 selector: matchLabels: app: kl-test # 指定 pv 的标签 kl-test
以下是使用pvc的yaml文件举例:
# 注意该 worker 节点需要安装 nfs-utils apiVersion: apps/v1 kind: Deployment metadata: name: nfs-pvc namespace: klvchen spec: replicas: 1 selector: matchLabels: app: nfs-pvc template: metadata: labels: app: nfs-pvc spec: containers: - name: nginx image: nginx:1.7.9 imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: web volumeMounts: - name: kl-test mountPath: /usr/share/nginx/html volumes: - name: kl-test persistentVolumeClaim: claimName: kl-test
二十一、k8s的容器运行时接口CRI
为了实现k8s与容器运行时的解耦,k8sV1.5以后推出了容器运行时接口CRI的概念,只要实现了该接口的任何容器运行时都可以与k8s结合使用完成集群部署,容器运行时负责启动、停止和管理容器。
v1.24 之前的 Kubernetes 版本集成了docker engine,使用名为dockershim的容器运行时组件,在V1.24版本后就不在集成,因此,此版本后的集群搭建,需要手动安装容器运行时,推荐使用containerd作为k8s的容器运行时。
二十二、k8s集群资源监控
k8s集群监控的指标包含节点资源利用率、节点数、运行pods、容器、应用程序等。我们需要搭建监控平台,常用的k8s集群监控平台方案为prometheus+grafana。搭建过程此次省略。
二十三、k8s的管理界面dashboard看板
Dashboard 是基于网页的 Kubernetes 用户界面。 可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中,也可以对容器应用排错,还能管理集群资源,使用 Dashboard 获取运行在集群中的应用的概览信息,也可以创建或者修改 Kubernetes 资源。默认情况下不会部署 Dashboard。使用如下命令可以部署dashboard:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.0/aio/deploy/recommended.yaml
关于详细的部署细节请参考我的另一篇文章从零开始搭建高可用的k8s集群。