当前位置: 首页 > 科技观察

K8S前端入门指南

时间:2023-03-14 18:00:23 科技观察

什么是K8S在回答这个问题之前,我们先来了解一下Web应用部署方式的演进过程。主机模式刚接触软件开发时,人们部署应用的方式通常是这样的:首先你需要一个服务器,然后在服务器上安装一个WebServer(例如:Nginx或ApacheServer)。接下来,根据应用的运行时需求,安装相应的软件包(比如代码是用Node.js写的,就需要安装Node.js运行环境)。最后,根据应用程序的其他功能,安装相应的软件,如数据库。在部署应用程序的同时,将各种软件程序添加到主机中。至此,这个Web应用提供的服务如下图所示:随着Web应用的复杂度越来越高,这种部署方式的弊端也逐渐显现出来。现代服务器的性能非常强大。如果一台主机上只有几个程序在运行,可能会导致机器资源利用率低下。而且,由于程序直接运行在宿主机上,程序之间存在资源竞争关系,会相互影响。如果一个程序导致宿主机死机或挂机,其他程序将无法正常工作。2013年Docker容器技术出现后,这种部署方式逐渐被淘汰。容器容器技术的实现方式有很多种,比较主流的是Docker。它的标识很好地体现了“容器”的特性——程序就像容器一样,运行在宿主机上,相互隔离。不同于传统的主机部署模式,容器化技术提供了一个隔离的环境。程序互不影响,也不影响宿主机的稳定性。以Docker方式运行的容器,可以理解为虚拟机(但又不同于虚拟机,虚拟机是硬件的虚拟化;Docker是操作系统层的虚拟化)。它包含运行程序所需的运行环境和程序代码,启动后可以通过端口映射将容器自身的服务暴露给宿主和外部用户。容器之间除了相互隔离之外,还可以通过Docker引擎进行互联。容器之间的访问通常是通过内部IP的方式进行的。随着Web应用规模的不断扩大,单一主机已经不能满足性能需求。当前的部署是基于多主机和多容器。那么,如何管理这些宿主资源和应用容器呢?这个问题的答案指向了本文的主角——K8S。开源容器管理平台K8S的全称是Kubernetes,因为首字母k和末尾字母s之间有8个字母,所以简称为K8S(同理还有i18n等)。由谷歌开源,是主流的容器管理平台。它的logo也很有意思,K8S就像一个舵,让用户在茫茫大海中驾驶满载集装箱的船驶向成功的彼岸。借助K8S提供的能力,运维人员——甚至是前端开发人员——可以轻松地在集群环境中部署和管理容器。此外,K8S还可以很好地支持以下功能:负载均衡、高可用、高并发(多实例)集群管理需要先安装并运行K8S环境。由于这部分操作需要服务器支持,这里不再介绍。以下所有内容均以读者可以连接任何K8S系统为前提。K8S核心知识点在K8S中,所有的资源都是通过声明式配置来管理的,它们被称为K8S对象。以命名空间为例:apiVersion:v1kind:Namespacemetadata:name:demo-spacespec:finalizers:-kubernetesstatus:phase:Active不同类型的对象需要的配置不完全相同,但应该都有以下基本配置:apiVersion-用于创建此对象的KubernetesAPI的版本对yaml中可以使用的配置项的字段和格式有不同的要求。kind-你要创建的对象的类别metadata-一些有助于唯一标识对象的数据spec-你期望的对象的状态更多详细文档,请访问K8S官网。常见的K8S对象包括Namespace、Ingress、Service、Development和Pod。其中,Namespace是一个虚拟的概念,用来为集群划分不同的命名空间。通常,同一Namespace中的资源应该具有唯一的命名。其他几类资源之间的关系如下图所示:Ingress通常包含一个集群中的若干物理主机,它们都是集群的节点,这些节点需要一个统一的IP进行访问。Ingress提供了这个能力,它是整个集群的流量入口。Ingresscontroller的实现有很多种,比较常见的实现是基于Nginx的。Ingress-nginx文档:https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/Sevice流量从Ingress进入集群后,应该分配到不同的容器中进行处理。但是一般来说,容器的IP并不是固定的,因为在K8S中,容器的数量是可以随时增减的,此时容器的IP是动态的。Service是一个抽象的概念,其作用是解耦Ingress和Pod之间的关系。它会动态获取可用Pod的信息并传递给Ingress,以确保正确的负载均衡。无论是容器数量的正常增减(扩容)还是容器自身意外重启,流量总会分配给可用的容器。DevelopmentDevelopment用于管理副本集和容器配置。K8S不建议用户直接操作副本集配置,所以Development通常是用户需要接触的最基本的配置。副本集是按所需规模稳定运行的容器集合。通过Development,可以实现如下功能:修改replicas数量,然后replicaset会自动增加或删除容器,以修改数量修改容器配置。此时会先启动新容器,然后关闭旧容器依次更新容器。在此阶段,服务将始终可用。PodPod是K8S中最小的部署单元,它包含了一组容器(可以是一个也可以是多个)。同一个Pod中的不同容器可以通过localhost:方法访问彼此暴露的服务,同时容器可以访问公共数据卷。一个多容器的使用场景通常是一个主容器加上多个sidecar,它们相互配合来实现功能需求。下面是一个多容器协作的例子:上图中的WebServer容器可以对外提供资源访问服务。同时FilePuller作为sidecar容器,可以同时更新远程内容到本地存储,保证WebServer提供的内容是最新的。kubectl工具介绍kubectl是K8S提供的一个命令行工具,可以让用户在本地向K8S集群发送操作指令。MacOS下安装教程:https://kubernetes.io/zh/docs/tasks/tools/install-kubectl-macos/安装完成后创建配置文件:touch$HOME/.kube/config然后添加cluster到文件配置:以下配置仅供参考,连接集群时需要改成对应的配置。apiVersion:v1#集群配置,可以多个;cluster必须包含server字段clusters:-cluster:server:https://dami.netname:dami-c3#context配置,可以多个,每个context必须包含Clustername,namespacename,usernamecontexts:-context:cluster:dami-c3namespace:demo-spaceuser:manooogname:c3-demo-space-context#当前默认的context配置,所有kubectl命令都会默认使用这个contextcurrent-context:c3-demo-space-contextkind:config#用户信息,包括用户名和对应的权限信息,可能是tokenusers:-name:manoooguser:token:保存配置后,就可以使用kubectl命令对集群进行操作了。eg:kubectlgetingress#获取当前上下文对应命名空间中的Ingress。配置kubectl的函数有很多。根据不同的使用习惯,同一个功能可以有不同的使用方式。下面是一些我经常使用的命令。查看配置kubectlgetservice/-oyaml上述命令的意思是获取名为xxx的服务对应的配置文件,并以yaml格式输出。对于不同的资源,通常通过/来区分。上面在应用配置文件中提到,K8S中的所有资源都可以通过声明式配置获取。当我们要创建资源时,可以先创建对应的配置文件。然后使用如下命令使配置生效:kubectlcreate-fservice.yamlkubectlapply-fservice.yaml以上命令都可以应用配置文件,不同的是create一般用于第一次创建,apply用于修改已有的配置文件。我更喜欢使用apply命令。两者在使用上的区别:https://stackoverflow.com/questions/47369351/kubectl-apply-vs-kubectl-create查看Pod输出kubectllogs中获取对应容器的输出Pod信息。如果Pod中只有一个容器,则container-name可以省略。进入容器内的命令行环境。在容器运行过程中,可能会出现异常情况。这时候就需要进入集装箱内部进行检查。这时候可以使用如下命令:kubectlexec-itdemo-5b7846d65b-nvnnm--sh如果Pod包含多个容器,还需要使用-c参数指定要进入的容器名称。DEMO这个demo的目标是启动一个Node.js后端程序,有3个实例,实现如下效果:curldemo-20211215.io/20211215//->HelloWorldhttp://demo-20211215.io域名指向集群Ip,请求路径/20211215时,期望收到Node.js后端返回的字符串:HelloWorld。由于这个示例域名不存在,我们这里使用Hosts进行配置。在本地Hosts中添加一条记录,将域名直接绑定到测试集群的IngressIP:demo-20211215.io现在执行测试命令,会得到如下提示:$curl-vdemo-20211215.io/2021215*Trying...*TCP_NODELAYset*连接到demo-20211215.io()端口80(#0)>GET/2021215HTTP/1.1>主机:demo-20211215.io>User-Agent:curl/7.64.1>Accept:*/*>80/TCP51s配置分析:metadata.name//服务名,在同一个命名空间中,该字段是唯一的spec.selector.app//这是一个选择器,可以选择标签包含app=demo-app-将20211215的pod添加到此服务并创建部署。将以下配置保存为deployment.yaml:apiVersion:apps/v1kind:Deploymentmetadata:name:demo-deployment-20211215spec:replicas:3selector:matchLabels:app:demo-app-20211215template:metadata:labels:app:demo-app-20211215规格:容器:-名称:demo-20211215图片:rxh1212/demo-20211215ports:-containerPort:3000创建和查看的命令不再举例。配置分析:metadata.name//部署的名称也是唯一的spec.replicas//命名空间中必须唯一的pod实例数,这里是3,表示将部署三个应用实例。spec.selector.matchLabels//应该和spec.template.metadata.labels一样,表示pod的标签。spec.template.spec.containers//是一个数组,表示此部署使用的容器。这里我使用名为rxh1212/demo-20211215的镜像,它是我单独编译的,包含一个Node.js应用程序。代码如下:"usestrict"constexpress=require("express")//ConstantsconstPORT=3000constHOST="0.0.0.0"//Appconstapp=express()app.get("/20211215",(req,res)=>{res.send("HelloWorld")})app.listen(PORT,HOST)console.log(`Runningonhttp://${HOST}:${PORT}`)之后镜像启动后,会在3000端口提供Http服务,如果访问/20211215,会收到HelloWorld响应。该镜像的封装编译过程在此略过。有兴趣的可以在评论区留言。言归正传,创建部署后,访问demo-20211215.io/20211215得到如下响应:$curldemo-20211215.io/20211215