【技术架构】快速掌握Kubernetes的关键环节梳理
先来看一下大多数公司软件开发的场景:
自己搭建虚拟机,然后安装redis,nginx,mq,mysql,tomcat,jdk,marven等。然后搭完以后镜像出来,换到另外一台上,换个ip,单独做测试环境。然后每次部署,通过ssh连接到linux服务器,kill -9,然后备份,重启服务器,打war包,真心烦人。
可能,后来会使用jekins,开发环境发布部署就随便发,测试。非常方便。
最后,公司搞大了!开始使用微服务,分布式,大数据等。所以使用docker把微服务作为一个个单独的容器,然后单独运行,不管nginx,redis,mysql,还是hadop等。对于docker都是一个模式,下载docker镜像,run一下,就OK了。是不是很历害!
但一旦拥抱了docker容器,用户就需要一个编排框架来调度和管理容器。最常见的编排框架有Kubernetes、Mesos、Docker Swarm。Kubernetes是目前市场上最成熟的、最具扩展性的解决方案,占有最大的市场份额。
Kubernetes正迅速成为云计算中部署和管理软件的新标准。不过,Kubernetes既然能提供极高的价值,也肯定会附带一个陡峭的学习曲线。作为一个新手,试图解析官方文档可能会很困难。这个系统由许多不同的片段组成,所以我们可以先从不同片段的关键要素出发去学习。
接下来,先了解一下Dock 容器的基本名词概念:
容器镜像(images)
容器镜像是启动容器的基石。
容器镜像是由文件系统叠加而成。最底端是一个文件引导系统,即bootfs。Docker用户不会与引导文件系统有直接的交互。Docker镜像的第二层是root文件系统rootfs,通常是一种或多种操作系统,例如ubuntu等。在Docker中,文件系统永远都是只读的,在每次修改时,都是进行拷贝叠加从而形成最终的文件系统。Docker称这样的文件为镜像。一个镜像可以迭代在另一个镜像的顶部。位于下方的镜像称之为父镜像,最底层的镜像称之为基础镜像。最后,当从一个镜像启动容器时,Docker会在最顶层加载一个读写文件系统作为容器。
容器镜像是轻量的、可执行的独立软件包 ,包含软件运行所需的所有内容:代码、运行时环境、系统工具、系统库和设置。
Docker容器(Container)
容器(container)的定义和镜像(image)几乎一模一样,唯一区别在于容器的最上面那一层是可读可写的。
镜像是静态的,镜像的每一层都只是可读的,而容器是动态的里面运行着我们指定的应用,容器里面的应用可能会新建一个文件,修改一个目录,这些操作所带来的改变并不会作用到镜像里面,因为镜像只是可读的。所以通过镜像创建容器就是在镜像上加一个可读写的层。
一个镜像可创建多个容器,每个容器都有各自的一个可读写层,这些层相互独立共享下面的镜像。
容器的定义并没有提及容器是否在运行,一个运行态容器(running container)被定义为一个可读写的统一文件系统加上隔离的进程空间和包含其中的进程。
想要快速了解Kubernetes如何管控容器,少走弯路,必须先搞清楚Kubernetes的关键名词概念:
节点(Node)
节点是Kubernetes中最小的计算硬件单元。它是集群中单个机器的表示。在大多数生产系统中,节点很可能是数据中心中的物理机器,或者是托管在像谷歌云平台这样的云供应商上的虚拟机。不过,不要让惯例限制了你的想象力,从理论上讲,你可以把任何东西做成一个结点。
把机器看作一个“节点”,可以让我们插入一个抽象层。现在,我们不必担心任何单个机器的独特特性,而是可以简单地将每台机器看作一组可以使用的CPU和RAM资源。这样,任何机器都可以替代Kubernetes集群中的任何其他机器。
集群(Cluster)
虽然使用单个节点是有用的,但它与Kubernetes理念不同。一般来说,你应该将集群看作一个整体,而无需担心单个节点的状态。
在Kubernetes中,节点汇聚资源,形成更强大的机器。当你将程序部署到集群中时,它将智能地处理将工作分配给你的各个节点。如果添加或删除了任何节点,集群将根据需要在工作中进行转换。这对程序或程序员来说都不重要,因为机器实际上是在运行代码。
持久卷(Persistent Volumes)
因为在集群上运行的程序不能保证在特定的节点上运行,所以无法将数据保存到文件系统中的任意位置。如果一个程序试图将数据保存到一个文件中,但随后又被转移到一个新的节点上,那么该文件将不再是程序期望的位置。由于这个原因,与每个节点相关的传统本地存储被当作临时缓存来保存程序,但本地保存的任何数据都不能持久。
为了永久存储数据,Kubernetes使用持久卷(Persistent Volumes)。虽然所有节点的CPU和RAM资源都被集群有效地汇集和管理,但持久的文件存储却不是。相反,本地或云驱动器可以作为持久卷附加到集群上。这可以看作是将外部硬盘插入到集群中。持久卷提供了可以挂载到集群的文件系统,而不与任何特定节点相关联。持久卷是一个系统的资源,因此没有所属的命名空间。
容器(Container)
在Kubernetes上运行的程序被打包成Linux容器。容器是一个被广泛接受的标准,因此已经有许多预先构建的映像可以部署在Kubernetes上。
容器化允许你创建自足式的Linux执行环境。任何程序和它的所有依赖项都可以打包成一个文件,然后在网络上共享。任何人都可以下载该容器并在其基础设施上部署它,所需的设置非常少。创建一个容器可以通过编程方式完成,从而形成强大的CI和CD管道。
可以将多个程序添加到单个容器中,但是如果可能的话,你应该将自己限制为每个容器的一个进程。拥有很多小容器比一个大容器好。如果每个容器都有一个紧密的焦点,那么更新更容易部署,并且问题更容易诊断。
由于容器本身是非持久化的,因此需要解决在容器中运行应用程序遇到的一些问题。首先,当容器崩溃时,kubelet将重新启动容器,但是写入容器的文件将会丢失,容器将会以镜像的初始状态重新开始;第二,在通过一个Pod中一起运行的容器,通常需要共享容器之间一些文件。Kubernetes通过存储卷解决上述的两个问题。
Pod
Pod是Kubernetes 中的最小部署单元,与你过去使用的其他系统不同,Kubernetes不直接运行容器;相反,它将一个或多个容器封装到一个称为Pod的高级结构中。相同Pod中的任何容器都将共享相同的命名空间和本地网络。容器可以很容易地与其他容器在相同的容器中进行通信,就像它们在同一台机器上同时保持一定程度的隔离。
Pod被用作Kubernetes的复制单元。如果你的应用程序太受欢迎,单个的Pod实例无法承载负载,那么可以配置Kubernetes以在必要时将你的Pod的新副本部署到集群。即使在没有重载的情况下,在生产系统中任何时候都要有多个副本,以保证负载均衡和故障抵抗。
Pod可以容纳多个容器,但在可能的情况下应该限制自己。因为Pod作为一个单位被放大和缩小时,所有在一个Pod里的容器都必须在一起缩放,不管它们是否需要。这将导致资源的浪费和成本增加。为了解决这个问题,Pod应该保持尽可能小的大小,通常只保留一个主进程和紧密耦合的辅助容器(这些辅助容器通常被称为“侧三轮摩托车”)。
Pod是自己有生命周期的,Pod消失后数据也会消失,所以我们要把数据放在一个容器的外面。
在Kubernetes上删除Pod,存储卷也会随之而删除的,这一点区分docker。
在Kubernetes支持多种类型的卷,而Pod可以同时使用各种类型和任意数量的存储卷。在Pod中通过指定下面的字段来使用存储卷
部署(Deployment)
虽然Pod是Kubernetes的基本计算单元,但它们通常不是直接在集群上启动的。相反,Pod通常由一个抽象层来管理:部署。
部署的主要目的是声明一个Pod应该同时运行多少个副本。当将部署添加到集群中时,它将自动地旋转加速所需的Pod数量,然后监视它们。如果一个Pod消失,部署将自动重新创建它。
使用部署,你不必手动处理Pod。你只需声明系统的期望状态,它将自动为你管理。
入口(Ingress)
使用上面描述的概念,你可以创建一个节点集群,并将Pod部署到集群上。不过,还有一个问题需要解决:允许外部通信流进入你的应用程序。
默认情况下,Kubernetes提供隔离舱和外部世界。如果你想要与运行在Pod中的服务通信,你必须打开一个通信通道。称作入口(ingress)。
有多种方法可以将入口添加到集群中。最常见的方法是添加入口控制器或负载均衡器。这两个选项之间的精确权衡超出了本文的范围,但是你必须知道,在你可以与Kubernetes进行实验之前,你需要处理的是入口。
以上是对Kubernetes的简单描述,旨在为你提供开始试验所需的基础知识。想必现在你已经了解了组成系统的部分,现在是时候使用它们来部署一个真正的应用程序了。
要在本地使用Kubernetes,可以使用Minikube在你的个人硬件上创建一个虚拟集群。如果你准备尝试云服务,谷歌Kubernetes引擎有一系列教程,可帮助你入门。
了解了上述概念后,在使用中的一些注意事项也是要说明一下:
1) 不要将数据储存在容器中
容器随时都可以停止、销毁或迁移,比方说,一个容器里运行的应用版本是1.0,我们分分钟就可以把这个应用升级到1.1,同时还不会对数据造成任何影响。所以如果用户想要存数据的话,最好是用数据卷来存储。不过在用卷存数据的时候大家还是要注意一点,如果有两个容器共用一个数据卷,都往里面写数据的话,是有可能造成程序崩溃的。我们在设计应用程序的时候应该考虑到这一点,为保万无一失,应用程序应该具备特定的机制,以确保在往共享数据存储区写入数据的时候不会出错。
2) 不要把应用程序分块交付
在部分用户看来,容器跟虚拟机没什么两样,所以有些人往往会把应用程序部署到当前运行的若干个容器中。这种做法在开发阶段没有太大的问题,因为做开发的时候我们会很频繁地进行部署和调试,但是到了持续交付(CD)阶段,下一步就是QA测试和正式投产了,这种做法就不太适合了。在这一阶段,我们应该充分考虑到容器的不可变特性,最好是将应用程序打包到一个镜像中交付。
3) 不要把镜像体积建得很大
镜像越大,就越难发布。镜像中只包含必要的文件和library就可以了,能让应用或者进程运行起来就行。千万不要在镜像中安装些没必要的东西,在构建镜像的时候要避免使用yum这种update命令,免得系统自动下载很多不相干的文件到新镜像层中。
4) 建镜像的时候不要只建一层
大家都知道,Docker的文件系统是分层的,在建镜像的时候我们应该这么建,将操作系统单独建一层,作为基础镜像,然后用户名定义文件、运行时安装环境、配置文件都要分别建一层镜像,最后才是应用镜像层。这么做的话,我们以后重建、管理以及发布镜像的时候就要轻省得多了。
5) 不要把本地运行的容器转成镜像
换句话说就是创建镜像的时候不要用“docker commit”命令来创建。用这种办法建镜像是完全不可取的,因为这种办法是不能重复的。我们在建镜像的时候应该从Dockerfile创建,或者用其他S2I(从源文件构建镜像)的方式来创建,这样镜像才具有可再生性,而且如果我们把镜像存在git之类提供版本控制能的系统里的话,还可以对Dockerfile的改动进行跟踪。
6) 给镜像打tag的时候不要只打“latest"
latest其实就相当于Maven里头的“快照”。因为容器的文件系统是分层的,我们最好是给镜像多打几个tag。如果只有latest的话,可能过段时间我们再来运行应用程序的时候就发现程序运行不起来了,因为应用的父层(就是Dockerfile里面的跟在FROM命令后面的那一层)被更新的版本覆盖了,而新版本又不能向下兼容,还有可能就是从build cache里面取镜像的时候取到了错的“latest”镜像。在产品环境中部署容器的时候也要避免使用latest,不然容易造成无法跟踪记录镜像版本的问题。
7) 不要在单个容器里面运行多个进程
容器本来就是用来运行单个应用的(比如http daemon,应用服务器,数据库等等),如果我们非要在一个容器里跑几个应用,那么在管理每个应用进程、存取日志、升级应用的时候就会很麻烦。
8)不要把认证口令存在镜像中,用环境变量比较好
如果我们把用户名/密码值对存在镜像里的话,就只有采用硬编码的方式来挨个处理,估计这种麻烦事没人愿意去干。所以我们最好是用环境变量的方从容器外部获取此类信息。
9) 不要用root用户的角色来运行进程
Docker容器默认是以root权限运行的。不过随着技术的成熟,docker也会提供安全性更高的默认操作选项。在现有技术条件下,以root权限运行会对其他应用带来安全隐患,而且在有些运行环境下root权限是取不到的,所以我们在跑容器的时候应该用USER命令来指定非root权限的用户。
10) 不要过分依赖IP地址
每个容器都有一个内部IP,这个IP不是固定的,我们启动容器或者停止容器的时候IP都会变。如果我们要让应用或者微服务模块在容器之间进行通信的话,正确的做法是通过设置环境变量来传递主机名和端口号。
- 点赞
- 收藏
- 关注作者
评论(0)