
暂无个人介绍
在短短四年多的时间里,谷歌内部关于容器管理的项目已经颠覆了VMware、微软、甲骨文以及其他所有未来很具前景的数据中心。 那么到底是什么改变了一切呢?在所有软件依赖操作系统平台的时代,数据中心已经确立了其目的和功能,Kubernetes的功能显得并不那么显著。Kubernetes是一个大规模的、正在进行的软件资源重组的产物,这些资源共同组成了一个网络应用程序。这种一致性围绕一个称为workloads的概念展开,workloads是一个或多个应用程序或者一个或多个服务跨多个处理器执行工作的广义概念。 workloads是工作任务job——例如,管理供应链、物流监督、库存跟踪、帮助证券市场。Kubernetes已经成为现代的任务控制系统。“您可以将Kubernetes视为应用模式的平台,”Google软件工程师Janet Kuo在KubeCon 2017大会专题会场上解释道。 “这些模式使您的应用程序易于部署,易于运行,并且易于继续运行。”虚拟机的衰落 越来越多的数据中心基础设施致力于关注workloads的健康状况,而不是服务器的健康状况。无论它们是物理处理器还是虚拟机,服务器都可能失败。故障对这些workloads的可用性和功能的影响应该小于最小阈值——它们看起来根本不应该受到影响。直到2016年,开源社区已经提出了一些方法来协调workloads,以获得最大的可用性。在很短的时间内,Kubernetes成为了在开源领域进行投资的企业的选择。至于原因,足够写一本完整的书,如果写得足够好,说不定它还可以被改编成一部艺术影院电影,赢得评论家的青睐,不过却永远无法赢得奥斯卡奖。也许,这是唯一重要的原因:谷歌早期推动Linux基金会建立云原生计算基金会(CNCF)的举动给了Kubernetes足够的时间在最广泛的人群中有机地培养一批追随者。整个开源商业模式围绕着支持的价值。那些不再希望被局限于单一供应商的企业(诚然,并不是所有的企业)欣赏支持系统中多元化的新价值。一群供应商的行动(如果不总是一致地),至少在同一目标上进行一些协调,则优于单一供应商,在没有特定方向的情况下领导垄断的平台。为什么现在Kubernetes兴起? Kubernetes不属于任何一家公司,尽管它基于一个名为Borg的项目,最初是在谷歌内部开发的,谷歌通常被认为是Kubernetes开发社区的实际领导者。也就是说,微软已经完全围绕Kubernetes重新调整了它的整个服务器系统理念,并雇佣了几个主要的创建者。作为一个开源项目,Kubernetes由Linux基金会的一个代理机构——云原生计算基金会(CNCF)管理。谷歌最初设计Borg是为了适应它自己的内部用途。因此,以谷歌的搜索引擎本身为例是非常公平的:在搜索查询中搜索匹配条目的基本工作是由数百个(可能是数千个)分担责任的单个服务执行的。我会说“数不清”,但这不仅是错误的,而且与Kubernetes的观点相反。它确实统计了整个网络中组成活动作业(或作业)的所有服务和组件。Docker是不是和这个有关? 遗憾的是,对于包含这些分布式程序片段的容器来说,目前没有比“容器”更好的术语了。(有一段时间,我们称这些东西为“Docker Containers”,以区别于“Tupperware containers”,但如今,Docker只是容器生态系统的一部分;另外,容器的格式不止一种。如果您熟悉ZIP文件,它使用数学压缩将多个文件组合成一个文件,那么您已经对现代软件容器有了相当多的了解。它们实际上使用相同的方法将多个文件压缩在一起。这些文件仅由程序需要运行的可执行元素和数据组成,而不需要查看网络中的其他地方。这些元素中的一个可能实际上是一个小型操作系统——通常是Linux的微型化版本,或者来自微软(Microsoft), Windows的一个小亲戚,名为Nano Server。为此容器化部署方法(例如搜索查询响应)编写的程序可以查看缓存网页的索引以查找尚未选择的条目,检查该条目的语义上下文以匹配查询的内容,对结果进行排名,并将其注册在列表中以供以后收集和检索。 该程序将终止。这是分布式服务的一个特征,它使它与PC应用程序如此不同:它满足请求然后停止。 它知道它是更广泛工作的一部分,所以一旦它履行其功能,它就不复存在了。 软件工程师借用现代哲学的概念来描述这一方面:Ephemeralism。 与基于GUI的应用程序不同,它实际上花费大部分周期等待其用户的响应,短暂服务实现其功能然后到期。在一个容器化的网络中(再次表示遗憾,没有更好的词来描述),程序彼此孤立地运行。 即使它们可能共享相同的处理器和内存空间,容器外部的主机操作系统也会保持其分离。 (从理论上讲,这种联合依赖是可利用的,尽管自然条件下还没有已知的威胁)通信只能通过软件定义的网络在容器之间进行。 更复杂的SDN将战略性地为这些容器提供网络地址,同时考虑如何将它们收集在一起以执行共同的工作。ORCHESTRATE WORKLOADS是什么? 这是orchestration的使用场景。与“container”不同,“orchestration”这个词完美地描述了Kubernetes所扮演的角色。虽然有些人已经用乐队指挥来说明这个概念,但是在音乐和分布式应用中,指挥家和orchestrator之间存在着很大的区别。orchestration的行为为单个应用程序提供了协同工作的模式,就像乐队中的乐器一样。当作曲家创作出软件的原始模式,包括它的旋律线条和节奏(组装一个软件容器的术语实际上是作曲)时,管弦乐师会让乐曲听起来。“这就是为什么我称Kubernetes为‘可组合的平台’的原因,”在最近一次公司网络研讨会上,Red Hat的产品策略总监Brian Gracely解释道。“它应该是什么样子,有某种框架——其中一些来自Kubernetes社区,一些来自社区多年的经验,关于如何部署应用程序。”orchestrator的主要工作是在其信任下维护应用程序的运行状态。在另一个时代,这项工作被委托给了操作系统。但那时候,这个平台还是一个只有一个存储库和专用存储设备的处理器。现在,没有什么东西可以将容器化的服务与应用程序的更广泛的上下文关联起来。实际上,协调器获取所有这些服务的功能和工作产品,通过某种形式的manifest来组织它们,并提供某种应用程序的外观。更改manifest,您可能会得到一个完全不同的应用程序。 Kubernetes与其他类型的应用程序在结构上没有区别。它不是虚拟机。它的orchestrator 在操作系统上运行。在运行时,它维护一个节点集群,这是引用物理或虚拟服务器的一种更抽象的方式。每个节点上都有容器的pods 。在它们的每个内部都有一个称为kubelet的客户端代理,它代表orchestrator 独立地管理分配给它的节点的函数。但即便如此,这也是一个和其他项目一样的项目。 所以Kubernetes不像Hadoop,它真正地重构了在服务器上运行的应用程序的结构。尽管如此,orchestrator 带来的分布式模型与2016年之前流行的模型有很大不同。部署模式不会随着时代而改变,比如时尚方向、个人口味或政治取向等。如果我们诚实的话,Kubernetes的突然崛起并不是因为世界上所有企业突然意识到需要在云计算中加入一些应用程序。Kubernetes是谷歌需要在数万个节点上管理其全局可访问的workloads的产物。世界上很少有其他组织类似于谷歌,或者拥有谷歌的数据中心概要。并不是每家公司都有自己的搜索引擎——如果你仔细想想,这就是谷歌存在的原因。分布式系统的吸引力 那么,为什么Kubernetes或container orchestration对企业这么大吸引力呢?它真正吸引人的原因与workloads 本身无关,更多的是与围绕它们的开发和部署模型有关: 连续性——当应用程序由粒度组件组成时,通过单独更新和改进这些组件,应用程序就更容易按粒度演进。协调器可以根据各个更改对整体workloads的影响做出适当的调整。应用程序的特性改进不再需要在大规模的检修中实现——这通常会对其可用性产生负面影响。持续集成和持续交付的概念(CI/CD,“D”通常表示“部署”)可以通过一个平台更容易地实现自动化,这个平台从一开始就被设计成理解部署本身的更小、更易于管理的步骤。 弹性——Kubernetes维护容器组的活动副本(称为replica sets),目的是为了在任何容器或容器分组(Kubernetes称之为pod)失败时保持正常运行时间和响应能力。这意味着数据中心不必复制整个应用程序,并触发负载均衡器,以便在主应用程序失败时切换到辅助应用程序。实际上,复制集中的多个pod通常在任何时候都在运行,协调器的工作是在应用程序的整个生命周期中保持这种多元性。 可伸缩性——对于使用Kubernetes协调分布式workloads的组织来说,最大的好处是,根据预先设置的策略,workloads 可以在系统中按需成倍增长,从而再次进行伸缩。为了减少混乱的可能性,Kubernetes把相关的容器组合成一个pod。可以将名为autoscaler的服务设置为自动将pods复制到不同节点,因为它确定分配给这些pods的资源没有得到尽可能多的利用。KUBERNETES 是平台,还是其他什么? 到底Kubernetes和VMware vSphere谁是平台,仍然存在一些不确定性。不可否认,Kubernetes是一个“引擎”,是为分布式软件系统提供动力的主要元素。然而,Kubernetes并不提供这些元素本身,正如Windows的前辈MS-DOS最初并不提供自己的硬盘优化器或备份过程一样。 但正如许多用户会断言的那样,Kubernetes是一个平台的中心,这个平台可以由任意数量的能够协同工作的服务组成。有人说今天的CNCF是维护的目的,整理和培养其他独立的多元性,开源项目——例如,监控系统,如Prometheus,日志数据经理如Fluentd(不是一个错字),和信任的内容的身份验证器等公证,可能共同组成一个平台。在撰写本文时,CNCF已经认证了59个发行版,其中许多都是商业发行版,其中包括协调器以及其他CNCF工具或其供应商自己的工具。“你会发现Kubernetes没有提供所有这些东西,”Red Hat优雅地说。“他们都是地方社区,通过不同的供应商,通过开源插件项目,给予市场很多的选择,给他们选择,让他们可插入性为这些不同的元素,并允许公司最终决定,在这个更广泛的框架,如何构建最好的平台,我们想做什么,为我们挑选最好的有意义的部分,但仍拥有一切的互操作性和可支持的?”然而,正如Gracely的评论本身所表明的那样,由于这些集合的任何一个产品都无疑是一个平台,而Kubernetes是其中心的推动者,那么所有这些结果都应该是“Kubernetes平台”。Red Hat的OpenShift就是一个突出的例子,还有最新2.0版本的Rancher。大到何种程度? 不论Kubernetes被数据中心经理和CIO认为是一个平台还是一个引擎,这都不是问题。如果orchestrator 要继续在企业中取得进展,就不能把它看作是实验室的实验,或者开发人员喜欢但没有人理解的疯狂工具之一。“引擎”意味着需要一个完整的底盘(或者,借用我的另一个gig中的一句话,一个“新堆栈”),因此给一些评估人员造成这样的印象,即它在设计上是不完整的。一个平台必须为企业提供这样的方向:它很快就能承载所有的应用程序,而不仅仅是带有curly-Q和microservices的流行的应用程序。由于这个原因,CNCF一直将Kubernetes作为一个平台,能够通过容器化的方式承载新旧应用程序,即使将旧的应用程序从虚拟机转移到容器的好处还有待评估。与分布式模型相比,预容器时代应用程序的定义特征之一是它们不能被分解、细分或缩放。现代开发人员将这种应用程序称为整体应用程序。在最近的一次开源峰会上,CNCF执行董事Dan Kohn吹嘘了一种被称为"lift-and-shift."的整体过渡模式的优点。他将其定义为“一个概念,即你可以使用任何一个软件,你可以把它包装在一个容器中。”我们被训练成把容器看作是非常小的东西,只有最少的库,和最少的需要运行的软件。但是如果你有一个8g的Java应用程序,你可以用一个容器来包装它。甚至在你把它移到云计算之前,将它包含在其中就会为你创造一些价值。 各企业在起跑线发力 bargain Kohn和其他Kubernetes支持者提出企业是基于平台,基于Kubernetes或与Kubernetes集成的平台至少能够支持预先存在的应用程序 - 尽管是在不同的背景或主题中 - 同时它是 值得信赖,迎来这个全新的基础架构。帮助CNCF实现其目标的将是一组新的基于云的公共平台,用于承载应用程序,尽管将容器而不是VM作为业务模型的基础。谷歌Kubernetes引擎(GKE,前谷歌容器引擎),Azure Kubernetes服务(部,以前Azure容器服务),IBM Cloud Kubernetes服务(原IBM Bluemix容器服务),亚马逊Kubernetes弹性容器服务, Pivotal Container Service(PKS,也许你感应一个主题与“K”代表“C”字),以及最近VMware Kubernetes引擎,所有人都在推进突然非常真实的概念,即容器即服务(CaaS),作为PaaS的一种进化形式。所有这些都是除了Red Hat的OpenShift Online之外的内容,OpenShift是早期与Kubernetes共同开发CaaS概念的先驱。现在,您可以使用Docker组合一个容器,而这些CaaS平台中的任何一个都可以在自己的Kubernetes集群上承载该容器。在所有这些情况下,容器都将VM替换为消费单元,因此您不再需要站在云上自己的虚拟基础设施上,只需运行应用程序。这就是Kubernetes革命将会得到巨大回报的地方。到目前为止,基于云的资源交付仅用于托管应用程序,而不是用于安装应用程序的虚拟化操作系统,这是一个非常健康的市场。这是一个如此健康、如此突然的市场,VMware只能加入,别无选择。随着新的、非单块的应用程序在公共云中不断成熟,这个市场成功的标志将是企业将在多长时间内不再担心是否或如何实现“lift-and-shift.”。 本文转自DockOne-什么是Kubernetes,以及Orchestration如何重新定义数据中心
鸟瞰Kubernetes Minikube本身自带了一些好东西。你可以访问官方自带的面板:minikube dashboard 从这里,你可以查看你的集群详细信息和部署应用。 总结 现在你已经了解了在Windows上安装Docker和Kubernetes的所有选项。如果你遇到了文章没提到的错误,请通过@learnk8s或者slack告诉我们。接下来,你可以在你的环境里面部署更多应用了。请看文档。 本文转自DockOne-如何在Windows 10上运行Docker和Kubernetes?
今天是Google Developer Advocate Sandeep Dinesh的七部分视频和博客系列的第二部分,介绍如何充分利用您的Kubernetes环境。当您开始在Kubernetes之上构建越来越多的服务时,简单的任务开始变得更加复杂。 例如,团队无法创建具有相同名称的Kubernetes Service或Deployment。 如果你有成千上万的Pod,只是列出它们都需要一些时间,更不用说实际管理它们了!当然,这些还只是冰山一角。在本期Kubernetes最佳实践中,让我们来看看如何使用Kubernetes命名空间来更轻松地管理您的Kubernetes资源。 什么是命名空间(Namespace)? 您可以将命名空间视为Kubernetes集群中的虚拟集群。 您可以在单个Kubernetes集群中拥有多个命名空间,并且它们在逻辑上彼此隔离。 他们可以为您和您的团队提供组织,安全甚至性能方面的帮助!默认命名空间(Default Namespace) 在大多数Kubernetes发行版中,集群开箱即用,命名空间默认为default。事实上,Kubernetes上有三个命名空间:default、kube-system(用于Kubernetes组件)和kube-public( 用于公共资源)。 kube-public现在并没有真正使用过,而且通常单独隔离一个kube-system是个好主意,尤其是在Google Kubernetes Engine这样的托管系统中。 默认命名空间是你创建服务和应用程序的默认位置,如果你不指定namespace参数的话。这个命名空间绝对没有什么特别之处,只是Kubernetes工具是开箱即用的设置使用这个命名空间,而且你无法删除它。 它很适合入门和小型生产系统,我建议不要在大型生产系统中使用它。 这是因为团队很容易在没有意识到的情况下,意外地覆盖或破坏其他服务。 相反,我们应该创建多个命名空间并使用它们来将服务划分为可管理的块。创建命名空间 不要害怕创建命名空间。 它们不会增加性能损失,而且实际上,在许多情况下它们可以提高性能,因为这样的话Kubernetes API使用的是较小的对象集合。可以使用单个命令来创建命名空间。 如果你想创建一个名为test的命名空间,你可以运行如下命令:kubectl create namespace test 或者您可以像创建其他任何Kubernetes资源一样,创建一个YAML文件并应用它。test.yaml:kind: Namespace apiVersion: v1 metadata: name: test labels: name: test kubectl apply -f test.yaml 查看命名空间 您可以使用以下命令查看所有命名空间:kubectl get namespace 如上图,您可以看到三个内置命名空间,以及名为test的新命名空间。 在命名空间中创建资源 让我们看一个简单的YAML,它用来创建一个Pod:apiVersion: v1 kind: Pod metadata: name: mypod labels: name: mypod spec: containers: - name: mypod image: nginx 您可能会注意到在任何地方都没有提到名称空间。 如果在此文件上运行kubectl apply,它将在当前活动的命名空间中创建Pod。 除非您更改它,否则这将是“默认”命名空间。有两种方法可以明确告诉Kubernetes您要在哪个Namespace中创建资源。一种方法是在创建资源时设置namespace标识:kubectl apply -f pod.yaml --namespace=test 您还可以在YAML声明中指定命名空间:apiVersion: v1 kind: Pod metadata: name: mypod namespace: test labels: name: mypod spec: containers: - name: mypod image: nginx 如果在YAML声明中指定命名空间,则将始终在该命名空间中创建资源。如果您尝试使用namespace标志来设置另一个命名空间,则该命令将会失败。在命名空间中查看资源 如果你试图找到你的Pod,你可能会注意到你不能!$ kubectl get pods No resources found. 这是因为所有命令都是针对当前active的命名空间运行的。 要查找Pod,您需要指定namespace。$ kubectl get pods --namespace=test NAME READY STATUS RESTARTS AGE mypod 1/1 Running 0 10s 这可能会很快让人觉得很烦,特别是如果您是一个开发团队的开发人员,该团队使用自己的命名空间来处理所有事情,并且不希望对每个命令都指定namespace。 让我们看看我们如何解决这个问题。管理active命名空间 初始状态下,您的活动命名空间是default。 除非在YAML中指定命名空间,否则所有Kubernetes命令都将使用当前active命名空间。不幸的是,尝试使用kubectl管理您的active命名空间可能会很痛苦。 幸运的是,有一个非常好的工具叫做kubens(由优秀的Ahmet Alp Balkan创建)可以让它变得轻而易举!运行kubens命令时,您应该看到所有命名空间,并突出显示active命名空间: 要将active命名空间切换到test命名空间,请运行: kubens test 现在您可以看到test命名空间处于active状态: 现在,如果你运行kubectl命令,命名空间将是test而不是default! 这意味着您不需要指定命名空间来查看测试命名空间中的Pod。$ kubectl get pods NAME READY STATUS RESTARTS AGE mypod 1/1 Running 0 10m 跨命名空间通信 命名空间彼此“隐藏”,但默认情况下它们不是完全隔离的。一个命名空间中的服务可以与另一个命名空间中的服务进行通信。 这通常非常有用,例如让您的团队的服务(在您的命名空间中)与另一个团队的服务(在另一个命名空间中)进行通信。当您的应用想要访问Kubernetes Service时,您可以使用内置的DNS服务发现,只需将您的应用指向该Service的名称即可。 但是,您可以在多个命名空间中创建具有相同名称的Service!值得庆幸的是,通过使用扩展形式的DNS地址很容易解决这个问题。Kubernetes中的服务使用通用DNS模式公开其endpoint。 它看起来像这样:<Service Name>.<Namespace Name>.svc.cluster.local 通常,您只需要服务名称,DNS将自动解析为完整地址。 但是,如果需要访问另一个命名空间中的服务,则需使用服务名称加上命名空间名称。例如,如果要连接到test命名空间中的database服务,可以使用以下地址:database.test 如果要连接到production命名空间中的database服务,可以使用以下地址:database.production 警告:如果您创建一个映射到“com”或“org”等TLD的命名空间,然后创建一个与网站名称相同的服务,例如“google”或“reddit”,Kubernetes将拦截“google.com“或”reddit.com“的请求并将其发送到您的服务。 这通常对于测试和代理非常有用,但也可以轻松破坏集群中的内容!注意:如果确实要隔离命名空间,则应使用网络策略(Network Policies)来完成此操作。 更多信息请继续关注未来剧集!命令空间粒度 一个常见问题是要创建多少个命名空间以及用于何种目的。 什么是可管理的块? 创建太多的命名空间,它们会让你变得没有效率,但是创建太少,你会错过它们的好处。我认为答案在于您的项目或公司处于什么阶段 ——从小团队到成熟企业,每个阶段都有自己的组织架构。 根据您的具体情况,您可以采用对应的命名空间策略。小团队 在这种情况下,您是一个小团队的一员,该团队正在开发5-10个微服务,可以轻松地进行团队沟通。 在这种情况下,将所有生产服务放到“默认”命名空间是个不错的选择。 根据你的个人喜好,你可能希望有一个production和development的命名空间,但你很有可能是在本地机器上使用类似Minikube搭建的开发环境。快速成长的团队 在这种情况下,您有一个快速发展的团队,正在开发10多个微服务。 您开始将团队分成多个子团队,每个团队都拥有自己的微服务。 虽然每个人都可能知道整个系统是如何工作的,但是与其他人协调每一个变化变得越来越困难。 尝试在本地计算机上启动整个堆栈每天都变得越来越复杂。此时有必要使用多个集群或命名空间进行生产和开发。 每个团队可以选择拥有自己的命名空间,以便于管理。大公司 在一家大公司,并不是每个人都了解其他人。 某个团队正致力于其他团队可能不了解的功能。 团队可能正在使用服契约(service contract)与其他微服务通信(例如,gRPC),也有可能使用服务网格(Service Mesh)进行通信(例如,istio)。 试图在本地运行整个堆栈是不可能的。 强烈建议使用Kubernetes-aware Continuous Delivery系统(例如,Spinnaker)。此时,每个团队肯定需要自己的命名空间。 每个团队甚至可以选择多个名称空间来运行其开发和生产环境。 设置RBAC和ResourceQuotas也是一个好主意。 多个集群开始显得很有意义,但可能不一定是必要的。注意:我将在后面的文章中深入研究gRPC、Istio、Spinnaker、RBAC和Resources!企业 在这种规模下,有些群体甚至不知道其他群体的存在。 某个组也可能是外部公司,服务之间通过标准文档定义的API来通信。 每个小组都有多个拥有一定数量微服务的团队。 这时使用我上面提到的所有工具是必要的。 人们不应该手工部署服务,同时应该被锁定在他们不拥有的命名空间之外。此时,拥有多个集群以减少配置不当的应用程序导致的爆炸半径,以及简化计费和资源管理可能是有意义的。 结论 命名空间可以帮助您组织Kubernetes资源,同时可以提高团队的开发速度。 请继续关注未来的Kubernetes最佳实践剧集,我将向您展示如何锁定命名空间中的资源并为您的群集引入更多安全性和隔离性! 本文转自DockOne-Kubernetes最佳实践S01E02:如何使用命名空间管理Kubernetes资源?
今天是Google Developer Advocate Sandeep Dinesh关于如何充分利用Kubernetes环境的七部分视频和博客系列的第三部分。分布式系统很难管理。 一个重要原因是有许多动态部件都为系统运行起作用。 如果一个小部件损坏,系统必须检测它,绕过它并修复它。 这一切都需要自动完成!健康检查(Health Check)是让系统知道您的应用实例是否正常工作的简单方法。 如果您的应用实例不再工作,则其他服务不应访问该应用或向其发送请求。 相反,应该将请求发送到已准备好的应用程序实例,或稍后重试。 系统还应该能够使您的应用程序恢复健康状态。 默认情况下,当Pod中的所有容器启动时,Kubernetes开始向Pod发送流量,并在崩溃时重新启动容器。 虽然这在开始时可以“足够好”,但您还可以通过创建自定义运行状况检查来使部署更加健壮。 幸运的是,Kubernetes使这个相对简单,所以没有理由不去这么干! 在本期Kubernetes最佳实践中,让我们了解readiness和liveness探针的细节,何时使用哪种探针,以及如何在Kubernetes集群中进行设置。健康检查的类型 Kubernetes为您提供两种类型的健康检查,了解两者之间的差异及其用途非常重要。Readiness Readiness探针旨在让Kubernetes知道您的应用何时准备好其流量服务。 Kubernetes确保Readiness探针检测通过,然后允许服务将流量发送到Pod。 如果Readiness探针开始失败,Kubernetes将停止向该容器发送流量,直到它通过。Liveness Liveness探针让Kubernetes知道你的应用程序是活着还是死了。 如果你的应用程序还活着,那么Kubernetes就不管它了。 如果你的应用程序已经死了,Kubernetes将删除Pod并启动一个新的替换它。健康检查是如何提供帮助的? 让我们看看两个场景,Readiness探针和Liveness探针可以帮助您构建鲁棒性更强的应用程序。Readiness 让我们假设您的应用需要一分钟的时间来预热并开始。 即使该过程已启动,您的服务在启动并运行之前也无法运行。 如果要将此部署扩展为具有多个副本,也会出现问题。 新副本在完全就绪之前不应接收流量,但默认情况下,Kubernetes会在容器内的进程启动后立即开始发送流量。 通过使用Readiness探针,Kubernetes等待应用程序完全启动,然后才允许服务将流量发送到新副本。Liveness 让我们假设另一种情况,你的应用程序有一个令人讨厌的死锁情况,导致它无限期挂起并停止提供请求服务。 因为该服务还在运行,默认情况下Kubernetes认为一切正常并继续向已经broken的Pod发送请求。 通过使用Liveness探针,Kubernetes会检测到应用程序不再提供请求并重新启动有问题的Pod。探针类型 下一步是定义测试Readiness和Liveness的探针。 有三种类型的探测:HTTP、Command和TCP。 您可以使用它们中的任何一个进行Liveness和Readiness检查。HTTP HTTP探针可能是最常见的自定义Liveness探针类型。 即使您的应用程序不是HTTP服务,您也可以在应用程序内创建轻量级HTTP服务以响应Liveness探针。 Kubernetes去ping一个路径,如果它得到的是200或300范围内的HTTP响应,它会将应用程序标记为健康。 否则它被标记为不健康。您可以在此处阅读有关HTTP探针的更多信息。Command 对于Command探针,Kubernetes则只是在容器内运行命令。 如果命令以退出代码0返回,则容器标记为健康。 否则,它被标记为不健康。 当您不能或不想运行HTTP服务时,此类型的探针则很有用,但是必须是运行可以检查您的应用程序是否健康的命令。您可以在此处阅读有关Command探针的更多信息。TCP 最后一种类型的探针是TCP探针,Kubernetes尝试在指定端口上建立TCP连接。 如果它可以建立连接,则容器被认为是健康的;否则被认为是不健康的。如果您有HTTP探针或Command探针不能正常工作的情况,TCP探测器会派上用场。 例如,gRPC或FTP服务是此类探测的主要候选者。您可以在此处阅读有关TCP探针的更多信息。配置探针的初始化延迟时间 可以通过多种方式配置探针。 您可以指定它们应该运行的频率,成功和失败阈值是什么,以及等待响应的时间。 有关配置探针的文档非常清楚地介绍了其不同的选项及功能。但是,使用Liveness探针时需要配置一个非常重要的设置,就是initialDelaySeconds设置。如上所述,Liveness探针失败会导致Pod重新启动。 在应用程序准备好之前,您需要确保探针不会启动。 否则,应用程序将不断重启,永远不会准备好!我建议使用p99延迟启动时间作为initialDelaySeconds,或者只是取平均启动时间并添加一个缓冲区。 随着您应用的启动时间变得越来越快,请确保更新这个数值。结论 大多数人会告诉你健康检查是分布式系统的基本要求,Kubernetes也不例外。 使用健康检查为您的Kubernetes服务奠定了坚实的基础,更好的可靠性和更长的正常运行时间。 值得庆幸的是,Kubernetes让您轻松做到这些! 本文转自DockOne-Kubernetes最佳实践S01E03:Kubernetes集群健康检查最佳实践
很难相信Kubernetes 1.0是三年前才发布的,它现在的影响力已经空前绝后了。 Kubernetes生态系统确实很庞大,很多大企业(比如Bloomberg)和小公司都在使用这一可能会成为IT历史上最为成功的开源软件。对于Kubernetes来说,去年是尤为重要的一年,它成为成熟的平台并且被广泛使用。重要的开发节点如下。2018年三月份,Kubernetes成为有史以来CNCF的第一个“毕业”项目。最近,Kubernetes荣获了2018 OSCON最大影响力奖。Google Cloud Next' 17 Kubernetes:云时代的Linux 在Google Cloud Next' 17大会上,Jim Zemlin,Linux基金会的常务董事说,Kubernetes是“云时代的Linux”,这从一方面反应了Kubernetes是开源世界里有史以来发展最快的项目。The New Stack的最新分析里,基于Cloud Native Computing Foundation(CNCF,云原生计算基金会)的调查数据,表明在容器世界里很难找到比Kubernetes更成功的解决方案:更为重要的是,Kubernetes是几乎所有采用容器技术的场景里占统治地位的解决方案: 的确,这样的数据也只有Linux Kernel的速度能与之媲美了。 持续增长的生态系统 当然,如果没有开源社区的支持,Kubernetes无法如此成功。虽然从技术上看,它很复杂,它解决了很多和容器编排相关的难题,但是它不是银弹。比如,Kubernetes没有单个的存储或者网络解决方案,以及监控,日志和打包管理系统能力。同时,Kubernetes是云原生世界的重要组成部分,它和其他项目密切合作,并且支持很多不同的项目的发展,从而成为生态系统的重要支撑。 Kubernetes是CNCF提供的一项技术。同时,除了Kubernetes,CNCF还托管了20多个不同的项目。虽然这些项目是独立的,并且有各自的管控,发布流程和功能范围,但是他们也会反馈回Kubernetes社区。同时,云原生项目的全球生态系统更为庞大。CNCF在云原生领域已经越来越重要,它几乎包含了如今云原生世界里已有的所有技术。CNCF也提供认证和培训项目。开放的社区 活跃的开发社区助力Kubernetes成为非常成功的开源项目。在Google公司内部创建的几年后,Kubernetes的管控流程也帮助支撑平台的应用。健康的社区是任何成功的开源项目的核心。同时,开源社区不是“静态资产”。要想持续成功并且往前发展,任何开源项目都需要持续增长的贡献者才能生存。这也正是Kubernetes社区在多个项目上工作的原因,一直致力于吸纳贡献者,包括Kubernetes导师计划,Kubernetes贡献手册以及Office Hour,以及“贡献者见面会”,Outreachy以及Google Summer of Code(GSoC),这是针对开源项目新贡献者的最流行最知名的项目。一些杰出的贡献者也会获得业界的认可。版本,特性和路线图 首先,Kubernetes是一种技术。很显然,如果该项目没有给企业提供这么大的收益,这个项目就不会如此成功。Kubernetes每年发布四个最重要的版本,每次都会发布新特性。patch版本(提供安全patch和bug的fix)的发布更为频繁,保持代码基的最新状态。 当然Kubernetes的开发流程肯定并不容易,Kubernetes版本和计划流程则更加复杂。如今,Kubernetes并不仅仅是一个技术项目,它还是一个成功的产品,在关注开发流程本身的同时,也需要同样关注产品路线图对未来的定义流程。 Kubernetes对业界影响巨大。要想让它能够保持与Linux相一致的持续的轨迹速率,基于上述原因,它将需要开发者社区的持续支持,CNCF和其他商业化参与者,都将为平台的发展铺平道路,使其成为IT行业的主流组成部分。 本文转自DockOne-Kubernetes三周年,这仅仅是伟大征途的开始
这篇是Google Developer Advocate Sandeep Dinesh的七部分视频和博客系列的第五部分,介绍如何充分利用您的Kubernetes环境。对于分布式系统,处理故障是关键。 Kubernetes通过监视系统状态并重新启动已停止执行的服务的控制器来解决这个问题。 另一方面,Kubernetes通常可以强制终止您的应用程序,作为系统正常运行的一部分。在本期“Kubernetes最佳实践”中,让我们来看看如何帮助Kubernetes更有效地完成工作并体验下如何减少应用程序停机时间。在容器出现之前的世界中,大多数应用程序在VM或物理机器上运行。 如果应用程序崩溃,启动替换程序需要很长时间。 如果您只有一台或两台机器来运行应用程序,那么这种恢复时间是不可接受的。相反,在崩溃时使用进程级监视来重新启动应用程序变得很常见。 如果应用程序崩溃,进程监视可以捕获退出代码并立即重新启动应用程序。随着像Kubernetes这样的系统的出现,不再需要进程监控系统,因为Kubernetes会重启崩溃的应用程序本身。 Kubernetes使用事件循环来确保容器和节点等资源是健康的。 这意味着您不再需要手动运行这些进程监视器。 如果资源未通过运行状况检查,Kubernetes会自动轮转更换。查看这一集视频,了解如何为您的服务设置自定义健康检查。Kubernetes终止生命周期 Kubernetes不仅可以监控应用程序的崩溃。 它可以创建更多应用程序副本,以便在多台计算机上运行,更新应用程序,甚至可以同时运行多个版本的应用程序! 这意味着Kubernetes可以终止一个完全健康的容器有很多原因。 如果您使用滚动更新更新部署,Kubernetes会在启动新Pod时慢慢终止旧Pod。 如果释放节点,Kubernetes将终止该节点上的所有Pod。 如果节点资源不足,Kubernetes将终止Pod以释放这些资源。 查看第三集,可以了解有关资源的更多信息。重要的是,您的应用程序要优雅地处理终止,以便最终用户受到的影响最小,并且恢复时间尽可能快(Time-to-recovery)!实际上,这意味着您的应用程序需要处理SIGTERM消息并在收到它时开始关闭。 这意味着你需要保存所有需要保存的数据,关闭网络连接,完成剩下的任何工作以及其他类似任务。一旦Kubernetes决定终止您的Pod,就会发生一系列事件。 让我们看看Kubernetes终止生命周期的每一步。1. Pod被设置为“终止”状态,并从所有服务的端点列表中删除 此时,Pod停止获得新的流量。 在Pod中运行的容器不会受到影响。2. preStop Hook被执行 preStop Hook是一个发送到Pod中的容器特殊命令或Http请求。如果您的应用程序在接收SIGTERM时没有正常关闭,您可以使用此Hook来触发正常关闭。 接收SIGTERM时大多数程序都会正常关闭,但如果您使用的是第三方代码或管理系统则无法控制,所以preStop Hook是在不修改应用程序的情况下触发正常关闭的好方法。3. SIGTERM信号被发送到Pod 此时,Kubernetes将向Pod中的容器发送SIGTERM信号。 这个信号让容器知道它们很快就会被关闭。您的代码应该监听此事件并在此时开始干净地关闭。 这可能包括停止任何长期连接(如数据库连接或WebSocket流),保存当前状态或类似的东西。即使您使用preStop Hook,如果您发送SIGTERM信号,测试一下应用程序会发生什么情况也很重要,这样您在生产环境中才不会感到惊讶!4. Kubernetes等待优雅的终止 此时,Kubernetes等待指定的时间称为优雅终止宽限期。 默认情况下,这是30秒。 值得注意的是,这与preStop Hook和SIGTERM信号并行发生。 Kubernetes不会等待preStop Hook完成。如果你的应用程序完成关闭并在terminationGracePeriod完成之前退出,Kubernetes会立即进入下一步。如果您的Pod通常需要超过30秒才能关闭,请确保增加优雅终止宽限期。 您可以通过在Pod的YAML中设置terminationGracePeriodSeconds选项来实现。 例如,要将其更改为60秒:5. SIGKILL信号被发送到Pod,并删除Pod 如果容器在优雅终止宽限期后仍在运行,则会发送SIGKILL信号并强制删除。 此时,所有Kubernetes对象也会被清除。结论 Kubernetes可以出于各种原因终止Pod,并确保您的应用程序优雅地处理这些终止,这是创建稳定系统和提供出色用户体验的核心。 本文转自DockOne-Kubernetes最佳实践S01E05:如何优雅地终止
这篇是Google Developer Advocate Sandeep Dinesh关于如何充分利用Kubernetes环境的七部分视频和博客系列的第四部分。当Kubernetes调度Pod时,容器是否有足够的资源来实际运行是很重要的。 如果大型应用程序被调度到资源有限的节点上,则节点可能会耗尽内存或CPU资源,并且可能会停止工作!应用程序有可能占用比其应占有的资源更多的资源。 这可能是因为一个团队调整了更多的副本,而不是人工减少延迟(嘿,调整更多副本比让你的代码更高效容易得多!),或者一个错误的配置修改使CPU占用100%,进而导致程序失去控制。 无论问题是由糟糕的开发人员,或者糟糕的代码,亦或是运气不好引起的,重要的是你能掌控你自己。在本篇Kubernetes最佳实践中,让我们来看看如何使用资源请求和限制来解决这些问题。请求和限制 请求和限制是Kubernetes用于控制CPU和内存等资源的机制。 请求是保证容器能够得到的资源。 如果容器请求资源,Kubernetes会将其调度到可以为其提供该资源的节点上。 另一方面,限制则是确保容器的资源请求永远不会超过某个值。 容器只允许达到限制设定的资源值,无法获得更多资源。 重要的是要记住,限制永远不会低于请求。 如果你试试这个,Kubernetes将抛出一个错误,不会让你运行容器。请求和限制基于单个容器。 虽然Pod通常包含一个容器,但通常也会看到Pods包含多个容器。 Pod中的每个容器都有自己的限制和请求,但由于Pod总是被认为是一个组,因此您需要将组内每个容器的限制和请求加在一起以获取Pod的聚合值。要控制容器可以拥有的请求和限制,可以在Container级别和Namespace级别设置配额。 如果您想了解有关命名空间的更多信息,请参阅我们博客系列中的上一篇文章!让我们看看这些是如何工作的。容器设置 有两种类型的资源:CPU和内存。 Kubernetes调度程序使用这些来确定运行Pod的位置(即哪个节点)。请点击这里获取这些内容介绍的相关文档。如果您是在Google Kubernetes Engine中运行,则默认名称空间已经为您设置了一些请求和限制。 这些默认设置仅仅适用于Hello World应用,更改成适合您的应用非常重要。 资源的典型Pod spec可能看起来像这样。 这个Pod有两个容器: Pod中的每个容器都可以设置自己的请求和限制,这些都是附加的设置。 因此在上面的示例中,Pod的总请求为500 mCPU,内存为128 MiB,总需求为1 CPU和256MiB。 CPU CPU资源以毫秒定义。 如果您的容器需要运行两个完整的核心,那么您将设置值2000m。 如果您的容器只需要1/4的核心,那么您将设置一个250m的值。关于CPU请求要记住的一件事是,如果您输入的值大于最大节点的核心数,则永远不会调度您的Pod。 假设您有一个需要四个核心的Pod,但您的Kubernetes群集由双核VM组成——您的Pod将永远不会被调度!除非您的应用程序专门用于利用多个核心(科学计算和某些数据库),否则通常最好将CPU请求保持在1或更低,并运行更多副本以扩展它。 这为系统提供了更大的灵活性和可靠性。就CPU限制而言,事情其实很有趣。 CPU被认为是可压缩资源。 如果您的应用程序开始达到您的CPU限制,Kubernetes会开始限制您的容器。 这意味着CPU将受到人为限制,使您的应用程序性能可能更差! 但是,它不会被终止或退出。 您可以使用Liveness探针的运行状况检查来确保性能未受影响。内存 内存资源以字节为单位定义。 通常,你给内存一个mebibyte值(这基本上与兆字节相同),实际上你可以提供从字节到PB的任何单位。和CPU一样,如果您输入的内存请求大于节点上的内存量,则你的Pod永远不会被调度。与CPU资源不同,内存无法压缩。 因为没有办法限制内存使用量,如果容器超过其内存限制,它将被终止。 如果您的Pod由Deployment,StatefulSet,DaemonSet或其他类型的控制器管理,则控制器会轮转替换。节点 请务必记住,您无法设置大于节点提供的资源的请求。 例如,如果您拥有一个双核群集,具有2.5核心请求的Pod则永远不会被调度到这里! 您可以在此处找到Kubernetes Engine VM相关的文档资源。命名空间设置 在一个理想的世界里,Kubernetes的容器设置足以照顾好一切,但这个世界是一个黑暗而可怕的地方。 人们很容易忘记设置资源限制,或者流氓团队可以设置非常高的请求和限制,并占用超过他们公平份额的群集。要防止出现这些情况,可以在命名空间级别设置ResourceQuotas和LimitRanges。ResourceQuotas 创建命名空间后,可以使用ResourceQuotas将其锁定。ResourceQuotas非常强大,但我们只看看如何使用它们来限制CPU和内存资源的使用。资源配额可能如下所示:看一下这个例子,你可以看到有四个部分。 配置每个部分都是可选的。 requests.cpu是命名空间中所有容器的最大组合CPU请求(以毫秒为单位)。 在上面的示例中,您可以拥有50个具有10m请求的容器,5个具有100m请求的容器,甚至一个具有500m请求的容器。 只要命名空间中请求的总CPU和小于500m!requests.memory是命名空间中所有容器的最大组合内存请求。 在上面的示例中,您可以拥有50个具有2MiB请求的容器,5个具有20MiB请求的容器,甚至是具有100MiB请求的单个容器。 只要命名空间中请求的总内存小于100MiB!limits.cpu是命名空间中所有容器的最大组合CPU限制。 它就像requests.cpu,但是这里指的是限制。limits.memory是命名空间中所有容器的最大组合内存限制。 它就像requests.memory,但是同样地这里指的是限制。如果您使用的是生产和开发命名空间(与每个团队或服务的命名空间不同),则常见的模式是在生产命名空间上没有配额,在开发命名空间上则是没有严格的配额。 这使得生产能够在流量激增的情况下获取所需的所有资源。LimitRange 您还可以在命名空间中创建LimitRange。 与命名空间作为整体查看的配额不同,LimitRange适用于单个容器。 这有助于防止人们在命名空间内创建超小容器或超大容器。LimitRange可能如下所示:看一下这个例子,你可以看到有四个部分。 同样,设置每个部分都是可选的。 default section设置容器中容器的默认限制。 如果在limitRange中设置这些值,则任何未明确设置这些值的容器都将被分配默认值。defaultRequest section设置Pod中容器的默认请求。 如果在limitRange中设置这些值,则任何未明确设置这些值的容器都将被分配默认值。max section将设置Pod中容器可以设置的最大限制。 默认部分不能高于此值。 同样,在容器上设置的限制不能高于此值。 请务必注意,如果设置了此值且默认部分未设置,则任何未自行显式设置这些值的容器都将被指定为最大值作为限制。min section设置Pod中容器可以设置的最小请求。 defaultRequest部分不能低于此值。 同样,在容器上设置的请求也不能低于此值。 请务必注意,如果设置了此值且defaultRequest部分未设置,则min值也将成为defaultRequest值。Kubernetes Pod的生命周期 Kubernetes调度程序使用这些资源请求来运行您的工作负载。 了解其工作原理非常重要,这样您才能正确调整容器。假设您想要在群集上运行Pod。 假设Pod Spec有效,Kubernetes调度程序将使用round-robin负载平衡来选择节点来运行您的工作负载。注意:例外情况是,如果使用nodeSelector或类似机制强制Kubernetes在特定位置安排Pod。 使用nodeSelector时仍会发生资源检查,但Kubernetes只会检查具有所需标签的节点。然后Kubernetes检查节点是否有足够的资源来满足Pod容器上的资源请求。 如果没有,则移动到下一个节点。如果系统中的所有节点都没有剩余资源来填充请求,那么Pod将进入“挂起”状态。 通过使用节点自动缩放器(Node Autoscaler)等Kubernetes Engine功能,Kubernetes Engine可以自动检测此状态并自动创建更多节点。 如果有多余的容量,自动缩放器(autoscaler)也可以减少和删除节点,以节省您的钱!但限制怎么处理? 如您所知,限制必须高于请求。 如果您有一个节点,其中所有容器限制的总和实际上高于机器上可用的资源,该怎么办?在这一点上,Kubernetes进入了一种被称为“过度使用状态”(overcommitted state)的状态。这是事情变得有趣的地方。 由于CPU可以被压缩,因此Kubernetes将确保您的容器获得他们请求的CPU并且将限制其余部分。 内存无法压缩,因此如果Node耗尽内存,Kubernetes需要开始决定终止哪些容器。让我们想象一下我们有一台机器内存不足的情况。 Kubernetes会做什么?注意:Kubernetes 1.9及以上版本如下。 在以前的版本中,它使用稍微不同的过程。 请参阅此文档以获得深入的解释。Kubernetes寻找使用比他们要求的更多资源的Pod。 如果您的Pod的容器没有请求,那么默认情况下它们使用的数量超过了他们的要求,因此这些是终止的主要候选者。 其他主要候选人是已经超过他们的要求但仍然在他们的限制之下的容器。如果Kubernetes发现多个已经超过其请求的Pod,则它将按Pod的优先级对这些进行排名,并首先终止最低优先级的Pod。 如果所有Pod具有相同的优先级,Kubernetes将终止最多资源请求的Pod。在非常罕见的情况下,Kubernetes可能会被迫终止仍在其请求范围内的Pod。 当关键系统组件(如kubelet或Docker)开始占用比为它们保留的资源更多的资源时,就会发生这种情况。结论 虽然您的Kubernetes集群可以在不设置资源请求和限制的情况下正常工作,但随着团队和项目的增长,您将开始遇到稳定性问题。 添加对您的Pod和命名空间的请求和限制只需要一点额外的努力,并且可以避免您遇到许多令人头疼的问题! 本文转自DockOne-Kubernetes最佳实践(四):资源请求和限制
这是Google Developer Advocate Sandeep Dinesh关于如何充分利用Kubernetes环境的七部分视频和博客系列的第六部分。如果您像大多数Kubernetes用户一样,您可能会使用群集外的服务。 例如,您可能使用Twillio API发送短信,或者使用Google Cloud Vision API进行图像分析。如果您的不同环境中的应用程序连接到同一外部端点,并且没有计划将外部服务引入Kubernetes集群,则可以直接在代码中使用外部服务端点。 但是,在许多情况下情况并非如此。 一个很好的例子是数据库。 虽然某些云原生数据库(如Cloud Firestore或Cloud Spanner)使用单个端点进行所有访问,但大多数数据库都有针对不同实例的单独端点。此时,您可能认为找到端点的一个好方法是使用ConfigMaps。 只需将端点地址存储在ConfigMap中,并在代码中将其用作环境变量。 虽然这种解决方案有效,但也存在一些缺点。 您需要修改部署以包含ConfigMap并编写其他代码以从环境变量中读取。 但最重要的是,如果端点地址发生更改,则可能需要重新启动所有正在运行的容器以获取更新的端点地址。在本期“Kubernetes最佳实践”中,让我们学习如何利用Kubernetes的内置服务发现机制来运行集群外部的服务,就像集群内的服务一样! 这使您可以在dev和prod环境中进行校验,最终只需要在集群中迁移服务,而根本不必更改代码。场景1:具有IP地址的群集外的数据库 一种非常常见的情况是您托管自己的数据库,但在群集外部执行此操作,例如在Google Compute Engine实例上。 如果您在Kubernetes内部和外部运行某些服务,或者需要比Kubernetes允许的更多自定义或控制,这是非常常见的。您希望在某些时候,可以迁移集群内的所有服务,但在此之前,您将生活在混合世界中。 值得庆幸的是,您可以使用静态Kubernetes服务来缓解一些痛苦。在此示例中,我使用Cloud Launcher创建了一个MongoDB服务器。 由于它是在与Kubernetes集群相同的网络(或VPC)中创建的,因此可以使用高性能内部IP地址进行访问。 在Google Cloud中,这是默认设置,因此您无需任何特殊配置。现在我们有了IP地址,第一步是创建服务: kind: Service apiVersion: v1 metadata: name: mongo Spec: type: ClusterIP ports: - port: 27017 targetPort: 27017 您可能会注意到此服务没有Pod选择器。 这会创建一个服务,但它不知道在哪里发送流量。 这允许您手动创建将从此服务接收流量的Endpoints对象。kind: Endpoints apiVersion: v1 metadata: name: mongo subsets: - addresses: - ip: 10.240.0.4 ports: - port: 27017 您可以看到端点手动定义数据库的IP地址,并使用与服务相同的名称。 Kubernetes使用端点中定义的所有IP地址,就像它们是常规的Kubernetes Pod一样。 现在,您可以使用简单的连接字符串访问数据库:mongodb://mongo 根本不需要在代码中使用IP地址! 如果将来IP地址发生变化,您可以使用新IP地址更新端点,并且您的应用程序无需进行任何更改。 场景2:具有URI的远程托管数据库 如果您使用来自第三方的托管数据库服务,则可能会为您提供可用于连接的统一资源标识符(URI)。 如果他们为您提供IP地址,则可以使用方案1中的方法。在这个例子中,我有两个在mLab上托管的MongoDB数据库。 其中一个是我的开发数据库,另一个是生产。这些数据库的连接字符串如下: mongodb://<dbuser>:<dbpassword>@ds149763.mlab.com:49763/dev mongodb://<dbuser>:<dbpassword>@ds145868.mlab.com:45868/prod mLab为您提供动态URI和动态端口,您可以看到它们都是不同的。 让我们使用Kubernetes为这些差异创建一个抽象层。 在这个例子中,让我们连接到dev数据库。您可以创建一个叫“ExternalName”的Kubernetes服务,它为您提供静态Kubernetes服务,将流量重定向到外部服务。 此服务在内核级别执行简单的CNAME重定向,因此对性能的影响非常小。该服务的YAML如下所示:kind: Service apiVersion: v1 metadata: name: mongo spec: type: ExternalName externalName: ds149763.mlab.com 现在,您可以使用更简化的连接字符串:mongodb://<dbuser>:<dbpassword>@mongo:<port>/dev 由于“ExternalName”使用CNAME重定向,因此无法进行端口重新映射。 对于具有静态端口的服务,这可能没问题,但不幸的是,在示例中它的端口是动态的。 mLab的免费套餐为您提供动态端口号,您无法更改它。 这意味着您需要为dev和prod使用不同的连接字符串。但是,如果你可以获得IP地址,那么你可以进行端口重映射,我将在下一篇中解释。场景3:具有URI和端口重新映射的远程托管数据库 虽然CNAME重定向适用于每个环境具有相同端口的服务,但在每个环境的不同端点使用不同端口的情况下,它略显不足。 谢天谢地,我们可以使用一些基本工具解决这个问题。第一步是从URI获取IP地址。如果对URI运行nslookup,hostname或ping命令,则可以获取数据库的IP地址。您现在可以创建重新映射mLab端口的服务以及此IP地址的端点。 kind: Service apiVersion: v1 metadata: name: mongo spec: ports: - port: 27017 targetPort: 49763 --- kind: Endpoints apiVersion: v1 metadata: name: mongo subsets: - addresses: - ip: 35.188.8.12 ports: - port: 49763 注意:URI可能使用DNS对多个IP地址进行负载均衡,因此如果IP地址发生变化,此方法可能存在风险! 如果从上面的命令中获得多个IP地址,则可以将所有这些地址包含在端点YAML中,并且Kubernetes将对所有IP地址的流量进行负载均衡。这样,您无需指定端口即可连接到远程数据库。 Kubernetes服务透明地重新映射端口!mongodb://<dbuser>:<dbpassword>@mongo/dev 结论 将外部服务映射成内部服务使您可以灵活地将这些服务引入集群,同时最大限度地减少重构工作。 即使你今天不计划这么做,那你也永远不知道明天会发生什么! 此外,它还可以更轻松地管理和了解您的组织正在使用哪些外部服务。如果外部服务具有有效的域名并且您不需要端口重新映射,则使用“ExternalName”服务类型是将外部服务映射到内部服务的简单快捷的方法。 如果您没有域名或需要进行端口重新映射,只需将IP地址添加到端点并使用它。 本文转自DockOne-Kubernetes最佳实践S01E06:Kubernetes的内置服务发现机制运行集群外部服务
每个人都知道,保持应用程序最新以及优化安全性和性能是一种很好的做法。 Kubernetes和Docker可以更轻松地执行这些更新,因为您可以使用更新构建新容器并相对轻松地部署它。就像您的应用程序一样,Kubernetes不断获得新功能和安全更新,因此底层节点和Kubernetes基础架构也需要保持最新。在本期Kubernetes最佳实践中,让我们来看看Google Kubernetes Engine如何让您的Kubernetes集群轻松升级!集群的两个部分:Master和Node 在升级群集时,需要更新两个部分:Mater和Node。 需要首先更新Master,Node随后。 让我们看看如何使用Kubernetes Engine升级它们。零停机更新Master Kubernetes Engine会在发布点发布时会自动升级Master,但通常不会自动升级到新版本(例如,1.7到1.8)。 准备好升级到新版本后,只需单击Kubernetes Engine控制台中的升级主按钮即可。但是,您可能已经注意到该对话框显示以下内容: “更改主版本可能会导致几分钟的控制平面停机。 在此期间,您将无法编辑此群集。”当主服务器关闭进行升级时,deployments,services将继续按预期工作。 但是,任何需要Kubernetes API的东西都会停止工作。 这意味着kubectl将停止工作,那些使用Kubernetes API获取有关群集信息的应用程序将停止工作,您基本上无法在集群升级时对群集进行任何更改。那么如何更新Master而不会导致停机呢?具有Kubernetes Engine区域集群的高可用Masters 虽然标准的zonal Kubernetes Engine集群只有一个Master支持它们,但您可以创建regional集群,提供多区域,高可用性的Master(注意:Kubernetes Engine区域集群最近普遍可用)。创建群集时,请务必选择regional选项: 就是这样! Kubernetes引擎自动在三个zone中创建Node和Master,Master位于负载平衡的IP地址后面,因此Kubernetes API将在升级期间继续工作。 零停机更新Node 升级节点时,您可以使用几种不同的策略。 我想关注两个: 滚动更新 使用节点池迁移 滚动更新 更新Kubernetes Node的最简单方法是使用滚动更新。 这是Kubernetes Engine用于更新Node的默认升级机制。滚动更新以下列方式工作。 一个接一个,一个释放,一个锁存,直到该Node上不再运行Pod。 然后删除该Node,并使用更新的Kubernetes版本创建新Node。 该Node启动并运行后,将更新下一个Node。 这一直持续到所有Node都更新为止。您可以通过在节点池(Node Pool)上启用自动节点升级,让Kubernetes Engine完全为您管理此过程。 如果您不选择此选项,Kubernetes Engine仪表板会在升级可用时提醒您: 只需单击该链接,然后按照提示开始滚动更新。 警告:确保您的Pod由ReplicaSet,Deployment,StatefulSet或类似的东西管理。 独立Pod不会被重新调度!虽然在Kubernetes Engine上执行滚动更新很简单,但它有一些缺点。一个缺点是您在群集中获得的节点容量少一个。 通过扩展节点池以添加额外容量,然后在升级完成后将其缩小,可以轻松解决此问题。滚动更新的完全自动化特性使其易于操作,但您对该过程的控制较少。 如果出现问题,还需要时间回滚到旧版本,因为您必须停止滚动更新然后撤消它。使用节点池(Node Pool)迁移 您可以创建新节点池,等待所有节点运行,然后一次在一个节点上迁移工作负载,而不是像滚动更新那样升级“活跃的”节点池。我们假设我们的Kubernetes集群现在有三个VM。 您可以使用以下命令查看节点:kubectl get nodes NAME STATUS AGE gke-cluster-1-default-pool-7d6b79ce-0s6z Ready 3h gke-cluster-1-default-pool-7d6b79ce-9kkm Ready 3h gke-cluster-1-default-pool-7d6b79ce-j6ch Ready 3h 创建新的节点池 要创建名为pool-two的新节点池,请运行以下命令:gcloud container node-pools create pool-two 注意:请记住此自定义命令,以便新节点池与旧池相同。 如果需要,还可以使用Kubernetes Engine GUI创建新节点池。现在,如果您检查节点,您会注意到有三个节点具有新池名称:$ kubectl get nodes NAME STATUS AGE gke-cluster-1-pool-two-9ca78aa9–5gmk Ready 1m gke-cluster-1-pool-two-9ca78aa9–5w6w Ready 1m gke-cluster-1-pool-two-9ca78aa9-v88c Ready 1m gke-cluster-1-default-pool-7d6b79ce-0s6z Ready 3h gke-cluster-1-default-pool-7d6b79ce-9kkm Ready 3h gke-cluster-1-default-pool-7d6b79ce-j6ch Ready 3h 但是,Pod仍然在旧节点上! 让我们来迁移Pod到新节点上。释放旧节点池 现在我们需要将工作负载迁移到新节点池。 让我们以滚动的方式一次迁移一个节点。首先,cordon(隔离)每个旧节点。 这将阻止新的Pod安排到它们上面。kubectl cordon <node_name> 一旦所有旧节点都被隔离,就只能将Pod调度到新节点上。 这意味着您可以开始从旧节点中删除Pod,Kubernetes会自动在新节点上调度它们。警告:确保您的Pod由ReplicaSet,Deployment,StatefulSet或类似的东西管理。 独立Pod不会被重新调度!运行以下命令以释放每个节点。 这将删除该节点上的所有Pod。kubectl drain <node_name> --force 释放节点后,确保新的Pod已启动并运行,然后再转到下一个节点。如果您在迁移过程中遇到任何问题,请取消旧池的保护,然后隔离并释放新池。 Pod会被重新调度回旧池。删除旧节点池 一旦所有Pod安全地重新调度,就可以删除旧池了。将default-pool替换为要删除的池。gcloud container node-pools delete default-pool 您刚刚成功更新了所有节点!结论 通过使用Kubernetes Engine,您只需点击几下即可使Kubernetes集群保持最新状态。如果您没有使用像Kubernetes Engine这样的托管服务,您仍然可以将滚动更新或节点池方法用在您自己的集群升级上。 不同之处在于您需要手动将新节点添加到集群中,并自行执行主升级,这可能很棘手。我强烈建议使用Kubernetes Engine regional集群来实现高可用Master和自动节点升级,以获得无烦恼的升级体验。 如果您需要对节点更新进行额外控制,则使用节点池可以为您提供该控制,而不会放弃Kubernetes Engine为您提供的托管Kubernetes平台的优势。到这里,我们要结束关于Kubernetes最佳实践的系列文章的第一季了。 如果您对希望我解决的其他主题有所了解,可以在Twitter上找到我。 本文转自DockOne-Kubernetes最佳实践S01E07:零停机更新Kubernetes集群
我们希望微服务是可复制的,可替换的工作节点,这样可以轻松进行升级或降级,同时无需任何停机时间,并花费最少代价的管理。我们可以说我们希望他们成为我们的小黄人(minions)。本文我们将通过一个简单的例子来了解Kubernetes可以通过创建和编排一群“小黄人"来为我们做些什么。您可以与本文一起编码或从此处克隆项目。先决条件 需要将使用Docker容器化微服务以便在Kubernetes中运行它们。我们将使用Minikube,而不是使用云托管的Kubernetes,以便可以在本地沙箱运行。目的 我们的小黄人军团将是Java微服务。我们希望军团中有不同类型的工作角色,以便能够了解Kubernetes可以为我们做些什么。因此,我们的目标是让每个微服务都响应一个简单的http请求,其响应如下:使用ASCII字来表示minion的类型。 构建Java Minion服务 我们可以通过Spring Boot Web应用程序来启动我们的微服务,程序使用具有Web启动依赖性的Spring Initializr初始化:在项目中,创建一个使用@RestController注释的Controller来处理请求。使用@RequestMapping(method = GET)来提供响应主体。所以首先我们可以这样做: @RequestMapping( method=GET) @ResponseBody public String minion() throws UnknownHostException { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("Host: ").append(InetAddress.getLocalHost().getHostName()).append("<br/>"); return stringBuilder.toString(); } 但这并不能完全满足需求。我们可以输出ASCII字,但选择哪种minion类型?为此可以使用一个技巧。创建一个可以采用我们选择的任何minion类型的应用程序。要做到这一点,需要它包含一个ASCII艺术字库。因此,我们创建了一个名为MinionsLibrary的类,使用@Component注解,在内部我们创建了一个地图,我们使用此博客中的一些minions初始化:@Component public class MinionsLibrary { private Map<String,String> map = new HashMap<>(); public MinionsLibrary(){ map.put("one-eyed-minion",<COPY-PASTE MINION ASCII ART HERE>); map.put("two-eyed-minion",<COPY-PASTE MINION ASCII ART HERE>); map.put("sad-minion",<COPY-PASTE MINION ASCII ART HERE>); map.put("happy-minion",<COPY-PASTE MINION ASCII ART HERE>); } } 或者你可以从https://github.com/ryandawsonu ... /demo获取。然后告诉微服务是哪种minion类型。使用Spring应用程序的名称属性(我们稍后可以使用Docker环境变量设置)来执行此操作。它还将帮助我们稍后在响应中显示我们的应用程序版本,所以现在的Controller变为:@RestController public class Controller { private final String version = "0.1"; private MinionsLibrary minionsLibrary; @Value("${spring.application.name}") private String appName; public Controller(MinionsLibrary minionsLibrary){ this.minionsLibrary=minionsLibrary; } @RequestMapping( method=GET) @ResponseBody public String minion() throws UnknownHostException { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("Host: ").append(InetAddress.getLocalHost().getHostName()).append("<br/>"); stringBuilder.append("Minion Type: ").append(appName).append("<br/>"); stringBuilder.append("IP: ").append(InetAddress.getLocalHost().getHostAddress()).append("<br/>"); stringBuilder.append("Version: ").append(version).append("<br/>"); stringBuilder.append(minionsLibrary.getMinion(appName)); return stringBuilder.toString(); } } 现在选择'image'包以匹配应用程序名称,该名称将是minion类型名称(例如'单眼小黄人')。容器化并部署 需要为我们的应用程序创建一个Docker镜像。我们想在Docker镜像中构建可执行的jar,然后在容器启动时启动Java应用程序。可以使用多阶段Docker构建来完成此任务。 Dockerfile是:FROM maven:3.5-jdk-8 as BUILDMINION COPY src /usr/src/myapp/src COPY pom.xml /usr/src/myapp RUN mvn -f /usr/src/myapp/pom.xml clean package -DskipTests FROM openjdk:alpine COPY --from=BUILDMINION /usr/src/myapp/target/*.jar /maven/ CMD java $JAVA_OPTS -jar maven/*.jar 从开始到'FROM openjdk:alpine'是构建JAR,然后jar包被拷贝到基于轻量的openjdk:alpine镜像的下一阶段构建。使用JAVA_OPTS参数来限制程序的内存占用(关于降低内存,可以参考该文章)。然后使用命令“docker build . -t minion”构建一个镜像。通过创建Kubernetes部署文件来部署它。我们称之为“minion-army.yml”。这将包含每个minion类型的条目。这是其中的一个minion类型:apiVersion: apps/v1beta1 kind: Deployment metadata: name: one-eyed-minion labels: serviceType: one-eyed-minion spec: replicas: 2 template: metadata: name: one-eyed-minion labels: serviceType: one-eyed-minion spec: containers: - name: one-eyed-minion image: minion:latest imagePullPolicy: Never ports: - containerPort: 8080 env: - name: JAVA_OPTS value: -Xmx64m -Xms64m - name: SPRING_APPLICATION_NAME value: "one-eyed-minion" --- apiVersion: v1 kind: Service metadata: name: one-eyed-minion-entrypoint namespace: default spec: selector: serviceType: one-eyed-minion ports: - port: 8080 targetPort: 8080 nodePort: 30080 type: NodePort 请注意,“SPRING_APPLICATION_NAME”变量会自动与spring.application.name属性匹配,以便此minion服务成为单眼小黄人类型。有两个这种minion类型的实例(副本)可用,Kubernetes服务将自动将请求路由到其中一个或另一个。该服务将通过Minikube以端口30080暴露对外提供服务 (对于真正的Kubernetes,该服务的这一点会有所不同,因为我们使用LoadBalancer而不是NodePort,并且不会限制在minikube端口范围)。服务将使用与服务匹配的Pod来处理它。我们将为每种类型提供一种服务。minion类型的部署将创建两个Pod。每个人都是这种类型的工作节点。我们可以为每个minion类型重复上面的配置,每次增加外部端口号以便使用不同的端口,或者我们可以使用这个GitHub存储库,它还具有其他配置,可以在不停机的情况下进行小型版本升级(如果我们使用Helm,我们可以避免重复,但我们不想添加比我们更多的工具)。创建军团 首先启动mMinikube:minikube start --memory 4000 --cpus 3 等待它开始,然后将您的Docker registry链接到Minikube,并为Minikube构建minion图像:eval $(minikube docker-env) docker build . -t minion 然后我们可以部署军团:kubectl create -f minion-army.yml 并看到类型:open http://$(minikube ip):30080 open http://$(minikube ip):30081 open http://$(minikube ip):30082 open http://$(minikube ip):30083 每个看起来都很像文章开头的快乐小黄人页面。我们可以通过“kubectl get pods”来查看整个军队,或者“minikube dashboard”进到Pods页面:创造更多的部队 我们可以在minikube dashboard的Deployments部分下创建更多特定类型的minions:一个小黄人倒下,另一个替补他的位置 假设从浏览器点击快乐小黄人服务时得到的:如果杀死“happy-minion-58c9c46d67-j84s9”会发生什么?可以通过仪表板的Pod部分删除: kubectl delete pod happy-minion-58c9c46d67-j84s9 如果你在浏览器中点击刷新几次(杀死小黄人兵可能需要一点时间),你会看到该服务会使用该类型的另一个小黄人。如果浏览Pod部分,您将看到Kubernetes创建了一个新的Pod来代替您删除的那个,以保证该部署中有两个节点。Minion升级 我们还可以为小黄人进行滚动升级。为此,我们应该在minions-army.yml文件的每个Deployment部分的'spec'部分下面(它可以直接位于同一级别的'replicas'下面):minReadySeconds: 10 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 然后将Controller类中的版本更改为0.2,保存它然后执行:docker build . -t minion:0.2 然后打开minion-army.yml并找到 - 用“0.2”替换所有“最新”,保存更改并执行:kubectl apply -f minion-army.yml --record 刷新其中一个minion类型的浏览器,以查看版本更改是否与kubectl rollout status部署中看到的内容一致,其中<deployment_name>是minion类型(例如one-eyed-minion)。小黄人回滚 要查看已部署的历史记录,请执行kubectl rollout history deployment <deployment_name>,回滚执行 kubectl rollout undo deployment <deployment_name> --to-revision = 1(可能需要一段时间)。销毁军团 用以下方法摧毁军队:kubectl delete -f minion-army.yml 用“minikube stop”停止minikube。 本文转自DockOne-p2p-Java程序员如何快速理解Kubernetes
K8s的出现为PaaS行业的发展打了一针兴奋剂,Docker+k8s的技术路线已经成为了容器云的主流。尤其针对大流量,大弹性的应用场景来说,k8s将其从繁杂的运维、部署工作中彻底拯救出来。然而事情往往没有那么简单而美好,当我们使用k8s去管理一些大规模集群的时候,我们会发现有很多问题等待我们解决。比如,当集群中的所有节点同时去镜像仓库拉取镜像的时候,这种大规模并发很有可能阻塞仓库的出口,导致大家的下载速度都慢得难以忍受,这就是k8s镜像分发的阿喀琉斯之踵。我们当然可以采取镜像仓库集群化的方法来缓解这个瓶颈,然而这种做法始终是治标不治本,此外还会造成维护成本升高,以及镜像同步时效性差等问题。 那么如何解决这个让人困扰的问题呢? p2p看起来是一个好办法,去中心化的做法,不但可以降低对仓库节点的依赖,同时也可以为用户节省宝贵的外网流量。但是在k8s集群中拉取镜像的场景内使用p2p技术要面对一系列困难,包括节点间数据安全性的问题、无预热前提下的大规模并发拉取、非侵入式将集群改造成p2p网络等。经过不断的探索与实验,我们--华为云容器镜像服务,终于摸索出了一套在k8s集群中完美整合p2p下载的方案。该方案的提出有效的解决了我们遇到的实际问题,提高了镜像下载速度,并为客户节省了大量带宽资源。p2p改造后的集群下载测试结果如上图所示,我们限制了镜像仓库的下载带宽,并测试了200节点与500节点情况下,不同下载方式的状况。随着节点数量的增大,并发量触及到镜像仓库下载瓶颈的可能性也随之增大,然而使用了p2p下载方式改造的k8s集群并没有受到该瓶颈的制约,表现远强于传统下载方式。尤其是对于较大的镜像,差距更为明显。 我们是如何把p2p下载整合到k8s集群内的呢?在华为云容器镜像服务中我们是这样做的,我们改变了所有集群节点去镜像仓库拉取镜像的传统做法。在用户节点中注入peer客户端,使用peer客户端截取docker client的拉取镜像请求,并将请求重定向。经过修改后只有部分节点(约10%)真正到达镜像仓库去获取镜像数据,剩余节点转为自集群内的其它节点获取镜像。要实现这个方案,主要的开发点有三处:需要对镜像仓库进行改造,部署并改造新的tracker服务器,以及在用户节点注入peer客户端。对镜像仓库的改造 要想实现集群的p2p下载功能,所下载的文件必须包含有种子,因此要对已有的镜像仓库进行改造。当用户push镜像到镜像仓库时,仓库自动计算镜像所有layer的SHA值,并为每一个layer生成一个种子文件。为了保证集群的安全性验证,仓库为每个接入的peer下发由仓库私钥签名的jwt token,这个token将会进入集群网络,并保证集群节点的认证安全。部署改良后的tracker服务器传统的Tracker服务器的主要功能是为所有peer客户端提供peer列表。针对k8s集群场景,我们所搭建的Tracker服务器除了负责分发peer列表外,还增加了许多其它特性。1. 判断peer节点的集群归属,因为tracker服务器可能对多个集群进行服务,而每个集群中的节点网络相对独立,因此tracker负责记录peer节点的集群归属信息很有必要,这样可以避免分发出网络不互通的peer列表。2. Tracker服务器负责监控每个peer的状态,自动将部分下载完成的peer节点资源释放,因为集群节点属于客户资源,一旦完成任务应该尽快将资源释放出去。当Tracker判断当前集群中完成节点比例已足够支撑下载时,会指示部分已完结节点终结任务释放资源,并将其移除出任务列表。当所有peer节点均已完成下载,而且一段时间内没有新的下载请求进入,那么Tracker会指示将全部集群节点资源释放。3. Tracker按比例(约10%)指定部分peer节点到镜像仓库下载,以此将数据带入整个集群网络。4. Tracker提供peer节点的安全性验证,保证同属于一个集群并下载同一个资源的peer节点有获取peer列表的权限。容器化的peer客户端在这个解决方案中,我们创造性的将peer客户端容器化,并通过华为云容器引擎(CCE)的插件功能,将peer容器分发给整个集群。peer容器可以通过修改docker客户端代理的方式,拦截下载镜像请求的相关接口,将从镜像仓库下载操作转化为p2p网络下载操作,并把获取到的镜像数据归还给docker客户端,以此快速的把镜像数据分发到整个集群网络。该peer客户端还具有以下特性:• 根据tracker的分配,修改下载地址,小部分从镜像仓库下载,大部分从集群网络中下载。• 在peer客户端的tcp握手协议中注入jwt token安全校验,保证节点数据安全,防止假冒节点盗取数据。• Peer客户端采用优先级下载方式,能够保证在下载过程中就可以将数据传输给docker客户端,而不是当数据全部下载完全后再传输给docker,以此最大限度的节省下载实际。经过p2p方案改造后的k8s集群,下载速度得到了提升的同时,也为客户节约了大量带宽。当前这个特性已经在菊厂云上开放给用户使用,感兴趣的同学们快来体验吧。 本文转自DockOne-p2p-如何拯救k8s镜像分发的阿喀琉斯之踵
有一个问题就是现在我的业务分配在多个Pod上,那么如果我某个Pod死掉岂不是业务完蛋了,当然也会有人说Pod死掉没问题啊,K8S自身机制Deployment和Controller会动态的创建和销毁Pod来保证应用的整体稳定性,那这时候还会有问题,那就是每个Pod产生的IP都是动态的,那所以说重新启动了我对外访问的IP岂不是要变了,别急,下面我们来解决下这个问题。可以通过Service来解决如上所遇到的问题,那么什么是Service呢?Service是kubernetes最核心的概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求进行负载分发到后端的各个容器应用上。 简单来说Service就是一个把所有Pod都池化的一个组,然后对外统一固定一个IP,具体是哪些Pod可以通过之前介绍到的Label标签来进行设置。在创建Service之前先看看我们在部署应用的时候创建的nginx.yml刚在有提到,就是说哪些Pod被Service池化是根据Label标签来的,那么可以看到图上所标识的nginx字样,后面我们创建Service会用到。 下面来创建个Service看看解释下这个yml文件哈,其意思就是说呢V1是api的版本,然后Kind表示当前资源类型为Service,selector选择之前Label标签为nginx的Pod作为Service池化的对象,最后说的是把Service的8080端口映射到Pod的80端口。 执行kubectl apply创建Servie nginx-svc1 kubcetl apply –f nginx-svc.yml创建完成之后nginx-svc会分配到一个cluster-ip,可以通过该ip访问后端nginx业务。创建完之后可以查看service详情查看后端都包含哪些pod1 kubectl describe service nginx-svc那它是怎么实现的呢?答案是通过iptables实现的地址转换和端口转换,可以用iptables-save查看。 那这时候有人说了,还是不能外网访问啊,别急下面我们来进行外网地址访问设置。在实际生产环境中,对Service的访问可能会有两种来源:Kubernetes集群内部的程序(Pod)和Kubernetes集群外部,为了满足上述的场景,Kubernetes service有以下三种类型:1.ClusterIP:提供一个集群内部的虚拟IP(与Pod不在同一网段),以供集群内部的pod之间通信使用。2.NodePort:在每个Node上打开一个随机端口并且每个Node的端口都是一样的,通过<NodeIP>:NodePort的方式Kubernetes集群外部的程序可以访问Service。3.LoadBalancer:利用Cloud Provider特有的Load Balancer对外提供服务,Cloud Provider负责将Load Balancer的流量导向Service。本篇文章我们着重讲下第二种方式,也就是NodePort方式,修改nginx-svc.yml文件,也就是刚才前面创建的Service文件,相信细心的同学会发现在之前截图的时候已经做好了NodePort,因为我的环境已经配置好了所以这样就不在截图了,配置很简单,可以网上看下截图,就是添加一个type:NodePort,然后重新创建下nginx-svc,命令的话和创建的命令一样,我们来看看创建完事的结果。如果刚开始你没有设置NodePort这个type的时候在端口那只会显示一个8080端口,而设置了之后会看到多了一个端口也就是31337,那8080大家斗志是cluster-ip监听的端口,那31337就是在节点上新起的一个端口,Kubernetes会从30000~32767中分配一个可用的端口,每个节点都会监听这个端口,并转发给Service,也就是防止说一个节点挂了影响访问。可能有人会问了,说这里的Service可不可以固定?当时可以了,可以在Service nginx-svc.yml文件里面添加一个nodeport。 最后我们可以验证下,我这里就不截图了,太长了。 curl x.x.x.x:31337那OK可能会有人说这个访问是随机的还是负载均衡的?答案是负载均衡的,依旧是采用iptables实现的,感兴趣的可以自己研究下iptables里面做的那些规则,这里就不再赘述了。 本文转自DockOne-深入玩转K8S之外网如何访问业务应用
Rook是一款运行在Kubernetes集群中的存储服务编排工具,在0.8版本中,Rook已经变成Beta发行版,如果还没有尝试过Rook,可以现在尝鲜。Rook是什么,为什么很重要?Ceph运行在Kubernetes集群中很久了,为什么要有这么大的变动?如果以前玩过Ceph集群,肯定深知维护Ceph集群的复杂性,Rook就是为此而生,使用Kubernetes分布式平台简化大量针对Ceph存储的操作和维护工作。Rook通过一个操作器(operator)完成后续操作,只需要定义需要的状态就可以了。Rook通过操作器监控状态需求变化,并将配置文件分配到集群上生效。操作器关注包括各种集群运行和启停所需的状态信息。本文将详细讨论这些细节。Mons Ceph集群中最重要的信息是Mons的quorum,一般集群中有有三个Mons,保持高可用以及quorum可用性以防数据不可用,当创建集群是,Rook将会: 启动特定节点上的Mons,确保他们在quorum中 定期确定Mons状态,确保他们在quorum中 如果某个Mon出现故障,并且没有重新启动,操作器会往quorum中添加一个新的mon,并将失效的移除quorum 更新Ceph客户端和Daemons的IP地址 Mgr Mgr是一个无状态服务,提供集群信息。除了启动核心功能外,Rook还参与配置其它两个Mgr插件: 搜集Prometheus状态 启动Ceph面板,并启动服务点 OSDs 集群中最具挑战的是OSD部分,存储部分的核心部件。大规模集群会在上线前大量使用OSD。Rook会根据如何使用,以两种模式配置他们,可以参考更多OSD专题。完全自动化 最简单使用方式就是“使用所有资源”模式。意味着操作者自动在所有节点上启动OSD设备,Rook会用如下标准监控并发现可用设备: 设备没有分区 设备没有格式化的文件系统 Rook不会使用不满足以上标准的设备。操作完毕后(一般要几分钟),就拥有一套OSD配置完毕的存储集群。 自声明模式 第二种模式给用户更大的选择控制权限,用户可以指定哪些节点或者设备会被使用。有几个层级方式进行配置: 节点: 声明哪些节点上会启动OSD 采用“标签”方式用Kubernetes来声明节点 设备: 声明启动OSD的设备名 声明设备过滤规则,并在其上启动OSD 采用SSD或者NVME设备创建bluestore的metadata分区,bluestore数据分区分布在各种设备上 这种模式很灵活,可以选择需要启动OSD的设备。客户访问 Kubernetes中,需要存储的客户端都会使用PV并挂载到Pod上,Rook提供FlexVolume插件,此插件可以使访问Ceph集群更加简便,只需要声明存储类相关的池,然后在Pod上声明指向存储类的PVC,本例将解释在Pod中挂载RBD镜像的具体步骤。RGW 除了基础的RADOS集群,Rook还会帮助管理对象空间。如果声明需要一个对象存储空间,Rook将: 为对象空间创建metadata和数据池 启动RGW Daemon,如果需要还可以运行高可用实例 通过RGW Daemon创建Kubernetes设备提供负载均衡。对象存储则在存储内部提供 MDS 最后,但不仅限如此,rook还可以配置CephFS提供共享存储空间服务。当生命在集群中需要文件系统时,Rook会: 为CephFS创建metadata和数据池 创建文件系统 使用MDS启动期望数量的实例 文件系统可以被集群中Pod使用,通过相关或者独立路径为每个Pod提供访问方式。参看如下示例。Ceph工具 即使实现了自动化,仍然需要使用Ceph工具维护正常运转。但是随着越来越自动化,工具的依赖度会慢慢降低。同时,如果需要运行Ceph工具,要么启动一个工具箱,或者通过连接到Mon Daemon容器执行这些工具。下一步工作 Rook运行前提是有一套存储需要配置的Kubernetes集群。Rook的目标就是让配置存储的工作越来越简单,当然目前我们还只是处在这一工作的开始。我们正在积极开发Rook项目,期待有更多功能出现。我们也希望更多专家出现在社区,如果有问题,可以通过Rook Slack和我们沟通。 本文转自DockOne-Rook:基于Ceph的Kubernetes存储解决方案
最近,有人问我NodePorts,LoadBalancers和Ingress之间有什么区别。 它们都是将外部流量引入群集的方式,但是分别以不同的方式完成。 让我们来具体看看它们是如何工作的,以及何时使用它们。注意:此处的所有内容均适用于Google Kubernetes Engine。 如果您在另一个云上运行,使用minikube或其他东西,这些将略有不同。 我也没有深入了解技术细节。 如果您有兴趣了解更多信息,官方文档是一个很好的资源!ClusterIP ClusterIP服务是默认的Kubernetes服务。 它为您提供集群内的其他应用程序可以访问的服务。 没有外部访问权限。ClusterIP服务的YAML如下所示: apiVersion: v1 kind: Service metadata: name: my-internal-service spec: selector: app: my-app type: ClusterIP ports: - name: http port: 80 targetPort: 80 protocol: TCP 如果您无法从互联网访问ClusterIP服务,我为什么要谈论它呢? 事实证明,您可以使用Kubernetes代理访问它!启动Kubernetes代理: $ kubectl proxy --port=8080 现在,您可以使用Kubernetes API以访问此服务:http://localhost:8080/api/v1/proxy/namespaces/<NAMESPACE>/services/<SERVICE-NAME>:<PORT-NAME>/ 因此,要访问我们上面定义的服务,您可以使用以下地址:http://localhost:8080/api/v1/proxy/namespaces/default/services/my-internal-service:http/ 什么时候使用Kubernetes Proxy访问服务? 在某些情况下,您可以使用Kubernetes代理来访问您的服务。 调试您的服务或出于某种原因直接从您的笔记本电脑连接它们 允许内部流量,显示内部dashboard等 因为此方法要求您作为经过身份验证的用户运行kubectl,所以不应使用此方法将服务公开给Internet或将其用于生产服务。NodePort NodePort服务是将外部流量直接发送给您的服务的最原始方式。 顾名思义,NodePort在所有节点(VM)上打开一个特定端口,并且发送到该端口的任何流量都将转发到该服务。注意:上图并不是技术上最精确的图示,但我认为它说明了NodePort的工作原理。 NodePort服务的YAML如下所示:apiVersion: v1 kind: Service metadata: name: my-nodeport-service spec: selector: app: my-app type: NodePort ports: - name: http port: 80 targetPort: 80 nodePort: 30036 protocol: TCP 基本上,NodePort服务与普通的“ClusterIP”服务有两点不同。 首先,类型是“NodePort”。还有一个名为nodePort的附加端口,用于指定在节点上打开的端口。 如果您不指定此端口,它将选择一个随机端口。 大多数时候你应该让Kubernetes选择端口;正如thockin所说,对于你可以使用的端口有很多必要的说明。什么时候使用这种方法? 这种方法有许多缺点: 每个端口只能有一个服务 您只能使用端口30000-32767 如果您的Node/VM的IP地址发生变化,您需要处理好 出于这些原因,我不建议在生产中使用此方法直接公开您的服务。 如果您运行的服务不必始终可用,或者您的成本非常敏感,则此方法适合您。 比如演示应用程序或临时的东西。LoadBalancer LoadBalancer服务是将服务公开给Internet的标准方法。 在GKE上,这将启动Network Load Balancer,它将为您提供单个IP地址,以便将所有流量转发到您的服务。什么时候使用? 如果要直接公开服务,这是默认方法。 您指定的端口上的所有流量都将转发到该服务。 没有过滤,没有路由等。这意味着您可以向其发送几乎任何类型的流量,如HTTP、TCP、UDP、Websockets、gRPC等等。最大的缺点是您使用LoadBalancer公开的每个服务都将获得自己的IP地址,并且您必须为每个公开的服务支付LoadBalancer,这可能会变得昂贵!Ingress 与上述所有示例不同,Ingress实际上不是一种服务。 相反,它位于多个服务的前面,充当集群中的“智能路由器”或入口点。您可以使用Ingress执行许多不同的操作,并且有许多类型的Ingress控制器,都具有不同的功能。默认的GKE ingress controller将为您启动HTTP(S)负载均衡器。 这将允许您执行基于路径和子域的路由到后端服务。 例如,您可以将foo.yourdomain.com上的所有内容发送到foo服务,并将youdomain.com/bar/下的所有内容发送到bar服务。使用L7 HTTP负载均衡器的GKE上的Ingress对象的YAML可能如下所示: apiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-ingress spec: backend: serviceName: other servicePort: 8080 rules: - host: foo.mydomain.com http: paths: - backend: serviceName: foo servicePort: 8080 - host: mydomain.com http: paths: - path: /bar/* backend: serviceName: bar servicePort: 8080 什么时候使用? Ingress可能是暴露您的服务的最有效方式,但也可能是最复杂的。 有许多类型的Ingress控制器,来自Google Cloud Load Balancer、Nginx、Contour、Istio等。 还有Ingress控制器的插件,如cert-manager,可以为您的服务自动配置SSL证书。如果要在同一IP地址下公开多个服务,Ingress是最有用的,并且这些服务都使用相同的L7协议(通常是HTTP)。 如果您使用GCP集成,您只需支付一个负载均衡器,并且因为Ingress是“智能”的,您可以获得大量开箱即用的功能(如SSL、身份验证、路由等)。 本文转自DockOne-Kubernetes NodePort、LoadBalancer和Ingress介绍
本文将Kubernetes比如主题公园,用类比的手法,将Kubernetes相关的一些核心概念比作主题公园的类似功能的设备,包括Kubernetes container,Kubernetes pod,Kubernetes containerPort,Kubernetes resources,Kubernetes labels,Kubernetes memory,Kubernetes probes,Kubernetes node pool,Kubernetes CPU,Kubernetes nodes等一些概念。Kubernetes(下文简称k8s)有它自身的一些抽象概念和术语。但我们只要稍加想象,就可以将那些抽象的事物和很熟悉的概念联系起来。我希望将它类比做香肠工厂,但不幸的是k8s不仅仅是香肠,所以我们不得不将它类比做主题公园。下面的描述基于Google Kubernetes Engine,如果你运行在其他的供应商或者运行在自己的k8s上,一些概念可能会有少许不同。你是谁你是Kubenelius Fizzbuzz,一个刚毕业于MBA的学生,拥有Boxes Of All Sizes 的专长,你要去实践你最伟大的想法:KubePark主题世界(KubePark Theme World)。有趣的旅行计划为了启动KubePark计划,你需要去计划这场有趣的旅行。你决定,对于每一个景点,你需要填写类似这样的一个模板(k8s部署):你可以说在这里KubePark将会拥有3个1909年的旋转木马。 一个1909年的旋转木马由两部分组成:一个经典的旋转木马加上一个棉花糖的摊位。每个部分(k8s容器)将由无人机在一个巨大的盒子里传送,完整的组装好,并准备好一旦打开就可以转动。 每一个景点将被栅栏包围,以将它与其他景点隔离,这样一个景点的问题就不能蔓延到其他景点。但是景点的各部分组成了不可分隔的一个整体(k8s pod),所以在它们之间没有任何障碍或防火墙,它们可以毫无困难的在各部分之间交流或移动,这可能会破坏游客的感受(k8s,除非你想要共享文件会需要一点配置)。 有一个栅栏意味着你需要计划一下游客的访问的门(k8s 容器端口)开在哪里。在1909年的旋转木马中,游客将经过棉花糖的摊位;在享受一个蓬松的的棉花糖的同时,旋转木马旅行将变得更加有趣。这个模板还指定了大小(k8s内存)和电源(k8s CPU)需求(k8s资源),标记(k8s 标签)1909年的旋转木马为一个对小孩友好的、经典的的景点的旋转木马。接下来我们将看你将如何使用这些信息。模板的另一面,并没有被展示在上面,你将写下每一部分的维护说明(k8s probes)。你的工作人员将会定期按照那些说明查看是否有某个部分需要替换。如果有的话,维护人员会将有缺陷的部件拿走,无人机将会带来另一个巨大的盒子来替换它。所以不要再试图去修复任何东西,只是简单的替换整个摊位或者旋转木马。在替换期间景点将对游客关闭,即使某些部分依然处于工作状态:1909年的旋转木马体验必须有棉花糖和旋转木马设备,两者是不可分离的。土地按照有趣的旅行计划,是时候建立这个公园了。你和房东签署了一个租赁合同(k8s node pool)。这个合同包含两块土地(k8s nodes),每一块都有特定的大小(k8s内存)和发电机(k8s CPU)。请注意,这份合同并不是针对两块特定的土地,但对于具有这些特征的任何两个块土地,找到它们是房东的工作。凭借现代化的客运方式,它们甚至不需要相邻。 房东通过给每块(土地)安装带刺的金属网来保护每块(土地)的安全。你获得了免费的灾害保险(k8s自动装载)箱:如果一块(土地)变得不可用了,房东将会用一块有完全相同的特质的土地去替换它。找到并确定这块(土地)不可用的时候替换它是房东的唯一责任。最后,合同提供的任何一块(土地)都将被贴上标签(k8s标签),并作为地中海气候的平原地带。接下来我们将看到如何去使用这些信息。建立KubePark你已经工作足够努力,所以你将公园的建设的细节委托给你的控制人员。那些人员将确保你在计划中指定的确切数量和类型的景点一直在工作。意料之外的慷慨,你的房东给你的控制人员提供了一个空中交通管制塔(k8s集群master),并由他去关注管制塔所需要的任何维护和提升工作。少关心一件事情!从管制塔的特权地位和你的有趣的旅行计划考虑,控制人员将决定哪块土地建设哪个景点,确保每块(土地)的可利用空间和备用电源可以承载一个景点所必须的那些东西(k8s的服务质量)。一个景点总是会得到所必须的最小功率(k8s CPU),从来不会超过它的最大功率(因为它会造成短路和燃烧),并且如果有多余的空间(超过最小值)就会得到额外的功率。如果没有多余的空间,景点的设备移动就变得更加的缓慢。康茄舞会路线有最小和最大尺寸(k8s内存)。它总是会得到特定的最小尺寸,但是如果超过最大的尺寸,它将会关闭。大家都知道太长时间的康茄舞总是以野蛮的暴乱告终。如果那块(土地)里有多余的空间,它将获得额外的空间(超过最小空间),但如果没有,超过最小空间的景点将被关闭。由交通管制塔来决定哪条路线以是康茄舞会路线。由于有足够的空间和电源,团队可以毫无问题地创建您计划的所有景点。公园快准备好了!开启KuberPark为了帮助游客到达他们要去的景点,你决定去使用最简单的类似下图的彩色的路径查询器(k8s nodePort service):该图片贡献者:London Victoria station floor lines by Cmglee. 在这里,你决定创建一条蓝线,带游客去你称之为“经典”的景点,一条绿线,带他们去你称之为“过山车”的景点。你可以根据需要使用特定的或通用的标记,如果需要,还可以组合多个标记。这个道路查询器不仅仅是针对游客的,你的内部人员也可以使用他,甚至于你可以创建只有你们团队(k8s clusterIP service)可见的路径,例如找到员工食堂。因为这块(土地)被铁丝网完全包围着,你需要以某种方式让游客进入公园。一种选择是,从一条彩色的小路一直走到铁丝网,在上面挖个洞(k8s loadBalancer service),但是这意味着你需要以某种方式保护每一条有颜色的道路。所以你选择设置一些入场的门(k8s ingress).你的房东提供了一些标准的大门,从而让你的团队不需要去维护他们,他们也不需要你这块(土地)的空间或能量。房东还会负责修建一个从大门到这块(土地)的立交桥。也许将来你会选择更专业的入场门(k8s就像Traefik)。你给准入门的工作人员一些简单的指示:1. 带着6岁以下儿童的游客应使用红色通道。2. 带着青少年的游客应使用绿色通达。3. ...启动和运行!有了这个,你的公园准备好了:难道就这些吗? 如你所愿。你可以阅读更多的关于Kubernetes缩放,污染和亲和性,状态集,持久卷和无领导服务。 本文转自DockOne-解释图片中的Kubernetes:类比做主题公园
欢迎来到深入学习Kubernetes API Server的系列文章,在本系列文章中我们将深入的探究Kubernetes API Server的相关实现。如果你对Kubernetes 的内部实现机制比较感兴趣或者正在进行Kubernetes 项目的相关开发工作,那么本系列文章能够为你提供一些帮助。了解学习过go语言,会在某些方面帮助你更好的理解本系列文章。在本期文章中,我们首先会对Kubernetes API Server进行一个总体的大致说明,然后对API的技术术语及请求流作说明。在下几期的文章中则主要对API Server与etcd存储的交互以及对API Server进行相关扩展进行探讨,说明。API Server的总体说明 从理论上来说,Kubernetes 是由一些具有不同角色的节点组成的。作为控制面的主节点主要部署有API Server, Controller Manager 和 Scheduler(s)等组件。API Server作为Kubernetes 中的管理中心,是唯一能够与存储etcd交互通信的组件。它主要能够提供如下服务:1. 作为 Kubernetes API的服务端,为集群内的节点以及kubectl工具提供API服务。2. 作为集群组件的代理,例如Kubernetes UI3. 通过API Server能够对Kubernetes的API对象比如pods,services进行增删查改等操作。4. 保证在分布式存储系统(etcd)中的Kubernetes API对象的状态一致。Kubernetes API是一个HTTP形式的 API,JSON格式是它主要的序列化架构。同时它也支持协议缓冲区(Protocol Buffers)的形式,这种形式主要是用在集群内通信中。出于可扩展性原因考虑, Kubernetes可支持多个API版本,通过不同的API路径的方式区分。比如/api/v1 和 /apis/extensions/v1beta1,不同的API版本代表了这个API处于不同的版本稳定性阶段。 1. Alpha 阶段,比如v1alpha1,在默认状态下为关闭状态。只在某个分支中支持使用,在将来可能会被废弃。一般只支持在测试环境中短期使用。2. Beta阶段,比如v2beta3,在默认状态下为开启状态。表示这部分代码已经经过测试,基本功能已经通过验证。但是这个状态的API对象将来还是有可能发生不可兼容的改动以过度到stable 稳定阶段。3. Stable阶段,比如v1,是一个稳定的软件发布阶段,API对象一般之后不会有太大改动。接下去,我们介绍一下HTTP API主要结构。首先我们需要区分三种不同的API形式:core group API (在/api/v1路径下,由于某些历史原因而并没有在/apis/core/v1路径下)和named groups API(在对应的/apis/$NAME/$VERSION路径下)及system-wide API(比如/metrics,/healthz)。一个HTTP API的主要结构如下所示:接下去我们主要列举batch group下的两个API例子来讲解说明。在Kubernetes 1.5版本中,batch 群组下有/apis/batch/v1 和 /apis/batch/v2alpha1两个API版本来供开发者使用。我们来看一下API的整体实现(下面列举的API例子我们是通过proxy 命令kubectl proxy --port=8080直接访问API获得)。 $ curl http://127.0.0.1:8080/apis/batch/v1 { "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "batch/v1", "resources": [ { "name": "jobs", "namespaced": true, "kind": "Job" }, { "name": "jobs/status","namespaced": true, "kind": "Job" }] }在将来,将会使用更新的alpha 版本:$ curl http://127.0.0.1:8080/apis/batch/v2alpha1 {"kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "batch/v2alpha1", "resources": [ { "name": "cronjobs", "namespaced": true, "kind": "CronJob" }, { "name": "cronjobs/status", "namespaced": true, "kind": "CronJob" }, { "name": "jobs", "namespaced": true, "kind": "Job" }, { "name": "jobs/status", "namespaced": true, "kind": "Job" }, { "name": "scheduledjobs", "namespaced": true, "kind": "ScheduledJob" }, { "name": "scheduledjobs/status", "namespaced": true, "kind": "ScheduledJob" } ] }总体上来说 Kubernetes API 支持对API对象的增删查改( create, update, delete, retrieve)通过使用JSON作为默认格式的HTTP (POST, PUT, DELETE, GET)方式来实现。大多数API对象会区分对象想要达到的预期状态以及当前对象所处的实际状态。所以一个规范的API描述应该对于这两种状态都有完整的描述说明并在储存(etcd)中记录。API Server的术语说明在对API Server以及HTTP API结构进行总体说明后,接下去我们对一些术语来进行说明解释。Kubernetes 的主要API对象主要有pods, services, endpoints, deployment等。一个API对象主要由以下条目Kind:是一个API对象的类型。它告诉了client(比如kubectl)这种API对象所代表的实体类型。比如一个podapiVersion: v1 kind: Pod metadata: name: webserver spec: containers: - name: nginx image: nginx:1.9 ports: - containerPort: 80目前API中有三种Kinds类型:1. Object对象代表了系统中持久存在的实体,一个object对象可能具有多个resources资源能让客户端来执行一些特定的操作。比如Pod和namespace.2. Lists 代表了一些resources资源或者object实体对象的集合。比如PodLists和 NodeLists.3. 代表了一个对实体对象的操作或一个非实体存在的状态过程。比如binding或者status等。API Group :是一组相关的Kind的集合。比如在Kind:Job以及Kind:ScheduleJob都属于batch的API Group.Version:每个API Group下面都能存在有多个version版本。比如在一个group群组中最早有第一个v1alpha1版本,后来中间发展到了v1beta1 版本,最终发展到v1的稳定版本。如果在系统创建了一个v1beta1 版本的对象,那么它能过被Group任一支持的版本(比如v1)检索到。这是由于API server能够支持不同版本对象之间的无损耗转换。Resource :代表以JSON格式通过HTTP发送或检索的资源实体。它既可以使一个单独的resource 资源(比如.../namespaces/default)也可以是一组resource 资源(比如.../jobs)一个API Group群组,一个Version版本,一个Resource(GVR)资源就能过定义一个唯一的HTTP路径。实际上,一个job对象的API路径为/apis/batch/v1/namespaces/$NAMESPACE/jobs,因为jobs并不是cluster侧的资源,所以需要有namespace字段。与之相对node作为cluster侧的资源,它的API路径就没有$NAMESPACE的部分。 值得注意的是Kinds不一定只在同一个Group群组下存在不同的Version版本,它在不同的Group群组也有可能存在不同的Version版本。比如Deployment 一开始在extensions group群组中作为alpha 版本存在,但最后它发展成GA version版本时拥有了一个新的独立的Group群组apps.k8s.io。因此,如果想要区分唯一的Kinds,必须要有API Group,Version以及Kind(GVK)三部分。API请求流过程在对Kubernetes API中的术语有了了解之后,接下去我们将讨论API请求的处理流程。相关API主要在k8s.io/pkg/api可以看到,它既处理来自集群内的API请求也处理来自集群外的API请求。当API Server接收到一个HTTP的Kubernetes API请求时,它主要处理流程如下所示:1. HTTP 请求通过一组定义在DefaultBuildHandlerChain()(config.go)函数中的过滤处理函数处理,并进行相关操作(相关过滤处理函数如下图所示)。这些过滤处理函数将HTTP 请求处理后存到中ctx.RequestInfo,比如用户的相关认证信息,或者相应的HTTP请求返回码。2. 接着multiplexer (container.go)基于HTTP路径会将HTTP 请求发给对应的各自的处理handlers 。3. routes (在routes/*定义)路由将HTTP路径与handlers 处理器关联。4. 根据每个API Group注册的处理程序获取HTTP请求相关内容对象(比如用户,权限等),并将请求的内容对象存入存储中。完整的处理流程如下图所示再次提醒,为简洁起见,我们省略了上图中HTTP路径的$NAMESPACE字段。 下面我们来仔细看一下定义在DefaultBuildHandlerChain()(config.go)函数中的相关filters过滤处理函数:1. 定义在 requestinfo.go中的WithRequestInfo()函数主要获取HTTP请求的RequestInfo内容。2. 定义在 maxinflight.go的中的WithMaxInFlightLimit()函数限制请求的 in-flight数量。3. 定义在timeout.go的中的WithTimeoutForNonLongRunningRequests()函数主要定义了类似GET, PUT, POST, DELETE等non-long-running请求的超时时间。4. 定义在wrap.go 中的WithPanicRecovery()函数主要定义了当发生panic之后的相关处理。5. 定义在cors.go中的WithCORS()函数主要提供了CORS 实现。CORS代表跨源资源共享,它是一种机制,允许能够处理嵌入在HTML页面中的JavaScript的XMLHttpRequests请求。6. 定义在authentication.go 中的WithAuthentication()函数主要对请求中的用户信息进行验证,并将用户信息存到相应的context中。如果认证成功,那么Authorization HTTP头将会在request请求体中移除。7. 定义在 audit.go 中的WithAudit()函数主要将request的用户信息进行相关处理。然后将Request请求的源IP,用户名,用户操作及namespace等信息记入到相关审计日志中。8. 定义在impersonation.go中的WithImpersonation()函数主要处理用户模拟,通过尝试修改请求的用户(比如sudo)的方式。9. 定义在authorization.go中的WithAuthorization()函数主要请求中的用户权限就行验证,如果验证通过则发送给相应的handler进行处理,如果权限验证不通过则拒绝此次请求,返回相应错误。本部分文章主要对API Server进行了一个总体介绍。下一部分,我们将对API资源的序列化以及如何存入到相关分布式存储中进行探究。 本文转自DockOne-深度剖析Kubernetes API Server三部曲 - part 1
欢迎来到深入学习Kubernetes API Server的系列文章的第二部分。在上一部分中我们对APIserver总体,相关术语及request请求流进行探讨说明。在本部分文章中,我们主要聚焦于探究如何对Kubernetes 对象的状态以一种可靠,持久的方式进行管理。之前的文章中提到过 API Server自身是无状态的,并且它是唯一能够与分布式存储etcd直接通信的组件。etcd的简要说明 在*nix操作系统中,我们一般使用/etc来存储相关配置数据。实际上etcd的名字就是由此发展而来,在etc后面加上个”d”表示”distributed”分布式。任何分布式系统都需要有像etcd这样能够存储系统数据的东西,使其能够以一致和可靠的方式检索相关数据。为了能实现分布式的数据访问,etcd使用Raft 协议。从概念上讲,etcd支持的数据模型是键值(key-value)存储。在etcd2中,各个key是以层次结构存在,而在etcd3中这个就变成了遍布模型,但同时也保持了层次结构方式的兼容性。使用容器化版本的etcd,我们可以创建上面的树,然后按如下方式检索它: $ docker run --rm -d -p 2379:2379 \ --name test-etcd3 quay.io/coreos/etcd:v3.1.0 /usr/local/bin/etcd \--advertise-client-urls http://0.0.0.0:2379 --listen-client-urls http://0.0.0.0:2379$ curl localhost:2379/v2/keys/foo -XPUT -d value="some value"$ curl localhost:2379/v2/keys/bar/this -XPUT -d value=42$ curl localhost:2379/v2/keys/bar/that -XPUT -d value=take$ http localhost:2379/v2/keys/?recursive=trueHTTP/1.1 200 OK Content-Length: 327Content-Type: application/jsonDate: Tue, 06 Jun 2017 12:28:28 GMTX-Etcd-Cluster-Id: 10e5e39849dab251X-Etcd-Index: 6X-Raft-Index: 7X-Raft-Term: 2{"action": "get","node": {"dir": true,"nodes": [{"createdIndex": 4,"key": "/foo","modifiedIndex": 4,"value": "some value"},{"createdIndex": 5,"dir": true,"key": "/bar","modifiedIndex": 5,"nodes": [{"createdIndex": 5,"key": "/bar/this","modifiedIndex": 5,"value": "42"},{"createdIndex": 6,"key": "/bar/that","modifiedIndex": 6,"value": "take"}]}]}}现在我们已经大致了解了etcd是如何工作的,接下去我们继续讨论etcd在Kubernetes是如何被使用的。集群中的etcd在Kubernetes中,etcd是控制平面中的一耳光独立组成部分。在Kubernetes1.5.2版本之前,我们使用的是etcd2版本,而在Kubernetes1.5.2版本之后我们就转向使用etcd3版本了。值得注意的是在Kubernetes1.5.x版本中etcd依旧使用的是v2的API模型,之后这将开始变为v3的API模型,包括使用的数据模型。站在开发者角度而言这个似乎没什么直接影响,因为API Server与存储之前是抽象交互,而并不关心后端存储的实现是etcd v2还是v3。但是如果是站在集群管理员的角度来看,还是需要知道etcd使用的是哪个版本,因为集群管理员需要日常对数据进行一些备份,恢复的维护操作。你可以API Server的相关启动项中配置使用etcd的方式,API Server的etcd相关启动项参数如下所示:$ kube-apiserver -h...--etcd-cafile string SSL Certificate Authority file used to secure etcd communication.--etcd-certfile string SSL certification file used to secure etcd communication.--etcd-keyfile string SSL key file used to secure etcd communication....--etcd-quorum-read If true, enable quorum read.--etcd-servers List of etcd servers to connect with (scheme://ip:port) …...Kubernetes 存储在etcd中的数据,是以JSON字符串或Protocol Buffers 格式存储。下面我们来看一个具体的例子:在apiserver-sandbox的命名空间中创建一个webserver的pod。然后我们使用 etcdctl工具来查看相关etcd(在本环节中etcd版本为3.1.0)数据。$ cat pod.yamlapiVersion: v1kind: Podmetadata:name: webserverspec:containers:- name: nginximage: tomaskral/nonroot-nginxports:- containerPort: 80$ kubectl create -f pod.yaml $ etcdctl ls //kubernetes.io/openshift.io$ etcdctl get /kubernetes.io/pods/apiserver-sandbox/webserver{"kind": "Pod","apiVersion": "v1","metadata": {"name": "webserver",...下面我们来看一下这个pod对象是如何最终存储到etcd中,通过kubectl create -f pod.yaml的方式。下图描绘了这个总体流程: 客户端(比如kubectl)提供一个理想状态的对象,比如以YAML格式,v1版本提供。 Kubectl将YAML转换为JSON格式,并发送。 对应同类型对象的不同版本,API Server执行无损耗转换。对于老版本中不存在的字段则存储在annotations中。 API Server将接受到的对象转换为规范存储版本,这个版本由API Server指定,一般是最新的稳定版本,比如v1。 5. 最后将对象通过JSON 或protobuf方式解析为一个value,通过一个特定的key存入etcd当中。 我们可以通过配置 kube-apiserver的启动参数--storage-media-type来决定想要序列化数据存入etcd的格式,默认情况下为application/vnd.kubernetes.protobuf格式。我们也可以通过配置--storage-versions启动参数,来确定存入etcd的每个群组Group对象的默认版本号。 现在让我们来看看无损转换是如何进行的,我们将使用Kubernetes 对象Horizontal Pod Autoscaling (HPA)来列举说明。HPA顾名思义是指通过监控资源的使用情况结合ReplicationController控制Pod的伸缩。 首先我们期待一个API代理(以便于我们能够在本地直接访问它),并启动ReplicationController,以及HPA 。 $ kubectl proxy --port=8080 & $ kubectl create -f https://raw.githubusercontent.com/mhausenblas/kbe/master/specs/rcs/rc.yaml kubectl autoscale rc rcex --min=2 --max=5 --cpu-percent=80 kubectl get hpa/rcex -o yaml 现在,你能够使用httpie ——当然你也能够使用curl的方式——向API server 请求获取HPA对象使用当前的稳定版本(autoscaling/v1),或者使用之前的版本(extensions/v1beta1),获取的两个版本的区别如下所示: $ http localhost:8080/apis/extensions/v1beta1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex > hpa-v1beta1.json $ http localhost:8080/apis/autoscaling/v1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex > hpa-v1.json $ diff -u hpa-v1beta1.json hpa-v1.json { "kind": "HorizontalPodAutoscaler", - "apiVersion": "extensions/v1beta1",+ "apiVersion": "autoscaling/v1","metadata": {"name": "rcex","namespace": "api-server-deepdive",- "selfLink": "/apis/extensions/v1beta1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex",+ "selfLink": "/apis/autoscaling/v1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex","uid": "ad7efe42-50ed-11e7-9882-5254009543f6","resourceVersion": "267762","creationTimestamp": "2017-06-14T10:39:00Z"},"spec": {- "scaleRef": {+ "scaleTargetRef": {"kind": "ReplicationController","name": "rcex",- "apiVersion": "v1",- "subresource": "scale"+ "apiVersion": "v1"},"minReplicas": 2,"maxReplicas": 5,- "cpuUtilization": {- "targetPercentage": 80- }+ "targetCPUUtilizationPercentage": 80我们能够看到HorizontalPodAutoscale的版本从v1beta1变为了v1。API server能够在不同的版本之前无损耗转换,不论在etcd中实际存的是哪个版本。在了解整个存储流程之后,我们下面来探究一下API server如何将数据进行编码,解码存入etcd中以JSON 或protobuf的方式,同时也考虑到etcd的版本。API Server将所有已知的Kubernetes对象类型保存在名为Scheme的Go类型注册表(registry)中。在此注册表中,定义每种了Kubernetes对象的类型以及如何转换它们,如何创建新对象,以及如何将对象编码和解码为JSON或protobuf。当API Server从客户端接收到一个对象时,比如kubectl,通过HTTP路径,能够知道这个对象的具体版本号。首先会为这个对象使用对应的版本Scheme创建一个空对象,然后通过JSON或protobuf将HTTP传过来的对象内容进行解码转换。解码完成后创建对象,存入etcd中。 在API中可能会有很多版本,如果要支持每个版本之间的直接转换,这样往往处理起来比较麻烦。比如某个API下面有三个版本,那么它就要支持一个版本到另两个版本的直接转换(比如v1 ⇔ v1alpha1, v1 ⇔ v1beta1, v1beta1 ⇔ v1alpha1)。为了避免这个问题,在API server中有一个特别的 “internal” 版本。当两个版本之间需要转换时,先转换为internal版本,再转换为相应转换的版本。这样的话,每个版本只要支持能够转换为internal版本,那么就能够与其它任何版本进行间接的转换。所以一个对象先转换为internal版本,然后在转换为稳定的v1版本,然后在存入etcd中。v1beta1 ⇒ internal ⇒ v1在转换的第一步中,如果某些字段用户没有赋值指定,那么这些会被赋为一个默认值。比如在v1beta1 中肯定没有在v1版本新增的一个字段。在这种情况下,用户肯定无法在v1beta1 版本为这个字段赋值。这时候,在转换的第一步中,我们会为这个字段赋一个默认值以生成一个有效的internal。校验及准入在转换过程中有两个重要的步骤,如下图所示:v1beta1 ⇒ internal ⇒ | ⇒ | ⇒ v1 ⇒ json/yaml ⇒ etcdadmission validation准入和校验是创建和更新对象存入etcd之前必须通过的步骤。它们的一些规则如下所示:1. 准入(Admission):查看集群中的一些约束条件是否允许创建或更新此对象,并根据此集群的相关配置为对象设置一些默认值。在Kubernetes有很多这种约束条件,下面列举一些例子:NamespaceLifecycle:如果命名空间不存在,则拒绝该命名空间下的所有传入请求。LimitRanger:强制限制命名空间中资源的使用率。ServiceAccount:为pod创建 service account 。DefaultStorageClass:如果用户没有为PersistentVolumeClaims赋值,那么将它设置为一个默认值。ResourceQuota:对群集上的当前用户强制执行配额约束,如果配额不足,可能会拒绝请求。2. 校验(Validation):检查传入对象(在创建和更新期间)是否格式是否合法以及相关值是否有效。比如:1. 检查必填字段是否已填。2. 检查字符串格式是否正确(比如只允许小写形式)。3. 是否有些字段存在冲突(比如,有两个容器的名字一样)。校验(Validation)并不关心其它类型的对象实例,换言之,它只关心每个对象的静态检查,无关集群配置。准入(Admission)可以用flag --admission-control=<plugins>来启动或禁用。它们中的大多数可以有集群管理配置。此外,在Kubernetes 1.7中可以用webhook机制来扩展准入机制,使用控制器来实现对对象的传统的校验。迁移存储对象关于存储对象迁移的最后说明:当Kubernetes需要升级到新的版本时,根据每个版本的相关文档步骤备份相关集群的数据是至关重要的。这一方面是由于etcd2到etcd3的转变,另一方面是由于Kubernetes 对象的Kind及version的不断发展。在etcd中,每个对象是首选存储版本(preferred storage version)存在的。但是,随着时间的推移,etcd存储中的对象可能以一个非常老的版本存在。如果在将来某个时间这个对象版本被废弃了,那么将无法再解码它的protobuf 或JSON。因此,在集群升级之前需要重写,迁移这些数据。下面这些资料能够对version切换提供一些帮助:请参阅集群管理文档升级API版本部分。Upgrading to a different API version下一次,在深入学习Kubernetes APIServer的第三部分中,我们将讨论如何使用Custom Resource Definitions扩展和自定义API资源。 本文转自DockOne-深度剖析Kubernetes API Server三部曲 - part 2
Aqua 是一家专注于Docker、Kubernetes、Mesos安全的公司,最近他们开源了一款测试工具叫做kube-hunter。kube-hunter翻译成中文是Kubenetes猎人,而这个猎人寻找的“猎物”就是Kubernetes集群中的安全漏洞。我们最近发布了一款叫做Kube-hunter 的免费工具。你只需提供你的Kubernetes集群的IP或者DNS名称,Kube-hunter就会探查集群中的安全漏洞——这个过程就像是自动化的渗透测试。注意:这个工具目的是为了帮助测试你自己部署的集群,以便你找到潜在的安全问题。请不要用这个工具探测不属于你的集群! 如果你的集群的Kubernetes监控面板被暴露到公网或者你的kubelets可以被外界访问,Kube-hunter会向你发出警告。运行Kube-hunter Kube-hunter是开源的,我们同时也提供容器化的版本,使它可以很容易的运行起来。容器化的版本需要和我们的kube-hunter网站一起使用,在这个网站上你可以很容易的查看结果,并将结果共享给你的团队。在kube-hunter网站,输入你的email, 你将会得到一个包含token的Docker命令,复制该命令并运行它(确保你安装了Docker),系统将会弹出框提示你输入你想测试的Kubernetes的集群的地址。当所有测试运行完后,你将会看到一个用于查看结果的唯一URL(与之前的token相关联),你可以将这个URL分享给需要查看结果的其他任何人。被动猎人以及active猎人 (此处应该翻译为主动猎人,但是下文中active更容易理解)Kube-hunter默认只运行“被动猎人”,被动猎人指的是一系列测试,这些测试是用于探测集群中的潜在访问点(如开放端口)。你也可以打开“主动打猎”模式,只需要加上--active参数。当使用active模式时,kube-hunter会尝试利用“被动猎人”发现的任何弱点来进行一些额外的测试。active猎人目的是为了展示攻击者可能做的事情,虽然我们并不想这些测试做任何破坏性的操作,但是你应该谨慎使用,因为active猎人可能会改变集群的状态或其中运行的代码。例如:有一个 active猎人 尝试进入一个容器并运行uname命令。负责任的进行渗透测试 或许我不该强调这么多遍,但是你绝对不能把kube-hunter用于别人的集群!虽然这个工具极大可能被用于攻击其他网站,但是绝对不是我们的目的(如果您使用kube-hunter网站,在您接受的条款和条件中明确的禁止了这类事情)。在发布kube-hunter之前,我们仔细考虑了它会被坏人利用的可能性;但事实上,他们可能已经通过一些通用工具,例如port scanning (端口扫描)进行了类似的测试。我们希望为Kubernetes管理员,操作员和工程师提供一种简单的方法来识别他们集群中的安全漏洞,以便这些漏洞被攻击者利用之前被解决掉。测试 你可以在kube-hunter网站上找到已经实现的测试列表,或者使用--list参数运行kube-hunter。这些测试尚不全面,但确实有很多猎人可以发现许多常见问题。开源测试 基础的测试代码是开源的(你可以在不使用网站的情况下运行),我们欢迎大家的反馈,想法,希望大家能贡献其他猎人的代码! 本文转自DockOne-Kube-hunter:一个用于Kubernetes渗透测试的开源工具
Kubernetes是一个开源容器管理平台,它现在已经成为了云原生的中流砥柱。自从把它移交给Cloud Native Compute Foundation(云原生计算基金)后,该项目在业界上取得了史无前例的关注,目前没有一个公有云环境不提供Kubernetes托管服务。Kubernetes正迅速成为现代容器化应用运行的管理平台。随着Kubernetes的崛起,它带来了一个全新的生态系统的形成。目前有各种各样的ISV和SaaS提供商为构建云原生环境提供了构建工具。这个蓬勃的生态可以和当时微软和VMware在Windows和VSphere鼎盛时代相媲美。但他们最大的区别就是云原生的产品大多数都是开源的,但在云上提供一个可用的商业版本。下面是业界五个值得关注的开源项目,这些项目在Kubernetes的基础上进行大幅度扩展,使得其成为运行Web规模和企业应用的强大平台。1. Istio 在Kubernetes之后,Istio是最受欢迎的云原生技术。它就是一种服务网格,能够安全的连接一个应用程序之间的多个微服务。你也可以将它视为内部和外部的负载均衡器,具有策略驱动的防火墙,支持各种全面指标。开发者和使用者倾向于Istio的原因是因为它具有无侵入式的部署模式,而且任何Kubernetes的服务都能够在不需要改动代码和配置的情况下和Istio进行无缝连接。Google最近宣布在GCP上管理Istio服务,除此之外IBM,Pivotal,Red Hat,Tigera和Weaveworkds都是支持这个项目的活跃贡献者。Istio为ISV提供了向企业提供定制化解决方案和工具的绝佳机会,这个项目有望成为建设云原生平台的项目,我希望每一个托管Kubernetes服务的平台都能够都能够托管Istio服务。2. Prometheus Prometheus是一个部署在Kubernetes上用于观察工作负载的云原生监控工具。它通过全面的指标和丰富的DashBoard填补了云原生世界中存在的重要空白。在Kubernetes之后,它是唯一从云原生计算基金中毕业的项目。Prometheus通过聚合可通过集中式DashBoard的指标来填充Istio的空白。从核心指标中可以反映Kubernetes集群中特殊应用的指标的健康状态,可以说它几乎可以监控到一切。它整合了像Grafana这样主流的数据可视化工具,Kubernetes接下来推出的有关于扩展和监控的功能都依赖于Prometheus,这使得它成为云原生平台建设中的不可或缺的一项。3. Helm 如果说Kubernetes是新型的操作系统的话,Helm 就是应用程序安装程序。根据Debian安装包和Red Hat Linux RMPS设计,Helm通过执行单个命令,提供了更简洁和更强大的部署云原生工作负载能力。Kubernetes应用暴露了大量的像deployments(部署),services(服务),ingress controllers(入口控制器),persistant volumes(持久化挂载目录)等更多的元素。Helm则通过提供统一安装工具,将云原生应用程序所有依赖关系聚合到称之为图表的部署单元中。由于被CNCF进行管理,Helm项目的积极参与者目前有Bitnami,Google,Microsoft,CodeFresh和Ticketmaster。Helm正朝着成为真正意义上的云原生应用程序安装程序。 4. Spinnaker 云原生技术最值得关注之一的是软件的交付速度。Spinnaker是一个最初在Netflix上构建的开源项目,它实现了这一承诺。Spinnaker是一个版本管理工具,它是一种发布管理工具,可以为部署云原生应用程序提高速度。通过对比传统的IaaS环境(像Amazon EC2和当代运行在Kubernetes上的CaaS平台),无缝填补了传统虚拟机和容器之间的空白。其多云功能使得其成为跨不同云平台部署应用程序的理想平台。Spinnaker可作为当前所有主流的云环境自托管平台,像Armory这样的初创公司目前正在提供SLA下的商业级,企业级Spinnaker。5. KubeLess 事件驱动计算目前已成为当代应用程序结构不可或缺的一部分。功能即服务(FaaS)是当前无服务计算交付模型之一,它通过基于事件的调用来填补容器。现代的应用程序会被当做服务并打包成容器或者是作为方法运行在相同的环境下,随着Kubernetes成为云原生计算的首选平台,运行功能时必须在容器中进行。在云原生生态系统中,来自于Bitnami的Kubeless项目是当前最流行的无服务项目。它与AWS lambda的兼容性与对主流语言的支持使得它成为理想的选择。CNCF目前还没有将无服务项目纳入其中,到目前为止最近接的是通过CloudEvent——用一种平常的方法来描述事件数据的规范,如果Kubeless成为CNCF中的一个项目的话,它将会十分有意思。随着企业开始接受新的范例,一系列支撑当代云原生应用,云原生工作负载的工具和平台也不断快速的演进。Janakiram MSV是Janakiram & Associates的分析师,高级指导顾问和架构师。 本文转自DockOne-超越Kubernetes:值得关注的5大云原生技术
整体目标 在这一篇中,我们将使用Jenkins在此基础上构建一条完整的持续交付流水线,并且让团队不同成员能够基于该流水线展开基本的协作。 开发: 持续提交代码并能够通过持续集成(CI)过程快速获取反馈,在通过CI验证后,能够自动化部署到开发环境,以便后续的进一步功能测试(手动/自动自动化测试)等; 测试: 在需要对项目功能进行验证时,可以一键部署测试环境,并且在此环境基础上可以完成功能验收(手动),以及全量的自动化验收测试等; 运维:一键部署生产环境,同时发布创建版本,以便在发布异常时能够快速回归。 资料来源: https://dzone.com/articles/eas ... ns-he 示例项目的代码可以从GitHub下载,示例项目为containerization-spring-with-helm。接下来,我们将分阶段介绍如何通过Jenkinsfile定义整个过程。项目构建阶段 当前阶段Jenkinsfile定义如下: stage('Build And Test') { steps { dir('containerization-spring-with-helm') { sh 'docker build -t yunlzheng/spring-sample:$GIT_COMMIT .' } } } 在Build And Test阶段,我们直接通过源码中的Dockerfile定义了整个持续集成阶段的任务,通过docker的Multi-Stage Builds特性,持续集成的所有任务全部通过Dockerfile进行定义,这样无论是在本地还是持续集成服务器中,我们都可以非常方便的进行运行CI任务。发布镜像和Helm阶段 当前阶段Jenkinsfile定义如下:stage('Publish Docker And Helm') { steps { withDockerRegistry([credentialsId: 'dockerhub', url: '']) { sh 'docker push yunlzheng/spring-sample:$GIT_COMMIT' } script { def filename = 'containerization-spring-with-helm/chart/values.yaml' def data = readYaml file: filename data.image.tag = env.GIT_COMMIT sh "rm $filename" writeYaml file: filename, data: data } script { def filename = 'containerization-spring-with-helm/chart/Chart.yaml' def data = readYaml file: filename data.version = env.GIT_COMMIT sh "rm $filename" writeYaml file: filename, data: data } dir('containerization-spring-with-helm') { sh 'helm push chart https://repomanage.rdc.aliyun.com/helm_repositories/26125-play-helm --username=$HELM_USERNAME --password=$HELM_PASSWORD --version=$GIT_COMMIT' } } } Push镜像 通过withDockerRegistry的上下文,Jenkins会确保docker client首先通过credentials dockerhub中定义的用户名和密码完成登录后,再运行docker push任务。这里我们使用当前代码版本的COMMIT_ID作为镜像的Tag,从而将Docker镜像版本与源码版本进行一一对应;重写Chart镜像版本 通过readYaml读取chart的values.yaml内容到变量data后,通过writeYaml重写values.yaml中的镜像tag版本与当前构建镜像版本一致。重写Chart版本 与镜像一样,我们希望Chart的版本与源码版本能够一一对应。上传Chart 这里我们直接使用阿里云效提供的Helm仓库服务,点击开通私有仓库服务。通过Helm Push插件发布Chart到Helm仓库。其中环境变量$HELM_USERNAME和$HELM_PASSWORD是通过jenkins的Credentials加载到环境变量中:environment { HELM_USERNAME = credentials('HELM_USERNAME') HELM_PASSWORD = credentials('HELM_PASSWORD') } 部署到开发/测试环境阶段 当前阶段Jenkinsfile定义如下:stage('Deploy To Dev') { steps { dir('containerization-spring-with-helm') { dir('chart') { sh 'helm upgrade spring-app-dev --install --namespace=dev --set ingress.host=dev.spring-example.local .' } } } } stage('Deploy To Stageing') { steps { input 'Do you approve staging?' dir('containerization-spring-with-helm') { dir('chart') { sh 'helm upgrade spring-app-staging --install --namespace=staging --set ingress.host=staging.spring-example.local .' } } } } 在Jenkinsfile中我们分别定义了两个阶段Deploy To Dev和Deploy To Stageing。我们通过Kubernetes的命名空间划分单独的开发环境和测试环境。并且通过覆盖ingress.host确保能够通过ingress域名dev.spring-example.local和staging.spring-example.local访问到不同环境。 对于Staging环境而言,通过input确保该流程一定是通过人工确认的。使用helm upgrade命令可以在特定命名空间下部署或者升级已有的Chart:helm upgrade spring-app-staging --install --namespace=staging --set ingress.host=staging.spring-example.local . 部署到生产环境阶段 当前阶段Jenkinsfile定义如下:stage('Deploy To Production') { steps { input 'Do you approve production?' script { env.RELEASE = input message: 'Please input the release version', ok: 'Deploy', parameters: [ [$class: 'TextParameterDefinition', defaultValue: '0.0.1', description: 'Cureent release version', name: 'release'] ] } echo 'Deploy and release: $RELEASE' script { def filename = 'containerization-spring-with-helm/chart/Chart.yaml' def data = readYaml file: filename data.version = env.RELEASE sh "rm $filename" writeYaml file: filename, data: data } dir('containerization-spring-with-helm') { dir('chart') { sh 'helm lint' sh 'helm upgrade spring-app-prod --install --namespace=production --set ingress.host=production.spring-example.local .' } sh 'helm push chart https://repomanage.rdc.aliyun.com/helm_repositories/26125-play-helm --username=$HELM_USERNAME --password=$HELM_PASSWORD --version=$RELEASE' } } } 在最后一个Deploy To Production阶段中,与Dev和Stageing的部署不同在于当人工确认部署测试环境之后,我们需要用户手动输入当前发布的版本,以确保对当前发布的Chart版本能完成一个基线的定义: 这里,我们需要确保当前定义的版本是符合Sem规范的,因此这里使用了helm lint对Chart定义进行校验。 小结 通过代码提交版本(COMMIT_ID)关联了源码版本,镜像版本以及Chart版本。同时对于正式发布的软件版本而言,单独定义了正式发布的版本号。对于实践持续交付的研发团队而言,我们可以通过上述一条流水线基本实现软件交付的整个生命周期。而对于传统交付模式的团队,则可以通过将上述过程分拆到多条流水线(开发流水线,测试流水线,发布流水线)来适应自己的发布模式。回到我们的总体目标而言,通过基础设施及代码的方式,我们定义了一个相对完备且自描述的应用。通过流水线即代码的方式,定义了应用的端到端交付过程。通过Docker定义项目的构建过程,通过Helm实现Kubernetes下应用的发布管理,通过Jenkinsfile定义了软件的整个交付过程,并且不同职能的团队成员,可以方便的在此基础上实现协作。最后借用《持续交付》的话“提前并频繁地做让你感到痛苦的事!“ ,希望大家都能够Happy Coding。 本文转自DockOne-使用Helm优化Kubernetes下的研发体验:实现持续交付流水线
带你了解Kubernetes架构的设计意图、Kubernetes系统的架构开发演进过程,以及背后的驱动原因。------------ 1、背景各种平台都会遇到一个不可回避的问题,即平台应该包含什么和不包含什么,Kubernetes也一样。Kubernetes作为一个部署和管理容器的平台,Kubernetes不能也不应该试图解决用户的所有问题。Kubernetes必须提供一些基本功能,用户可以在这些基本功能的基础上运行容器化的应用程序或构建它们的扩展。本文旨在明确Kubernetes架构的设计意图,描述Kubernetes的演进历程和未来的开发蓝图。本文中,我们将描述Kubernetes系统的架构开发演进过程,以及背后的驱动原因。对于希望扩展或者定制kubernetes系统的开发者,其应该使用此文档作为向导,以明确可以在哪些地方,以及如何进行增强功能的实现。如果应用开发者需要开发一个大型的、可移植的和符合将来发展的kubernetes应用,也应该参考此文档,以了解Kubernetes将来的演化和发展。 从逻辑上来看,kubernetes的架构分为如下几个层次: 核心层(Nucleus): 提供标准的API和执行机制,包括基本的REST机制,安全、Pod、容器、网络接口和存储卷管理,通过接口能够对这些API和执进行扩展,核心层是必需的,它是系统最核心的一部分。应用管理层(Application Management Layer ):提供基本的部署和路由,包括自愈能力、弹性扩容、服务发现、负载均衡和流量路由。此层即为通常所说的服务编排,这些功能都提供了默认的实现,但是允许进行一致性的替换。治理层(The Governance Layer):提供高层次的自动化和策略执行,包括单一和多租户、度量、智能扩容和供应、授权方案、网络方案、配额方案、存储策略表达和执行。这些都是可选的,也可以通过其它解决方案实现。接口层(The Interface Layer):提供公共的类库、工具、用户界面和与Kubernetes API交互的系统。生态层(The Ecosystem):包括与Kubernetes相关的所有内容,严格上来说这些并不是Kubernetes的组成部分。包括CI/CD、中间件、日志、监控、数据处理、PaaS、serverless/FaaS系统、工作流、容器运行时、镜像仓库、Node和云提供商管理等。2、系统分层就像Linux拥有内核(kernel)、核心系统类库、和可选的用户级工具,kubernetes也拥有功能和工具的层次。对于开发者来说,理解这些层次是非常重要的。kubernetes APIs、概念和功能都在下面的层级图中得到体现。2.1 核心层:API和执行(The Nucleus: API and Execution) 核心层包含最核心的API和执行机。这些API和功能由上游的kubernetes代码库实现,由最小特性集和概念集所组成,这些特征和概念是系统上层所必需的。这些由上游KubNeNETs代码库实现的API和函数包括建立系统的高阶层所需的最小特征集和概念集。这些内容被明确的地指定和记录,并且每个容器化的应用都会使用它们。开发人员可以安全地假设它们是一直存在的,这些内容应该是稳定和乏味的。2.1.1 API和集群控制面板Kubernetes集群提供了类似REST API的集,通过Kubernetes API server对外进行暴露,支持持久化资源的增删改查操作。这些API作为控制面板的枢纽。遵循Kubernetes API约定(路径约定、标准元数据等)的REST API能够自动从共享API服务(认证、授权、审计日志)中收益,通用客户端代码能够与它们进行交互。作为系统的最娣层,需要支持必要的扩展机制,以支持高层添加功能。另外,需要支持单租户和多租户的应用场景。核心层也需要提供足够的弹性,以支持高层能扩展新的范围,而不需要在安全模式方面进行妥协。如果没有下面这些基础的API机和语义,Kubernetes将不能够正常工作:认证(Authentication): 认证机制是非常关键的一项工作,在Kubernetes中需要通过服务器和客户端双方的认证通过。API server 支持基本认证模式 (用户命名/密码) (注意,在将来会被放弃), X.509客户端证书模式,OpenID连接令牌模式,和不记名令牌模式。通过kubeconfig支持,客户端能够使用上述各种认证模式。第三方认证系统可以实现TokenReview API,并通过配置认证webhook来调用,通过非标准的认证机制可以限制可用客户端的数量。1、The TokenReview API (与hook的机制一样) 能够启用外部认证检查,例如Kubelet2、Pod身份标识通过”service accounts“提供3、The ServiceAccount API,包括通过控制器创建的默认ServiceAccount保密字段,并通过接入许可控制器进行注入。授权(Authorization):第三方授权系统可以实现SubjectAccessReview API,并通过配置授权webhook进行调用。1、SubjectAccessReview (与hook的机制一样), LocalSubjectAccessReview, 和SelfSubjectAccessReview APIs能启用外部的许可检查,诸如Kubelet和其它控制器。REST 语义、监控、持久化和一致性保证、API版本控制、违约、验证1、NIY:需要被解决的API缺陷:2、混淆违约行为3、缺少保障4、编排支持5、支持事件驱动的自动化6、干净卸载NIY: 内置的准入控制语义、同步准入控制钩子、异步资源初始化 — 发行商系统集成商,和集群管理员实现额外的策略和自动化NIY:API注册和发行、包括API聚合、注册额外的API、发行支持的API、获得支持的操作、有效载荷和结果模式的详细信息。NIY:ThirdPartyResource和ThirdPartyResourceData APIs (或她们的继承者),支持第三方存储和扩展API。NIY:The Componentstatuses API的可扩展和高可用的替代,以确定集群是否完全体现和操作是否正确:ExternalServiceProvider (组件注册)The Endpoints API,组件增持需要,API服务器端点的自我发布,高可用和应用层目标发行The Namespace API,用户资源的范围,命名空间生命周期(例如:大量删除)The Event API,用于对重大事件的发生进行报告,例如状态改变和错误,以及事件垃圾收集NIY:级联删除垃圾收集器、finalization, 和orphaningNIY: 需要内置的add-on的管理器 ,从而能够自动添加自宿主的组件和动态配置到集群,在运行的集群中提取出功能。1、Add-ons应该是一个集群服务,作为集群的一部分进行管理2、它们可以运行在kube-system命名空间,这么就不会与用户的命名进行冲突API server作为集群的网关。根据定义,API server必需能够被集群外的客户端访问,而Node和Pod是不被集群外的客户端访问的。客户端认证API server,并使用API server作为堡垒和代理/通道来通过/proxy和/portforward API访问Node和Pod等Clients authenticate the API server and also use itTBD:The CertificateSigningRequest API,能够启用认证创建,特别是kubele证书。理想情况下,核心层API server江仅仅支持最小的必需的API,额外的功能通过聚合、钩子、初始化器、和其它扩展机制来提供。注意,中心化异步控制器以名为Controller Manager的独立进程运行,例如垃圾收集。API server依赖下面的外部组件:持久化状态存储 (etcd,或相对应的其它系统;可能会存在多个实例)API server可以依赖:身份认证提供者The TokenReview API实现者 实现者The SubjectAccessReview API实现者2.1.2 执行在Kubernetes中最重要的控制器是kubelet,它是Pod和Node API的主要实现者,没有这些API的话,Kubernetes将仅仅只是由键值对存储(后续,API机最终可能会被作为一个独立的项目)支持的一个增删改查的REST应用框架。Kubernetes默认执行独立的应用容器和本地模式。Kubernetes提供管理多个容器和存储卷的Pod,Pod在Kubernetes中作为最基本的执行单元。Kubelet API语义包括:The Pod API,Kubernetes执行单元,包括:1、Pod可行性准入控制基于Pod API中的策略(资源请求、Node选择器、node/pod affinity and anti-affinity, taints and tolerations)。API准入控制可以拒绝Pod或添加额外的调度约束,但Kubelet才是决定Pod最终被运行在哪个Node上的决定者,而不是schedulers or DaemonSets。2、容器和存储卷语义和生命周期3、Pod IP地址分配(每个Pod要求一个可路由的IP地址)4、将Pod连接至一个特定安全范围的机制(i.e., ServiceAccount)5、存储卷来源:5.1、emptyDir5.2、hostPath5.3、secret5.4、configMap5.5、downwardAPI5.6、NIY:容器和镜像存储卷 (and deprecate gitRepo)5.7、NIY:本地存储,对于开发和生产应用清单不需要复杂的模板或独立配置5.8、flexVolume (应该替换内置的cloud-provider-specific存储卷)6、子资源:绑定、状态、执行、日志、attach、端口转发、代理 NIY:可用性和引导API 资源检查点 容器镜像和日志生命周期 The Secret API,启用第三方加密管理 The ConfigMap API,用于组件配置和Pod引用 The Node API,Pod的宿主 1、在一些配置中,可以仅仅对集群管理员可见 Node和pod网络,业绩它们的控制(路由控制器) Node库存、健康、和可达性(node控制器) 1、Cloud-provider-specific node库存功能应该被分成特定提供者的控制器 pod终止垃圾收集 存储卷控制器 1、Cloud-provider-specific attach/detach逻辑应该被分成特定提供者的控制器,需要一种方式从API中提取特定提供者的存储卷来源。The PersistentVolume API 1、NIY:至少被本地存储所支持The PersistentVolumeClaim API 中心化异步功能,诸如由Controller Manager执行的pod终止垃圾收集。当前,控制过滤器和kubelet调用“云提供商”接口来询问来自于基础设施层的信息,并管理基础设施资源。然而,kubernetes正在努力将这些触摸点(问题)提取到外部组件中,不可满足的应用程序/容器/OS级请求(例如,PODS,PersistentVolumeClaims)作为外部“动态供应”系统的信号,这将使基础设施能够满足这些请求,并使用基础设施资源(例如,Node、和PersistentVolumes)在Kubernetes进行表示,这样Kubernetes可以将请求和基础设施资源绑定在一起。对于kubelet,它依赖下面的可扩展组件: 镜像注册 容器运行时接口实现 容器网络接口实现 FlexVolume 实现(”CVI” in the diagram) 以及可能依赖: NIY:第三方加密管理系统(例如:Vault) NIY:凭证创建和转换控制器 2.2 应用层:部署和路由应用管理和组合层,提供自愈、扩容、应用生命周期管理、服务发现、负载均衡和路由— 也即服务编排和service fabric。这些API和功能是所有Kubernetes分发所需要的,Kubernetes应该提供这些API的默认实现,当然可以使用替代的实现方案。没有应用层的API,大部分的容器化应用将不能运行。Kubernetes’s API提供类似IaaS的以容器为中心的基础单元,以及生命周期控制器,以支持所有工作负载的编排(自愈、扩容、更新和终止)。这些应用管理、组合、发现、和路由API和功能包括: 默认调度,在Pod API中实现调度策略:资源请求、nodeSelector、node和pod affinity/anti-affinity、taints and tolerations. 调度能够作为一个独立的进度在集群内或外运行。 NIY:重新调度器 ,反应和主动删除已调度的POD,以便它们可以被替换并重新安排到其他Node 持续运行应用:这些应用类型应该能够通过声明式更新、级联删除、和孤儿/领养支持发布(回滚)。除了DaemonSet,应该能支持水平扩容。 1、The Deployment API,编排更新无状态的应用,包括子资源(状态、扩容和回滚)2、The DaemonSet API,集群服务,包括子资源(状态)3、The StatefulSet API,有状态应用,包括子资源(状态、扩容)4、The PodTemplate API,由DaemonSet和StatefulSet用来记录变更历史终止批量应用:这些应该包括终止jobs的自动剔除(NIY) 1、The Job API (GC discussion)2、The CronJob API发现、负载均衡和路由 1、The Service API,包括集群IP地址分配,修复服务分配映射,通过kube-proxy或者对等的功能实现服务的负载均衡,自动化创建端点,维护和删除。NIY:负载均衡服务是可选的,如果被支持的化,则需要通过一致性的测试。2、The Ingress API,包括internal L7 (NIY)3、服务DNS。DNS使用official Kubernetes schema。应用层可以依赖: 身份提供者 (集群的身份和/或应用身份) NIY:云提供者控制器实现 Ingress controller(s) 调度器和重新调度器的替代解决方案 DNS服务替代解决方案 kube-proxy替代解决方案 工作负载控制器替代解决方案和/或辅助,特别是用于扩展发布策略 2.3 治理层:自动化和策略执行策略执行和高层自动化。这些API和功能是运行应用的可选功能,应该挺其它的解决方案实现。每个支持的API/功能应用作为企业操作、安全和治理场景的一部分。需要为集群提供可能的配置和发现默认策略,至少支持如下的用例: 单一租户/单一用户集群 多租户集群 生产和开发集群 Highly tenanted playground cluster 用于将计算/应用服务转售给他人的分段集群 需要关注的内容:1、资源使用2、Node内部分割3、最终用户4、管理员5、服务质量(DoS)自动化APIs和功能: 度量APIs (水平/垂直自动扩容的调度任务表) 水平Pod自动扩容API NIY:垂直Pod自动扩容API(s) 集群自动化扩容和Node供应 The PodDisruptionBudget API 动态存储卷供应,至少有一个出厂价来源类型 1、The StorageClass API,至少有一个默认存储卷类型的实现 动态负载均衡供应 NIY:PodPreset API NIY:service broker/catalog APIs NIY:Template和TemplateInstance APIs 策略APIs和功能:授权:ABAC和RBAC授权策略方案1、RBAC,实现下面的API:Role, RoleBinding, ClusterRole, ClusterRoleBinding The LimitRange API The ResourceQuota API The PodSecurityPolicy API The ImageReview API The NetworkPolicy API 管理层依赖: 网络策略执行机制 替换、水平和垂直Pod扩容 集群自动扩容和Node提供者 动态存储卷提供者 动态负载均衡提供者 度量监控pipeline,或者它的替换 服务代理 2.4 接口层:类库和工具这些机制被建议用于应用程序版本的分发,用户也可以用其进行下载和安装。它们包括Kubernetes官方项目开发的通用的类库、工具、系统、界面,它们可以用来发布。 Kubectl — kubectl作为很多客户端工具中的一种,Kubernetes的目标是使Kubectl更薄,通过将常用的非平凡功能移动到API中。这是必要的,以便于跨Kubernetes版本的正确操作,并促进API的扩展性,以保持以API为中心的Kubernetes生态系统模型,并简化其它客户端,尤其是非GO客户端。 客户端类库(例如:client-go, client-python) 集群联邦(API server, controllers, kubefed) Dashboard Helm 这些组件依赖: Kubectl扩展 Helm扩展 2.5 生态在有许多领域,已经为Kubernetes定义了明确的界限。虽然,Kubernetes必须提供部署和管理容器化应用需要的通用功能。但作为一般规则,在对Kubernete通用编排功能进行补足的功能领域,Kubernetes保持了用户的选择。特别是那些有自己的竞争优势的区域,特别是能够满足不同需求和偏好的众多解决方案。Kubernetes可以为这些解决方案提供插件API,或者可以公开由多个后端实现的通用API,或者公开此类解决方案可以针对的API。有时,功能可以与Kubernetes干净地组合在而不需要显式接口。此外,如果考虑成为Kubernetes的一部分,组件就需要遵循Kubernetes设计约定。例如,主要接口使用特定域语言的系统(例如,Puppet、Open Policy Agent)与Kubenetes API的方法不兼容,可以与Kubernetes一起使用,但不会被认为是Kubernetes的一部分。类似地,被设计用来支持多平台的解决方案可能不会遵循Kubernetes API协议,因此也不会被认为是Kubernetes的一部分。 内部的容器镜像:Kubernetes不提供容器镜像的内容。 如果某些内容被设计部署在容器镜像中,则其不应该直接被考虑作为Kubernetes的一部分。例如,基于特定语言的框架。 在Kubernetes的顶部 1、持久化集成和部署(CI/CD):Kubernetes不提供从源代码到镜像的能力。Kubernetes 不部署源代码和不构建应用。用户和项目可以根据自身的需要选择持久化集成和持久化部署工作流,Kubernetes的目标是方便CI/CD的使用,而不是命令它们如何工作。2、应用中间件:Kubernetes不提供应用中间件作为内置的基础设施,例如:消息队列和SQL数据库。然而,可以提供通用目的的机制使其能够被容易的提供、发现和访问。理想的情况是这些组件仅仅运行在Kubernetes上。3、日志和监控:Kubernetes本身不提供日志聚合和综合应用监控的能力,也没有遥测分析和警报系统,虽然日志和监控的机制是Kubernetes集群必不可少的部分。4、数据处理平台:在数据处理平台方面,Spark和Hadoop是还有名的两个例子,但市场中还存在很多其它的系统。5、特定应用运算符:Kubernetes支持通用类别应用的工作负载管理。6、平台即服务 Paas:Kubernetes为Paas提供基础。7、功能即服务 FaaS:与PaaS类似,但Faa侵入容器和特定语言的应用框架。8、工作量编排: “工作流”是一个非常广泛的和多样化的领域,通常针对特定的用例场景(例如:数据流图、数据驱动处理、部署流水线、事件驱动自动化、业务流程执行、iPAAS)和特定输入和事件来源的解决方案,并且通常需要通过编写代码来实现。9、配置特定领域语言:特定领域的语言不利于分层高级的API和工具,它们通常具有有限的可表达性、可测试性、熟悉性和文档性。它们复杂的配置生成,它们倾向于在互操作性和可组合性间进行折衷。它们使依赖管理复杂化,并经常颠覆性的抽象和封装。10、Kompose:Kompose是一个适配器工具,它有助于从Docker Compose迁移到Kubernetes ,并提供简单的用例。Kompose不遵循Kubernetes约定,而是基于手动维护的DSL。11、ChatOps:也是一个适配器工具,用于聊天服务。支撑Kubernetes 1、容器运行时:Kubernetes本身不提供容器运行时环境,但是其提供了接口,可以来插入所选择的容器运行时。2、镜像仓库:Kubernetes本身不提供容器的镜像,可通过Harbor、Nexus和docker registry等搭建镜像仓库,以为集群拉取需要的容器镜像。3、集群状态存储:用于存储集群运行状态,例如默认使用Etcd,但也可以使用其它存储系统。4、网络:与容器运行时一样,Kubernetes提供了接入各种网络插件的容器网络接口(CNI)。5、文件存储:本地文件系统和网络存储6、Node管理:Kubernetes既不提供也不采用任何综合的机器配置、维护、管理或自愈系统。通常针对不同的公有/私有云,针对不同的操作系统,针对可变的和不可变的基础设施。7、云提供者:IaaS供应和管理。8、集群创建和管理:社区已经开发了很多的工具,利润minikube、kubeadm、bootkube、kube-aws、kops、kargo, kubernetes-anywhere等待。 从工具的多样性可以看出,集群部署和管理(例如,升级)没有一成不变的解决方案。也就是说,常见的构建块(例如,安全的Kubelet注册)和方法(特别是自托管)将减少此类工具中所需的自定义编排的数量。后续,希望通过建立Kubernetes的生态系统,并通过整合相关的解决方案来满足上述需求。矩阵管理选项、可配置的默认、扩展、插件、附加组件、特定于提供者的功能、版本管理、特征发现和依赖性管理。Kubernetes不仅仅是一个开源的工具箱,而且是一个典型集群或者服务的运行环境。 Kubernetes希望大多数用户和用例能够使用上游版本,这意味着Kubernetes需要足够的可扩展性,而不需要通过重建来处理各种场景。虽然在可扩展性方面的差距是代码分支的主要驱动力,而上游集群生命周期管理解决方案中的差距是当前Kubernetes分发的主要驱动因素,可选特征的存在(例如,alpha API、提供者特定的API)、可配置性、插件化和可扩展性使概念不可避免。然而,为了使用户有可能在Kubernetes上部署和管理他们的应用程序,为了使开发人员能够在Kubernetes集群上构建构建Kubernetes扩展,他们必须能够对Kubernetes集群或分发提供一个假设。在基本假设失效的情况下,需要找到一种方法来发现可用的功能,并表达功能需求(依赖性)以供使用。集群组件,包括add-ons,应该通过组件注册 API进行注册和通过/componentstatuses进行发现。启用内置API、聚合API和注册的第三方资源,应该可以通过发现和OpenAPI(Savigj.JSON)端点来发现。如上所述,LoadBalancer类型服务的云服务提供商应该确定负载均衡器API是否存在。类似于StorageClass,扩展和它们的选项应该通过FoeClass资源进行注册。但是,使用参数描述、类型(例如,整数与字符串)、用于验证的约束(例如,ranger或regexp)和默认值,从扩展API中引用fooClassName。这些API应该配置/暴露相关的特征的存在,例如动态存储卷供应(由非空的storageclass.provisioner字段指示),以及标识负责的控制器。需要至少为调度器类、ingress控制器类、Flex存储卷类和计算资源类(例如GPU、其他加速器)添加这样的API。假设我们将现有的网络存储卷转换为flex存储卷,这种方法将会覆盖存储卷来源。在将来,API应该只提供通用目的的抽象,即使与LoadBalancer服务一样,抽象并不需要在所有的环境中都实现(即,API不需要迎合最低公共特性)。NIY:需要为注册和发现开发下面的机制: 准入控制插件和hooks(包括内置的APIs) 身份认证插件 授权插件和hooks 初始化和终结器 调度器扩展 Node标签和集群拓扑 NIY:单个API和细粒度特征的激活/失活可以通过以下机制解决: 所有组件的配置正在从命令行标志转换为版本化配置。 打算将大部分配置数据存储在配置映射(ConfigMaps)中,以便于动态重新配置、渐进发布和内省性。 所有/多个组件共同的配置应该被分解到它自己的配置对象中。这应该包括特征网关机制。 应该为语义意义上的设置添加API,例如,在无响应节点上删除Pod之前需要等待的默认时间长度。 NIY:版本管理操作的问题,取决于多个组件的升级(包括在HA集群中的相同组件的副本),应该通过以下方式来解决:为所有新的特性创建flag网关总是在它们出现的第一个小版本中,默认禁用这些特性,提供启用特性的配置补丁;在接下来的小版本中默认启用这些特性NIY:我们还需要一个机制来警告过时的节点,和/或潜在防止Master升级(除了补丁发布),直到/除非Node已经升级。NIY:字段级版本管理将有助于大量激活新的和/或alpha API字段的解决方案,防止不良写入过时的客户端对新字段的阻塞,以及非alpha API的演进,而不需要成熟的API定义的扩散。Kubernetes API server忽略不支持的资源字段和查询参数,但不忽略未知的/未注册的API(注意禁用未实现的/不活动的API)。这有助于跨多个版本的集群重用配置,但往往会带来意外。Kubctl支持使用服务器的Wagger/OpenAPI规范进行可选验证。这样的可选验证,应该由服务器(NYY)提供。此外,为方便用户,共享资源清单应该指定Kubernetes版本最小的要求,这可能会被kubectl和其他客户端验证。服务目录机制(NIY)应该能够断言应用级服务的存在,例如S3兼容的群集存储。与安全相关的系统分层为了正确地保护Kubernetes集群并使其能够安全扩展,一些基本概念需要由系统的组件进行定义和约定。最好从安全的角度把Kubernetes看作是一系列的环,每个层都赋予连续的层功能去行动。 用于存储核心API的一个或者多个数据存储系统(etcd) 核心APIs 高度可信赖资源的APIs(system policies) 委托的信任API和控制器(用户授予访问API /控制器,以代表用户执行操作),无论是在集群范围内还是在更小的范围内 在不同范围,运行不受信任/作用域API和控制器和用户工作负载 当较低层依赖于更高的层时,它会使安全模型崩溃,并使系统变得更加复杂。管理员可以选择这样做以获得操作简单性,但这必须是有意识的选择。一个简单的例子是etcd:可以将数据写入etcd的任何组件现在都在整个集群上,任何参与者(可以破坏高度信任的资源)都几乎可以进行逐步升级。为每一层的进程,将上面的层划分成不同的机器集(etcd-> apiservers +控制器->核心安全扩展->委托扩展- >用户工作负载),即使有些可能在实践中崩溃。如果上面描述的层定义了同心圆,那么它也应该可能存在重叠或独立的圆-例如,管理员可以选择一个替代的秘密存储解决方案,集群工作负载可以访问,但是平台并不隐含地具有访问权限。这些圆圈的交点往往是运行工作负载的机器,并且节点必须没有比正常功能所需的特权更多的特权。最后,在任何层通过扩展添加新的能力,应该遵循最佳实践来传达该行为的影响。当一个能力通过扩展被添加到系统时,它有什么目的?使系统更加安全为集群中的每一个人,启用新的“生产质量”API在集群的子集上自动完成一个公共任务运行一个向用户提供API的托管工作负载(spark、数据库、etcd)它们被分为三大类:1、集群所需的(因此必须在内核附近运行,并在存在故障时导致操作权衡)2、暴露于所有集群用户(必须正确地租用)3、暴露于集群用户的子集(像传统的“应用程序”工作负载运行)如果管理员可以很容易地被诱骗,在扩展期间安装新的群集级安全规则,那么分层被破坏,并且系统是脆弱的。 本文转自DockOne-Kubernetes系统架构演进过程与背后驱动的原因
Linkerd社区对自身服务网格平台进行一轮最新更新,旨在进一步提高开发人员与服务拥有者的效率,同时与持续发展的Kubernetes生态系统实现紧密集成。另外,此次更新还为Linkerd在日益拥挤的服务网格领域中争取到一些喘息空间。Bouyant公司CEO兼Linkerd社区初始开发者之一William Morgan表示,2.0版本最重要的特征就在于基础代码库进行了完全重写,且更加注重对服务网格部署的简化。此次重写将控制层由JVM编程语言变更为Go编程语言。Morgan表示,与此前的迭代相比,这轮大规模调整使得Linkerd 2.0“体积更小,而速度更快。”除了性能优势之外,转向Go语言还令Linkerd与Kubernetes生态系统更为接近。Morgan指出,“我们的大多数用户都身在Kubernetes阵营,因此我们希望尽早解决这个问题。”Morgan解释称,除了与Kubernetes生态系统相结合之外,Go编程语言也“更容易上手”,并将推动这套平台迎来更为重大的创新。Linkerd与Kubernetes亦同属于云原生计算基金会(简称CNCF)生态系统中的组成部分。服务边挂 该平台还拥有新的服务边挂设计,即允许平台仅运行一项服务,同时在无需配置或代码变更的前提下实现自动化观察、可靠性与运行时诊断。Morgan解释道,这种方法与当前的服务网格平台相反——因为后者采取“全有或全无”的价值主张。他指出,路由是一项部署强度较高的任务,特别是考虑到最终用户对于云原生空间往往并不熟悉。Morgan在描述当前进程时表示,“这是一项重要技术,需要投入大量时间才能学习完成。此外,用户还必须将相关知识与实际技术结合起来,这明显会提高使用门槛。”而对于缺少云原生工程师或意见领袖的技术支持的服务拥有者而言,这道门槛无疑更高。Morgan指出,“人们并不关心Kubernetes、Docker或者服务网格,他们只是想让其直接发挥作用。”在Linkerd 2.0的帮助下,Morgan认为这些服务拥有者能够使用边挂方式启动单一服务,从而简化过渡流程。“服务拥有者将获得可靠性、可见性与调试能力,从而在白天高效运作,并在夜晚安心入睡。”Morgan指出,此次Linkerd更新能够在数秒之内完成服务安装,这一能力与其它服务网格平台“形成了鲜明的对比。”服务网格空间 随着服务网格空间受到越来越多新产品的关注,这种对比也显得愈发必要。Istio无疑是其中最值得一提的对手,其目前已经得到谷歌、IBM以及Lyft等企业的采用。这套平台于今年7月迎来了自己的通用版本(1.0)。考虑到其源自谷歌的身份,Istio当然受到Google Cloud Platform的官方支持,但目前仍未能与Amazon Web Services(简称AWS)以及微软Azure建立官方对接。此外,其亦严重依赖于同属于云原生基金会的Envoy服务代理平台。红帽公司Istio产品经理Brian Harrington最近解释称,Istio与Linkerd之间存在明显不同,因为前者充当的实际是用于服务网格管理的控制层。其能够处理Enovy边挂部署——即将Envoy部署在运行中的容器Pod旁侧,并通过同Kubernetes或Apache Mesos等容器编排层平台的配合实现部署协调。Harrington同时补充称,“通过这种流量拦截过程,Istio得以执行其「神奇的」服务组件自动连接能力。”Linkerd社区此前已经添加过相关支持,允许用户同时运行Linkerd与Istio——其中的具体方法是将Istio作为Linkerd实例的控制层。此外,Linkerd的关注重点与HasiCorp的Consul服务网格平台也比较相似。HashiGroup创始人兼联席CTO Mitchell Hasimoto最近在采访当中表示,与Istio相比,Consul无需强制要求用户接受所有组件即可建立服务网格,这将为组织提供更多选择。Hashimoto指出,“Istio在Kubernetes中的运行难度更低,但Consul则更具全局能力。” 未来方向 Morgan认为,尽管目前的服务网格选项越来越多,但Linkerd社区仍将专注于提高平台的可用性。他指出,凭借着这一关注重点,Linkerd平台本身仍将为广大用户所喜欢,并预计Linkerd将“比计划时间更早”由云原生基金会的“孵化”项目转变为全面“结业”项目。Morgan最后总结称,“对我们来说,需求始终来自项目背后的用户社区,而这才是我们需要关注的重点所在。” 本文转自DockOne-Linkerd 2.0迎来更新,向着Kubernetes再进一步
如果您的应用程序是面向大量用户、会吸引大量流量,那么一个不变的目标一定是在高效满足用户需求的同时、不让用户感知到任何类似于“服务器繁忙!”的情况。这一诉求的典型解决方案是横向扩展部署,以便有多个应用程序容器可以为用户请求提供服务。但是,这种技术需要可靠的路由功能,需要可以有效地在多个服务器之间分配流量。本文分享的内容就是要解决负载均衡解决方案的问题。Rancher 1.6是Docker和Kubernetes的容器编排平台,为负载均衡提供了功能丰富的支持。在Rancher 1.6中,用户可以通过使用开箱即用的HAProxy负载均衡器,来提供基于HTTP / HTTPS / TCP主机名/路径的路由。而在本文中,我们将探讨如何在原生使用Kubernetes进行编排的Rancher 2.0平台上实现这些流行的负载均衡技术。Rancher 2.0 负载均衡功能 通过Rancher 2.0,用户可以开箱即用地使用由NGINX Ingress Controller支持的原生Kubernetes Ingress功能进行7层负载均衡。因为Kubernetes Ingress仅支持HTTP和HTTPS协议,所以目前如果您使用的是Ingress支持,那么负载均衡仅限于上述这两种协议。对于TCP协议,Rancher 2.0支持在部署Kubernetes集群的云上配置第4层TCP负载均衡器。后文中我们还将介绍如何通过ConfigMaps为TCP均衡配置NGINX Ingress Controller。HTTP/HTTPS 负载均衡功能在Rancher 1.6中,您添加了端口/服务规则以配置HAProxy负载均衡器,以均衡目标服务。您还可以配置基于主机名/路径的路由规则。例如,下面让我们来看看一个在Rancher 1.6上启动了两个容器的服务。启动的容器正在监听私有80端口。为了均衡两个容器之间的外部流量,我们可以为应用程序创建一个负载均衡器,如下所示。在这里,我们会配置负载均衡器,将进入端口80的所有流量转发到目标服务的容器端口,然后Rancher 1.6在负载均衡器服务上放置了一个方便的链接到公共端点。 Rancher 2.0提供了一种使用非常相似的、由NGINX Ingress Controller支持的、使用Kubernetes Ingress的负载均衡器功能。下文中我们一起来看看我们该如何做。 Rancher 2.0 Ingress Controller部署Ingress只是一种规则,控制器组件会将这一规则应用于实际负载均衡器中。实际负载均衡器可以在集群外部运行,也可以在集群中部署。通过RKE(Rancher Kubernetes安装程序),Rancher 2.0让用户可以开箱即用地在配置的集群上部署NGINX Ingress Controller和负载均衡器,以处理Kubernetes Ingress规则。请注意,NGINX Ingress Controller默认安装在RKE配置的集群上。通过云提供商(如GKE)配置的集群具有自己的Ingress Controller来配置负载均衡器。本文的范围仅适用于使用RKE安装的NGINX Ingress Controller。RKE将NGINX Ingress Controller部署为Kubernetes DaemonSet——因此NGINX实例会部署在集群中的每个节点上。NGINX就像一个Ingress Controller,在整个集群中监听Ingress创建,它还会将自身配置为满足Ingress规则的负载均衡器。DaemonSet配置有hostNetwork以暴露两个端口——端口80和端口443。有关如何部署NGINX Ingress Controller DaemonSet和部署配置选项的详细信息,请参阅此处:https://rancher.com/docs/rke/v ... lers/如果您是Rancher 1.6用户,那么将Rancher 2.0 Ingress Controller以DaemonSet的形式部署,会带来一些你需要知悉的重要的改变。在Rancher 1.6中,您可以在堆栈中部署可扩展的负载均衡器服务。因此,如果您在Cattle环境中有四台主机,则可以部署一台规模为2的负载均衡器服务,并通过端口80在这两个主机IP地址上指向您的应用程序。然后,您还可以在剩余的两台主机上启动另一台负载均衡器,以通过端口80再次均衡不同的服务(因为负载均衡器使用不同的主机IP地址)。Rancher 2.0 Ingress Controller是一个DaemonSet——因此它全局部署在所有可调度节点上,以便为整个Kubernetes集群提供服务。因此,在对Ingress规则进行编程时,你需要使用唯一的主机名和路径指向工作负载,因为负载均衡器节点IP地址和端口80/443是所有工作负载的公共访问点。 现在让我们看看如何使用Ingress将上述1.6示例部署到Rancher 2.0上。在Rancher UI上,我们可以导航到Kubernetes Cluster和Project,并选择【部署工作负载/Deploy Workloads】功能,在命名空间下部署所需镜像的工作负载。让我们将工作负载的规模设置为两个副本,如下所示: 以下是工作负载选项卡上部署和列出工作负载的方式: 要达到这两个pod之间的均衡,您必须创建Kubernetes Ingress规则。要创建此规则,请导航到您的集群和项目,然后选择“ 负载均衡”选项卡。 与Rancher 1.6中的服务/端口规则类似,您可以在此处指定针对工作负载的容器端口的规则。 基于主机和路径的路由 Rancher 2.0允许您添加基于主机名或URL路径的Ingress规则。根据您的规则,NGINX Ingress Controller将流量路由到多个目标工作负载。下面让我们看看如何使用相同的Ingress规范将流量路由到命名空间中的多个服务。比如如下两个在命名空间中部署的工作负载:我们可以使用相同的主机名但不同的路径添加Ingress来均衡这两个工作负载的流量。 Rancher 2.0还为Ingress记录中的工作负载提供了方便的链接。如果配置外部DNS以对DNS记录进行编程,则可以将此主机名映射到Kubernetes Ingress地址。 Ingress地址是您的集群中Ingress Controller为您的工作负载分配的IP地址。您可以通过浏览此IP地址来达到工作负载。使用kubectl查看控制器分配入口地址。 您可以使用Curl来测试基于主机名/路径的路由规则是否正常工作,如下所示: 以下是使用基于主机名/路径的规则的Rancher 1.6配置规范,与2.0 Kubernetes Ingress YAML规范进行比较: HTTPS /证书选项 Rancher 2.0 Ingress功能还支持HTTPS协议。您可以在配置Ingress规则时上载证书并使用它们,如下所示:添加Ingress规则时选择证书: Ingress限制 尽管Rancher 2.0支持HTTP- / HTTPS-基于主机名/路径的负载均衡,但要突出的一个重要区别是在为工作负载配置Ingress时需要使用唯一的主机名/路径。原因是Ingress功能仅允许将端口80/443用于路由,负载均衡器和Ingress Controller则可作为DaemonSet全局启动。从最新的Rancher 2.x版本开始,Kubernetes Ingress不支持TCP协议,但我们将在下一节中讨论使用NGINX Ingress Controller的解决方法。TCP负载均衡选项四层负载均衡器 对于TCP协议,Rancher 2.0支持在部署Kubernetes集群的云提供程序中配置四层负载均衡器。为集群配置此负载均衡器设备后,Layer-4 Load Balancer在工作负载部署期间选择for port-mapping 选项时,Rancher会创建Load Balancer服务。此服务会让Kubernetes的相应云提供商配置负载均衡器设备。然后,此设备将外部流量路由到您的应用程序pod。请注意,上述功能需要该Kubernetes云提供商满足负载均衡器服务的要求,按此文档配置:https://rancher.com/docs/ranch ... ders/一旦负载均衡器配置成功,Rancher将在Rancher UI中为您的工作负载的公共端点提供一个链接。 通过ConfigMaps支持NGINX Ingress Controller TCP 如上所述,Kubernetes Ingress本身不支持TCP协议。因此,即使TCP不是NGINX的限制,也无法通过Ingress创建来配置NGINX Ingress Controller以进行TCP负载均衡。但是,您可以通过创建一个Kubernetes ConfigMap,来使用NGINX的TCP负载均衡功能,具体可参阅这里:https://github.com/kubernetes/ ... es.md。您可以创建Kuberenetes ConfigMap对象,来将pod配置参数存储为键值对,与pod镜像分开,更多细节可以参考这里:https://kubernetes.io/docs/tas ... gmap/要配置NGINX以通过TCP暴露服务,您可以添加或更新命名空间tcp-services中的ConfigMap ingress-nginx。此命名空间还包含NGINX Ingress Controller pod。ConfigMap条目中的密钥应该是您要公开访问的TCP端口,其值应为格式<namespace/service name>:<service port>。如上所示,我暴露了Default命名空间中存在的两个工作负载。例如,上面ConfigMap中的第一个条目告诉NGINX我想在外部端口上暴露运行在default命名空间上的myapp工作负载,并监听在外部端口6790上的私有端口80。 将这些条目添加到Configmap,将自动更新NGINX pod,以配置这些工作负载来进行TCP负载均衡。您可以执行部署在ingress-nginx命名空间中的这些pod,并查看如何在/etc/nginx/nginx.conf文件中配置这些TCP端口。<NodeIP>:<TCP Port>在NGINX配置/etc/nginx/nginx.conf更新后,应该可以使用公开的工作负载。如果它们不可访问,则可能必须使用NodePort服务来暴露TCP端口。Rancher 2.0负载均衡的限制Cattle提供了功能丰富的负载均衡器支持(详细介绍在此:https://rancher.com/docs/ranch ... ncers)。其中一些功能在Rancher 2.0中暂时没有等效功能:当前NGINX Ingress Controller不支持SNI。TCP负载均衡需要集群中的云提供程序启用的负载均衡器设备。Kubernetes上没有对TCP的Ingress支持。只能通过Ingress为端口80/443配置HTTP / HTTPS路由。此外,Ingress Controller作为Daemonset进行全局部署,而不是作为可扩展服务启动。此外,用户无法随机分配外部端口来进行负载均衡。因此,用户需要确保它们配置的主机名/路径组合是唯一的,以避免使用相同的两个端口发生路由冲突。无法指定端口规则优先级和排序。Rancher 1.6增加了对draining后端连接和drain超时的支持。Rancher 2.0暂不支持此功能。目前在Rancher 2.0中,不支持指定自定义粘性策略和自定义负载均衡器配置以附加到默认配置。原生Kubernetes对此有一定的支持,不过也只限于定制NGINX配置:https://kubernetes.github.io/i ... ADME/。将负载均衡器配置从Docker Compose迁移到Kubernetes YAML?Rancher 1.6通过启动自己的微服务提供负载均衡器支持,该微服务启动并配置了HAProxy。用户添加的负载均衡器配置在rancher-compose.yml文件中指定,而不是标准的docker-compose.yml。Kompose工具适用于标准的docker-compose参数,但在本文的情况下,是无法解析Rancher负载均衡器配置结构的。截至目前,我们暂时无法使用Kompose工具将负载均衡器配置从Docker Compose转换为Kubernetes YAML。结 论由于Rancher 2.0基于Kubernetes并使用NGINX Ingress Controller(与Cattle使用HAProxy相比),因此原先Rancher 1.6中Cattle支持的一些负载均衡器的功能目前暂时没有直接等效功能。但是,Rancher 2.0支持流行的HTTP / HTTPS基于主机名/路径的路由,这种路由最常用于实际部署。还通过Kubernetes Load Balancer服务使用云提供商提供四层(TCP)支持。2.0中的负载均衡功能也具有类似的直观UI体验。Kubernetes生态系统在不断发展,我相信我们能找到更多适合所有负载均衡细微差别的解决方案! 本文转自DockOne-Kubernetes上的负载均衡详解
这篇文章作者分别阐述了Kubernetes与Serverless的优缺点,实际上两者可能并不是竞争关系,在某些架构中,两者可以同时存在以满足不同的需求。但是最终的目的都是为了使应用程序部署更方便快捷,更易管理,更具成本效益以及对开发人员友好。Kubernetes和Serverless都是令人兴奋的强大的平台,它们可以通过多种方式为企业在敏捷性,扩展性以及计算性能上获取巨大的提升。但是,不要忘记Kubernetes能提供一些Serverless所没有的功能,反之亦然。成功部署其中任何一个方案的关键点在于哪种技术更适用于当前的场景。Kubernetes的兴起 Kubernetes是为大规模的云计算设计的,一开始就是因为Google使用了超大规模的部署才开发了Kubernetes。之后Kubernetes被改造成可以小规模使用,并且适用于大多数大型云提供商,这归功于它过去几年迅猛的发展。根据Cloud Native Computing Foundation(CNCF)的用户调查,Kubernetes的增长远超过所有其他形式的编排软件。自首次亮相以来,Kubernetes已成为主流。但是,正如从主机迁移到客户端服务器上总会遇到各种问题,在采用完全基于容器架构的过程中也依然会遇到各种问题,尽管这种容器架构是由Kubernetes进行编排的。容器扩展并不是实时的,你需要等到容器上线,同时你也必须处理容器管理问题。据CNCF调查表明,存储,安全以及网络问题仍然是通过Kubernetes部署其架构的程序员最关心的问题。那么选择Serverless呢? Serverless架构,在很多方面只是对微服务架构的重新打包和重新构想,这种架构正在与Kubernetes形成竞争,因为它允许扩展应用程序和部署,无需担心复杂性和配置问题。这两个问题正是使用Kubernetes和容器的痛点。 但不要把两者当做一样的。Serverless也被称为功能即服务(FaaS),Serverless体系结构仍然需要运行服务器,但是它是事件驱动的架构,相比之下,容器化应用程序本质上仍然是传统的应用程序,只是分成许多较小的部分或服务。 使用容器化的应用程序,它永远不会完全关闭。即使没有人访问它,容器仍然需要存在并运行。你可以将它们缩小到单个实例,但它们仍在运行并需要花钱。 一个Serverless应用,如果没有请求使用它的功能,那么它的成本可能降低到零,实际上如果没有请求,它们会停止运行,这可以显著的降低成本,并且有利于更迅速的伸缩。访问Serverless程序的请求越多,它的体量就会变得越大。关于Serverless架构会取代容器化的应用程序的想法似乎是一个不合理的建议,并非一切功能都能被简化成一个短暂的功能(function)。一些程序需要在应用程序运行时持久化数据以及状态,Serverless的设计很难满足这种需求,但是大家对Serverless的兴趣却在快速增长。例如,根据MarketsandMarkets Research的数据,FaaS(Function as a Service)市场预计将从2016年的1.88美元飙升至2021年的77.2亿美元。然而,这不是一场零和博弈(即参与游戏的个体必须通过其他个体的损失来获益,所有个体不能同时获益或者损失),而Serverless的增长并不一定预示着Kubernetes和容器的死亡。实际上,它甚至可能帮助扩展Kubernetes的使用,至少可以通过主要的FaaS提供商来扩展其Serverless产品。Serverless架构很可能通过仅仅支付使用的服务而不用支付运行容器或一组容器所需的开销来进一步降低成本,但是这件事需要进行权衡,不经常访问的Serverless代码虽然运行成本不高,但在运行时(如Java)或底层容器用于服务请求的情况下,可能会遇到延迟增加的问题。这些额外的延迟可能令人无法接受。从一个开发者的角度来说,FaaS可以极大的促进效率的提升,使程序员的开发过程更加愉悦,程序员可以更快的将小块代码推到生产环境而不用顾虑配置和管理的开销,从而提高生产力。结论 应用程序开发和部署策略,都在不断发展。通常,从一个架构到另一个架构的迁移标志着第一个架构的终结,但情况并非总是如此。至少现在,还没有一个通用的解决方案可以解决低成本的,大规模的交付应用程序所遇到的所有问题。与任何部署模型一样,架构师需要在成本,性能和可管理性之间进行权衡。Kubernetes——以及其他的容器化技术——已经拥有了它们应得的地位,Kubernetes市场的迅速普及和发展证明了它正满足市场需求。虽然我并没看出容器化的必要性,如果不必要,那么容器编排也没任何意义,这种解决方案也并总是适用。同样Serverless的FaaS显然填补了市场的需求,并且整体上呈现出显着的增长。当然,增长并不一定意味着合适,但市场倾向于自我纠正以弥补这一点。同样,Kubernetes vs.Serverless不是零和博弈。Serverless的增长并不表示Kubernetes的死亡。每个技术在现代应用程序的开发和部署中都发挥着重要作用。在过去的20年中,应用程序部署一直朝着更小,更易管理,更具成本效益和开发人员友好的架构迈进,并且毋庸置疑这种趋势会持续下去。虽然Serverless可能是将应用程序抽象到其最基本组件的逻辑结论,但并非所有应用程序都能以这种方式抽象出来。同理可得,出于对持久性和可伸缩性的需求,某些应用程序将会需要容器,需要容器的编排和管理。如果这两种技术没有直接相互竞争,它们很难不会有持续性的显著增长。 本文转自DockOne-选择Serverless还是Kubernetes?这种争辩并没有意义
前言 kubernetes中部署的pod默认根据资源使用情况自动调度到某个节点。可在实际项目的使用场景中都会有更细粒度的调度需求,比如:某些pod调度到指定主机、某几个相关的服务的pod最好调度到一个节点上、Master节点不允许某些pod调度等。使用kubernetes中的节点调度、节点亲和性、pod亲和性、污点与容忍可以灵活的完成pod调度,从而满足项目中较为复杂的调度需求。下面用一些项目中的使用场景聊聊pod调度使用法则。原创作者:钟亮 pod调度主要包含以下内容 nodeSelector nodeAffinity podAffinityz Taints 指定节点调度kubectl get nodes #获取当前Kubernetes集群全部节点 NAME STATUS ROLES AGE VERSION dev-10 Ready <none> 45d v1.8.6 dev-7 Ready master 45d v1.8.6 dev-8 Ready <none> 45d v1.8.6 dev-9 Ready <none> 45d v1.8.6 nodeName通过主机名指定pod调度到指定节点spec: nodeName: dev-8 #设置要调度节点的名称 containers: - image: nginx imagePullPolicy: Always name: my-nginx resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 nodeSelectorkubernetes中常用label来管理集群的资源,nodeSelector可通过标签实现pod调度到指定节点上。举列:使用nodeSelector将pod调度到dev-9节点上step1:给dev-9打标签kubectl label nodes dev-9 test=nginx #设置标签 kubectl get nodes --show-labels |grep test=nginx #查询标签是否设置成功 setp2:nodeSelector设置对应标签spec: containers: - image: nginx imagePullPolicy: Always name: my-nginx resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst nodeSelector: #设置标签 test: nginx restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 查询pod已经成功调度到dev-9节点 kcc get pods -o wide -n ns-team-1-env-1|grep my-nginx my-nginx-556dcc8c5c-27f7k 1/1 Running 0 2m 10.244.2.238 dev-9 nodeAffinitynode 节点 Affinity ,从字面上很容易理解nodeAffinity就是节点亲和性,Anti-Affinity也就是反亲和性。节点亲和性就是控制pod是否调度到指定节点,相对nodeSelector来说更为灵活,可以实现一些简单的逻辑组合。nodeAffinity策略preferredDuringSchedulingIgnoredDuringExecution #软策略,尽量满足 requiredDuringSchedulingIgnoredDuringExecution #硬策略,必须满足 根据具体一些常用场景感受下 场景1:必须部署到有 test=nginx 标签的节点 spec: containers: - name: with-node-affinity image: nginx affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: test operator: In values: - nginx 场景2:最好部署到有test=nginx 标签的节点 spec: containers: - name: with-node-affinity image: nginx affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: test operator: In values: - nginx 场景3:不能部署在dev-7,dev-8节点;最好部署到有test=nginx标签的节点 spec: containers: - name: with-node-affinity image: nginx affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: NotIn values: - dev-7 - dev-8 preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: test operator: In values: - nginx 通过以上场景可以看出使用节点亲和性可灵活的控制pod调度到节点,实现一些有逻辑组合的调度 Kubernetes中的operator提供了下面几种过滤条件: - In:label 的值在某个列表中 - NotIn:label 的值不在某个列表中 - Gt:label 的值大于某个值 - Lt:label 的值小于某个值 - Exists:某个 label 存在 - DoesNotExist:某个 label 不存 podAffinitynodeSelector和nodeAffinity 都是控制pod调度到节点的操作,在实际项目部署场景中,希望根据服务与服务之间的关系进行调度,也就是根据pod之间的关系进行调度,Kubernetes的podAffinity就可以实现这样的场景,podAffinity的调度策略和nodeAffinity类似也有:requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution场景:希望my-nginx服务my-busybox服务 最好部署在同一个节点上 my-nginx基本信息 apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: app: my-nginx #标签 app = my-nginx name: my-nginx namespace: ns-team-1-env-1 ... ... my-busybox基本信息 apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: app: my-busybox #标签 app = my-busybox name: my-busybox namespace: ns-team-1-env-1 ... ... 在my-busybox设置pod亲和性 apiVersion: extensions/v1beta1 kind: Deployment metadata: name: my-busybox labels: app: my-busybox spec: containers: - name: my-busybox image: nginx affinity: podAffinity: #my-busybox 添加pod亲和性,与打了标签app = my-nginx的pod靠近 requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - my-nginx topologyKey: kubernetes.io/hostname 以上例子表示,有一个pod在运行,并且这个pod有标签是app=my-nginx,my-busybox这个pod就会 与其调度到同一个节点上,可预见因为上面我们的my-nginx已经调度到dev-9,因此my-nginx和 my-busybox都会在dev-9节点上查询到 Taints & tolerationsTaints:污点,tolerations:容忍。在实际项目实践中有时候不希望某些服务调度到指定节点上,比如master节点只运行Kubernetes 组件,应用服务不希望调度到master上。这种场景只需要在master节点设置一个污点。#设置污点 kubectl taint nodes dev-7 system=service:NoSchedule 设置了污点就不能调度了么?No 如果设置了污点还是希望某些pod能够调度上去,可以给pod针对污点加容忍。#添加容忍 tolerations: - key: "system" operator: "Equal" value: "service" effect: "NoSchedule" 添加了容忍system=service , 这样就可以调度到节点设置了污点:system=service:NoSchedule 的主机上,其他没有添加容忍的服务就不会调度到打了污点的节点上。 effect 有三种设置 - NoSchedule:pod不能调度到标记了taints的节点 - PreferNoSchedule:pod最好不要调度到标记了taints的节点 - NoExecute:设置了污点,马上踢出该节点中没有设置对应污点的Tolerate设置的pod 参考文档https://kubernetes.io/docs/con ... node/https://kubernetes.io/docs/con ... tion/关于睿云智合深圳睿云智合科技有限公司成立于2012年,总部位于深圳,并分别在成都、深圳设立了研发中心,北京、上海设立了分支机构,核心骨干人员全部为来自金融、科技行业知名企业资深业务专家、技术专家。早期专注于为中国金融保险等大型企业提供创新技术、电子商务、CRM等领域专业咨询服务。自2016年始,在率先将容器技术引进到中国保险行业客户后,公司组建了专业的容器技术产品研发和实施服务团队,旨在帮助中国金融行业客户将容器创新技术应用于企业信息技术支持业务发展的基础能力改善与提升,成为中国金融保险行业容器技术服务领导品牌。此外,凭借多年来在呼叫中心领域的业务经验与技术积累,睿云智合率先在业界推出基于开源软交换平台FreeSwitch的微服务架构多媒体数字化业务平台,将语音、视频、webchat、微信、微博等多种客户接触渠道集成,实现客户统一接入、精准识别、智能路由的CRM策略,并以容器化治理来支持平台的全应用生命周期管理,显著提升了数字化业务处理的灵活、高效、弹性、稳定等特性,为帮助传统企业向“以客户为中心”的数字化业务转型提供完美的一站式整体解决方案。 本文转自DockOne-Kubernetes-项目中pod调度使用法则
过去的几年中,在云计算领域的开源社区中最有争议的话题莫过于Cloud Foundry和Kubernetes的关系。大家的疑问紧紧围绕着三个问题:“它们会互相取代对方吗?”,“它们是互斥的吗?” ,“还是说它们是可以融合的?”。放眼望去在目前的商业产品中,两者几乎没什么关联和集成,都可以运行在各种IaaS之上。多年以来,我们一直从事Cloud Foundry部署的相关工作,从原来传统的BOSH CPI到现在的容器化Cloud Foundry的工作。在这期间,我们对两者结合的多种技术选型的进行探索,包括可行性分析、最佳实践、经验总结和价值意义等方面,旨在最大程度上利用和发挥两者的优势,并提升开发者体验。本文将从Cloud Foundry部署的层面,来介绍业界和我们如何去把两套系统进行整合的。当然任何整合方案都会有各自的优缺点,而且所有的整合还在一个快速发展的阶段,所以在这里算是给大家抛砖引玉,为以后的工作和学习提供一些帮助。 Cloud Foundry和Kubernetes简介 Cloud Foundry Cloud Foundry是一个独立于云的平台即服务解决方案,也是业界最成功的PaaS平台。Cloud Foundry提供了一个可轻松运行、扩展和维护应用程序的环境和快速便捷的开发者体验。Cloud Foundry支持Java、NodeJS、Ruby、Python等大多数语言和环境。开源的Cloud Foundry由Cloud Foundry基金会开发并支持,基金会包括Pivotal、IBM、VMware以及其它许多厂商。商业版本的Cloud Foundry,如IBM Bluemix和Pivotal Cloud Foundry,是基于开源的Cloud Foundry项目并在其基础上提供企业级的支持。Kubernetes Kubernetes是一个来源于谷歌Borg项目的开源云平台。它由Cloud Native Computing Foundation发起,该基金会的成员包括了许多行业巨头,如AWS、Azure、Intel、IBM、RedHat、Pivotal等许多公司。Kubernetes首要的功能是一个容器编排和容器生命周期的管理。尽管不限于此,但它通常是被用来运行Docker容器,它的受众人群更广泛一些,比如想要构建在容器服务之上的应用和服务开发人员。有一些解决方案基于Kubernetes提供了PaaS体验,比如IBM的Container Service和RedHat的OpenShift等。两个平台的比较 两个平台都很成熟和完整,而且随着不断的开发和改进,所以两个平台的相同和不同之处也会随之变。当然两大项目有很多相似之处,包括容器化,命名空间和身份验证等机制。而两者也有各自的优势:Cloud Foundry的优势在于: 成熟的身份验证系统UAA,用户组和multi-tenancy的支持 方便快捷的cf push 自带负载均衡Router 强大的日志和metrics整合 成熟的部署工具BOSH Kubernetes的优势在于: 大量社区和第三方支持,提供强大的扩展性 完善的容器生命周期和自动伸缩管理 方便快捷的容器化应用部署 良好、多样的持久层支持 多种开源UI支持 现阶段两者的关系及结合情况 从上面的比较,我们可以看到,两大平台各有各的优势及强大之处。业界有很多厂商都想尝试将两者融合,从而将优势发挥到最大化。从现在来看两者整合主要有以下三种方式:Kubernetes CPI 我们知道BOSH是Cloud Foundry官方指定的部署工具,它是一个针对大规模分布式系统的部署和生命周期管理的开源工具。但是BOSH不仅仅局限于部署Cloud Foundry,也可以应用于别的分布式系统,只需要其提供符合要求的Release即可。CPI全称Cloud Provider Interface,是BOSH用来与IaaS通信完成虚拟机实例和模板的创建和管理的一个API接口,CPI目前能够支持Amazon的AWS、微软的Azure、IBM的SoftLayer等IaaS平台,国内阿里云也提供了CPI的支持。BOSH的主控制器Director通过CPI与底层的IaaS层交互,将BOSH manifest.yaml 文件定义任务及组件部署到IaaS层的VM上,如下图所示:所以,第一种整合,也就是开发一套Kubernetes的CPI,通过BOSH和manifest.yaml的配置将Cloud Foundry部署到Kubernetes上。现在有一些大的厂商如IBM、SAP在开发相应的Kubernetes CPI,大家可以在GitHub中搜索到。我个人觉得,这种方式虽然容易上手,但还是以IaaS的角度来看待Kubernetes,底层还是通过BOSH来管理的,没能最大地发挥Kubernetes平台的优势的。 Cloud Foundry Container Runtime(CFCR) Cloud Foundry基金会在2017年底宣布把Pivotal和谷歌捐献的Kubo项目改名为CFCR(Cloud Foundry Container Runtime)。总体来说是利用BOSH来部署Cloud Foundry和Kubernetes,并通过Application Runtime管理Cloud Foundry的Application Service;通过Container Runtime管理Kubernetes的Container Service,比如一些无法部署在Cloud Foundry内的服务,如数据库、监控等。然后通过Open Service Broker将两者连接起来,如下图所示:容器化Cloud Foundry 将Cloud Foundry所有组件容器化,并部署到Kubernetes上是一种比较新型的整合方式。现在有一些大的厂商做这方面的工作,比如SUSE、IBM和SAP。其核心就是通过将Cloud Foundry的BOSH Release转化成Docker镜像,然后通过Kubernetes的部署工具Helm来将Cloud Foundry更加自然地部署到Kubernetes上。只有在生成Cloud Foundry组件的Docker镜像是需要用到BOSH Cli去转化BOSH release,部署及部署之后的管理,都不需要BOSH,而是交给Kubernetes来对所有Cloud Foundry组件的Pod、服务及相应配置资源的生命周期进行管理。这样既享受到了Cloud Foundry带来的良好的开发者体验,又用到了Kubernetes强大的管理和扩展能力:IBM在两者结合上做的工作和成果 IBM主要是和SUSE合作,在上面第三种方案的基础上,创建一套可以在IBM Cloud上快速部署的企业级Cloud Foundry。这个新的服务名称叫做IBM Cloud Foundry Enterprise Environment(CFEE),他的主要构件流程如下: 首先我们需要将BOSH的Release通过SUSE的转换工具Fissile将其编译并制作成CF组件相应的Docker镜像。 之后我们需要通过Fissile将预设的参数转换成Helm或者Kubernetes的配置资源文件。 利用IBM Cloud强大的服务资源,增加企业级服务的支持,比如数据库服务,安全服务和负载均衡服务。 最后发布新的IBM CF版本,用户可以直接在IBM Cloud的Catalog里面找到并自动部署出一套CFEE环境。 和传统的BOSH CPI的部署方式的比较,新的Cloud Foundry版本发布后,管理员只需执行一次来构建任务来创建新版本Docker镜像和Helm配置,之后就可以重复使用了。这种部署Cloud Foundry的方式时间大概在15-20分钟,和之前BOSH部署4-6个小时相比,快了很多。IBM已于2018上半年的Cloud Foundry Summit上发布了Experimental版本:在刚过去的2018年10月初,IBM又发布了CFEE的GA版,主要为企业级客户提供更好的服务、带来更大的价值。部署出来的CFEE环境不但与其他环境是相互隔离的,而且CFEE环境中部署的Cloud Foundry应用还可以很轻松地使用IBM Cloud上丰富的服务资源,如AI,大数据,IoT和Blockchain等: 感兴趣的用户可以通过IBM Cloud的Catalog找到CFEE的介绍: https://console.bluemix.net/do ... ating。在部署页面中,用户可以配置CFEE的域名,Diego-cell的个数以及CF版本等信息,然后CFEE可以实现全自动化一键部署,大概1个小时左右,一个企业级的CF环境就部署成功了:当然在部署完成后,CFEE还提供了强大的管理,监控和命令行等功能,如图所示,用户可以很轻松地在页面上查看Cloud Foundry和应用的使用情况,创建相应的资源或者绑定IBM的外部服务等: 未来两者结合的发展方向 当然两者结合的探索工作不止于此,现在越来越多的厂商和开发者加入到两者整合的研究中。其中比较火的有IBM主导的Cloud Foundry孵化项目Eirini,其想法是想将Cloud Foundry中的Diego-cell容器Garden替换成Kubernetes的容器,从而将两者更紧密地连接在一起:还有像SUSE主导的Fissile项目,未来其想法是想通过BOSH命令可以直接生成CF的Docker镜像和Helm配置,从而可以更好地和社区融合到一起。 总结 综上所述,Kubernetes之于Cloud Foundry的关系不是挑战也不是竞争关系,Cloud Foundry希望与其更好地融合,就像Cloud Foundry Foundation执行董事Abby Kearns说的:“Cloud Foundry是结构化的PaaS平台,其他平台是非结构化的,用户的需求是多元化的,并不是一定要如何容器化,而是希望平台能够更开放、支持更多的类型。”不管未来两者怎么发展,两者的结合绝对是适应市场的趋势,同时又能给用户带来多方面的价值提升。未来两者还能碰撞出什么样的火花?让我们拭目以待! 本文转自DockOne-Cloud Foundry和Kubernetes结合的过去与未来
如果你读过该系列的前一篇文章,那么在你的集群里应该已经成功运行着Gitea的deployment了。下一步是能够通过Web浏览器访问它。本文会介绍一些Kubernetes的网络基础知识,并且可以让外部网络可以访问Gitea容器。打开容器端口 Pod默认是和外部隔离的。为了能够将流量导入应用程序,我们需要打开该容器计划使用的一系列端口。Gitea容器内的软件会监听3000端口上的http请求,以及22端口上的SSH链接。可以通过如下YAML文件打开这些端口:apiVersion: extensions/v1beta1 kind: Deployment metadata: name: gitea-deployment spec: replicas: 1 selector: matchLabels: app: gitea template: metadata: labels: app: gitea spec: containers: - name: gitea-container image: gitea/gitea:1.4 ports: #+ - containerPort: 3000 #+ name: http #+ - containerPort: 22 #+ name: ssh 在集群里应用这个更新过的文件:$ kubectl apply -f gitea.yaml 这时,就可以运行命令kubectl describe deployment来查看新打开的端口了。Pod应该已经打开了3000和22端口。$ kubectl describe deployment | grep Ports Ports: 3000/TCP, 22/TCP 使用端口转发进行调试 容器上的端口应该已经开启了,但是我们仍然需要能够和集群里的pod通信。为了调试的方便,我们可以使用kubectl port-forward来连接Pod。# grab the name of your active pod $ PODNAME=$(kubectl get pods --output=template \ --template="{{with index .items 0}}{{.metadata.name}}{{end}}") # open a port-forward session to the pod $ kubectl port-forward $PODNAME 3000:3000 这样,kubectl就会将本机3000端口的所有连接转发到云上的pod里。在浏览器打开 http://localhost:3000,你就可以和服务器交互了,服务器就像运行在本地一样。创建外部LoadBanlancer 既然已经验证了Pod工作正常,就可以暴露到公网上了。我们需要添加一个新的Kubernetes资源,它会预配一个公网IP地址并且将入站请求路由到Pod上。可以使用称为Service(服务)的Kubernetes资源来达到这一目的。可以使用好几种不同类型的服务,但是这里我们使用LoadBalancer。apiVersion: extensions/v1beta1 kind: Deployment metadata: name: gitea-deployment spec: replicas: 1 selector: matchLabels: app: gitea template: metadata: labels: app: gitea spec: containers: - name: gitea-container image: gitea/gitea:1.4 ports: - containerPort: 3000 name: http - containerPort: 22 name: ssh --- kind: Service #+ apiVersion: v1 #+ metadata: #+ name: gitea-service #+ spec: #+ selector: #+ app: gitea #+ ports: #+ - protocol: TCP #+ targetPort: 3000 #+ port: 80 #+ name: http #+ - protocol: TCP #+ targetPort: 22 #+ port: 22 #+ name: ssh #+ type: LoadBalancer #+ 和Deployment一样,Service使用选择器(第29-30行)。这个选择器告诉LoadBanlancer将流量路由到哪些Pod里。当LoadBanlancer收到请求时,它会智能地将负载分发给匹配这个选择器的所有Pod。这里,因为我们仅有一个Pod,所以负载均衡很简单。LoadBanlancer管理的端口定义在31-39行。除了定义一个唯一名称以及协议类型(TCP/UDP)之外,用户还必须定义“port”和“targetPort”。这两个字段定义了外部IP的端口(port)和容器使用端口(targetPort)的映射。在33和34行里,LoadBanlancer会监听80端口的请求(Web浏览器查看网页的默认端口),并且将请求转发给Pod的3000端口。我们再次需要将更改应用到集群里:$ kubectl apply -f gitea.yaml 等待几分钟变更生效后,检查服务:$ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL_IP AGE gitea-service LoadBalancer 10.27.240.34 35.192.x.x 2m 几分钟后,就能看到一个外部IP自动添加到了服务上。在web浏览器里打开这个IP就可以和pod上的web服务器交互了。Pod间通信:ClusterIP服务 如果你尝试打开Gitea的注册页面,会看到还缺了一些东西:Gitea要求一个数据库才能提供这个功能。要解决这个问题,我们可以选择在Gitea Pod里以side-car的形式添加一个MySQL容器,或者单独创建一个MySQL的Pod。这两种方法都有各自的优势和缺点,选择哪一个取决于具体的需求。本文我们创建一个新的Pod。我们创建一个名为mysql.yaml的新YAML文件来管理数据库:apiVersion: extensions/v1beta1 kind: Deployment metadata: name: mysql-deployment spec: replicas: 1 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:5.6 ports: - containerPort: 3306 # Ignore this for now. It will be explained in the next article env: - name: MYSQL_ALLOW_EMPTY_PASSWORD value: "true" --- kind: Service apiVersion: v1 metadata: name: mysql-service spec: selector: app: mysql ports: - protocol: TCP port: 3306 type: ClusterIP 大部分内容很类似。我们声明了一个Deployment来管理这个pod,通过Service管理网络连接。这里,service的类型是“ClusterIP”;这意味着这个IP仅仅暴露在集群内部,这和之前使用LoadBanlancer将Gitea服务暴露到外部不一样。在集群里应用这个新YAML文件:$ kubectl apply -f mysql.yaml 这时你可以看到集群里新加了一个Pod、Deployment和Service。$ kubectl get pods NAME READY STATUS RESTARTS AGE gitea-pod 1/1 Running 0 9m mysql-pod 1/1 Running 0 9s $ kubectl get deployments NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE gitea-deployment 1 1 1 1 11m mysql-deployment 1 1 1 1 5m $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL_IP AGE gitea-service LoadBalancer 10.27.240.34 35.192.x.x 2m mysql-service ClusterIP 10.27.254.69 <none> 6m ClusterIP服务会自动生成一个内部IP地址,显示在上面控制台输出的“CLUSTER-IP”一栏。集群内的任何容器都可以使用这一地址访问MySQL pod。但是,并不推荐直接使用内部IP地址。Kubernetes提供了更为简单的方式来访问这个新服务:可以在地址字段输入“mysql-service”即可。这是因为内建了“kube-dns”Pod,它为所有服务管理内部的DNS解析。这样,用户可以忽略随机生成的内部IP地址,仅仅使用静态的,可读的服务名称就可以了。 要让Gitea能够和MySQL Pod通信,仅仅需要在Web UI的“host”字段输入服务的名称和端口就可以了。如果一切和预期一样,就会看到“access denied”的错误。这意味着Pod可以成功通信了,但是它们需要更多的配置信息才能完成认证。下一篇博客会继续介绍如何完成认证。下一步 本文介绍了Kubernetes网络的基础知识,包括容器端口,端口转发,LoadBanlancer以及ClusterIP服务和kube-dns。当然,网络是一个很大的领域,没有讨论的还有很多。如果你想了解Kubernetes的底层网络实现,可以参考这个系列。如果你对控制集群里的哪些Pod可以通信感兴趣,可以阅读网络策略。如果你想更好地控制服务间的相互通信,可以研究服务网格产品,比如Istio。备注 LoadBanlancer在云服务,比如GCP上工作很好,但是如果你在Docker或者Minikube上运行本地集群,就需要使用NodePort服务代替 外部端口可以设置成任意值,但是要记住如果你想访问非标准的东西,在GKE上需要设置防火墙规则。 这里使用MySQL因为Gitea是这么设计的。MySQL是一种遗留服务,并非Kubernetes原生的,如果你想从头开始设计应用程序,可能别的存储方案更为适合。 Kubernetes管理一些隐藏的pod和服务。可以运行kubectl get pods --namespace=kube-system查看它们。 本文转自DockOne-Kubernetes网络基础介绍
早在 Docker 正式发布几个月的时候,LeanCloud 就开始在生产环境大规模使用 Docker,在过去几年里 Docker 的技术栈支撑了我们主要的后端架构。这是一篇写给程序员的 Docker 和 Kubernetes 教程,目的是让熟悉技术的读者在尽可能短的时间内对 Docker 和 Kubernetes 有基本的了解,并通过实际部署、升级、回滚一个服务体验容器化生产环境的原理和好处。本文假设读者都是开发者,并熟悉 Mac/Linux 环境,所以就不介绍基础的技术概念了。命令行环境以 Mac 示例,在 Linux 下只要根据自己使用的发行版和包管理工具做调整即可。Docker 速成 首先快速地介绍一下 Docker:作为示例,我们在本地启动 Docker 的守护进程,并在一个容器里运行简单的 HTTP 服务。先完成安装: $ brew cask install docker 上面的命令会从 Homebrew 安装 Docker for Mac,它包含 Docker 的后台进程和命令行工具。Docker 的后台进程以一个 Mac App 的形式安装在 /Applications 里,需要手动启动。启动 Docker 应用后,可以在 Terminal 里确认一下命令行工具的版本:$ docker --version Docker version 18.03.1-ce, build 9ee9f40 上面显示的 Docker 版本可能和我的不一样,但只要不是太老就好。我们建一个单独的目录来存放示例所需的文件。为了尽量简化例子,我们要部署的服务是用 Nginx 来 serve 一个简单的 HTML 文件 html/index.html。$ mkdir docker-demo $ cd docker-demo $ mkdir html $ echo '<h1>Hello Docker!</h1>' > html/index.html 接下来在当前目录创建一个叫 Dockerfile 的新文件,包含下面的内容:FROM nginx COPY html/* /usr/share/nginx/html 每个 Dockerfile 都以 FROM ... 开头。 FROM nginx 的意思是以 Nginx 官方提供的镜像为基础来构建我们的镜像。在构建时,Docker 会从 Docker Hub 查找和下载需要的镜像。Docker Hub 对于 Docker 镜像的作用就像 GitHub 对于代码的作用一样,它是一个托管和共享镜像的服务。使用过和构建的镜像都会被缓存在本地。第二行把我们的静态文件复制到镜像的 /usr/share/nginx/html 目录下。也就是 Nginx 寻找静态文件的目录。Dockerfile 包含构建镜像的指令,更详细的信息可以参考这里。然后就可以构建镜像了:$ docker build -t docker-demo:0.1 . 请确保你按照上面的步骤为这个实验新建了目录,并且在这个目录中运行 docker build。如果你在其它有很多文件的目录(比如你的用户目录或者 /tmp)运行,Docker 会把当前目录的所有文件作为上下文发送给负责构建的后台进程。这行命令中的名称 docker-demo 可以理解为这个镜像对应的应用名或服务名,0.1 是标签。Docker 通过名称和标签的组合来标识镜像。可以用下面的命令来看到刚刚创建的镜像:$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE docker-demo 0.1 efb8ca048d5a 5 minutes ago 109MB 下面我们把这个镜像运行起来。Nginx 默认监听在 80 端口,所以我们把宿主机的 8080 端口映射到容器的 80 端口:$ docker run --name docker-demo -d -p 8080:80 docker-demo:0.1 用下面的命令可以看到正在运行中的容器:$ docker container ps CONTAINER ID IMAGE ... PORTS NAMES c495a7ccf1c7 docker-demo:0.1 ... 0.0.0.0:8080->80/tcp docker-demo 这时如果你用浏览器访问 http://localhost:8080,就能看到我们刚才创建的「Hello Docker!」页面。在现实的生产环境中 Docker 本身是一个相对底层的容器引擎,在有很多服务器的集群中,不太可能以上面的方式来管理任务和资源。所以我们需要 Kubernetes 这样的系统来进行任务的编排和调度。在进入下一步前,别忘了把实验用的容器清理掉:$ docker container stop docker-demo $ docker container rm docker-demo 安装 Kubernetes 介绍完 Docker,终于可以开始试试 Kubernetes 了。我们需要安装三样东西:Kubernetes 的命令行客户端 kubctl、一个可以在本地跑起来的 Kubernetes 环境 Minikube、以及给 Minikube 使用的虚拟化引擎 xhyve。$ brew install kubectl $ brew cask install minikube $ brew install docker-machine-driver-xhyve Minikube 默认的虚拟化引擎是 VirtualBox,而 xhyve 是一个更轻量、性能更好的替代。它需要以 root 权限运行,所以安装完要把所有者改为 root:wheel,并把 setuid 权限打开:$ sudo chown root:wheel /usr/local/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve $ sudo chmod u+s /usr/local/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve 然后就可以启动 Minikube 了:$ minikube start --vm-driver xhyve 你多半会看到一个警告说 xhyve 会在未来的版本被 hyperkit 替代,推荐使用 hyperkit。不过在我写这个教程的时候 docker-machine-driver-hyperkit 还没有进入 Homebrew, 需要手动编译和安装,我就偷个懒,仍然用 xhyve。以后只要在安装和运行的命令中把 xhyve 改为 hyperkit 就可以。如果你在第一次启动 Minikube 时遇到错误或被中断,后面重试仍然失败时,可以尝试运行 minikube delete 把集群删除,重新来过。Minikube 启动时会自动配置 kubectl,把它指向 Minikube 提供的 Kubernetes API 服务。可以用下面的命令确认:$ kubectl config current-context minikube Kubernetes 架构简介 典型的 Kubernetes 集群包含一个 master 和很多 node。Master 是控制集群的中心,node 是提供 CPU、内存和存储资源的节点。Master 上运行着多个进程,包括面向用户的 API 服务、负责维护集群状态的 Controller Manager、负责调度任务的 Scheduler 等。每个 node 上运行着维护 node 状态并和 master 通信的 kubelet,以及实现集群网络服务的 kube-proxy。作为一个开发和测试的环境,Minikube 会建立一个有一个 node 的集群,用下面的命令可以看到:$ kubectl get nodes NAME STATUS AGE VERSION minikube Ready 1h v1.10.0 部署一个单实例服务 我们先尝试像文章开始介绍 Docker 时一样,部署一个简单的服务。Kubernetes 中部署的最小单位是 pod,而不是 Docker 容器。实时上 Kubernetes 是不依赖于 Docker 的,完全可以使用其他的容器引擎在 Kubernetes 管理的集群中替代 Docker。在与 Docker 结合使用时,一个 pod 中可以包含一个或多个 Docker 容器。但除了有紧密耦合的情况下,通常一个 pod 中只有一个容器,这样方便不同的服务各自独立地扩展。Minikube 自带了 Docker 引擎,所以我们需要重新配置客户端,让 docker 命令行与 Minikube 中的 Docker 进程通讯:$ eval $(minikube docker-env) 在运行上面的命令后,再运行 docker image ls 时只能看到一些 Minikube 自带的镜像,就看不到我们刚才构建的 docker-demo:0.1 镜像了。所以在继续之前,要重新构建一遍我们的镜像,这里顺便改一下名字,叫它 k8s-demo:0.1。$ docker build -t k8s-demo:0.1 . 然后创建一个叫 pod.yml 的定义文件:apiVersion: v1 kind: Pod metadata: name: k8s-demo spec: containers: - name: k8s-demo image: k8s-demo:0.1 ports: - containerPort: 80 这里定义了一个叫 k8s-demo 的 Pod,使用我们刚才构建的 k8s-demo:0.1 镜像。这个文件也告诉 Kubernetes 容器内的进程会监听 80 端口。然后把它跑起来:$ kubectl create -f pod.yml pod "k8s-demo" created kubectl 把这个文件提交给 Kubernetes API 服务,然后 Kubernetes Master 会按照要求把 Pod 分配到 node 上。用下面的命令可以看到这个新建的 Pod:$ kubectl get pods NAME READY STATUS RESTARTS AGE k8s-demo 1/1 Running 0 5s 因为我们的镜像在本地,并且这个服务也很简单,所以运行 kubectl get pods 的时候 STATUS 已经是 running。要是使用远程镜像(比如 Docker Hub 上的镜像),你看到的状态可能不是 Running,就需要再等待一下。虽然这个 pod 在运行,但是我们是无法像之前测试 Docker 时一样用浏览器访问它运行的服务的。可以理解为 pod 都运行在一个内网,我们无法从外部直接访问。要把服务暴露出来,我们需要创建一个 Service。Service 的作用有点像建立了一个反向代理和负载均衡器,负责把请求分发给后面的 pod。创建一个 Service 的定义文件 svc.yml:apiVersion: v1 kind: Service metadata: name: k8s-demo-svc labels: app: k8s-demo spec: type: NodePort ports: - port: 80 nodePort: 30050 selector: app: k8s-demo 这个 service 会把容器的 80 端口从 node 的 30050 端口暴露出来。注意文件最后两行的 selector 部分,这里决定了请求会被发送给集群里的哪些 pod。这里的定义是所有包含「app: k8s-demo」这个标签的 pod。然而我们之前部署的 pod 并没有设置标签:$ kubectl describe pods | grep Labels Labels: <none> 所以要先更新一下 pod.yml,把标签加上(注意在 metadata: 下增加了 labels 部分):apiVersion: v1 kind: Pod metadata: name: k8s-demo labels: app: k8s-demo spec: containers: - name: k8s-demo image: k8s-demo:0.1 ports: - containerPort: 80 然后更新 pod 并确认成功新增了标签:$ kubectl apply -f pod.yml pod "k8s-demo" configured $ kubectl describe pods | grep Labels Labels: app=k8s-demo 然后就可以创建这个 service 了:$ kubectl create -f svc.yml service "k8s-demo-svc" created 用下面的命令可以得到暴露出来的 URL,在浏览器里访问,就能看到我们之前创建的网页了。$ minikube service k8s-demo-svc --url http://192.168.64.4:30050 横向扩展、滚动更新、版本回滚 在这一节,我们来实验一下在一个高可用服务的生产环境会常用到的一些操作。在继续之前,先把刚才部署的 Pod 删除(但是保留 service,下面还会用到):$ kubectl delete pod k8s-demo pod "k8s-demo" deleted 在正式环境中我们需要让一个服务不受单个节点故障的影响,并且还要根据负载变化动态调整节点数量,所以不可能像上面一样逐个管理 pod。Kubernetes 的用户通常是用 Deployment 来管理服务的。一个 deployment 可以创建指定数量的 pod 部署到各个 node 上,并可完成更新、回滚等操作。首先我们创建一个定义文件 deployment.yml:apiVersion: extensions/v1beta1 kind: Deployment metadata: name: k8s-demo-deployment spec: replicas: 10 template: metadata: labels: app: k8s-demo spec: containers: - name: k8s-demo-pod image: k8s-demo:0.1 ports: - containerPort: 80 注意开始的 apiVersion 和之前不一样,因为 Deployment API 没有包含在 v1 里,replicas: 10 指定了这个 deployment 要有 10 个 pod,后面的部分和之前的 pod 定义类似。提交这个文件,创建一个 deployment:$ kubectl create -f deployment.yml deployment "k8s-demo-deployment" created 用下面的命令可以看到这个 deployment 的副本集(replica set),有 10 个 Pod 在运行。$ kubectl get rs NAME DESIRED CURRENT READY AGE k8s-demo-deployment-774878f86f 10 10 10 19s 假设我们对项目做了一些改动,要发布一个新版本。这里作为示例,我们只把 HTML 文件的内容改一下, 然后构建一个新版镜像 k8s-demo:0.2:$ echo '<h1>Hello Kubernetes!</h1>' > html/index.html $ docker build -t k8s-demo:0.2 . 然后更新 deployment.yml:apiVersion: extensions/v1beta1 kind: Deployment metadata: name: k8s-demo-deployment spec: replicas: 10 minReadySeconds: 10 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 template: metadata: labels: app: k8s-demo spec: containers: - name: k8s-demo-pod image: k8s-demo:0.2 ports: - containerPort: 80 这里有两个改动,第一个是更新了镜像版本号 image: k8s-demo:0.2,第二是增加了 minReadySeconds: 10 和 strategy 部分。新增的部分定义了更新策略:minReadySeconds: 10 指在更新了一个 pod 后,需要在它进入正常状态后 10 秒再更新下一个 pod;maxUnavailable: 1 指同时处于不可用状态的 pod 不能超过一个;maxSurge: 1 指多余的 Pod 不能超过一个。这样 Kubernetes 就会逐个替换 service 后面的 pod。运行下面的命令开始更新:$ kubectl apply -f deployment.yml --record=true deployment "k8s-demo-deployment" configured 这里的 --record=true 让 Kubernetes 把这行命令记到发布历史中备查。这时可以马上运行下面的命令查看各个 Pod 的状态:$ kubectl get pods NAME READY STATUS ... AGE k8s-demo-deployment-774878f86f-5wnf4 1/1 Running ... 7m k8s-demo-deployment-774878f86f-6kgjp 0/1 Terminating ... 7m k8s-demo-deployment-774878f86f-8wpd8 1/1 Running ... 7m k8s-demo-deployment-774878f86f-hpmc5 1/1 Running ... 7m k8s-demo-deployment-774878f86f-rd5xw 1/1 Running ... 7m k8s-demo-deployment-774878f86f-wsztw 1/1 Running ... 7m k8s-demo-deployment-86dbd79ff6-7xcxg 1/1 Running ... 14s k8s-demo-deployment-86dbd79ff6-bmvd7 1/1 Running ... 1s k8s-demo-deployment-86dbd79ff6-hsjx5 1/1 Running ... 26s k8s-demo-deployment-86dbd79ff6-mkn27 1/1 Running ... 14s k8s-demo-deployment-86dbd79ff6-pkmlt 1/1 Running ... 1s k8s-demo-deployment-86dbd79ff6-thh66 1/1 Running ... 26s 从 AGE 列就能看到有一部分 Pod 是刚刚新建的,有的 Pod 则还是老的。下面的命令可以显示发布的实时状态:$ kubectl rollout status deployment k8s-demo-deployment Waiting for rollout to finish: 1 old replicas are pending termination... Waiting for rollout to finish: 1 old replicas are pending termination... deployment "k8s-demo-deployment" successfully rolled out 由于我输入得比较晚,发布已经快要结束,所以只有三行输出。下面的命令可以查看发布历史,因为第二次发布使用了 --record=true 所以可以看到用于发布的命令。$ kubectl rollout history deployment k8s-demo-deployment deployments "k8s-demo-deployment" REVISION CHANGE-CAUSE 1 <none> 2 kubectl apply --filename=deploy.yml --record=true 这时如果刷新浏览器,就可以看到更新的内容「Hello Kubernetes!」。假设新版发布后,我们发现有严重的 bug,需要马上回滚到上个版本,可以用这个很简单的操作:$ kubectl rollout undo deployment k8s-demo-deployment --to-revision=1 deployment "k8s-demo-deployment" rolled back Kubernetes 会按照既定的策略替换各个 Pod,与发布新版本类似,只是这次是用老版本替换新版本:$ kubectl rollout status deployment k8s-demo-deployment Waiting for rollout to finish: 4 out of 10 new replicas have been updated... Waiting for rollout to finish: 6 out of 10 new replicas have been updated... Waiting for rollout to finish: 8 out of 10 new replicas have been updated... Waiting for rollout to finish: 1 old replicas are pending termination... deployment "k8s-demo-deployment" successfully rolled out 在回滚结束之后,刷新浏览器就可以确认网页内容又改回了「Hello Docker!」。结语 我们从不同层面实践了一遍镜像的构建和容器的部署,并且部署了一个有 10 个容器的 deployment, 实验了滚动更新和回滚的流程。Kubernetes 提供了非常多的功能,本文只是以走马观花的方式做了一个快节奏的 walkthrough,略过了很多细节。虽然你还不能在简历上加上「精通 Kubernetes」,但是应该可以在本地的 Kubernetes 环境测试自己的前后端项目,遇到具体的问题时求助于 Google 和官方文档即可。在此基础上进一步熟悉应该就可以在别人提供的 Kubernetes 生产环境发布自己的服务。LeanCloud 的大部分服务都运行在基于 Docker 的基础设施上,包括各个 API 服务、中间件、后端任务等。大部分使用 LeanCloud 的开发者主要工作在前端,不过云引擎是我们的产品中让容器技术离用户最近的。云引擎提供了容器带来的隔离良好、扩容简便等优点,同时又直接支持各个语言的原生依赖管理,为用户免去了镜像构建、监控、恢复等负担,很适合希望把精力完全投入在开发上的用户。 本文转自DockOne-Docker 和 Kubernetes 从听过到略懂:给程序员的旋风教程
一项技术成熟的标志不仅仅在于它有多流行,还在于它有多不起眼并且易于使用。比如,没有人会去思考墙上的插座,除非你恰好需要给你的手机充电但又一个都找不到,这只是我们日常生活中所用到的大量技术的一个例子而已。自从Google受到它内部集群和容器管理系统Borg以及Omega的启发,在四年多之前率先开源了Kubernetes容器控制器之后,我们就一直在打赌它会在公有云和私有云的容器管理上一统天下。具有讽刺意味的是,当初负责Google基础架构管理的人并不是很情愿将这样的知识产权公开,但是开源信徒们正确地预测到,将Kubernetes贡献给世界之后,Google将会从开源社区得到巨大的信誉,并且有利于创造一个和Google类似的容器化私有云环境,也可能将Goolge的模式传播给竞争对手的云产品,同时也能帮助其扩张自身的云平台。可以肯定地说,掌舵Kubernetes以及相关监控工具Prometheus的云原生计算基金会(CNCF),已经完成了Google及其成员所安排的工作,就是将Kubernetes转变成一个由横跨各种平台、供应商和客户的生态系统所支撑的工具,这也是为什么几个月前它在CNCF的状态从孵化变成了毕业,一种正式通过的礼仪,同时也有了很多重量级的生产环境用户。但Kubernetes依然只是一个半成品,还远未达到像Linux内核及其周围操作系统组件在过去25年中所做到的那种“隐形”状态(实际上可能只花了15年就达到了那个层次)。随着最近Kubernetes容器编排系统1.11版本的官宣,我们不禁陷入了沉思,Kubernetes到底何时才会消于无形却又无处不在。社区无疑是非常强大的。下面的数据虽然有点旧,但是也体现了2016年11月到2017年10月之间,GitHub上2800万用户的8500万个软件项目的活跃程度排名: 这是一个气泡图,Y轴绘制了每个项目的issues以及pull requests的数量,X轴则绘制了项目成员所完成的提交数量,气泡本身则显示了社区的相对大小。这个大小其实是每个项目中作者数量的平方根,Linux内核在当时以2843个作者居首,紧随其后的是Kubernetes的2380个和Docker的1346个。 Kubernetes很明智地将容器的运行格式“割让”给了Docker,并且在几年前就和曾经拥有rkt格式的CoreOS握手言和,再加上由于大家都已经认可Docker就是容器的格式,很显然也认可了Kubernetes将会成为管理由容器组成的那些复杂Pod的方法,这些Pod实际上包含并体现了现代微服务应用,甚至是一些传统的巨石型应用。西方经济体中的公有云三巨头——亚马逊AWS,微软Azure和Google云平台——都支持Kubernetes,而OpenStack和CloudStack开源云控制器也都有方法来运行Kubernetes。正如我们已经讨论过的,VMware提供了很多种不同的方法来交付Kubernetes容器服务(鉴于它的企业客户群保守的天性,或许太多了)。今年早些时候,RedHat收购了CoreOS,现在它有了自己的基于CoreOS Tectonic和OpenShift工具集的Kubernetes软件栈。Docker也已经不可避免地将Kubernetes作为它自己企业级软件栈中Swarm容器控制器的一个替代品。在中国,百度为了它的PaddlePaddle机器学习框架,也在其云上拥抱Kubernetes,并且已经有了基于Kubernetes的云容器引擎。阿里巴巴有云容器服务,腾讯则有Kubectl容器引擎。Windows Server也将在2019年初支持原生的Kubernetes。这其实影响了企业中的许多根基(原文:This covers a lot of bases in the enterprise),现在世界上大多数人都能够按照Google想要的方式来编排容器——当然,还有社区的意见。Google通过Kubernetes将Borg开放(也确实让它成为了多用户模式并且更好用)的行为是一个开明自利的完美案例。现如今,理论上讲,将业务负载从本地数据中心或者竞争对手的公有云上转移到Google云平台上将会简单很多。当然,反过来也一样。如果客户对Google云平台不满意,他们也可以轻松地将他们的容器化应用从上面转移回到其竞争对手那里或者本地的服务器上。对于如何吸引客户并将他们留在自己的云平台上,Google完全掌握了主动权,这似乎是个相当不错的激励(原文:It is up to Google to attract customers and keep them on its cloud, which seems like a decent incentive)。一种技术想要达到那种无处不在的形态,必须在其消于无形之前与其他的技术很好地整合在一起。反之亦然。他们总是齐头并进。但很明显,有一个巨大的开发者社区和一个巨大的平台生态系统都已经拥抱了Kubernetes,现在我们就看看到底Kubernetes会被如何接受并且是否能像我们猜想得那样变得无处不在。我们之前与Josh Berkus进行了一次有关Kubernetes 1.11版本的对话,他是在Red Hat工作的Kubernetes社区管理员并且主导了这次更新,我们还聊了一点关于Kubernetes的潜能。结果我们得知,为了让Kubernetes更好地适应大范围的使用,底层的大量工作已经完成——当然还有更多的工作有待完成。在这一点上,Kubernetes 1.11版本是个不错的案例。Berkus说在这个版本中新增或改进的25个核心功能当中,有7个是面向用户的功能——即用户能实实在在看到和感受到——而另外18个则是Kubernetes内部的一些重构——用户看不到但同样也会从中受益。“我们还难以弄清楚何时会将Kubernetes当做一个成熟的产品来看待,因为一些像这样的重构工作还很繁重,”Berkus告诉The Next Platform,提及了其中一项关于和公有云对接的重构任务,它会在接下来的几个版本中持续进行。“在最早的Kubernetes 1.0版本中,与云的整合功能在核心代码中。所以,一个像Digital Ocean这样新的云服务供应商想要提供Kubernetes服务,他们必须拿到被Kubernetes认可的补丁才能让他们的所有功能都正常工作。这显然很糟糕,也没人认为这是一件好事。我们的云服务供应商团队一直在努力将云服务供应商的整合工作转变为一种定义清晰且相较而言功能经得起验证的API(原文:The cloud provider team has been working hard in turning the cloud provider stuff into a clearly defined and relatively feature proof API.)。在Kubernetes中,实际上我们已经移除了第一个云平台,那就是OpenStack,他们本来就要彻底重构自己的云服务供应商代码而且自愿成为第一批从Kubernetes核心代码中移除并完全使用API的人。有一堆这样的事情需要我们去做。我们需要将云供应商们移出并让他们使用云API,也需要将所有的存储供应商转移到云存储接口(CSI)API上去,还需要移动容器运行的所有东西,确保它正在使用容器运行的API而不是背后的通道(原文:We need to move all of the container runtime stuff and make sure it is using the container runtime API and not using back channels.)。按照代码的数量,这是当下Kubernetes项目中最主要的工作。所以,当我们将所有的东西都转变成API的时候,也就是Kubernetes变得成熟并且你们也不会期待任何激进改变的那个时间节点。”这个时刻的到来可能还要好几年时间,Berkus也同意,因为当API推出之后,它们的缺陷也会暴露出来——正如所有代码被创造出来时一样——它们将不得不缓慢且谨慎的迭代,从而不会破坏兼容性。考虑到那个让Kubernetes可以部署(尽管不成熟)的1.0版本刚刚走过第三个年头,从四年前Google将代码转储到GithHub开始,发展速度已经很快了(原文:Considering we are coming up on the third anniversary of the 1.0 release that made Kubernetes deployable (though not mature), and that is still a pretty fast ramp from a code dump from Google onto GitHub four years ago.)。Google的那份代码的确让自己占据了主动——那是一个用Go语言开发的简化版Borg,比起原版只适用于Google内部的特殊场景来说,它变得更加通用。Google不仅有效地“外包”了一部分可以让这个Borg的衍生品跨平台移植的工作,同时还让社区的合作伙伴们踊跃地参与到这部分工作中来。这才让我们接受了Kubernetes(原文:That brings us to Kubernetes adoption.)。没有人确切地知道,它一直是一个开源软件栈(原文:No one knows for sure, this being an open source software stack)。大部分参与到云计算技术中的公司,不管是在本地数据中心还是在公有云上,都在通过某种方式使用Kubernetes,但是对于那些一般的公司,它们在数量上仍然占了80%左右(粗略猜测)并且可能在计算资源上的开销(也就是直接购买硬件的开销)占据了市场的一半,这些公司还没有到那一步。但是他们也正在慢慢接受Kubernetes了。Kubernetes 1.11版本有一些可以让其更容易被接受的功能特性。一个自家的KubeDNS服务系统项目正在转换成为一个CNCF项目——CoreDNS规范,另外Berkus还说有一套更好的规范得到了社区更广泛的支持。1.11这个升级版本也支持Kubelets动态配置(通过一个配置文件或者一份ConfigMap),它是运行在一个容器集群中的所有系统上的Kubernetes守护进程;过去,管理员不得不进入Kubernetes中修改启动参数,然后再重启系统。自定义资源定义(CRDs)已经做了改进,可以更轻松地创建将Kubernetes集群视为一个系统的应用程序,它们不用直接深入到容器的粒度(这正是Borg的天才之处)。这些CRDs是Kubernetes得以扩展的主要方式,再加上允许Kubernetes深入到集群中并且控制虚拟机管理程序(Hypervisors)以及虚拟机的KubeVirt组件,这也只是其中一个例子。这个版本也包含了任务优先级和抢占功能,这意味着Kubernetes可以接受一项高优先级的任务并且立刻执行,即使这意味着要将其他应用中断,延后执行。最后一个重大变化是内核IPVS负载均衡工具,它能在集群上自动平衡Kubernetes服务所承受的负载。 本文转自DockOne-Kubernetes何时才会消于无形却又无处不在
Kubernetes是目前最为成功和发展最快的IT基础架构项目之一。Kubernetes在2014年作为内部的Google orchestrator Borg的开源版本推出。在2017年各企业使用Kubernetes的情形有所增加,而到了2018年,从软件开发商到航空公司,它已经被广泛应用在各类业务上,Kubernetes之所以能够迅速普及发展的原因之一就在于其开源的架构,以及忠实的社区中所提供的大量帮助手册,各类文档以及技术支持等。难怪Kubernetes就像任何成功的开源项目一样,在市场上总可以找到几个不同的发行版本(如同Linux一样),用来提供各种额外功能并针对特定类别的用户。为什么我们会有这么多的发行版本?答案也很明显:每家供应商都希望保证它们的性能。而既然Kubernetes是开源的,提供自有的Kubernetes发行版的公司也就不能出售它,但是,它们却提供对Kubernetes群集的支持和维护(也就是所说的“托管Kubernetes”)。当然,他们更愿意支持他们自己的产品,因此即使他们不对代码进行任何更改,他们也会去测试他们的发行版以了解他们的产品,并且关注在正式场景下的工作表现。假如现在贵公司正计划采用Kubernetes,同时并不急于设置和维护集群本身。该如何选择供应商?现在的供应商第一梯队有哪些公司?让我们把目光转向市场上存在的一些Kubernetes发行版,看看它们之间的不同,也同时和原版Kubernetes发行版来进行对比。"Vanilla Kubernetes" 它有什么特点? 如果我们从官方仓库安装Kubernetes,我们会得到......Kubernetes!鉴于Kubernetes有着大量的功能,所以在这里列出Kubernetes的所有功能是没有多大意义的。如果你不知道Kubernetes是什么,可以去参考下官方文档。简而言之,如果你安装了经典版Kubernetes,所有的功能都是可以使用的。其实你是获得了一款优缺点并存的开源产品,诸如版本更新,自由定制,社区中能得到的难以置信的支持,以及你不得不面对的或者向同行寻求帮助的各类bug(这是开源软件的常态,不要责怪Kubernetes!)。它背后的支持者是谁? Kubernetes是由CNCF和Kubernetes用户组成的多样化社区所共同支持的项目。如果你不知道CNCF是什么,请去仔细了解下——这是一个致力于云化技术、声誉良好的组织。Kubernetes是第一个从CNCF毕业的项目,其第二个项目Prometheus也宣布将于2018年8月毕业。如果你听说过Helm,containerd,CoreDNS等工具(如果没有,应该去再了解下),你就应该知道是CNCF支持着他们的发展。它的商业许可和定价是怎样的? Kubernetes是款完全免费的开源软件,你可以像其他任何开源产品一样,来安装、使用、以及升级到新版本。它易于安装吗? 从易到难,有很多种安装Kubernetes集群的方法,你可以使用minikube在本地安装Kubernetes进行实验和测试,或者使用kubeadm在云中引导群集。对于故障排除或最佳实践案例,你可以查阅各种资源,或者在官方GitHub仓库里创建提交问题。它的用途和目标受众是谁? Kubernetes开箱即用的特性对于那些已经熟悉该技术或想要试验它的人来说其实非常棒,但要知道Kubernetes虽然很强大,却绝不容易操作。如果在没有做好准备,以及具备丰富的经验的情况下,就投入生产,在处理问题时可能会导致严重的服务宕机,因此在将Kubernetes用于最终正式服务之前,建议还是应该多花一点时间去先学习下沙盒模式中的Kubernetes。RedHat OpenShift 它有什么特点? OpenShift在Kubernetes之前就已经是一个独立的项目了,并且采用了一种完全不同的技术路线。然而,RedHat也意识到Kubernetes越来越受到关注,所以他们在OpenShift第3版中明智地将其作为了核心。它与经典Kubernetes的主要区别在于: 高级和集成的用户管理 集成Docker仓库 集成CI流水线 集成资源模板 使用类似但有些许不同的术语,如用路由器代替了Ingresses,Projects代替了Namespaces等等。 它背后的支持者是谁? OpenShift是由RedHat提供支持,众所周知,RedHat是一个开源软件社区。根据Stackalytics的数据,RedHat是仅次于Google的Kubernetes项目的第三大社区贡献者,因此他们很有可能也是发行Kubernetes正式版本的的合法公司之一。它的商业许可和定价是怎样的? OpenShift有三种定价模式: OKD模式,OpenShift免费发行Kubernetes。 OpenShift 企业模式,可以由RedHat托管和管理,也可用客户端部署在本地。托管版本的起价为48,000美元/年,包括了3台主服务器,3台etcd服务器和4台应用程序节点。 OpenShift 在线模式,是在线提供的PaaS版本。每2 Gb内存规格价格约为每月50美元,与其他Kubernetes-as-a-Service提供商相比,还是有点贵的。 它易于安装吗? 它并不是很复杂,但是需要一些特定的配置,因此还是建议你使用Ansible这个配置管理工具用于安装配置。它的用途和目标受众是谁? OpenShift显然是一个企业级的发行版本,注重稳定性大于功能性。这就是为什么它发布的版本总是落后于Kubernetes一步。所以目前,虽然Kubernetes已经发布了1.11版本,但OpenShift才发布了基于Kubernetes 1.10的3.10版本。所以,OpenShift是面向那些更看重软件运行稳定性而不是功能性的企业客户。Tectonic 它有什么特点? Tectonic是一款非常受欢迎的Kubernetes发行版本,它目前正在与RedHat一起做集成。与原版Kubernetes相比,其特性如下: 易于安装 用户友好的Web界面 用户管理 对运营者的原生支持 它背后的支持者是谁? Tectonic是由CoreOS所创建的,这是一家致力于容器技术的公司。他们的产品组合包括了许多有价值和受欢迎的产品,譬如CoreOS Linux,Quay Docke仓库,Etcd K-V存储以及Flannel容器网络接口等。该公司已经被RedHat收购,因此我们可能会看到RedHat在未来几个月的集成演进路线图中宣布OpenShift和Tectonic的融合。它的商业许可和定价是怎样的? Tectonic提供商业许可,最多可免费使用10个节点,而对于更大的集群,每10个节点(含支持服务)的定价约为1,000美元/月。它易于安装吗? 是的,很容易,可以通过安装程序或Terraform来安装它。它的用途和目标受众是谁? Tectonic可以用于企业客户。然而,它未来发展方向却是不确定的。很可能该版本将完全退出舞台并将与OpenShift集成。因此如果你计划部署Kubernetes的话,从长远来看,Tectonic并不是一个最佳选择。Rancher 它有什么特点? Rancher是一个包含了Kubernetes的容器管理平台,所以我们也可以将其视为Kubernetes的一个发行版。它在原版Kubernetes中加入了一些新特性,主要是: 跨程序群集部署 用户管理 Web界面 集成的CI / CD流水线 它背后的支持者是谁? Rancher Kubernetes是由成立于2014年的Rancher Labs公司所支撑的,他们的拳头产品就是Rancher容器管理平台,他们同时也开发了RancherOS,一个以容器为中心的Linux发行版本。它的商业许可和定价是怎样的? Rancher是百分之百的开源软件,他们的商业模式是提供咨询和支持服务,但是其定价却是不公开的。它易于安装吗? 非常容易,并且有着很好的文档支持,可以使用它自己的名为RKE的Kubernetes安装工具。它的用途和目标受众是谁? 很难说Rancher的目标客户是谁,因为他们自己也对此并不是很清楚。从功能特性上来看,这个版本其实是适用于任何类型公司的。Canonical Kubernetes 它有什么特点? 这个发行版本可以算是一个能在主要公有云供应商以及类似OpenStack这样的私有云解决方案上轻松部署的vanilla Kubernetes,能够轻松设置和管理跨供应商和跨地域的Kubernetes集群。它的用户界面其实就是官方的Kubernetes仪表板。它背后的支持者是谁? 该版本是由广受欢迎的Linux发行商Ubuntu背后的公司Canonical所支持。它的商业许可和定价是怎样的? Canonical Kubernetes是完全免费的。但是,每个虚拟节点也可以选用一些服务支持包,起价是每年200美元(至少需要2500美元),维护服务包是从每十个节点14,600美元起售。它易于安装吗? 可以使用由Canonical开发的部署工具Conjure-up或Juju来完成安装。它的用途和目标受众是谁? Canonical Kubernetes并没有在Kubernetes上增加太多的功能,它和原版Kubernetes具有一样的功能。并且,它允许跨供应商和跨地域来设置Kubernetes集群,并且提供了企业级的支持。我们推荐那些已经或计划与Canonical有商业合作的公司来使用它。Kubernetes Distribution by Containerum 它有什么特点? Containerum有两款不同而互补的产品: 针对Kubernetes的产品称为KDC——Kubernetes Distribution by Containerum。KDC也是Kubernetes的原版版本,由Containerum团队来进行测试和支持。 开源Containerum平台,能以界面的方式安装在Kubernetes上,具有其他的额外功能,譬如用户管理,用量监控,CI / CD流水线等等。 它背后的支持者是谁? Containerum由拉脱维亚一家致力于容器技术的创业公司Exon LV提供支持。它的商业许可和定价是怎样的? KDC和Containerum平台都是完全开源的,因此你可以自由部署它们。他们公司的商业收入有两个来源: DevOps和基础设施咨询服务 通过Containerum支持Kubernetes发行版。安装和支持服务起价为每10个节点/年550美元。 与其他发行版相比,这个价格是非常有竞争力的。它易于安装吗? 它的设置相对简单,跟原版Kubernetes安装很相似。你还可以在已有了Helm图表的Kubernetes集群之上安装Containerum平台。它的用途和目标受众是谁? KDC + Containerum平台可能是最简单的Kubernetes发行版。它也可以满足那些已经拥有Kubernetes集群但又比原版Kubernetes集群需要更多功能的用户的需求。总结 我们这里给出的版本列表其实也并没有那么详尽——我试图收集一些著名的参与者以及还有一些并不为人所知的参与者。当然,最终的选择还是得取决于你自己现状考虑——一些公司愿意花费时间来提升培养内部能力,而有些公司则更愿意使用第三方服务。如果你只是在小型项目上使用Kubernetes或者仅仅是出于兴趣(当然,Kubernetes是很有趣的),而且不会上来就启动数百个微服务的话,最好还是选择使用标准版本。但是,如果对于大型项目和关键应用,建议还是使用第三方供应商提供的Kubernetes发行版,以便可以获得即时的技术支持以及故障排除。你觉得呢? 本文转自DockOne-Kubernetes不同发行版的比较
建设Kubernetes集群的安全是一回事,而维护它也会越来越困难。幸运的是,Kubernetes引入的新特性使这两件事都变得容易起来。Kubernetes(从1.6版本)引入了基于角色的访问控制(RBAC),允许系统管理员定义策略来限制集群使用者的行为。这意外着创建有限访问权限的用户是可能的。它允许你限制如Secrets等资源的访问,或限制用户对某个命名空间的访问。本文不会介绍如何实现RBAC,因为它已经有很多不错的详实的资料: https://medium.com/containerum ... 17d5d https://www.cncf.io/blog/2018/ ... etes/ https://docs.bitnami.com/kuber ... ster/ https://kubernetes.io/docs/ref ... rbac/ 本文将聚集于怎样使你业务的合规性和需求得到切实满足。为此,我们需要测试被应用的RBAC对象,以确保它们像我们期望地那样工作。我们的场景 某个组织有多组团队的人员,刚开始上手使用Kubernetes集群。这时要求一个团队不能去修改另一个团队的deployment,否则会导致不可预见的跨团队问题或停机。负责Kubernetes deployment维护的平台团队最近在集群里部署了RBAC,它会将团队成员的访问限制在其对应的命名空间。首先,团队不能看到其它团队命名空间的Pod。运行一周后,平台团队发现在一个被限制的命名空间的用户一直在读取其它命名空间的Secrets,但这是怎么做到的呢?他们没实现RBAC吗?事实上,他们做了。但就像对代码一样,我们需要测试我们的系统来看它是否符合期望。庆幸的是,Kubernetes的命令行工具kubectl提供了测试RBAC配置的工具。kubectl auth can-iCan I? can-i只是使用API检查是否某个操作可以被执行。它有以下选项 kubectl auth can-i VERB [TYPE | TYPE/NAME | NONRESOURCEURL] 现在可以检查当前用户是否可以执行某个操作。我们试一下:kubectl auth can-i create pods 这会返回"yes"或"no"及相应的退出码。但只要我们尝试测试另一用户的授权,就会碰到障碍。因为用以上指令,我们只能使用当前加载的./kube/config来测试。而每个用户一个文件是很不足取的。好在Kubernetes又提供了使用--as=和--as-group-来模拟用户的能力。我们修改下指令,来模拟一个不同的用户:kubectl auth can-i create pods --as=me 我们应该会看到它返回"no"和退出码1。这很棒,我们现在有了一堆指令来测试一组用户或单个用户是否可以访问任何Kubernetes资源,不论是查看Pod列表还是删除Secrets。自动化 但我们不要停下来,我们已经为实现一个可以描述需求列表的并且可集成到持续交付流水线的测试集铺平了道路,我们开始吧!要实现自动化,我们有很多技术和编程语言的选择,从JavaScript里的Ava和Mocha到Rspec,这里,我将使用一个纯bash实现的测试框架,它叫Bats。下面是一个例子,它测试一个团队命名空间的用户可以对其deployment做扩缩容。如果该文件设置了可执行权限,它可以像任何shell脚本一样执行,或通过bats filename来执行。#!/usr/bin/env bats @test "Team namespaces can scale deployments within their own namespace" { run kubectl auth can-i update deployments.apps --subresource="scale" --as-group="$group" --as="$user" -n $ns [ "$status" -eq 0 ] [ "$output" == "yes" ] done } 注意一下:--as和--as-group指令要求使用以下的RBAC规则:rules: - apiGroups: - authorization.k8s.io resources: - selfsubjectaccessreviews - selfsubjectrulesreviews verbs: - create 如上,你有了一个简单的测试Kubernetes RBAC规则的实现方法。将其集成到你的Kubernetes持续交付流水线中,我们就有了一个被验证过的更可靠的RBAC策略配置。这使得破坏策略的变化可以被及早发现。 本文转自DockOne-如何测试Kubernetes RBAC?
简单到老板也可以亲自部署 这篇博文演示了如何通过Docker和Kubernetes,用Keras部署深度学习模型,并且通过Flask提供REST API服务。这个模型并不是强壮到可供生产的模型,而是给Kubernetes新手一个尝试的机会。我在Google Cloud上部署了这个模型,而且工作的很好。另外用户可以用同样的步骤重现以上功能。如果用户担心成本,Google提供了大量免费机会,这个演示基本没有花钱。为什么用Kubernetes来做机器学习和数据科学 Kubernetes以及Cloud Native,正在席卷整个世界,我们已经感受到了。我们正处在一个由AI/Big Data/Cloud驱动的技术风暴中心,Kubernetes也正在加入这个中心。但是如果从数据科学角度看并没有使用Kubernetes的特殊原因。但是从部署,扩展和管理REST API方面来看,Kubernetes正在实现简易化的特性。步骤预览 在Google Cloud上创建用户 使用Keras/Flask/Docker搭建一个REST API的机器学习模型服务 用Kubernetes部署上述模型 enjoy it 步骤一:在Google Cloud上创建用户 我在Google Compute Engine上创建了一个对外提供服务的容器化深度学习模型,当然Google平台并不是必须的,只要能够安装Docker,随便选择平台模式。进入Google云平台,点击左侧屏幕选择Compute Engine,启动Google Cloud VM。然后选择“Create Instance”,可以看到已经运行的实例。 下一步选择计算资源。默认设置就足够,因为只是演示,我选择了4vCPUs和15G内存。 选择操作系统和磁盘大小。我选择了CentOS 7,100G硬盘。建议磁盘大于10G,因为每个Docker容器有1G大小。 最后一步是配置允许HTTP/S工作的防火墙策略。建议选择全部透明,以便减少麻烦。 选择“Create”,一切进展顺利。 步骤二:用Keras创建深度学习模型 SSH登录到虚机开始建立模型。最简单方式就是点击虚机下方的SSH图标,会在浏览器中打开一个终端。1、删除预装Docker sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine 2、安装最新Docker版本sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager — add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce 3、启动容器运行测试脚本sudo systemctl start docker sudo docker run hello-world 以下是正确输出:Hello from Docker! This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal 4、创建深度学习模型这里会借用Adrian Rosebrock的一个脚本,他提供了使用Keras的深度学习模型并通过Flask提供服务的教程,可以从这里访问。这个模型可以直接执行。但是我修改了两个配置信息:首先,改变了容器配置,默认flask使用127.0.0....作为默认服务地址,这会在容器内部运行时出现问题。我将它修改成0.0.0.0,这样就可以实现对外和对内都可以工作的IP地址。第二是关于Tensorflow的配置,可以从GitHub中找到这个问题描述。global graph graph = tf.get_default_graph() ... with graph.as_default(): preds = model.predict(image) 运行脚本,首先创建专用目录:mkdir keras-app cd keras-app 创建app.py文件:vim app.py# USAGE Start the server: python app.py Submit a request via cURL: curl -X POST -F image=@dog.jpg 'http://localhost:5000/predict' import the necessary packages from keras.applications import ResNet50 from keras.preprocessing.image import img_to_array from keras.applications import imagenet_utils from PIL import Image import numpy as np import flask import io import tensorflow as tf initialize our Flask application and the Keras model app = flask.Flask(__name__) model = None def load_model(): # load the pre-trained Keras model (here we are using a model # pre-trained on ImageNet and provided by Keras, but you can # substitute in your own networks just as easily) global model model = ResNet50(weights="imagenet") global graph graph = tf.get_default_graph() def prepare_image(image, target): # if the image mode is not RGB, convert it if image.mode != "RGB": image = image.convert("RGB") # resize the input image and preprocess it image = image.resize(target) image = img_to_array(image) image = np.expand_dims(image, axis=0) image = imagenet_utils.preprocess_input(image) # return the processed image return image @app.route("/predict", methods=["POST"]) def predict(): # initialize the data dictionary that will be returned from the # view data = {"success": False} # ensure an image was properly uploaded to our endpoint if flask.request.method == "POST": if flask.request.files.get("image"): # read the image in PIL format image = flask.request.files["image"].read() image = Image.open(io.BytesIO(image)) # preprocess the image and prepare it for classification image = prepare_image(image, target=(224, 224)) # classify the input image and then initialize the list # of predictions to return to the client with graph.as_default(): preds = model.predict(image) results = imagenet_utils.decode_predictions(preds) data["predictions"] = [] # loop over the results and add them to the list of # returned predictions for (imagenetID, label, prob) in results[0]: r = {"label": label, "probability": float(prob)} data["predictions"].append(r) # indicate that the request was a success data["success"] = True # return the data dictionary as a JSON response return flask.jsonify(data) if this is the main thread of execution first load the model and then start the server if __name__ == "__main__": print(("* Loading Keras model and Flask starting server..." "please wait until server has fully started")) load_model() app.run(host='0.0.0.0') 5、创建requirements.txt文件为了在容器内运行代码,需要创建requirements.txt文件,其中包括需要运行的包,例如keras、flask、一起其它相关包。这样无论在哪里运行代码,依赖包都保持一致。keras tensorflow flask gevent pillow requests 6、创建DockerfileFROM python:3.6 WORKDIR /app COPY requirements.txt /app RUN pip install -r ./requirements.txt COPY app.py /app CMD ["python", "app.py"]~ 首先让容器自行下载Python 3安装image,然后让Python调用pip安装requirements.txt中的依赖包,最后运行python app.py。7、创建容器sudo docker build -t keras-app:latest . 在keras-app目录下创建容器,后台开始安装Python 3 image等在步骤6中定义的操作。8、运行容器sudo docker run -d -p 5000:5000 keras-app 用sudo docker ps -a检查容器状态,应该看到如下输出:[gustafcavanaugh@instance-3 ~]$ sudo docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d82f65802166 keras-app "python app.py" About an hour ago Up About an hour 0.0.0.0:5000->5000/tcp nervous_northcutt 9、测试模型现在可以测试此模型。用狗的照片作为输入,可以返回狗的品种。在Adrian的示例中都有详细代码和图片,我们也使用它们,并保存自工作目录下,命名为dog.jpg。执行命令: curl -X POST -F image=@dog.jpg 'http://localhost:5000/predict' 应该得到如下输出:{"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true} 可以看到此模型成功将狗归类为比格犬。下一步,我们用Kubernetes部署容器模型。第三步:用Kubernetes部署模型 1、创建Docker Hub账号第一步需要在Docker hub上传模型,以便使用Kubernetes集中管理。2、登录到Docker Hubsudo docker login, 登录到Docker Hub,应该看到如下输出:Login Succeeded 3、给容器打标签给模型容器命名,上传前先给它打标签。sudo docker images,应该得到容器的id,输出如下:REPOSITORY TAG IMAGE ID CREATED SIZE keras-app latest ddb507b8a017 About an hour ago 1.61GB 打标签命令如下:#Format sudo docker tag <your image id> <your docker hub id>/<app name> My Exact Command - Make Sure To Use Your Inputs sudo docker tag ddb507b8a017 gcav66/keras-app 4、将模型容器上传到Docker Hub运行命令如下:#Format sudo docker push <your docker hub name>/<app-name> My exact command sudo docker push gcav66/keras-app 5、创建Kubernetes集群在Google Cloud Home界面,选择Kubernetes Engine。创建新集群: 选择集群内节点资源,因为要启动三个节点(每个节点4vCPU和15G内存),至少需要12vCPU和45G内存。 连接集群,Google’s Kubernetes自动会在VM上安装Kubernetes。 在Kubernetes中运行容器: kubectl run keras-app --image=gcav66/keras-app --port 5000 确认是否Pod正确运行kubectl get pods,输出如下:gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get pods NAME READY STATUS RESTARTS AGE keras-app-79568b5f57-5qxqk 1/1 Running 0 1m 为了安全起见,将服务端口暴露与80端口:kubectl expose deployment keras-app --type=LoadBalancer --port 80 --target-port 5000 确认服务正常启动:kubectl get service,正常输出如下:gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE keras-app LoadBalancer 10.11.250.71 35.225.226.94 80:30271/TCP 4m kubernetes ClusterIP 10.11.240.1 <none> 443/TCP 18m 提取cluster-IP,并将其合并于服务提交命令:curl -X POST -F image=@dog.jpg 'http://&lt;your service IP>/predict',得到正常输入如下:$ curl -X POST -F image=@dog.jpg 'http://35.225.226.94/predict' {"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true} 第四步:总结 本文提供了一个使用Keras和Flask提供REST API服务的深度学习模型,并把它集成到容器内部,上传到Docker Hub,并用Kubernetes部署,非常容易地实现了对外提供服务和访问。现在,我们可以对这个项目进行很多改进。对于初学者,可以改变本地Python服务到更加强壮的gunicorn;可以横向扩展Kubernetes,实现服务扩容;也可以从头搭建一套Kubernetes环境。 本文转自DockOne-用Python/Keras/Flask/Docker在Kubernetes上部署深度学习模型
Kubernetes主要用于无状态应用程序。 但是,在1.3版本中引入了PetSets,之后它们演变为StatefulSets。 官方文档将StatefulSets描述为“StatefulSets旨在与有状态应用程序和分布式系统一起使用”。对此最好的用例之一是对数据存储服务进行编排,例如MongoDB,ElasticSearch,Redis,ZooKeeper等。我们可以把StatefulSets的特性归纳如下: 有序索引Pod 稳定的网络ID 有序并行的Pod管理 滚动更新 这些细节可以在这里找到。StatefulSets的一个非常明显的特征是提供稳定网络ID,与Headless Services一起使用时,功能可以更加强大。我们在Kubernetes文档中随时可以查看的信息上不会花费很多时间,让我们专注于运行和扩展MongoDB集群。你需要一个可以运行的Kubernetes群集并启用RBAC(推荐)。 在本教程中,我将使用GKE集群,但是,AWS EKS或Microsoft的AKS或Kops管理的Kubernetes也是可行的替代方案。我们将为MongoDB集群部署以下组件: 配置HostVM的Daemon Set Mongo Pod的Service Account和ClusterRole Binding 为Pod提供永久性存储SSDs的Storage Class 访问Mongo容器的Headless Service Mongo Pods Stateful Set GCP Internal LB:从Kubernetes集群外部访问MongoDB(可选) 使用Ingress访问Pod(可选) 值得注意的是,每个MongoDB Pod都会运行一个Sidecar,以便动态配置副本集。Sidecar每5秒检查一次新成员。Daemon Set for HostVM Configuration kind: DaemonSet apiVersion: extensions/v1beta1 metadata: name: hostvm-configurer labels: app: startup-script spec: template: metadata: labels: app: startup-script spec: hostPID: true containers: - name: hostvm-configurer-container image: gcr.io/google-containers/startup-script:v1 securityContext: privileged: true env: - name: STARTUP_SCRIPT value: | #! /bin/bash set -o errexit set -o pipefail set -o nounset # Disable hugepages echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled echo 'never' > /sys/kernel/mm/transparent_hugepage/defrag Configuration for ServiceAccount, Storage Class, Headless SVC and StatefulSet apiVersion: v1 kind: Namespace metadata: name: mongo --- apiVersion: v1 kind: ServiceAccount metadata: name: mongo namespace: mongo --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: mongo subjects: - kind: ServiceAccount name: mongo namespace: mongo roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io --- apiVersion: storage.k8s.io/v1beta1 kind: StorageClass metadata: name: fast provisioner: kubernetes.io/gce-pd parameters: type: pd-ssd fsType: xfs allowVolumeExpansion: true --- apiVersion: v1 kind: Service metadata: name: mongo namespace: mongo labels: name: mongo spec: ports: - port: 27017 targetPort: 27017 clusterIP: None selector: role: mongo --- apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: mongo namespace: mongo spec: serviceName: mongo replicas: 3 template: metadata: labels: role: mongo environment: staging replicaset: MainRepSet spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: replicaset operator: In values: - MainRepSet topologyKey: kubernetes.io/hostname terminationGracePeriodSeconds: 10 serviceAccountName: mongo containers: - name: mongo image: mongo command: - mongod - "--wiredTigerCacheSizeGB" - "0.25" - "--bind_ip" - "0.0.0.0" - "--replSet" - MainRepSet - "--smallfiles" - "--noprealloc" ports: - containerPort: 27017 volumeMounts: - name: mongo-persistent-storage mountPath: /data/db resources: requests: cpu: 1 memory: 2Gi - name: mongo-sidecar image: cvallance/mongo-k8s-sidecar env: - name: MONGO_SIDECAR_POD_LABELS value: "role=mongo,environment=staging" - name: KUBE_NAMESPACE value: "mongo" - name: KUBERNETES_MONGO_SERVICE_NAME value: "mongo" volumeClaimTemplates: - metadata: name: mongo-persistent-storage annotations: volume.beta.kubernetes.io/storage-class: "fast" spec: accessModes: [ "ReadWriteOnce" ] storageClassName: fast resources: requests: storage: 10Gi 关键点: 应该使用适当的环境变量仔细配置Mongo的Sidecar,以及为Pod提供的标签,和为deployment和service的命名空间。 有关Sidecar容器的详细信息,请点击此处。 默认缓存大小的指导值是:“50%的RAM减去1GB,或256MB”。 鉴于所请求的内存量为2GB,此处的WiredTiger缓存大小已设置为256MB。 Inter-Pod Anti-Affinity确保在同一个工作节点上不会安排2个Mongo Pod,从而使其能够适应节点故障。 此外,建议将节点保留在不同的可用区中,以便集群能够抵御区域故障。 当前部署的Service Account具有管理员权限。 但是,它应该仅限于DB的命名空间。 上面提到的两个配置文件也可以在这里找到。部署MongoDB集群 kubectl apply -f configure-node.yml kubectl apply -f mongo.yml 你可以通过以下命令查看所有组件状况:root$ kubectl -n mongo get all NAME DESIRED CURRENT AGE statefulsets/mongo 3 3 3m NAME READY STATUS RESTARTS AGE po/mongo-0 2/2 Running 0 3m po/mongo-1 2/2 Running 0 2m po/mongo-2 2/2 Running 0 1m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE svc/mongo ClusterIP None <none> 27017/TCP 3m 如你所见,该服务没有Cluster-IP,也没有External-IP,它是Headless服务。 此服务将直接解析为StatefulSets的Pod-IP。让我们来验证一下DNS解析。 我们在集群中启动了一个交互式shell:kubectl run my-shell --rm -i --tty --image ubuntu -- bash root@my-shell-68974bb7f7-cs4l9:/# dig mongo.mongo +search +noall +answer ; <<>> DiG 9.11.3-1ubuntu1.1-Ubuntu <<>> mongo.mongo +search +noall +answer ;; global options: +cmd mongo.mongo.svc.cluster.local. 30 IN A 10.56.7.10 mongo.mongo.svc.cluster.local. 30 IN A 10.56.8.11 mongo.mongo.svc.cluster.local. 30 IN A 10.56.1.4 服务的DNS规则是<服务名称>.<服务的命名空间>,因此,在我们的例子中看到的是mongo.mongo。IPs(10.56.6.17,10.56.7.10,10.56.8.11)是我们的Mongo StatefulSets的Pod IPs。 这可以通过在集群内部运行nslookup来测试。root@my-shell-68974bb7f7-cs4l9:/# nslookup 10.56.6.17 17.6.56.10.in-addr.arpa name = mongo-0.mongo.mongo.svc.cluster.local. root@my-shell-68974bb7f7-cs4l9:/# nslookup 10.56.7.10 10.7.56.10.in-addr.arpa name = mongo-1.mongo.mongo.svc.cluster.local. root@my-shell-68974bb7f7-cs4l9:/# nslookup 10.56.8.11 11.8.56.10.in-addr.arpa name = mongo-2.mongo.mongo.svc.cluster.local. 如果你的应用程序部署在Kubernetes的群集中,那么它可以通过以下方式访问节点:Node-0: mongo-0.mongo.mongo.svc.cluster.local:27017 Node-1: mongo-1.mongo.mongo.svc.cluster.local:27017 Node-2: mongo-2.mongo.mongo.svc.cluster.local:27017 如果要从集群外部访问Mongo节点,你可以为每个Pod部署内部负载平衡或使用Ingress Controller(如NGINX或Traefik)创建一个内部Ingress。GCP Internal LB SVC Configuration(可选) apiVersion: v1 kind: Service metadata: annotations: cloud.google.com/load-balancer-type: Internal name: mongo-0 namespace: mongo spec: ports: - port: 27017 targetPort: 27017 selector: statefulset.kubernetes.io/pod-name: mongo-0 type: LoadBalancer 为mongo-1和mongo-2也部署2个此类服务。你可以将内部负载均衡的IP提供给MongoClient URI。root$ kubectl -n mongo get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE mongo ClusterIP None <none> 27017/TCP 15m mongo-0 LoadBalancer 10.59.252.157 10.20.20.2 27017:30184/TCP 9m mongo-1 LoadBalancer 10.59.252.235 10.20.20.3 27017:30343/TCP 9m mongo-2 LoadBalancer 10.59.254.199 10.20.20.4 27017:31298/TCP 9m mongo-0/1/2的外部IP是新创建的TCP负载均衡器的IP。 这些是您的子网或对等网络,如果有的话。通过Ingress访问Pod(可选) 也可以使用诸如Nginx之类的Ingress Controller来定向到Mongo StatefulSets的流量。 确保Ingress服务是内部服务,而不是通过PublicIP公开。 Ingress对象的配置看起来像这样:... spec: rules: - host: mongo.example.com http: paths: - path: '/mongo-0' backend: hostNames: - mongo-0 serviceName: mongo # There is no extra service. This is the headless service. servicePort: '27017' 请务必注意,您的应用程序至少应该知道一个当前处于启动状态的Mongo节点,这样可以发现所有其他节点。我在本地mac上使用Robo 3T作为mongo客户端。 连接到其中一个节点后并运行rs.status(),您可以查看副本集的详细信息,并检查是否已配置其他2个Pod并自动连接到副本集。rs.status()查看副本集名称和成员个数 每个成员都可以看到FQDN和状态。 此FQDN只能从群集内部访问。 每个secondary成员正在同步到mongo-0,mongo-0是当前的primary。 现在我们扩展mongo Pods的Stateful Set以检查新的Mongo容器是否被添加到ReplicaSet。root$ kubectl -n mongo scale statefulsets mongo --replicas=4 statefulset "mongo" scaled root$ kubectl -n mongo get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE mongo-0 2/2 Running 0 25m 10.56.6.17 gke-k8-demo-demo-k8-pool-1-45712bb7-vfqs mongo-1 2/2 Running 0 24m 10.56.7.10 gke-k8-demo-demo-k8-pool-1-c6901f2e-trv5 mongo-2 2/2 Running 0 23m 10.56.8.11 gke-k8-demo-demo-k8-pool-1-c7622fba-qayt mongo-3 2/2 Running 0 3m 10.56.1.4 gke-k8-demo-demo-k8-pool-1-85308bb7-89a4 可以看出,所有四个Pod都部署到不同的GKE节点,因此我们的Pod-Anti Affinity策略工作正常。扩展操作还将自动提供持久卷,该卷将充当新Pod的数据目录。root$ kubectl -n mongo get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE mongo-persistent-storage-mongo-0 Bound pvc-337fb7d6-9f8f-11e8-bcd6-42010a940024 11G RWO fast 49m mongo-persistent-storage-mongo-1 Bound pvc-53375e31-9f8f-11e8-bcd6-42010a940024 11G RWO fast 49m mongo-persistent-storage-mongo-2 Bound pvc-6cee0f97-9f8f-11e8-bcd6-42010a940024 11G RWO fast 48m mongo-persistent-storage-mongo-3 Bound pvc-3e89573f-9f92-11e8-bcd6-42010a940024 11G RWO fast 28m 要检查名为mongo-3的Pod是否已添加到副本集,我们将在同一节点上再次运行rs.status()并观察其差异。对于同一个的Replicaset,成员数现在为4。 新添加的成员遵循与先前成员相同的FQDN方案,并且还与同一主节点同步。 进一步的考虑 给Mongo Pod的Node Pool打上合适的label并确保在StatefulSets和HostVM配置的DaemonSets的Spec中指定适当的Node Affinity会很有帮助。 这是因为DaemonSet将调整主机操作系统的一些参数,并且这些设置应仅限于MongoDB Pod。 没有这些设置,对其他应用程序可能会更好。 在GKE中给Node Pool打Label非常容易,可以直接从GCP控制台进行。 虽然我们在Pod的Spec中指定了CPU和内存限制,但我们也可以考虑部署VPA(Vertical Pod Autoscaler)。 可以通过实施网络策略或服务网格(如Istio)来控制从集群内部到我们的数据库的流量。 如果你已经看到这里,我相信你已经浏览了整个博文。 我试图整理很多分散的信息并将其作为一个整体呈现。 我的目标是为您提供足够的信息,以便开始使用Kubernetes上的Stateful Sets,并希望你们中的许多人觉得它很有用。 我们非常欢迎您提出反馈、意见或建议。:) 本文转自DockOne-如何在Kubernetes上扩展MongoDB?
先决条件 Elasticsearch的基本知识,其Node类型及角色 运行至少有3个节点的Kubernetes集群(至少4Cores 4GB) Kibana的相关知识 部署架构图 Elasticsearch Data Node的Pod被部署为具有Headless Service的StatefulSets,以提供稳定的网络ID。 Elasticsearch Master Node的Pod被部署为具有Headless Service的副本集,这将有助于自动发现。 Elasticsearch Client Node的Pod部署为具有内部服务的副本集,允许访问R/W请求的Data Node。 Kibana和ElasticHQ Pod被部署为副本集,其服务可在Kubernetes集群外部访问,但仍在您的子网内部(除非另有要求,否则不公开)。 为Client Node部署HPA(Horizonal Pod Auto-scaler)以在高负载下实现自动伸缩。 要记住的重要事项: 设置ES_JAVA_OPT环境变量。 设置CLUSTER_NAME环境变量。 为Master Node的部署设置NUMBER_OF_MASTERS环境变量(防止脑裂问题)。如果有3个Masters,我们必须设置为2。 在类似的pod中设置正确的Pod-AntiAffinity策略,以便在工作节点发生故障时确保HA。 让我们直接将这些服务部署到我们的GKE集群。Master节点部署: apiVersion: v1 kind: Namespace metadata: name: elasticsearch --- apiVersion: apps/v1beta1 kind: Deployment metadata: name: es-master namespace: elasticsearch labels: component: elasticsearch role: master spec: replicas: 3 template: metadata: labels: component: elasticsearch role: master spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: role operator: In values: - master topologyKey: kubernetes.io/hostname initContainers: - name: init-sysctl image: busybox:1.27.2 command: - sysctl - -w - vm.max_map_count=262144 securityContext: privileged: true containers: - name: es-master image: quay.io/pires/docker-elasticsearch-kubernetes:6.2.4 env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: NODE_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: CLUSTER_NAME value: my-es - name: NUMBER_OF_MASTERS value: "2" - name: NODE_MASTER value: "true" - name: NODE_INGEST value: "false" - name: NODE_DATA value: "false" - name: HTTP_ENABLE value: "false" - name: ES_JAVA_OPTS value: -Xms256m -Xmx256m - name: PROCESSORS valueFrom: resourceFieldRef: resource: limits.cpu resources: limits: cpu: 2 ports: - containerPort: 9300 name: transport volumeMounts: - name: storage mountPath: /data volumes: - emptyDir: medium: "" name: "storage" --- apiVersion: v1 kind: Service metadata: name: elasticsearch-discovery namespace: elasticsearch labels: component: elasticsearch role: master spec: selector: component: elasticsearch role: master ports: - name: transport port: 9300 protocol: TCP clusterIP: None root$ kubectl apply -f es-master.yml root$ kubectl -n elasticsearch get all NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deploy/es-master 3 3 3 3 32s NAME DESIRED CURRENT READY AGE rs/es-master-594b58b86c 3 3 3 31s NAME READY STATUS RESTARTS AGE po/es-master-594b58b86c-9jkj2 1/1 Running 0 31s po/es-master-594b58b86c-bj7g7 1/1 Running 0 31s po/es-master-594b58b86c-lfpps 1/1 Running 0 31s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE svc/elasticsearch-discovery ClusterIP None <none> 9300/TCP 31s 有趣的是,可以从任何主节点pod的日志来见证它们之间的master选举,然后何时添加新的data和client节点。root$ kubectl -n elasticsearch logs -f po/es-master-594b58b86c-9jkj2 | grep ClusterApplierService [2018-10-21T07:41:54,958][INFO ][o.e.c.s.ClusterApplierService] [es-master-594b58b86c-9jkj2] detected_master {es-master-594b58b86c-bj7g7}{1aFT97hQQ7yiaBc2CYShBA}{Q3QzlaG3QGazOwtUl7N75Q}{10.9.126.87}{10.9.126.87:9300}, added {{es-master-594b58b86c-lfpps}{wZQmXr5fSfWisCpOHBhaMg}{50jGPeKLSpO9RU_HhnVJCA}{10.9.124.81}{10.9.124.81:9300},{es-master-594b58b86c-bj7g7}{1aFT97hQQ7yiaBc2CYShBA}{Q3QzlaG3QGazOwtUl7N75Q}{10.9.126.87}{10.9.126.87:9300},}, reason: apply cluster state (from master [master {es-master-594b58b86c-bj7g7}{1aFT97hQQ7yiaBc2CYShBA}{Q3QzlaG3QGazOwtUl7N75Q}{10.9.126.87}{10.9.126.87:9300} committed version [3]]) 可以看出,名为es-master-594b58b86c-bj7g7的es-master pod被选为master节点,其他2个Pod被添加到这个集群。名为elasticsearch-discovery的Headless Service默认设置为Docker镜像中的env变量,用于在节点之间进行发现。 当然这是可以被改写的。同样,我们可以部署Data和Client节点。 配置如下:Data节点部署:apiVersion: v1 kind: Namespace metadata: name: elasticsearch --- apiVersion: storage.k8s.io/v1beta1 kind: StorageClass metadata: name: fast provisioner: kubernetes.io/gce-pd parameters: type: pd-ssd fsType: xfs allowVolumeExpansion: true --- apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: es-data namespace: elasticsearch labels: component: elasticsearch role: data spec: serviceName: elasticsearch-data replicas: 3 template: metadata: labels: component: elasticsearch role: data spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: role operator: In values: - data topologyKey: kubernetes.io/hostname initContainers: - name: init-sysctl image: busybox:1.27.2 command: - sysctl - -w - vm.max_map_count=262144 securityContext: privileged: true containers: - name: es-data image: quay.io/pires/docker-elasticsearch-kubernetes:6.2.4 env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: NODE_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: CLUSTER_NAME value: my-es - name: NODE_MASTER value: "false" - name: NODE_INGEST value: "false" - name: HTTP_ENABLE value: "false" - name: ES_JAVA_OPTS value: -Xms256m -Xmx256m - name: PROCESSORS valueFrom: resourceFieldRef: resource: limits.cpu resources: limits: cpu: 2 ports: - containerPort: 9300 name: transport volumeMounts: - name: storage mountPath: /data volumeClaimTemplates: - metadata: name: storage annotations: volume.beta.kubernetes.io/storage-class: "fast" spec: accessModes: [ "ReadWriteOnce" ] storageClassName: fast resources: requests: storage: 10Gi --- apiVersion: v1 kind: Service metadata: name: elasticsearch-data namespace: elasticsearch labels: component: elasticsearch role: data spec: ports: - port: 9300 name: transport clusterIP: None selector: component: elasticsearch role: data Headless Service为Data节点提供稳定的网络ID,有助于它们之间的数据传输。在将持久卷附加到pod之前格式化它是很重要的。 这可以通过在创建storage class时指定卷类型来完成。 我们还可以设置标志以允许动态扩展。 这里可以阅读更多内容。... parameters: type: pd-ssd fsType: xfs allowVolumeExpansion: true ... Client节点部署:apiVersion: v1 kind: Namespace metadata: name: elasticsearch --- apiVersion: apps/v1beta1 kind: Deployment metadata: name: es-client namespace: elasticsearch labels: component: elasticsearch role: client spec: replicas: 2 template: metadata: labels: component: elasticsearch role: client spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: role operator: In values: - client topologyKey: kubernetes.io/hostname initContainers: - name: init-sysctl image: busybox:1.27.2 command: - sysctl - -w - vm.max_map_count=262144 securityContext: privileged: true containers: - name: es-client image: quay.io/pires/docker-elasticsearch-kubernetes:6.2.4 env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: NODE_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: CLUSTER_NAME value: my-es - name: NODE_MASTER value: "false" - name: NODE_DATA value: "false" - name: HTTP_ENABLE value: "true" - name: ES_JAVA_OPTS value: -Xms256m -Xmx256m - name: NETWORK_HOST value: _site_,_lo_ - name: PROCESSORS valueFrom: resourceFieldRef: resource: limits.cpu resources: limits: cpu: 1 ports: - containerPort: 9200 name: http - containerPort: 9300 name: transport volumeMounts: - name: storage mountPath: /data volumes: - emptyDir: medium: "" name: storage --- apiVersion: v1 kind: Service metadata: name: elasticsearch namespace: elasticsearch annotations: cloud.google.com/load-balancer-type: Internal labels: component: elasticsearch role: client spec: selector: component: elasticsearch role: client ports: - name: http port: 9200 type: LoadBalancer 此处部署的服务是从Kubernetes集群外部访问ES群集,但仍在我们的子网内部。 注释掉cloud.google.com/load-balancer-type:Internal可确保这一点。但是,如果我们的ES集群中的应用程序部署在集群中,则可以通过 http://elasticsearch.elasticsearch:9200 来访问ElasticSearch服务。创建这两个deployments后,新创建的client和data节点将自动添加到集群中。(观察master pod的日志)root$ kubectl apply -f es-data.yml root$ kubectl -n elasticsearch get pods -l role=data NAME READY STATUS RESTARTS AGE es-data-0 1/1 Running 0 48s es-data-1 1/1 Running 0 28s -------------------------------------------------------------------- root$ kubectl apply -f es-client.yml root$ kubectl -n elasticsearch get pods -l role=client NAME READY STATUS RESTARTS AGE es-client-69b84b46d8-kr7j4 1/1 Running 0 47s es-client-69b84b46d8-v5pj2 1/1 Running 0 47s -------------------------------------------------------------------- root$ kubectl -n elasticsearch get all NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deploy/es-client 2 2 2 2 1m deploy/es-master 3 3 3 3 9m NAME DESIRED CURRENT READY AGE rs/es-client-69b84b46d8 2 2 2 1m rs/es-master-594b58b86c 3 3 3 9m NAME DESIRED CURRENT AGE statefulsets/es-data 2 2 3m NAME READY STATUS RESTARTS AGE po/es-client-69b84b46d8-kr7j4 1/1 Running 0 1m po/es-client-69b84b46d8-v5pj2 1/1 Running 0 1m po/es-data-0 1/1 Running 0 3m po/es-data-1 1/1 Running 0 3m po/es-master-594b58b86c-9jkj2 1/1 Running 0 9m po/es-master-594b58b86c-bj7g7 1/1 Running 0 9m po/es-master-594b58b86c-lfpps 1/1 Running 0 9m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE svc/elasticsearch LoadBalancer 10.9.121.160 10.9.120.8 9200:32310/TCP 1m svc/elasticsearch-data ClusterIP None <none> 9300/TCP 3m svc/elasticsearch-discovery ClusterIP None <none> 9300/TCP 9m -------------------------------------------------------------------- Check logs of es-master leader pod root$ kubectl -n elasticsearch logs po/es-master-594b58b86c-bj7g7 | grep ClusterApplierService [2018-10-21T07:41:53,731][INFO ][o.e.c.s.ClusterApplierService] [es-master-594b58b86c-bj7g7] new_master {es-master-594b58b86c-bj7g7}{1aFT97hQQ7yiaBc2CYShBA}{Q3QzlaG3QGazOwtUl7N75Q}{10.9.126.87}{10.9.126.87:9300}, added {{es-master-594b58b86c-lfpps}{wZQmXr5fSfWisCpOHBhaMg}{50jGPeKLSpO9RU_HhnVJCA}{10.9.124.81}{10.9.124.81:9300},}, reason: apply cluster state (from master [master {es-master-594b58b86c-bj7g7}{1aFT97hQQ7yiaBc2CYShBA}{Q3QzlaG3QGazOwtUl7N75Q}{10.9.126.87}{10.9.126.87:9300} committed version [1] source [zen-disco-elected-as-master ([1] nodes joined)[{es-master-594b58b86c-lfpps}{wZQmXr5fSfWisCpOHBhaMg}{50jGPeKLSpO9RU_HhnVJCA}{10.9.124.81}{10.9.124.81:9300}]]]) [2018-10-21T07:41:55,162][INFO ][o.e.c.s.ClusterApplierService] [es-master-594b58b86c-bj7g7] added {{es-master-594b58b86c-9jkj2}{x9Prp1VbTq6_kALQVNwIWg}{7NHUSVpuS0mFDTXzAeKRcg}{10.9.125.81}{10.9.125.81:9300},}, reason: apply cluster state (from master [master {es-master-594b58b86c-bj7g7}{1aFT97hQQ7yiaBc2CYShBA}{Q3QzlaG3QGazOwtUl7N75Q}{10.9.126.87}{10.9.126.87:9300} committed version [3] source [zen-disco-node-join[{es-master-594b58b86c-9jkj2}{x9Prp1VbTq6_kALQVNwIWg}{7NHUSVpuS0mFDTXzAeKRcg}{10.9.125.81}{10.9.125.81:9300}]]]) [2018-10-21T07:48:02,485][INFO ][o.e.c.s.ClusterApplierService] [es-master-594b58b86c-bj7g7] added {{es-data-0}{SAOhUiLiRkazskZ_TC6EBQ}{qirmfVJBTjSBQtHZnz-QZw}{10.9.126.88}{10.9.126.88:9300},}, reason: apply cluster state (from master [master {es-master-594b58b86c-bj7g7}{1aFT97hQQ7yiaBc2CYShBA}{Q3QzlaG3QGazOwtUl7N75Q}{10.9.126.87}{10.9.126.87:9300} committed version [4] source [zen-disco-node-join[{es-data-0}{SAOhUiLiRkazskZ_TC6EBQ}{qirmfVJBTjSBQtHZnz-QZw}{10.9.126.88}{10.9.126.88:9300}]]]) [2018-10-21T07:48:21,984][INFO ][o.e.c.s.ClusterApplierService] [es-master-594b58b86c-bj7g7] added {{es-data-1}{fiv5Wh29TRWGPumm5ypJfA}{EXqKGSzIQquRyWRzxIOWhQ}{10.9.125.82}{10.9.125.82:9300},}, reason: apply cluster state (from master [master {es-master-594b58b86c-bj7g7}{1aFT97hQQ7yiaBc2CYShBA}{Q3QzlaG3QGazOwtUl7N75Q}{10.9.126.87}{10.9.126.87:9300} committed version [5] source [zen-disco-node-join[{es-data-1}{fiv5Wh29TRWGPumm5ypJfA}{EXqKGSzIQquRyWRzxIOWhQ}{10.9.125.82}{10.9.125.82:9300}]]]) [2018-10-21T07:50:51,245][INFO ][o.e.c.s.ClusterApplierService] [es-master-594b58b86c-bj7g7] added {{es-client-69b84b46d8-v5pj2}{MMjA_tlTS7ux-UW44i0osg}{rOE4nB_jSmaIQVDZCjP8Rg}{10.9.125.83}{10.9.125.83:9300},}, reason: apply cluster state (from master [master {es-master-594b58b86c-bj7g7}{1aFT97hQQ7yiaBc2CYShBA}{Q3QzlaG3QGazOwtUl7N75Q}{10.9.126.87}{10.9.126.87:9300} committed version [6] source [zen-disco-node-join[{es-client-69b84b46d8-v5pj2}{MMjA_tlTS7ux-UW44i0osg}{rOE4nB_jSmaIQVDZCjP8Rg}{10.9.125.83}{10.9.125.83:9300}]]]) [2018-10-21T07:50:58,964][INFO ][o.e.c.s.ClusterApplierService] [es-master-594b58b86c-bj7g7] added {{es-client-69b84b46d8-kr7j4}{gGC7F4diRWy2oM1TLTvNsg}{IgI6g3iZT5Sa0HsFVMpvvw}{10.9.124.82}{10.9.124.82:9300},}, reason: apply cluster state (from master [master {es-master-594b58b86c-bj7g7}{1aFT97hQQ7yiaBc2CYShBA}{Q3QzlaG3QGazOwtUl7N75Q}{10.9.126.87}{10.9.126.87:9300} committed version [7] source [zen-disco-node-join[{es-client-69b84b46d8-kr7j4}{gGC7F4diRWy2oM1TLTvNsg}{IgI6g3iZT5Sa0HsFVMpvvw}{10.9.124.82}{10.9.124.82:9300}]]]) leading master pod的日志清楚地描述了每个节点何时添加到集群。 这在调试问题时非常有用。部署完所有组件后,我们应验证以下内容:1、在kubernetes集群内部使用ubuntu容器进行Elasticsearch部署的验证。root$ kubectl run my-shell --rm -i --tty --image ubuntu -- bash root@my-shell-68974bb7f7-pj9x6:/# curl http://elasticsearch.elasticsearch:9200/_cluster/health?pretty { "cluster_name" : "my-es", "status" : "green", "timed_out" : false, "number_of_nodes" : 7, "number_of_data_nodes" : 2, "active_primary_shards" : 0, "active_shards" : 0, "relocating_shards" : 0, "initializing_shards" : 0, "unassigned_shards" : 0, "delayed_unassigned_shards" : 0, "number_of_pending_tasks" : 0, "number_of_in_flight_fetch" : 0, "task_max_waiting_in_queue_millis" : 0, "active_shards_percent_as_number" : 100.0 } 2、在kubernetes集群外部使用GCP内部LoadBalancer IP(这里是10.9.120.8)进行Elasticsearch部署的验证。root$ curl http://10.9.120.8:9200/_cluster/health?pretty { "cluster_name" : "my-es", "status" : "green", "timed_out" : false, "number_of_nodes" : 7, "number_of_data_nodes" : 2, "active_primary_shards" : 0, "active_shards" : 0, "relocating_shards" : 0, "initializing_shards" : 0, "unassigned_shards" : 0, "delayed_unassigned_shards" : 0, "number_of_pending_tasks" : 0, "number_of_in_flight_fetch" : 0, "task_max_waiting_in_queue_millis" : 0, "active_shards_percent_as_number" : 100.0 } 3、ES-Pods的Anti-Affinity规则验证。root$ kubectl -n elasticsearch get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE es-client-69b84b46d8-kr7j4 1/1 Running 0 10m 10.8.14.52 gke-cluster1-pool1-d2ef2b34-t6h9 es-client-69b84b46d8-v5pj2 1/1 Running 0 10m 10.8.15.53 gke-cluster1-pool1-42b4fbc4-cncn es-data-0 1/1 Running 0 12m 10.8.16.58 gke-cluster1-pool1-4cfd808c-kpx1 es-data-1 1/1 Running 0 12m 10.8.15.52 gke-cluster1-pool1-42b4fbc4-cncn es-master-594b58b86c-9jkj2 1/1 Running 0 18m 10.8.15.51 gke-cluster1-pool1-42b4fbc4-cncn es-master-594b58b86c-bj7g7 1/1 Running 0 18m 10.8.16.57 gke-cluster1-pool1-4cfd808c-kpx1 es-master-594b58b86c-lfpps 1/1 Running 0 18m 10.8.14.51 gke-cluster1-pool1-d2ef2b34-t6h9 请注意,同一节点上没有2个类似的Pod。 这可以在节点发生故障时确保HA。Scaling相关注意事项 我们可以根据CPU阈值为client节点部署autoscalers。 Client节点的HPA示例可能如下所示:apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: es-client namespace: elasticsearch spec: maxReplicas: 5 minReplicas: 2 scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: es-client targetCPUUtilizationPercentage: 80 每当autoscaler启动时,我们都可以通过观察任何master pod的日志来观察添加到集群中的新client节点Pod。对于Data Node Pod,我们必须使用K8 Dashboard或GKE控制台增加副本数量。 新创建的data节点将自动添加到集群中,并开始从其他节点复制数据。Master Node Pod不需要自动扩展,因为它们只存储集群状态信息,但是如果要添加更多data节点,请确保集群中没有偶数个master节点,同时环境变量NUMBER_OF_MASTERS也需要相应调整。部署Kibana和ES-HQ Kibana是一个可视化ES数据的简单工具,ES-HQ有助于管理和监控Elasticsearch集群。 对于我们的Kibana和ES-HQ部署,我们记住以下事项: 我们提供ES-Cluster的名称作为Docker镜像的环境变量 访问Kibana/ES-HQ部署的服务仅在我们组织内部,即不创建公共IP。 我们使用GCP内部负载均衡。 Kibana部署:apiVersion: v1 kind: Namespace metadata: name: elasticsearch --- apiVersion: apps/v1beta1 kind: Deployment metadata: name: es-kibana namespace: elasticsearch labels: component: elasticsearch role: kibana spec: replicas: 1 template: metadata: labels: component: elasticsearch role: kibana spec: containers: - name: es-kibana image: docker.elastic.co/kibana/kibana-oss:6.2.2 env: - name: CLUSTER_NAME value: my-es - name: ELASTICSEARCH_URL value: http://elasticsearch:9200 resources: limits: cpu: 0.5 ports: - containerPort: 5601 name: http --- apiVersion: v1 kind: Service metadata: name: kibana annotations: cloud.google.com/load-balancer-type: "Internal" namespace: elasticsearch labels: component: elasticsearch role: kibana spec: selector: component: elasticsearch role: kibana ports: - name: http port: 80 targetPort: 5601 protocol: TCP type: LoadBalancer ES-HQ部署:apiVersion: v1 kind: Namespace metadata: name: elasticsearch --- apiVersion: apps/v1beta1 kind: Deployment metadata: name: es-hq namespace: elasticsearch labels: component: elasticsearch role: hq spec: replicas: 1 template: metadata: labels: component: elasticsearch role: hq spec: containers: - name: es-hq image: elastichq/elasticsearch-hq:release-v3.4.0 env: - name: HQ_DEFAULT_URL value: http://elasticsearch:9200 resources: limits: cpu: 0.5 ports: - containerPort: 5000 name: http --- apiVersion: v1 kind: Service metadata: name: hq annotations: cloud.google.com/load-balancer-type: "Internal" namespace: elasticsearch labels: component: elasticsearch role: hq spec: selector: component: elasticsearch role: hq ports: - name: http port: 80 targetPort: 5000 protocol: TCP type: LoadBalancer 我们可以使用新创建的Internal LoadBalancers访问这两个服务。root$ kubectl -n elasticsearch get svc -l role=kibana NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kibana LoadBalancer 10.9.121.246 10.9.120.10 80:31400/TCP 1m root$ kubectl -n elasticsearch get svc -l role=hq NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hq LoadBalancer 10.9.121.150 10.9.120.9 80:31499/TCP 1m Kibana Dashboard http://&lt;External-Ip-Kibana-Service>/app/kibana#/home?_g=() ElasticHQ Dasboard http://&lt;External-Ip-ES-Hq-Service>/#!/clusters/my-es ES是最广泛使用的分布式搜索和分析系统之一,当与Kubernetes结合使用时,将消除有关扩展和HA的关键问题。 此外,使用Kubernetes部署新的ES群集需要时间。 本文转自DockOne-如何在Kubernetes上部署高可用和可扩展的Elasticsearch?
什么是容器,Kubernetes适合应用于什么地方,成功部署需要什么工具?当前,容器的使用可谓如火如荼。不仅受到开发人员的喜爱,而且也倍受企业追捧。如果贵公司的IT部门正在寻找一种更快速、更简单的应用开发方式时,那您应该考虑使用容器技术。但是行动之前,我们存在如下问题: 容器是什么?它解决了哪些问题? Kubernetes在容器和集群管理空间中的位置如何? 为什么它代表了企业应用实施的挑战? 在研究容器和集群管理工具是否适合您的应用程序开发需求时,您应该记住哪些注意事项? 关于容器、容器集群管理、Kubernetes的优缺点,以及如何从Kubernetes部署中获益最大。我们认为每个企业都需要了解这些基本要点。什么是容器?它们解决了哪些问题? 当程序员测试软件时,他们必须确保软件从一个计算环境迁移到另一个计算环境时,能够可靠地运行。这可能是从模拟环境迁到生产环境,也可能是从自有数据中心迁到云上的虚拟机。这带来的问题是不同的环境很难完全相同,环境内的软件有可能有差异,网络和安全配置也很大可能存在不同。而容器通过将整体开发环境的内容打成一个软件包的方式解决了这个问题。通过引入了一个环境一致性的层级,通过容器化把软件平台及其依赖项,操作系统分布和底层基础设施之间的差异等抽象出来。允许开发人员以相同的方式快速、可靠地部署应用,而无需考虑所部署的环境。事实上,使用容器可以让部署人员忽略全部的基础设施。因为容器包含了应用运行所需的所有元素——从代码到配置,包括软件内的模块、独立运行的软件包等。因此,容器的可移植性使它成为企业考虑多云策略的重要资本。容器还可以帮助用户为适应DevOps部署和推行高效、快速交付的做准备。通过容器,用户可以更新和升级,而无需担心反复重新部署引起的麻烦。容器的优势使得在现有系统中部署新应用程序和部署效率都超出用户的原有预期。容器比虚拟机更轻巧,资源消耗也更少,因此它的使用快速普及。最新的一项调查显示:在过去12个月里,有94%的受访者使用过或调研过某些容器技术。哪些公司在容器方面处于领先地位? 当前,Docker成为了容器技术的代名词。它拥有成熟的技术堆栈、强大的开源生态圈、与任何平台的兼容性,以及非常好的时间切入点(Docker是在虚拟机的流行度下降时推出的)。Docker已经将其竞争对手——rkt、OpenVZ和LXC等远远地甩在了后面。Docker所承载的环境恒定不可编辑,独立于底层设施,使它不管是在开发人员的机器上,还是在生产环境中,都能保持一致的运行效果。容器管理工具如何帮助有效地管理容器? 在企业级应用开发中,需要合适的工具来有效地实现DevOps,并有效管理容器技术和平台。这也是容器集群管理或容器编排解决方案的作用所在。但随着企业将容器应用到生产环境中,管理和维护在不同位置的容器、以及确保跨主机的容器之间的合理通信等方面都将出现问题。我们将这些相互关联的容器群体被称为“集群”。这就引出了容器集群管理的概念。容器集群管理工具提供了一个企业级框架,用于大规模容器的集成和管理,并确保用户在使用DevOps时,具备必要的连续性。基本上,它们可以帮助用户定义初始的容器部署,同时维护后端关键IT应用的性能,如可用性、可伸缩性和网络联通性。并且将所有的这些功能都做到合理、标准化和统一的。有哪些企业级容器集群管理解决方案? 虽然容器集群管理有多种选择。但Kubernetes无疑时容器化之战的胜利者,它是目前使用最广泛的开源解决方案。基于谷歌15年的持续开发,以及引人注目的开源社区(包括Red Hat、Canonical、CoreOS和Microsoft), Kubernetes比市场上任何其他产品成熟得都快。Kubernetes成为容器集群管理的不二选择,还因为它为开发人员提供了工具,让他们可以快速有效地响应客户需求,同时减轻在云中运行应用的负担。它通过去除许多部署和扩展容器化应用相关的手动任务来达到上述效果。这样,在不同环境间进行切换时,软件都能稳定可靠地运行。例如,您可以在节点集群(跨越公共、私有或混合云)上计划和部署任意数量的容器,然后由Kubernetes负责按照您的意愿管理这些负载。Kubernetes使容器的任务处理得到了简化,这些任务包括部署操作(水平自动缩放、滚动更新、金丝雀部署)和管理(监控资源、应用程序健康检查、调试应用程序等)。Kubernetes仍然有很高的进入门槛 凡事总会有"但是"。虽然,我们在之前讨论将Kubernetes选择为最佳容器管理平台时,讲了它的许多优势。但Kubernetes依然在配置和使用上,存在一定的困难。管理Kubernetes是一个耗时的过程,而且需要操作人员有很高的技能水平,企业可能要投入很多资金才行。也许对于接触不深的人说,Kubernetes貌似可以在数小时或数天内就跑起来。但这和将Kubernetes用于生产环境还相差很远,生产环境存在的一些额外功能要求如安全性、高可用性、灾难恢复、备份和维护等,要让Kubernetes都一一满足,难度就可想而知啦。这些要求会让按着Kubernetes走的企业很快就意识到,如果不引入熟练的技能或费钱的外部资源的话,基本就无法交付。如何选择合适的Kubernetes管理平台? 在为企业选择Kubernetes管理平台时,需要考虑如下方面: 生产环境成熟度:它是否提供了配置的自动化功能,没有配置问题?是否提供企业级的安全性?它在集群上,是否能自动化处理所有的管理任务?它是否能为应用提供高可用性、可伸缩性和自愈性? 后期兼容性:平台是否支持多云策略?虽然Kubernetes本质上就允许用户在任意环境下运行应用程序,而无需考虑托管环境的匹配问题。但还是需要确保Kubernetes管理平台具备这些功能,以便未来所需。 易于管理:是否包含自动智能监控和警报?它是否解决了Kubernetes原始数据的分析问题,提供全域面板来查看系统状态、错误、事件和警告? 技术支持和培训:随着企业应用向的容器策略倾斜,Kubernetes管理平台厂商是否提供24×7的技术支持和培训服务? 在上面所述的需求选项中,每个需求所对应的可选产品其实不多。其中Kublr是一个性价比高且和生产环境成熟度都不错的平台,它对Kubernetes的配置和管理进行了加速和流程化。它提供的自修复的、自动伸缩的解决方案,可以帮助用户将遗留系统迁移到单一引擎的云上。同时用户可以在后台无缝地进行维护、重构或替换。在模块之间实现了动态性、灵活性以及非常优秀的透明性。可称之为双赢。如何选择正确的Kubernetes管理平台供应商? 当您考虑并计划您的Kubernetes企业战略时,请了解应用Kubernetes的挑战和误导以及它演化过程中的各种障碍。找到Kubernetes平台中的属于你的真实目标,并花些时间做个Kubernetes管理平台产品的比较。最后,您可以自己看看平台的自动化工具是如何实现生产环境成熟度(最重要的特性)、后期兼容性、易于管理以及使用Kubernetes所需的支持。 本文转自DockOne-何谓Kubernetes以及企业如何从DevOps趋势中获益
在开始撰写本文之前,我想问你几个问题。 你或你的团队是否需要使用Kubernetes进行容器编排?你想学习Kubernetes是否很困惑从哪里开始? 你愿意改变你的组织吗?你想简化容器软件编排吗?然后我想告诉你,这篇文章是所有这些问题的答案。Kubernetes旨在简化事情,本文旨在为您简化Kubernetes!Kubernetes是一个由Google开发的强大的开源系统。它是为在集群环境中管理容器化应用程序而开发的。Kubernetes已经获得了普及,并且正在成为在云上部署软件的新标准。学习Kubernetes并不困难(如果导师很好),它提供了强大的能力。学习曲线有点陡峭。因此,让我们以简化的方式学习Kubernetes。本文介绍了Kubernetes的基本概念,架构,它是如何解决问题的等。Kubernetes是什么? 实际上Kubernetes本身就是一个用于在多台机器上运行和协调应用程序的系统。该系统管理容器化应用程序和服务的生命周期。为了管理生命周期,它使用不同的方法来提高可预测性,可伸缩性和高可用性。Kubernetes用户可以自由决定应用程序的运行和通信方式。还允许用户扩展/缩减服务,执行滚动更新,在不同应用程序版本之间切换流量等。Kubernetes还提供用于定义/管理应用程序的不同接口和平台原语。Kubernetes硬件 Kubernetes需要不同类型的硬件。我们需要了解的是Kubernetes本身是不需要硬件的,但功能系统需要硬件。Nodes Kubernetes中的节点是什么?Kubernetes中最小的计算单位之一被称为节点。它是一台机器,位于一个集群中。节点不一定需要是物理机器或硬件的一部分。它可以是物理机器或虚拟机。对于数据中心,节点是物理机器。对于Google Cloud Platform,节点是虚拟机。目前,我们正在讨论Kubernetes的硬件,所以让我们相应地考虑一下。但是不要将节点限制为“硬件”。任何机器中总是有一个抽象层。但在这里,没有必要担心机器的特性。实际上,我们可以简单地将每台机器视为一组CPU和RAM资源。这些计算机位于群集中,可以根据需要使用其资源。当我们谈到Kubernetes,你自然地这样想!在你自由地利用任何机器资源的那一刻,系统变得无比灵活。现在任何机器都可以成为Kubernetes集群中的一个节点。 Cluster 我们已经讨论了节点,对吗?他们似乎是小而可爱的处理单位。他们在自己的小房子里工作。所有这些听起来都很完美,但它还不是Kubernetes的方式!所以集群来了。您无需担心单个节点的状态,因为它们是集群的一部分。例如,如果单个节点表现不佳,则应该有人来管理所有这些。此外,集群是多个节点的集合。将所有节点的资源集中在一起,共同构成一个强大的机器。集群是很智能的。你知道为什么吗?当程序部署到集群上时,它会动态处理分发。简而言之,它将任务分配给各个节点。在该过程之间,如果添加或删除任何节点,则集群会根据需要移动任务。程序员不需要专注于诸如单个机器运行的代码之类的东西等等。哦,我还记得一些非常有趣的东西。你还记得星际迷航中的“Borg”吗?这个名字来自哪里?Google内部的Kubernetes项目的名字就是这个。 Persistent Volumes 如上所述,程序在集群上运行并由节点提供支持。但它们不在特定节点上运行。程序动态地运行。因此,需要存储信息,并且不能将其随机存储在任何文件系统中。为什么?例如,程序将数据保存到文件中。但是后来,该程序被重新定位到另一个节点。下次程序需要该文件时,它不会在预期的位置。位置地址已经改变。为了解决这个问题,与每个节点相关的传统本地存储被认为是用于保存程序的临时缓存。但是,任何本地保存的数据都不会持续存在。那么谁来永久存储数据呢?是的,持久性卷来永久存储它。集群管理所有节点的CPU和RAM资源。但是,集群不负责将数据永久存储在持久卷中。本地驱动器和云驱动器可以像持久卷一样附加到集群。这很类似于将外部驱动器插入集群。持久卷提供文件系统。它可以挂载到集群,而不与任何特定节点进行关联。 这是Kubernetes的硬件部分。 现在让我们转到软件部分。Kubernetes软件 Kubernetes的整体概念基于软件。所以这是Kubernetes的主要部分。Containers 在Kubernetes中,程序运行在Linux容器。这些容器基于预编译的镜像。镜像可以部署在Kubernetes上。你知道什么是容器化吗?它允许你创建Linux执行环境。程序及其依赖项打包在单个镜像中,并在网上共享。因此,任何人都可以按照需求下载镜像并将其部署在基础设施上。只需一点设置即可轻松部署。可以在程序的帮助下创建容器。这使得能够形成有效的CI/CD管道。容器能够处理多个程序。但建议每个容器限制一个进程,因为这有助于排除故障。更新容器很容易,如果它很小,部署也很容易。最好有许多小容器,而不是大容器。Pod Kubernetes有一些独特的特性,其中之一是它不直接运行容器。它将一个或多个容器包装成一个Pod。 Pod的理念是同一Pod中的任何容器使用相同的资源和相同的本地网络。好处是容器可以容易地相互通信。它们是孤立的,但随时可以通信。Pod可以在Kubernetes中复制。例如,应用程序变得流行并且单个Pod无法承受负载。此时,可以根据需要配置Kubernetes以部署Pod的新副本。但是,只有在重负载期间才需要进行复制。Pod也可以在正常条件下复制。这有助于统一负载平衡和抵抗故障。Pod能够容纳多个容器,但如果可能的话,应该限制为一个或两个容器。原因是Pod作为一个单元向上和向下扩展。Pod内的容器也必须与Pod一起缩放。在这个阶段,单个容器的需求并不重要。另一方面,这会导致资源浪费和昂贵的账单。 为避免这些,请将Pod限制为少数几个容器。如果你遇到过“sidecar”这个词,那就意味着辅助容器。所以有主进程容器,可能有一些辅助容器。Depoloyment 你已经注意到,Pod是Kubernetes的基本单位。但它们不是直接在群集上启动的。它们由多个抽象层进行管理,这就是Deployment存在的原因。主要目的是声明一次运行的副本数。 添加Deployment之后,它会监控Pod的数量。同样,如果Pod不存在,则Deployment会重新创建它。有趣的是,通过Deployment,可以无需处理Pod。 同时,通过声明系统状态,可以自动管理所有内容。通过Ingress向成功进军 我们已经讨论了Kubernetes的所有基本概念。使用它们,你可以创建节点、集群。创建集群后,就可以在集群上启动Pod的部署。但是,你如何允许外部流量到你的应用程序?我们还没有讨论过这个问题。根据Kubernetes的概念,它提供了Pod和外部世界之间的隔离。要与在Pod中运行的服务进行通信,需要打开一个通道。通道是沟通的媒介。它被称为“Ingress”。 有许多方法可以向集群添加Ingress。最常见的是通过Ingress Controller或负载均衡器。我们可以讨论这两种方法之间的区别,但目前不需要,因为它太具技术性。现在,您应该了解Ingress对于尝试Kubernetes非常重要。虽然你其他都做得很对,但是如果你不考虑Ingress,你将无法到达任何地方!Kubernetes如何解决问题? 在讨论了Kubernetes的部署部分之后,有必要了解Kubernetes的重要性。容器编排和Kubernetes 容器是虚拟机。它们轻巧,可扩展且独立。容器放在一起需要设置安全策略,限制资源利用率等。如果您的应用程序基础结构类似于下面共享的镜像,则容器编排是必要的。它可能是在几个容器上运行的Nginx/Apache + PHP/Python/Ruby/Node.js应用程序,通过数据库副本通讯。容器编排(Container orchestration)将帮助您自己管理所有内容。请考虑你的应用程序不断增长。例如,你继续添加更多特性/功能,并且在某个时间点,你意识到它突然变成了巨大的单体应用。 现在,管理庞大的应用程序是不可能的,因为它会占用太多CPU和RAM。所以你最终决定将应用程序分成更小的块。他们每个人都有一个特定的任务。现在,你的基础架构如下所示:因此,你需要一个带有队列系统的缓存层,以获得更好的异步性能。现在你会发现,存在服务发现,负载平衡,运行状况检查,存储管理,自动扩展等挑战。 在所有这些情况下,谁会来帮助你?是的,容器编排将成为你的救星!原因是容器编排功能非常强大,可以解决大部分挑战。那有什么选择呢? 主要候选者是Kubernetes,AWS ECS和Docker Swarm。在所有这些中,Kubernetes是最受欢迎的!Kubernetes提供最大的社区。Kubernetes解决了所描述的所有主要问题。它是可移植的,可运行在大多数云提供商,裸机,混合环境以及所有这些的组合上。此外,它也是可配置和模块化的。它提供自动放置,自动重启,自动复制和容器自动修复等功能。要点 最重要的是,Kubernetes拥有一个活跃的在线社区。这个社区的成员在网上以及在世界主要城市线下聚会。国际会议“KubeCon”已经证明是一个巨大的成功。Kubernetes还有一个官方的Slack小组。Google云平台,AWS,Azure,DigitalOcean等主要云提供商也提供其支持渠道。 你还在等什么? 本文转自DockOne-初学者的Kubernetes圣经
Kubernetes网络模型 谈到Kubernetes的网络模型,就不能不提它著名的“单Pod单IP”模型,即每个Pod都有一个独立的IP,Pod内所有容器共享网络namespace(同一个网络协议栈和IP)。“单Pod单IP”网络模型为我们勾勒了一个Kubernetes扁平网络的蓝图,在这个网络世界里:容器之间直接通信,不需要额外的NAT(网络地址转换);Node与容器之间,同样不需要额外的NAT;在其他容器和容器自身看到的IP也一样的。扁平化网络的优点在于:没有NAT的性能损耗,可追溯源地址进而为后面的网络策略做铺垫,易排错等。总体而言,如果集群内要访问Pod,走Service,至于集群外要访问Pod,走的是Ingress。Service和Ingress是Kubernetes专门的抽象出来的和服务发现相关的概念,后面会做详细讨论。类似于CRI之于Kubernetes的Runtime,Kubernetes使用CNI(Container Network Interface)作为Pod网络配置的标准接口。需要注意的是,CNI并不支持Docker网络,也就是说docker0网桥会被CNI的各类插件“视而不见”。 上图描绘了当用户在Kubernetes里创建了一个Pod后,CRI和CNI协同创建所有容器并为他们初始化网络栈的全过程。 具体过程如下:当用户在Kubernetes的Master那边创建了一个Pod后,Kubelet观察到新Pod的创建,于是首先调用CRI(后面的Runtime实现,比如:dockershim,containerd等)创建Pod内的若干个容器。在这些容器里面,第一个被创建的Pause容器是比较特殊的,这是Kubernetes系统“赠送”的容器,里面跑着一个功能十分简单的Go语言程序,具体逻辑是一启动就去select一个空的Go语言channel,自然就永远阻塞在那里了。一个永远阻塞而且没有实际业务逻辑的pause容器到底有什么用呢?用处大了。我们知道容器的隔离功能利用的是Linux内核的namespace机制,而只要是一个进程,不管这个进程是否处于运行状态(挂起亦可),它都能“占”着一个namespace。因此,每个Pod内的第一个系统容器Pause的作用就是为占用一个Linux的network namespace,而Pod内其他用户容器通过加入到这个network namespace的方式来共享同一个network namespace。用户容器和Pause容器之间的关系有点类似于寄居蟹和海螺的关系。因此,Container Runtime创建Pod内所有容器时,调用的都是同一个命令:$ docker run --net=none 意思是只创建一个network namespace,而不初始化网络协议栈。如果这个时候通过nsenter方式进入到容器,会看到里面只有一个本地回环设备lo。那么容器的eth0是怎么创建出来的呢?答案是CNI。CNI主要负责容器的网络设备初始化工作。Kubelet目前支持两个网络驱动,分别是:kubenet和CNI。Kubenet是一个历史产物,即将废弃,因此这里也不准备过多介绍。CNI有多个实现,官方自带的插件就有p2p,bridge等,这些插件负责初始化Pause容器的网络设备,也就是给eth0分配IP等,到时候Pod内其他容器就用这个IP与外界通信。Flanne,Calico这些第三方插件解决Pod之间的跨机通信问题。容器之间的跨机通信,可以通过bridge网络或overlay网络来完成。上图是一个bridge网络的示意图。Node1上Pod的网段是10.1.1.0/24,接的Linux网桥是10.1.1.1,Node2上Pod的网段是10.1.2.0/24,接的Linux网桥是10.1.2.1,接在同一个网桥上的Pod通过局域网广播通信。我们发现,Node1上的路由表的第二条是: 10.1.1.0/24 dev cni0 意思是所有目的地址是本机上Pod的网络包,都发到cni0这个Linux网桥去,进而广播给Pod。注意看第三条路由规则:10.1.2.0/24 via 192.168.1.101 10.1.2.0/24是Node2上Pod的网段,192.168.1.101又恰好是Node2的IP。意思是,目的地址是10.1.2.0/24的网络包,发到Node2上。这时候我们观察Node2上面的第二条路由信息:10.1.2.0/24 dev cni0 就会知道这个包会被接着发给Node2上的Linux网桥cni0,然后再广播给目标Pod。回程报文同理走一条逆向的路径。因此,我们可以得出一个小小的结论:bridge网络本身不解决容器的跨机通信问题,需要显式地书写主机路由表,映射目标容器网段和主机IP的关系,集群内如果有N个主机,需要N-1条路由表项。至于overlay网络,它是构建在物理网络之上的一个虚拟网络,其中VXLAN是当前最主流的overlay标准。VXLAN就是用UDP包头封装二层帧,即所谓的MAC in UDP。上图即一个典型overlay网络的拓扑图。和bridge网路类似,Pod同样接在Linux网桥上,目的地址是本机Pod的网络包同样发给Linux网桥cni0。不一样的是,目的Pod在其他节点上的路由表规则,例如: 10.1.0.0/16 dev tun0 这次是直接发给本机的TAP设备tun0,而tun0就是overlay隧道网络的入口。我们注意到,集群内所有机器都只需要这么一条路由表,而不需要像bridge网络那样,写N-1条路由表项。那如何才能将网络包正确地传递到目标主机的隧道口另一端呢?一般情况下,例如flannel的实现,会借助一个分布式的数据库,用于记录目的容器IP与所在主机的IP的映射关系,而且每个节点上都会运行一个agent,例如flanneld,会监听在tun0上,进行封包和解包操作。例如:Node1上的容器发包给Node2上的容器,flanneld会在tun0处将一个目的地址是192.168.1.101:8472的UDP包头(校验和置成0)封装到这个包的外层,然后接着主机网络的东风顺利到达Node2。监听在Node2的tun0上的flanneld捕获这个特殊的UDP包(检验和为0),知道这是一个overlay的封包,于是解开UDP包头,将它发给本机的Linux网桥cni0,进而广播给目的容器。什么是CNI CNI是Container Network Interface的缩写,是容器网络的标准化,试图通过JSON来描述一个容器网络配置。从上图可以看出,CNI是Kubernetes与底层网络插件之间的一个抽象层,为Kubernetes屏蔽了底层网络实现的复杂度,同时也解耦了Kubernetes的具体网络插件实现。 CNI主要有两类接口,分别是在创建容器时调用的配置网络接口:AddNetwork(net NetworkConfig, rt RuntimeConf) (types.Result,error)和删除容器时调用的清理网络接口:DelNetwork(net NetworkConfig, rt RuntimeConf)。不论是配置网络接口还是删除网络接口,都有两个入参,分别是网络配置和runtime配置。网络配置很好理解,Rumtime配置则主要是容器运行时传入的网络namespace信息。符合CNI标准的默认/第三方网络插件有:其中CNI-Genie是一个开源的多网络的容器解决方案,感兴趣的读者可以自行去Github上搜索。 下面我们将举几个CNI网络插件的例子。上图是一个host-local + bridge插件组合的例子,在这么一个JSON文件中,我们定义了一个名为mynet的网络,是一个bridge模型,而IP地址管理(ipam)使用的是host-local(在本地用一个文件记录已经分配的容器IP地址)且可供分配的容器网段是10.10.0.0/16。至于Kubernetes如何使用它们?Kubelet和CNI约好了两个默认的文件系统路径,分别是/etc/cni/net.d用来存储CNI配置文件和/opt/cni/bin目录用来存放CNI插件的二进制文件,在我们这个例子中,至少要提前放好bridge和host-local这两个插件的二进制,以及10-mynet.conf配置文件(叫什么名字随意,Kubelet只解析*.conf文件)。由于主流的网络插件都集成了bridge插件并提供了各自的ipam功能,因此在实际Kubernetes使用过程中我们并不需要操心上面过程,也无需做额外配置。 再来看一个最近Kubernetes V1.11版本合到社区主干的带宽控制插件的使用方法。当我们书写了以下Pod配置时:Kubernetes就会自动为我们这个Pod分别限制上传和下载的带宽为最高10Mb/s。注意,由于这个特性较新,我们需要自己在/etc/cni/net.d目录下写一个配置文件,例如my-net.conf: { "type": "bandwidth", "capabilities": {"bandwidth": true} } 这个配置文件会告诉Kubelet去调用CNI的默认bandwidth插件,然后根据Pod annotation里面关于带宽的ingress/egress值进行容器上行/下行带宽的限制。当然,CNI插件最后调用的还是Linux tc工具。Kubernetes Service机制 容器网络模型讲完后,我们再看下Kubernetes集群内访问的Service机制。先从一个简单的例子讲起,客户端访问容器应用,最简单的方式莫过于直接容器IP+端口了。但,简单的生活总是暂时的。 当有多个后端实例,如何做到负载均衡?如何保持会话亲和性?容器迁移,IP发生变化如何访问?健康检查怎么做?怎么通过域名访问?Kubernetes提供的解决方案是在客户端和后端Pod之间引入一个抽象层:Service。什么是Kubernetes的Service呢?Kubernetes的Service有时候也称为Kubernetes的微服务,代表的是Kubernetes后端服务的入口,它注意包含服务的访问IP(虚IP)和端口,因此工作在L4。既然Service只存储服务入口信息,那如何关联后端Pod呢?Service通过Label Selector选择与之匹配的Pod。那么被Service选中的Pod,当它们Running且Ready后,Kubernetes的Endpoints Controller会生成一个新的Endpoints对象,记录Pod的IP和端口,这就解决了前文提到的后端实例健康检查问题。另外,Service的访问IP和Endpoint/Pod IP都会在Kubernetes的DNS服务器里存储域名和IP的映射关系,因此用户可以在集群内通过域名的方式访问Service和Pod。 Kubernetes Service的定义如下所示:其中,spec.ClusterIP就是Service的访问IP,俗称虚IP,spec.ports[].port是Service的访问端口,而与之对应的spec.ports[].targetPort是后端Pod的端口,Kubernetes内部会自动做一次映射。 Kubernetes Endpoints的定义如下所示:其中,subsets[].addresses[].ip是后端Pod的IP,subsets[].ports是后端Pod的端口,与Service的targetPort对应。 下面我们来看下Kubernetes Service的工作原理。如上图所示,当用户创建Service和对应后端Pod时,Endpoints Controller会观察Pod的状态变化,当Pod处于Running且Ready状态时,Endpoints Controller会生成Endpoints对象。运行在每个节点上的Kube-proxy会观察Service和Endpoints的更新,并调用其load balancer在主机上模块刷新转发规则。当前主流的load balancer实现有iptables和IPVS,iptables因为扩展性和性能不好,越来越多的厂商正开始使用IPVS模式。 Kubernetes Service有这么几种类型:ClusterIP,NodePort和Load Balancer。其中,ClusterIP是默认类型,自动分配集群内部可以访问的虚IP——Cluster IP。NodePort为Service在Kubernetes集群的每个Node上分配一个端口,即NodePort,集群内/外部可基于任何一个NodeIP:NodePort的形式来访问Service。因此NodePort也成为“乞丐版”的Load Balancer,对于那些没有打通容器网络和主机网络的用户,NodePort成了他们从外部访问Service的首选。LoadBalancer类型的Service需要Cloud Provider的支持,因为Service Controller会自动为之创建一个外部LB并配置安全组,Kubernetes原生支持的Cloud Provider就那么几个:GCE,AWS。除了“外用”,Load Balancer还可以“内服”,即如果要在集群内访问Load Balancer类型的Service,kube-proxy用iptables或ipvs实现了云服务提供商LB(一般都是L7的)的部分功能:L4转发,安全组规则等。Kubernetes Service创建好了,那么如何使用,即如何进行服务发现呢?Kubernetes提供了两种方式:环境变量和域名。环境变量即Kubelet为每个Pod注入所有Service的环境变量信息,形如:这种方式的缺点是:容易环境变量洪泛,Docker启动参数过长影响性能甚至直接导致容器启动失败。 域名的方式是,假设Service(my-svc)在namespace(my-ns)中,暴露名为http的TCP端口,那么在Kubernetes的DNS服务器会生成两种记录,分别是A记录:域名(my-svc.my-ns)到Cluster IP的映射和SRV记录,例如:_http._tcp.my-svc.my-ns到一个http端口号的映射。我们会在下文Kube-dns一节做更详细的介绍。前文提到,Service的load balancer模块有iptables和IPVS实现。下面会一一进行分析。Iptables是用户空间应用程序,通过配置Netfilter规则表( Xtables )来构建linux内核防火墙。下面就是Kubernetes利用iptables的DNAT模块,实现了Service入口地址(10.20.30.40:80)到Pod实际地址(172.17.0.2:8080)的转换。IPVS是LVS的负载均衡模块,亦基于netfilter,但比iptables性能更高,具备更好的可扩展性。 如上所示,一旦创建一个Service和Endpoints,Kube-proxy的IPVS模式会做三样事情: 确保一块dummy网卡(kube-ipvs0)存在。至于为什么要创建dummy网卡,因为IPVS的netfilter钩子挂载INPUT chain,我们需要把Service的访问IP绑定在dummy网卡上让内核“觉得”虚IP就是本机IP,进而进入到INPUT chain。 把Service的访问IP绑定在dummy网卡上。 通过socket调用,创建IPVS的virtual server和real server,分别对应Kubernetes的Service和Endpoints。 好了,都说IPVS性能要好于iptables,无图无真相,上实测数据!通过上图我们可以发现,IPVS刷新规则的时延明显要低iptables几个数量级。 从上图我们又可以发现,IPVS相较于iptables,端到端的吞吐率和平均时延均由不小的优化。注意,这是端到端的数据,包含了底层容器网络的RTT,还能有30%左右的性能提升。 上图是iptables和IPVS在资源消耗方面的对比,孰优孰劣,不言而喻。 最后,问个开放性的问题。如何从集群外访问Kubernetes Service?前文已经提到,可以使用NodePort类型的Service,但这种“屌丝”的做法除了要求集群内Node有对外访问IP外,还有一些已知的性能问题(具体请参考本公众号另外一篇干货文章《记一次Docker/Kubernetes上无法解释的超时原因探寻之旅》)。使用LoadBalancer类型的Service?它又要求在特定的云服务上跑Kubernetes。而且Service只提供L4负载均衡功能,而没有L7功能,一些高级的,L7的转发功能,比如:基于HTTP header,cookie,URL的转发就做不了。在Kubernetes中,L7的转发功能,集群外访问Service,这些功能是专门交给Ingress的。Kubernetes Ingress 何谓Ingress?从字面意思解读,就是“入站流量”。Kubernetes的Ingress资源对象是指授权入站连接到达集群内服务的规则集合。具体含义看下面这个例子便一目了然:通常情况下,Service和Pod仅可在集群内部网络中通过IP地址访问。所有到达边界路由的流量或被丢弃或被转发到其他地方。Ingress就是在边界路由处开个口子,放你进来。因此,Ingress是建立在Service之上的L7访问入口,它支持通过URL的方式将Service暴露到Kubernetes集群外;支持自定义Service的访问策略;提供按域名访问的虚拟主机功能;支持TLS通信。 在上面这个例子,Ingress就可以基于客户端请求的URL来做流量分发,转发给不同的Service后端。 我们来看下Ingress资源对象的API定义:apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress spec: tls: - secretName: testsecret backend: serviceName: testsvc servicePort: 80 把上面这个Ingress对象创建起来后,kubectl get一把,会看到:其中,ADDRESS即Ingress的访问入口地址,由Ingress Controller分配,一般是Ingress的底层实现LB的IP地址,例如:Ingress,GCE LB,F5等;BACKEND是Ingress对接的后端Kubernetes Service IP + Port;RULE是自定义的访问策略,主要是基于URL的转发策略,若为空,则访问ADDRESS的所有流量都转发给BACKEND。 下面给出一个Ingress的rules不为空的例子。apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test spec: rules: - host: foo.bar.com http: paths: - path: /foo backend: serviceName: s1 servicePort: 80 - path: /bar backend: serviceName: s2 servicePort: 80 这个例子和上面那个最明显的区别在于,rules定义了path分别为/foo和/bar的分发规则,分别转发给s1:80和s2:80。Kubectl get一把一目了然:需要注意的是,当底层LB准备就绪时,Ingress Controller把LB的IP填充到ADDRESS字段。而在我们的例子中,这个LB显然还未ready。 Ingress是一个非常“极客”和需要DIY的产物,Kubernetes只负责提供一个API定义,具体的Ingress Controller需要用户自己实现!官方倒是提供了Nginx和GCE的Ingress Controller示例供开发者参考。实现一个Ingress Controller的大致框架无非是,List/Watch Kubernetes的Service,Endpoints,Ingress对象,刷新外部LB的规则和配置。这还不算,如果想要通过域名访问Ingress?需要用户自己配置域名和Ingress IP的映射关系,比如:host文件,自己的DNS(不是kube-dns)。下文会讲到,“高冷”的kube-dns只会负责集群内的域名解析,集群外的一概不管。Kubernetes DNS Kubernetes DNS说,刚刚谁念叨起本宫了?一言以蔽之,Kubernetes的DNS,就是用来解析Kubernetes集群内的Pod和Service域名的,而且一般是供Pod内的进程使用的!血统高贵,一般不给外人使用。那可能会有人好奇问一句,Pod到底怎么使用Kubernetes DNS呢?原来,kubelet配置--cluster-dns把DNS的静态IP传递给每个容器。Kubernetes DNS一般通过插件方式部署到Kubernetes上,并为之绑定一个Service,而Service的Cluster IP往往是固定的。Kubernetes DNS目前有两个实现,分别是kube-dns和CoreDNS。对于Service,Kubernetes DNS服务器会生成两类DNS记录,分别是:A记录和SRV记录。而A记录又对普通Service和headless Service有所区别。普通Service的A记录是:{service name}.{service namespace}.svc.cluster.local -> Cluster IP的映射关系。后面域名后面一串子域名:svc.cluster.local是Kubelet通过--cluster-domain配置的伪域名。Headless Service的A记录是:{service name}.{service namespace}.svc.cluster.local -> 后端Pod IP列表的映射关系。至于SRV记录,则是按照一个约定俗称的规定:_{port name}._{port protocol}.{service name}.{service namespace}.svc.cluster.local –> Service Port 实现了对服务端口的查询。对于Pod,A记录是:{pod-ip}.{pod namespace}.pod.cluster.local -> Pod IP 如果Pod IP是1.2.3.4,上面的{pod-ip}即1-2-3-4。Pod的A记录其实没什么用,因为如果都知道Pod IP了,还用查DNS吗?如果在Pod Spec指定hostname和subdomain,那么Kubernetes DNS会额外生成Pod的A记录就是:{hostname}.{subdomain}.{pod namespace}.pod.cluster.local –> Pod IP 同样,后面那一串子域名pod.cluster.local是kubelet配置的伪域名。让我们看下kube-dns的架构吧。如上图所示,kube-dns是“三进程”架构。 kubedns:List/Watch Kubernetes Service和Endpoints变化。接入SkyDNS,在内存中维护DNS记录,是dnsmasq的上游。 dnsmasq:DNS配置工具,监听53端口,为集群提供DNS查询服务。提供DNS缓存,降低kubedns压力。 exechealthz:健康检查,检查kube-dns和dnsmasq的健康。 需要注意的是,dnsmasq是个C++写的一个小程序,有内存泄露的“老毛病”。虽然kube-dns血统纯正,而且早早地进入到Kubernetes的“后宫”,也早有“名分”,但近来CoreDNS却独得Kubernetes SIG Network的圣宠。CoreDNS是个DNS服务器,原生支持Kubernetes,而且居然还是一个CNCF的项目!与kube-dns的三进程架构不同,CoreDNS就一个进程,运维起来更加简单。而且采用Go语言编写,内存安全,高性能。值得称道的是,CoreDNS采用的是“插件链”架构,每个插件挂载一个DNS功能,保证了功能的灵活、易扩展。尽管资历不深,但却“集万千宠爱于一身”,自然是有两把刷子的。值得一提的是,以上性能测试数据是不带cache情况下取得的,明显要高于kube-dns。那么为什么建议使用CoreDNS呢?Kubernetes官方已经将CoreDNS扶正,成为了默认模式。除了性能好以外,还有什么其他优势吗?CoreDNS修复了kube-dns的一些“令人讨厌”的“老生常谈”的问题: dns#55 - Allow custom DNS entries for kube-dns dns#116 - Missing ‘A’ records for headless service with pods sharing hostname dns#131 - ExternalName not using stubDomains settings dns#167 - Enable round robin A/AAAA records dns#190 - kube-dns cannot run as non-root user dns#232 - Use pod’s name instead of pod’s hostname in DNS SRV records 同时,还有一些吸引人的特性: Zone transfers - list all records, or copy records to another server Namespace and label filtering - expose a limited set of services Adjustable TTL - adjust up/down default service record TTL Negative Caching - By default caches negative responses (e.g. NXDOMAIN) 其中,原生支持基于namespace隔离和过滤Service和Pod的DNS记录这一条特性,在多租户场景下格外有用。Network Policy Kubernetes默认情况下,底层网络是“全连通”的。但如果我们需要实现以下愿景:即,只允许访问default namespace的Label是app=web的Pod,default namespace的其他Pod都不允许外面访问。这个隔离需求在多租户的场景下十分普遍。Kubernetes的解决方案是Network Policy。 Network Policy说白了就是基于Pod源IP(所以Kubernetes网络不能随随便便做SNAT啊!)的访问控制列表,限制Pod的进/出流量,用白名单实现了一个访问控制列表(ACL)。Network Policy作为Pod网络隔离的一层抽象,允许使用Label Selector,namespace selector,端口,CIDR这四个维度限制Pod的流量进出。和Ingress一副德行的是,Kubernetes对Netowrk Policy还是只提供了API定义,不负责实现!一般情况下,Policy Controller是由网络插件提供的。支持Network Policy的网络插件有Calico,Cilium,Weave Net,Kube-router,Romana。需要注意的是,flannel不在这个名单之列,似乎又找到了一个不用flannel的理由? 让我们先来见识几个默认网络策略:注:{}代表允许所有,[]代表拒绝所有。 如果要拒绝所有流量进入呢?比如,场景长这样:那么Network Policy对象应该定义成: 如果要限制部分流量进入呢?比如,场景长这样: 那么Network Policy对象应该定义成: 如果只允许特定namespace的Pod流量进入呢?比如,场景长这样: 那么Network Policy对象应该定义成: 如果限制流量从指定端口进入呢?比如,场景长这样: 那么,Network Policy对象应该定义成: 未来工作 最后,畅想下Kubernetes网络后面的发展趋势。首先,kubenet会被废弃。Kubenet本来就是一个特定时期的产物,那时候CNI尚未成熟,让Kubernetes亲自去干底层网络这种“苦差事”,尽管Kubernetes是有一万个不愿意,但如果完全没有网络连通方案,又会让用户诟病“过于高冷”,“易用性差”,甚至会让那时的竞争对手docker swarm有机可图。因此Kubernetes写了一个简单的网络插件,即kubenet,一个bridge模型的容器连通性解决方案。但随着CNI强势崛起,以及kubenet并不支持网络策略等硬伤,社区已经没了继续维护kubenet的热情,因此废弃kubenet也就被提上了议程。IPv4/IPv6双栈支持。经过大半年社区开发者的齐心协力,Kubernetes总算支持了IPv6。但目前的支持比较初级,IPv6还不能和IPv4混用。IPv4/IPv6的双栈支持,势在必行。Pod Ready++。Pod有Ready和非Ready状态之分,为何还要搞出个Ready++这种“量子化”的模糊界限呢?原因在于,一个Pod能否真的对外提供服务,除了依赖容器内进程ready(我们会放置探针,检查进程状态)这类内部条件外,还依赖诸如:Ingress,Service,网络策略,外部LB等一系列外部因素。Pod Ready++的提出,就是为了将外部因素一齐纳入Pod状态的考量。多网络。也许是Kubernetes的“单Pod单IP”的网络模型过于深入人心了,以至于在实现过程中都谨遵这一“金科玉律”。但我们知道,网络的世界纷繁复杂,一块网卡怎么可能cover所有场景呢?据最简单的例子,一般我们会为一个IO密集型的作业配两块网络,一块网卡作为数据信道,另一块网卡则作为控制信道。从单网络到多网络的迁移,道路并不平坦,甚至是处处荆棘和沼泽,且不说网络插件,Service,DNS,Ingress等实现要大改,光API兼容性就让你头疼。好消息是经过整整两年的拉锯,社区Network Plumbing WG终于取得了阶段性成果,如不出意外的话,应该是CRD + 多网路插件的形式支持Kubernetes的多网络,保持Kubernetes原生API的稳定。支持多网络的CNI插件有几个,但真真落到生产的没几个,CNI-genie是其中一个有众多粉丝基础和经过生产环境检验的Kubernetes多网络插件,了解一下?最后,谈下Service Mesh。严格说来,Service Mesh并不在Kubernetes核心的范围之内。但是,在Kubernetes的帮助下,应用上云后,还面临着服务治理的难题。现在大多数云原生的应用都是微服务架构,微服务的注册,服务之间的相互调用关系,服务异常后的熔断、降级,调用链的跟踪、分析等待一系列现实问题摆在各机构面前。Service Mesh(服务网络)就是解决这类微服务发现和治理问题的一个概念。在我看来,Service Mesh之于微服务架构就像TCP协议之于web应用。我们大部分人写Web应用,关心的是RESTful,HTTP等上层协议,很少需要我们自己操心网络报文超时重传、分割组装、内容校验等底层细节。正是因为有了Service Mesh,企业在云原生和微服务架构的实践道路上只需根据自己业务做适当的微服务拆分即可,无需过多关注底层服务发现和治理的复杂度。而Istio的出现,使得有些“学院派”的Service Mesh概念真正得到了落地,并且提供了真正可供操作、非侵入式的方案,让诸如Spring Cloud,Dubbo这些“老古董”第一次有了被淘汰的危机感。 本文转自DockOne-Kubernetes网络一年发展动态与未来趋势
Kubernetes是一个强大的开源系统,最初由谷歌开发,用于在集群环境中管理容器化应用程序。它的目标是提供更好的方法来管理不同基础设施之间的相关分布式组件和服务。在本指南中,我们将讨论一些Kubernetes的基本概念。我们将讨论系统的体系结构、它解决的问题,以及它用来处理容器部署和扩展的模型。Kubernetes是什么? 在基本级别上,Kubernetes是一个用于跨机器集群运行和协调容器应用程序的系统。它是一个平台,旨在通过提供可预测性、可伸缩性和高可用性的方法,完全管理容器化应用程序和服务的生命周期。作为Kubernetes用户,您可以定义应用程序的运行方式,以及它们与其他应用程序或外部交互的方式。您可以向上或向下扩展服务、执行优雅的滚动更新、不同版本应用程序的流量切换,以测试特性或回滚问题部署。Kubernetes提供了接口和可组合的平台基元,允许您以高度的灵活性、强大性和可靠性定义和管理应用程序。Kubernetes架构 要理解Kubernetes是如何提供这些功能的,了解它是如何在高层次上设计和组织的是很有帮助的。Kubernetes可以被视为一个以层(layer)为单位的系统,每个更高层抽象出更低层次的复杂性。在其基础上,Kubernetes使用共享网络将单个物理或虚拟机组合到一个集群中,以便在每个服务器之间进行通信。这个集群是配置所有Kubernetes组件、功能和工作负载的物理平台。集群中的机器在Kubernetes生态系统中每个都有一个角色。一个服务器(或高可用部署中的一个小组)充当主服务器。这个服务器充当集群的网关和大脑,它为用户和客户机公开API,检查其他服务器的健康状况,决定如何最好地分割和分配工作(称为“调度”),以及协调其他组件之间的通信。主服务器充当与集群的主要接触点,并负责Kubernetes提供的大部分集中逻辑。集群中的其他机器被指定为节点:服务器负责使用本地和外部资源接受和运行工作负载。为了帮助实现隔离、管理和灵活性,Kubernetes在容器中运行应用程序和服务,因此每个节点都需要配备一个容器运行时(比如Docker或rkt)。节点从主服务器接收工作指令,并相应地创建或销毁容器,调整网络规则以实现适当的流量路由与转发操作。如上所述,应用程序和服务本身在容器内的集群上运行。底层组件确保应用程序的期望状态与集群的实际状态匹配。用户通过直接与主API服务器或与客户机和库通信与集群进行交互。要启动应用程序或服务,需要以JSON或YAML的形式提交声明性计划,定义要创建什么以及应该如何管理它。然后,主服务器获取计划,通过检查系统的需求和当前状态,确定如何在基础设施上运行它。这组根据指定计划运行的用户定义应用程序代表了Kubernetes的最后一层。主服务器组件 如上所述,主服务器充当Kubernetes集群的主要控制平面。它是管理员和用户的主要联系点,也为相对不复杂的工作节点提供了许多集群范围的系统。总体而言,主服务器上的组件协同工作来接受用户请求,确定调度工作负载容器,验证客户端和节点,调整群集范围网络以及管理扩展和运行状况检查职责的最佳方法。这些组件可以安装在一台机器上,也可以分布在多个服务器上。在本节中,我们将研究与主服务器相关的每个单独组件。etcd Kubernetes需要运行的基本组件之一是全局可用的配置存储。 由CoreOS团队开发的etcd项目是一个轻量级的分布式键值存储,可以配置为跨越多个节点。Kubernetes使用etcd来存储可以由集群中的每个节点访问的配置数据。这可用于服务发现,并可帮助组件根据最新信息配置或重新配置自身。 它还有助于通过领导者选举和分布式锁定等功能维护群集状态。通过提供简单的HTTP/JSON API,用于设置或检索值的界面非常简单。与控制平面中的大多数其他组件一样,etcd可以在单个主服务器上配置,或者在生产方案中,可以在多台计算机之间分配。唯一的要求是每个Kubernetes机器都可以访问网络。kube-apiserver 最重要的主服务之一是API服务器。 这是整个群集的主要管理点,因为它允许用户配置Kubernetes的工作负载和组织单位。 它还负责确保etcd存储和已部署容器的服务详细信息一致。 它充当各种组件之间的桥梁,以维护集群健康并传播信息和命令。API服务器实现RESTful接口,这意味着许多不同的工具和库可以很容易地与它通信。 名为kubectl的客户端可用作从本地计算机与Kubernetes集群交互的默认方法。kube控制器管理器 控制器管理器是一项具有许多职责的通用服务。它主要管理不同的控制器,用于管理集群状态,管理工作负载生命周期以及执行例行任务。例如,复制控制器可确保为Pod定义的副本数(相同副本)与当前在群集上部署的数量相匹配。这些操作的详细信息将写入etcd,其中控制器管理器通过API服务器监视更改。当看到更改时,控制器读取新信息并实现满足所需状态的过程。这可能涉及向上或向下扩展应用程序,调整端点等。kube调度 实际将工作负载分配给集群中特定节点的过程是调度程序。此服务读取工作负载的操作要求,分析当前的基础结构环境,并将工作放在可接受的节点上。调度程序负责跟踪每台主机上的可用容量,以确保未调度超出可用资源的工作负载。调度程序必须知道总容量以及已分配给每台服务器上现有工作负载的资源。云控制器管理器 Kubernetes可以部署在许多不同的环境中,并且可以与各种基础架构提供商进行交互,以了解和管理集群中的资源状态。尽管Kubernetes能够支持附加存储与负载均衡器等常规资源形式,但其仍需要将这些形式与由不同云服务供应商提供的实际资源映射起来。云控制器管理器充当粘合剂,允许Kubernetes使提供者具有不同的功能,特性和API,同时在内部维护相对通用的构造。这允许Kubernetes根据从云提供商收集的信息更新其状态信息,在系统中需要更改时调整云资源,以及创建和使用其他云服务以满足提交到群集的工作要求。节点服务器组件 在Kubernetes中,通过运行容器执行工作的服务器称为节点。 节点服务器有一些要求,这些要求是与主组件通信,配置容器网络以及运行分配给它们的实际工作负载所必需的。一个容器运行时 每个节点必须具有的第一个组件是容器运行时。通常,通过安装和运行Docker可以满足此要求,但也可以使用rkt和runC等替代方案。容器运行时负责启动和管理容器,应用程序封装在相对隔离但轻量级的操作环境中。集群上的每个工作单元在其基本级别上实现为必须部署的一个或多个容器。每个节点上的容器运行时是最终运行提交到集群的工作负载中定义的容器的组件。kubelet 每个节点与群集组的主要联系点是一个名为kubelet的小型服务。该服务负责向控制平面服务中继信息,以及与etcd存储交互以读取配置细节或写入新值。kubelet服务与主组件通信以向集群进行身份验证并接收命令和工作。以清单的形式接收工作,该清单定义工作量和操作参数。然后,kubelet进程负责维护节点服务器上的工作状态。它控制容器运行时根据需要启动或销毁容器。kube-代理 为了管理单个主机子网并使服务可用于其他组件,在每个节点服务器上运行一个名为kube-proxy的小型代理服务。此过程将请求转发到正确的容器,可以进行原始负载平衡,并且通常负责确保网络环境是可预测和可访问的,但在适当时隔离。Kubernetes对象和工作量 虽然容器是用于部署应用程序的底层机制,但Kubernetes在容器接口上使用了额外的抽象层来提供扩展,弹性和生命周期管理功能。用户不是直接管理容器,而是定义由Kubernetes对象模型提供的各种基元组成的实例并与之交互。我们将介绍可用于定义下面这些工作负载的不同类型的对象。Pod Pod是Kubernetes处理的最基本单位。容器本身未分配给主机。相反,一个或多个紧密耦合的容器被封装在称为Pod的对象中。Pod通常表示应作为单个应用程序控制的一个或多个容器。Pod由密集操作的容器组成,共享生命周期,并且应始终安排在同一节点上。它们完全作为一个单元进行管理,并共享其环境,数量和IP空间。尽管它们是容器化的实现,但您通常应该将Pod视为一个单一的整体应用程序,以便最好地概念化集群如何管理Pod的资源和调度。通常,Pod包含满足工作负载一般用途的主容器,以及可选的一些帮助密切相关任务的帮助容器。这些程序受益于在自己的容器中运行和管理,但与主应用程序紧密相关。例如,当外部存储库中检测到更改时,Pod可能有一个运行主应用程序服务器的容器和一个帮助容器将文件下拉到共享文件系统。通常不鼓励在Pod级别进行水平缩放,因为还有其他更高级别的对象更适合该任务。通常,用户不应自行管理Pod,因为它们不提供应用程序中通常需要的某些功能(如复杂的生命周期管理和扩展)。相反,鼓励用户使用更高级别的对象,这些对象使用Pod或Pod模板作为基本组件,但实现其他功能。 复制控制器和复制集 通常,在与Kubernetes合作时,不是使用单个Pod,而是管理相同,复制的Pod组。这些是从Pod模板创建的,可以通过称为复制控制器和复制集的控制器进行水平扩展。复制控制器是一个对象,它定义Pod模板和控制参数,以通过增加或减少运行副本的数量来水平扩展Pod的相同副本。这是在Kubernetes中本地分配负载和增加可用性的简单方法。复制控制器知道如何根据需要创建新的Pod,因为复制控制器配置中嵌入了与Pod定义非常相似的模板。复制控制器负责确保群集中部署的Pod数量与其配置中的Pod数量相匹配。如果Pod或底层主机出现故障,控制器将启动新的Pod以进行补偿。如果控制器配置中的副本数量发生更改,控制器将启动或终止容器以匹配所需的数字。复制控制器还可以执行滚动更新,将一组Pod逐个滚动到新版本,从而最大限度地降低对应用程序可用性的影响。复制集是复制控制器设计的迭代,在控制器识别其要管理的Pod方面具有更大的灵活性。复制集开始取代复制控制器,因为它们具有更强的副本选择功能,但它们无法进行滚动更新以将后端循环到新版本,如复制控制器。相反,复制集旨在用于提供该功能的其他更高级别单元内部。与Pod一样,复制控制器和复制集很少是您直接使用的单元。虽然它们基于Pod设计以增加水平扩展和可靠性保证,但它们缺少一些在更复杂的对象中发现的细粒度生命周期管理功能。Deployment Deployment是直接创建和管理的最常见工作负载之一。Deployment使用复制集作为构建块,为整个体系添加灵活的生命周期管理功能。虽然使用复制集构建的部署可能会复制复制控制器提供的功能,但Deployment解决了滚动更新实现中存在的许多难点。使用复制控制器更新应用程序时,用户需要为将替换当前控制器的新复制控制器提交计划。使用复制控制器时,跟踪历史记录,在更新期间从网络故障中恢复以及回滚不良更改等任务要么很困难,要么由用户负责。Deployment是一个高级对象,旨在简化复制Pod的生命周期管理。通过更改配置可以轻松修改部署,Kubernetes将调整副本集,管理不同应用程序版本之间的转换,并可选择自动维护事件历史记录和撤消功能。由于这些功能,Deployment可能是您最常使用的Kubernetes对象类型。有状态集 有状态集是专门的Pod控制器,提供排序和唯一性保证。首先,当您有与部署顺序,持久数据或稳定网络相关的特殊要求时,这些用于进行更细粒度的控制。例如,有状态集通常与面向数据的应用程序相关联,如数据库,即使重新安排到新节点,也需要访问相同的卷。有状态集通过为每个Pod创建唯一的,基于数字的名称来提供稳定的网络标识符,即使将Pod移动到另一个节点也会持久存在。同样,当需要重新安排时,可以使用Pod传输持久存储卷。即使在删除Pod后,卷仍然存在,以防止意外数据丢失。在部署或调整比例时,有状态集根据其名称中的编号标识符执行操作。这提供了更高的可预测性和对执行顺序的控制,这在某些情况下很有用。守护进程集 守护程序集是另一种特殊形式的Pod控制器,它在集群中的每个节点上运行Pod的副本(如果指定,则为子集)。在部署有助于执行维护并为节点本身提供服务的Pod时,这通常很有用。例如,收集和转发日志,聚合度量以及运行增加节点本身功能的服务是守护进程集的流行候选者。由于守护程序集通常提供基本服务并且在整个机群中都需要,因此它们可以绕过Pod调度限制,从而阻止其他控制器将Pod分配给某些主机。例如,由于其独特的职责,主服务器经常被配置为不可用于正常的Pod调度,但是守护进程集能够逐个Pod地覆盖限制以确保基本服务正在运行。Job和Cron Jobs 到目前为止,我们所描述的工作负载都假定为长期运行,类似服务的生命周期。Kubernetes使用称为Job的工作负载来提供更多基于任务的工作流,其中运行的容器在完成工作后的一段时间后会成功退出。如果您需要执行一次性或批量处理而不是运行连续服务,则Job非常有用。以jobs为基础的是cron jobs。与Linux上的传统cron守护程序和按计划执行脚本的类Unix系统一样,Kubernetes中的cron jobs提供了一个用于运行具有调度组件的jobs的接口。 Cron jobs可用于安排job在将来执行或定期重复执行。 Kubernetes cron jobs基本上是对经典cron行为的重新实现,使用集群作为平台而不是单个操作系统。其他Kubernetes组件 除了可以在群集上运行的工作负载之外,Kubernetes还提供了许多其他抽象,可帮助您管理应用程序,控制网络和启用持久性。我们将在这里讨论一些更常见的例子。Services 到目前为止,我们一直在使用传统的类似于Unix的术语“Services”:表示长期运行的进程,通常是网络连接的,能够响应请求。但是,在Kubernetes中,Services是一个组件,充当基本内部负载平衡器和Pod的大使。Services将执行相同功能的Pod的逻辑集合组合在一起,以将它们显示为单个实体。这允许您部署可以跟踪和路由到特定类型的所有后端容器的Services。内部消费者只需要了解Services提供的稳定端点。同时,Services抽象允许您根据需要扩展或替换后端工作单元。无论其路由到的Pod的变化如何,服务的IP地址都保持稳定。通过部署Services,您可以轻松获得可发现性并简化容器设计。每当您需要为一个或多个Pod提供对另一个应用程序或外部使用者的访问权限时,您应该配置一项服务。例如,如果您有一组运行Web服务器的Pod应该可以从Internet访问,则Services将提供必要的抽象。同样,如果您的Web服务器需要存储和检索数据,您可能希望配置内部服务以授予它们访问数据库窗格的权限。虽然默认情况下Services只能使用内部可路由的IP地址,但只要选择多种策略之一,就可以在群集外部使用它们。 NodePort配置通过在每个节点的外部网络接口上打开静态端口来工作。到外部端口的流量将使用内部群集IP服务自动路由到相应的Pod。或者,LoadBalancer服务类型使用云提供程序的Kubernetes负载均衡器集成创建外部负载均衡器以路由到服务。云控制器管理器将创建适当的资源并使用内部Services服务地址对其进行配置。存储卷和持久存储卷 在许多容器化环境中,可靠地共享数据并保证容器重启之间的可用性是一项挑战。容器运行时通常提供一些机制来将存储连接到容器,该容器持续超出容器的生命周期,但实现通常缺乏灵活性。为了解决这个问题,Kubernetes使用自己的卷抽象,允许数据由Pod中的所有容器共享,并在Pod终止之前保持可用。这意味着紧密耦合的Pod可以轻松共享文件而无需复杂的外部机制。 Pod中的容器故障不会影响对共享文件的访问。 Pod终止后,共享卷被破坏,因此它不是真正持久数据的好解决方案。持久卷是一种机制,用于抽象与Pod生命周期无关的更强大的存储。相反,它们允许管理员为群集配置存储资源,用户可以请求并声明正在运行的Pod。使用持久卷完成Pod后,卷的回收策略将确定是否保留卷,直到手动删除或立即删除数据。持久性数据可用于防止基于节点的故障,并分配比本地可用的存储量更大的存储量。Label和Annotation 还有一条与其它概念相关,但又独立于其外的Kubernetes组织抽象存在,就是labeling。Kubernetes中的Label是一个语义标签,可以附加到Kubernetes对象上,以将它们标记为组的一部分。然后,可以在针对管理或路由的不同实例进行选择时选择这些。例如,每个基于控制器的对象使用Label来标识它们应该操作的Pod。服务使用标签来理解他们应该将请求路由到的后端Pod。Label以简单的键值对的形式给出。每个单元可以有多个Label,但每个单元每个键只能有一个条目。通常,“name”键用作通用标识符,但您还可以通过开发阶段,公共可访问性,应用程序版本等其他标准对对象进行分类。Annotation是一种类似的机制,允许您将任意键值信息附加到对象。虽然Label应该用于将Pod与选择标准语义信息相匹配,但Annotation更自由,并且可以包含更少的结构化数据。通常,Annotation是一种向对象添加丰富元数据的方法,这对于选择目的没有帮助。结论 Kubernetes是一个令人兴奋的项目,它允许用户在高度抽象的平台上运行可伸缩的、高可用的容器化工作负载。虽然Kubernetes的体系结构和内部组件集乍一看可能令人生畏,但它们的功能、灵活性和健壮的特性集在开源领域是无与伦比的。通过了解基本构建块是如何组合在一起的,您可以开始设计系统,充分利用平台的功能来按比例运行和管理工作负载。 本文转自DockOne-简单介绍Kubernetes
之前,我们基本都是单体Web应用程序:大型的代码库,随着新的功能和特性不断发展,最后它们都会变成巨大的,缓慢移动的,难以管理的巨人。 现在,越来越多的开发人员,架构师和DevOps专家认为,使用微服务比使用大型单体应用更好。 通常,使用基于微服务的体系结构意味着将你的单体应用分成至少两个应用程序:前端应用程序和后端应用程序(API)。在决定使用微服务之后,出现了一个问题:在什么环境下运行微服务更好? 我应该选择什么来使我的服务稳定,易于管理和部署?简短的回答是:使用Docker! 在本文中,我将介绍容器,解释Kubernetes,并教你如何使用CircleCI将应用程序容器化和部署到Kubernetes集群。Docker?什么是Docker? Docker是一款旨在让DevOps(和你的生活)更轻松的工具。使用Docker,开发人员可以在容器中创建,部署和运行应用程序。容器允许开发人员使用所需的所有部件(例如库和其他依赖项)打包应用程序,并将其作为一个包发布出去。使用容器,开发人员可以轻松将镜像(重新)部署到任何操作系统。 只需安装Docker,执行命令,你的应用程序即可启动并运行。哦,不要担心主机操作系统中新版本库的任何不一致。此外,您可以在同一主机上启动很多容器——不管是相同的应用程序还是其他应用程序,都没关系。 看起来Docker是一个很棒的工具。但是我应该如何以及在何处启动容器?运行容器的方式有很多选择: AWS Elastic Container Service(AWS Fargate或具有水平和垂直自动伸缩的预留实例); Azure或Google Cloud中具有预定义Docker镜像的云实例(包含模板,实例组和自动缩放); 在你自己的服务器上; 当然还有Kubernetes!Kubernetes是2014年由Google工程师专门为虚拟化和容器创建的。 Kubernetes?那是什么? Kubernetes是一个开源系统,允许你运行容器,管理容器,自动化部署,扩展部署,创建和配置Ingress,部署无状态或有状态应用程序以及许多其他内容。基本上,你可以启动一个或多个实例来安装Kubernetes,将其作为Kubernetes集群进行操作。然后获取Kubernetes集群的API端点,配置kubectl(管理Kubernetes集群的工具),Kubernetes即可投入使用。那我为什么要用Kubernetes呢? 使用Kubernetes,你可以最大限度地利用计算资源。 通过Kubernetes,你将成为你的船(基础设施)的船长,Kubernetes将为你扬帆。 使用Kubernetes,你的服务将是高可用的。最重要的是,通过Kubernetes,你将节省大量资金。看起来很有前途,特别是如果它会省钱!让我们来谈谈它!Kubernetes日复一日地更加受欢迎。让我们更深入地研究一下这幕后的内容。译者注:上图有个小错误,kubectl写成了kubecti。 Kubernetes是整个系统的名称,但与你的汽车一样,有许多小部件可以完美地彼此协同工作以使Kubernetes发挥其各种作用。让我们来了解它们分别是什么。主节点(Master Node)——整个Kubernetes集群的控制面板。主节点的组件可以在群集中的任何节点上运行。关键组成部分是: API Server:所有REST命令的入口点,是用户可以访问的主节点的唯一组件。 Datastore:Kubernetes群集使用的强大,一致且高可用的键值存储。 Scheduler:监视新创建的Pod并将它们分配给节点。Pod和Services部署到节点上主要由Scheduler来控制。 Controller manager:管理着处理集群中日常任务的所有控制器。 Worker Node:主要的节点代理,也称为minion(旧版本Kubernetes的叫法)。Pod在这里运行。工作节点包含所有必要的服务,这些服务包括管理容器之间的网络,与主节点通信以及将资源分配给已调度容器等。 Docker:运行在每个工作节点上,用来下载镜像和启动容器。 Kubelet:监视Pod的状态并确保容器已启动并运行。它还与数据存储通信,获取有关服务的信息并记录新创建的服务的详细信息。 Kube-proxy:单个工作节点上的服务的网络代理和负载均衡。它负责流量路由。 Kubectl:一个CLI工具,供用户与Kubernetes API Server进行通信。 那什么是Pod和Services? Pod是Kubernetes集群中最小的单元,就像一座巨大建筑物墙上的一块砖。 Pod是一组需要一起运行并且可以共享资源的容器(Linux名称空间,cgroups,IP地址)。Pod的生命周期是非永久性的。Service是在多个Pod之上的抽象,通常需要在它上面有层代理,以便其他服务通过虚拟IP地址与其通信。一个简单部署例子 我将使用简单的Ruby on Rails应用程序和GKE作为运行Kubernetes的平台。实际上,你可以在AWS或Azure中使用Kubernetes,甚至可以在你自己的硬件中创建集群,或使用你minikube在本地运行Kubernetes,所有的选项你都可以在这个页面Setup - Kubernetes上找到。 这个app的源码你可以在GitHub - d-kononov/simple-rails-app-in-k8s里找到。要创建新的Rails应用程序,请执行:rails new blog 在config/database.yml文件中配置生产MySQL连接:production: adapter: mysql2 encoding: utf8 pool: 5 port: 3306 database: <%= ENV['DATABASE_NAME'] %> host: 127.0.0.1 username: <%= ENV['DATABASE_USERNAME'] %> password: <%= ENV['DATABASE_PASSWORD'] %> 创建Article model、controller、views和migration,请执行:rails g scaffold Article title:string description:text 添加Gems到Gemfile:gem 'mysql2', '< 0.6.0', '>= 0.4.4' gem 'health_check' 创建Docker镜像,请下载Dockerfile,并执行:docker build -t REPO_NAME/IMAGE_NAME:TAG . && docker push REPO_NAME/IMAGE_NAME:TAG 是时候创建一个Kubernetes集群了。打开GKE页面并创建Kubernetes集群。创建集群后,单击“连接”按钮并复制命令——确保你已安装和配置了gCloud CLI工具(如何)和kubectl。在PC上执行复制的命令并检查与Kubernetes集群的连接;执行kubectl cluster-info。该应用程序已准备好部署到Kubernetes群集。让我们创建一个MySQL数据库。在gCloud控制台中打开SQL页面,为应用程序创建一个MySQL数据库实例。实例准备就绪后,创建用户和数据库并复制实例连接名称。此外,我们需要在API和服务页面中创建一个service account密钥,以便从sidecar容器访问MySQL数据库。您可以在此处找到有关该流程的更多信息。 将下载的文件重命名为service-account.json。 我们稍后会用到这个文件。我们准备将我们的应用程序部署到Kubernetes,但首先,我们应该为我们的应用程序创建secret——在Kubernetes中创建用于存储敏感数据的secret对象。上传之前下载的service-account.json文件:kubectl create secret generic mysql-instance-credentials \ --from-file=credentials.json=service-account.json 为应用程序创建secrets:kubectl create secret generic simple-app-secrets \ --from-literal=username=$MYSQL_PASSWORD \ --from-literal=password=$MYSQL_PASSWORD \ --from-literal=database-name=$MYSQL_DB_NAME \ --from-literal=secretkey=$SECRET_RAILS_KEY 请不要忘记替换必要的配置,或者记得正确地设置环境变量。在创建deployment之前,让我们看一下deployment.yaml。我把三个文件连成一个;第一部分是一个Service,它将公开端口80并转发到端口80和到3000的所有连接。该Service有一个selector,Service通过它知道哪个Pod应该转发连接。该文件的下一部分是Deployment,它描述了将在Pod中启动的部署策略容器,环境变量,资源,探针,每个容器的mounts以及其他信息。最后一部分是Horizontal Pod Autoscaler。HPA有一个非常简单的配置。请记住,如果您未在部署部分中为容器设置资源,则HPA将无法运行。您可以在GKE编辑页面中为Kubernetes集群配置Vertical Autoscaler。它也有一个非常简单的配置。是时候将它发布到GKE集群了!首先,我们通过rake-tasks-job.yaml进行部署。 执行:kubectl apply -f rake-tasks-job.yaml 这个Job对于CI/CD很有用。kubectl apply -f deployment.yaml 上面这条命令用来创建Service、Deployment和HPA。然后通过kubectl get pods -w命令来检查你的Pod:NAME READY STATUS RESTARTS AGE sample-799bf9fd9c-86cqf 2/2 Running 0 1m sample-799bf9fd9c-887vv 2/2 Running 0 1m sample-799bf9fd9c-pkscp 2/2 Running 0 1m 现在让我们为应用创建一个Ingress: 创建一个静态IP:gcloud compute addresses create sample-ip --global 创建Ingress:kubectl apply -f ingress.yaml 检查Ingress是否创建成功和查看它的IP地址:kubectl get ingress -w 为你的应用创建域名或者子域名 CI/CD 让我们使用CircleCI创建一个CI/CD管道。实际上,使用CircleCI创建CI/CD管道很容易,但请记住,快而脏的未经过测试的全自动部署过程仅适用于小型项目,但请不要在严肃的项目上这样做。如果任何新代码在生产中出现问题,那么你将会损失巨大。这就是为什么你应该考虑设计一个强大的部署过程,在完全推出之前启动canary任务,在canary启动后检查日志中的错误,等等。目前,我们有一个简单的小项目,所以让我们创建一个完全自动化,无测试的CI/CD部署过程。首先,您应该将CircleCI与您的代码仓库进行集成——你可以在此处找到具体内容。 然后我们应该创建一个包含CircleCI系统指令的配置文件。Config看起来很简单。要点是GitHub仓库中有两个分支:master和production。 主分支用于开发,用于新代码。当有人将新代码推送到主分支时,CircleCI启动主分支构建和测试代码的工作流。 生产分支用于将新版本部署到生产环境。生产分支的工作流程如下:推送新代码(如果从主分支到生产开放PR则更好)以触发新的构建和部署过程;在构建过程中,CircleCI创建新的Docker镜像,将其推送到GCR并创建新的部署;如果失败,CircleCI将触发回滚过程。 在运行任何构建之前,您应该在CircleCI中配置项目。在API和GCloud中的Service页面中创建一个新的service account,具有以下角色:完全访问GCR和GKE,打开下载的JSON文件并复制内容,然后在CircleCI的项目设置中创建一个新的环境变量GCLOUD_SERVICE_KEY并将service account文件的内容粘贴为值。 此外,您需要创建下一个env vars:GOOGLE_PROJECT_ID(可以在GCloud控制台主页上找到它),GOOGLE_COMPUTE_ZONE(GKE集群的区域)和GOOGLE_CLUSTER_NAME(GKE集群名称)。CircleCI的最后一步(部署)如下所示:kubectl patch deployment sample -p '{"spec":{"template":{"spec":{"containers":[{"name":"sample","image":"gcr.io/test-d6bf8/simple:'"$CIRCLE_SHA1"'"} ] } } } }' if ! kubectl rollout status deploy/sample; then echo "DEPLOY FAILED, ROLLING BACK TO PREVIOUS" kubectl rollout undo deploy/sample # Deploy failed -> notify slack else echo "Deploy succeeded, current version: ${CIRCLE_SHA1}" # Deploy succeeded -> notify slack fi deployment.extensions/sample patched Waiting for deployment "sample" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "sample" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "sample" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "sample" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "sample" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "sample" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "sample" rollout to finish: 2 of 3 updated replicas are available... Waiting for deployment "sample" rollout to finish: 2 of 3 updated replicas are available... deployment "sample" successfully rolled out Deploy succeeded, current version: 512eabb11c463c5431a1af4ed0b9ebd23597edd9 总结 看起来创建新的Kubernetes集群的过程并不那么难!CI/CD自动部署过程也非常棒!是!Kubernetes太棒了!使用Kubernetes,你的系统将更加稳定,易于管理,并使你成为系统的“船长”。更不用说,Kubernetes对系统施加了一些魔法,并为您的营销提供了+100分!现在你已经掌握了基础知识,可以进一步将其转换为更高级的配置。我计划在以后的文章中介绍更多内容,但与此同时,有一个挑战:使用位于集群内的有状态数据库(包括用于备份的sidecar Pod)为应用程序创建一个强大的Kubernetes集群,在其中安装Jenkins用于CI/CD管道,让Jenkins使用Pod作为构建的slave。使用certmanager为Ingress添加/获取SSL证书。使用Stackdriver为应用程序创建监控和报警系统。Kubernetes非常棒,因为它很容易扩展,没有供应商锁定,并且,因为你为实例付费,所以可以省钱。然而,不是每个人都是Kubernetes专家或者有时间建立一个新的集群。这里有一个可选方案:关于如何构建有效的初始部署管道并执行更少的操作任务,我的同事Toptaler Amin Shah Gilani使用Heroku,GitLab CI以及他能想到的其他自动化部署工具做了一套教程CI/CD:如何部署有效的初始化部署管道。 本文转自DockOne-Kubernetes部署实操教程
操作起来要简单快速,既要高效又要省钱,这样的Kubernetes集群怎么搭? Kubernetes是我主要学习的主题之一。我知道不光是我,还有一定数量的人愿意在工作之余进一步使用和研究它。本文是介绍关于如何创建一个高效的Kubernetes集群,用于在Scaleway上使用Terraform和Rancher 2.x的开发目的。我假设你已经知道了(或者至少听过): Kubernetes Terraform Rancher Scaleway Cloudflare 概 述下图的概要描述了本文尝试实现的内容:部署工作大部分将由Terraform自动完成。我们将创建一个Rancher服务器,在上面添加Rancher代理来创建和管理Kubernetes集群。域解析也将由Terraform自动配置到Cloudflare。所有的服务器都将部署到Scaleway上,而且它们非常的便宜(在撰写本文时Start1-S服务器只花费€3.99/mo和€0.008/小时!) 为什么选择Rancher?如果你想从头开始学习Kubernetes成为一名kubectl魔术师,这个出发点不错,我不反对。不过在我看来,仅仅是学习Kuberenetes是不够的,更重要的是你想用它做什么,你想实现什么。比如,我经常开发微服务和功能来进行测试(特别是webhook集成),并在Kubernetes上运行它。当然,在管理Kubernetes方面kubectl起到了很大的帮助,但是当我想检查某些服务的日志或创建一些secrets时,我不得不使用kubectl输入大量命令创建出大量的清单,这些常让我感到疲惫。有时候我只是想简单地部署一下资源,并不使用清单。我知道有很多工具(如stern、ksonnet等等)可以帮助实现这些工作流,但是我从Rancher 1.x开始就是忠实用户了,知道他们的UI可以让工作变得更简单直观。这也是我为什么在这个工具集中包含Rancher的原因。让我们开始吧!话不多说,我们开始部署一些资源吧。这儿有一些准备工作: 准备一个Scaleway账号 准备一个Cloudflare账号 在Cloudflare中设置至少1个zone(你可以得到一个免费的服务域比如Freenom) 克隆kenfdev/rancher-scaleway仓库 安装terraform 安装kubectl 我不会详细解释这些内容,因为如果都写的话这篇文章得爆炸了。你可以自己搜索找到相关的资源。在这里有一个重要的提示。我们将在Scaleway上部署服务器,这些都不是免费的。所以事先要确认这一点,它会花费一些钱(不过Scaleway非常便宜)。我不会对这些成本承担任何责任,所以如果你完成了,一定要破坏掉它们。从Scaleway获取信息我们将在Scaleway上自动部署服务器,为此,我们需要有一个token。在Scaleway中转到Credentials页面并点击Create new token。把Secret key记录下来,因为我们之后会用到这个。同时,前往Account页面找到ORGANIZATION ID做好记录。到现在为止你已经完成了Scaleway的部分,Terraform会处理好其他事情。 从Cloudflare获取API token 我们需要为创建的Rancher服务器设置DNS,为了实现这一点,我们需要获得Cloudflare的API token。前往Cloudflare下的My Profile,可以在这里找到API Keys。如下图所示点击Global API Key的View按钮。现在记录下这个token,到这里收集信息的工作结束了。 为Rancher服务器准备Terraform资源现在我们已经有了需要收集的信息,那么下一步就是用Terraform来部署了。先去克隆kenfdev/rancher-scaleway仓库。首先,前往rancherserver目录,按照terraform.tfvars.sample创建一个tfvars文件,打开它:tfvars文件看起来像这样: 需要对这些内容做修改: scw_token – 你的Scaleway Secret Token scw_org – 你的Scaleway Organization ID admin_password – 你将在Rancher服务器使用的密码 rancher_server_url – Rancher服务器所使用的URL。Terraform会为你的cloudflare zone设置一个rancher子域名。所以如果你的zone名为example.com,那么terraform会创建一个记录链接到rancher.example.com。 cloudflare_email – 你Cloudflare中的Email cloudflare_token – 你Cloudflare的API token cloudflare_zone – 你的Cloudflare zone 你也可以根据你自己的需求修改其他变量。部署Rancher Server现在我们准备好了,用Terraform来进行部署吧!点击terraform apply你会看到像下面这样的显示:5个资源? “为什么是5个资源?我们刚刚不是只部署了1个服务器吗?”你可能会这么问,其实我们确实部署了不止一个资源,我们部署了: 1个服务器(Rancher服务器) 1个为cloud-init进程引导Rancher服务器的用户数据 1个安全组 1条附加到安全组来控制流量的规则 1条指向Rancher服务器的Cloudflare DNS记录 这样便添加了5个资源,现在点击yes。如果一切进行顺利,你应该能看到这样的信息:如果检查Scaleway的话: 看起来服务器是部署好了,那DNS如何呢? 它看起来也添加到了Cloudflare!现在我们用这个地址访问Rancher服务器: https://rancher.your.zone/如果你使用的是Chrome的话应该会看到警告提示这是因为你还没有给服务器设置合法的SSL证书。单击左下角的按钮,忽略警告并强制访问页面。 瞧!我们用1条命令部署了一个带有DNS的Rancher服务器!很酷吧!输入你保存在tfvars中设置的密码,应该能登陆了。 现在你有了一个正在工作的Rancher服务器!你可以在UI上开始为各个位置创建Kubernetes集群,不过本文不会介绍这些用例。取而代之的,我们将在这里使用Terraform,在Scaleway上创建Rancher代理,自动设置这些由Rancher服务器管理的集群。 为Rancher代理准备Terraform资源返回kenfdev/rancher-scaleway仓库。现在,前往rancheragent目录。同样复制一份terraform.tfvars.sample给terraform.tfvars。现在打开terraform.tfvars: 和Rancher服务器时一样填入凭证信息,这里列出一些要记住的变量: rancher_server_address – Rancher Serve的地址,它可能是一个IP或者域名,不过我建议你在之前的步骤中设置成域名 count_agent_all_nodes – 统计创建的拥有all roles(etcd, controlplane, worker)的节点数。如果你只是取体验一下Kubernetes集群,那么有一个就足够了。 count_agent_etcd_nodes – 统计创建的拥有etcd role的节点数 count_agent_controlplane – 统计创建的拥有controlplane role的节点数 count_agent_worker_nodes – 统计创建的拥有worker role的节点数 部署Rancher代理现在再一次点击terraform apply,将rancher代理部署到Scaleway上。选择yes,服务器就部署好了。几分钟后你可以看到资源部署完毕: 同时,在Rancher服务器的UI下,你可以看到一个新的集群注册好了。 再过一会,你可以看到Kubernetes集群部署完毕(Active)! 这也很酷对吧?我们可以在Internet(Scaleway)上创建自己的Kubernetes集群,而这仅仅通过几行命令! 测试Ingress控制器现在我们可以使用Kubernetes集群了,让我们看看Nginx Ingress控制器是否按照预期工作。在rancheragent目录中,打开dns.tf 文件并添加下面的信息:这将创建一个Cloudflare记录来指向具有all roles的Rancher代理节点。点击terraform apply,在Cloudflare设置DNS。 现在我们访问http://default.your.zone ,我们还没有在集群上配置任何Ingress,因此需要访问默认后端。嘿!我们可以看到Ingress控制器正按我们期望的运作着。 通过kubectl访问集群最后,我们来试试能不能通过kubectl访问这个集群。拉取kubeconfig十分容易。只要在集群层找到按钮即可:把config文件复制或者下载到model上: 用kubectl和KUBECONFIG获取一些信息 成功获得了关于节点的信息,pods又是如何呢? 只用了简单的kubectl就能轻松获得信息,你也可以通过Rancher UI或者kubectl部署资源。 清 理如果你想继续使用集群,你可以从这时起继续使用,不过我下面要介绍的是如果你已经使用完毕,该如何把这一切清理掉。破坏掉Rancher代理和Cloudflare资源执行下面的命令:销毁Rancher服务器和Cloudflare资源 确保Scaleway上没有任何资源了(只有默认的安全组)。否则你还要为这些资源付费。 本文转自DockOne-如何创建高效、经济的Kubernetes集群
在过去几年,整个行业逐渐转向开发更小更专业的程序。 越来越多的企业把原先庞大稳定的巨型系统拆分成解耦的独立的组件。 这个方向是正确的。微型服务有以下几个优点: 快速部署:因为你可以快速的创建并且发布一个小型服务 更容易迭代:因为可以独立的为每个服务添加新功能 更加灵活:就算单个服务(组件)不能使用,其他的服务仍能正常运行。 从产品和开发的角度来说,微服务更完美。那么这种文化转变是怎样影响基础设施的呢?管理大规模的基础设施 当你只需要管理几个应用程序时,一切都很简单,你甚至可以用手指算出它们的个数,并且你有足够的时间专注与应用的发布和运维。 在大公司中,管理数百个应用虽然要求很高,但仍是可以做到的。将会有几个团队专门用于开发,打包,以及发布应用。另一方面,开发微服务将会带来新的挑战。对于每个应用,你可能会在重构的时候,将一个应用拆分成四个组件(服务),那么你至少需要四倍的时间去开发,打包,发布这些微服务。我们常常看到,一个小型服务由多种组件构成,例如前端应用,后台API,一个授权服务器,一个管理应用程序等等。实际上,当你开发了多个微服务,并且这些服务相互交互,你将会看到你的基础架构上部署了大量的组件(服务)。 事情变的越来越复杂。 你可能需要在计算资源上浪费很多钱 大部分服务被部署到虚拟机上,如Amazon EC2,Digital Ocean Droplets或Azure Virtual Machines。每个虚拟机都带有一个操作系统,这个系统会占用部分内存以及cpu资源。当你在Digital Ocean上创建1GB内存和1个vCPU Droplet时,除去操作系统的开销后,最终可以使用700MB内存和0.8 vCPU。换句话说,每五个虚拟机的操作系统的开销加起来就是一个完整的虚拟机。 你付了五个的钱,但只能使用四个。即便是在物理机上,你也无法绕开它。你仍然需要用操作系统运行服务。然而,操作系统上的浪费只是冰山一角。你也浪费了大量的钱在资源利用上 您可能已经意识到,当您将服务分解为更小的组件时,每个组件都会有不同的资源需求。 某些组件(如数据处理和数据挖掘应用程序)是CPU密集型的。其他的(例如一些实时应用程序的服务器)相比于CPU,可能会使用更多的内存。Amazon Web Services以及其他的云服务提供商的确有一长串适用于各种场景的计算资源:通用的,CPU优化的,内存优化的,存储优化的以及GPU计算的。 你需要为你的服务认真挑选合适的虚拟机。理想情况下,这个虚拟机可以满足你的服务的内存消耗以及CPU使用。您是否正在使用Java编写的关键Web组件?也许您应该使用针对计算密集型工作负载优化的c5.4xlarge。越能满足要求,你就能更好的利用资源。但实际上,这并不常见。你应该使用c5.2xlarge还是c5.4xlarge?下一级别(8个vCPU和16GB内存)是否更合适?在80%的情况下选择几个足够好的计算配置并将其用于所有组件要容易得多。事实上,对于所有工作负载使用相同配置的虚拟机又有什么问题么?完全没问题,只要你愿意将每个组件包装成2GB内存和vCPU计算容量。即便你的应用只需要1GB内存。是的,你可以将来再优化。但是老实说,这就像在开车时更换轮胎一样——非常危险。你花了很多精力来调整系统最后意识到应用程序再次改变而你又得从头开始。最终,你会做一个明智的选择:选择小型,中型和大型配置文件的虚拟机,并将其用于所有工作负载。你知道你必须忍受浪费数百兆字节的RAM和大量的CPU周期。其实很多公司都在做同样的事情,忍受着类似的低效。 有些甚至只利用到分配资源的10%。您在亚马逊上的EC2实例中支付1000美元,您实际上只使用了100美元。这听起来不像是花费预算的最佳方式。你应该把你花在不用的资源的钱拿回来。但是为什么每个服务的需求如此不同呢?!有时候选择合适的工具甚至会弊大于利 当开发人员可以自由地使用正确的工具时,他们通常会疯狂。前端选择Node.js,后台API选用Spring Boot,使用Flask和Celery来处理后台作业,使用React.js来创建客户端,你懂的。整个基础设施就像一个主题公园,数百个程序在不同的运行时运行。为一项工作选用合适的技术的确带来了更快的迭代速度,但是管理更多的编程语言也带来了额外的负担。虽然你可以控制工具和编程语言的数量,但是实际上,这往往很困难。两个应用程序的共享相同的JVM运行时,但是它们可能依赖于两组不同的依赖项和库。也许一个依靠ImageMagick来调整图像大小。另一个依赖于诸如PhantomJS或ZeroMQ之类的二进制文件在其路径中可用。你需要把这些依赖与应用一起打包。最终你可能需要处理数十种看上去相似的配置,但其实上各有不同。你不能把基础设施当做以后才处理的事情。在开发应用程序时,你应该从一开始就考虑好依赖项并将它们打包。理想情况下,您应该将运行组件所需的所有依赖归档为单个包。不要在发布前还在寻找缺失的依赖。是的,说总是比做容易。或许不。从流行业借鉴集装箱的概念 信息技术行业并不是唯一遇到这个问题的行业。 将货物运往全球并且保证它们独立运输也十分困难。想象你需要存储上千个不同形状大小的盒子。并且你需要额外注意如何打包这些货物,避免它们在运送中丢失。货运公司想出一个很好的解决方案:集装箱 货运公司不运送单个货物,而是运送集装箱。你想要安全的运送你的货物么?只需要将它们放置于集装箱中即可。当集装箱到目的地时,你保证能收到你所有的货物。你也可以将这一准则运用到你的应用上去。你想安全的部署你的应用及其所有的依赖么?只需要将它们打包在Linux的容器之中即可。一个Linux容器就像是一个货物的集装箱,只是它包含的是所有的文件,二进制文件,以及所有需要运行你的程序所需的库。这听上去是不是很像虚拟机? 精简版的虚拟机 事实上,虚拟机很像容器。他们封装了应用以及应用的所有依赖,就像容器一样。但是,虚拟机启动很慢,而且体积大浪费资源。事实上,你需要事先分配一些固定的CPU和内存来运行你的程序。虚拟机必须仿真硬件,并附带操作系统这个累赘。而另一方面,Linux容器,仅仅只是在你主机上运行的进程。实际上,在同一个操作系统和服务器上,您可以在运行数十个容器。 尽管他们运行在同一个机器上,容器里的进程彼此并不可见。在容器里面运行的应用是完全隔离的,它无法区分虚拟机与容器的区别。真是个好消息!Linux容器就像是虚拟机,但是更加高效。那么这些容器是由什么组成的呢?Linux容器是一个独立进程 它的神奇之处来自Linux内核中的两个功能:控制组和命名空间。控制组是限制特定进程可以使用的CPU或内存的便捷方式。例如,您可以说您的组件应该只使用2GB内存和四个CPU内核中的一个。另一方面,命名空间负责隔离进程并限制它可以看到的内容。组件只能看到与其直接相关的网络数据包。它将无法看到流经网络适配器的所有网络数据包。控制组和命名空间是底层基元。随着时间的推移,开发人员创建了越来越多的抽象层,以便更容易地控制这些内核功能。最初的抽象之一是LXC,但最佳示例是2013年发布的Docker。Docker不仅抽象了上述内核功能,而且使用起来也很容易。运行Docker容器非常简单:docker run <my-container> 由于所有容器都实现了标准接口,因此您可以使用相同的命令运行其他任何容器:docker run mysql 就可以运行MySQL数据库。应用程序的可移植性以及创建和运行进程的标准接口,使变得容器越来越受欢迎。容器超棒! 你节省了运行几十个操作系统的钱 你将应用程序打包为便携式单元 你有大量的容器 听起来容器还没有解决所有问题。你需要一种管理容器的方式。管理大规模容器 当您有数百个(而不是数千个)容器时,您应该找到一种在同一服务器上运行多个容器的方法。而且你应该提前想好容器如何在多个服务器上扩展。因此,您可以跨多个节点分配负载,并防止单个故障可能导致整个服务崩溃。跟踪基础架构中每个容器的部署位置听起来很浪费时间。也许有一种自动化方法?如果您可以使用算法来决定放置这些容器的位置,该怎么办?也许聪明有效地打包容器可以以最大化服务器密度?甚至可以保留已部署容器及其主机的列表?事实证明,有人正好有同样的想法,并提出了一个解决方案。Kubernetes:强大的容器编排工具 Kubernetes最初由Google创建。谷歌当时正在使用一种类似于容器的技术,并且必须找到一种有效的方式来安排工作负载。他们不想保留并手动更新一长串容器和服务器列表。因此,他们决定编写一个可以自动分析资源利用率,安排和部署容器的平台。但这个平台一开始是闭源的。几个Google员工决定将该平台重写为开源工具。接下来就是我们所知的历史。那么什么是Kubernetes?您可以将Kubernetes视为调度程序。Kubernetes检查您的基础设施(物理机或云,公共或私有)并检测每台机器的CPU和内存。当您请求部署一个容器时,Kubernetes会识别容器的内存要求,并找到满足您请求的最佳服务器。您无法决定部署应用程序的具体位置。数据中心已经把这个步骤抽象出来。换句话说,Kubernetes将使用您的基础设施像玩俄罗斯方块一样玩转容器。Docker容器就是方块;服务器是板,Kubernetes就是玩家。使用Kubernetes打包你的基础设施,意味着花同样的钱你能得到更多的计算资源。 于是你的总花费会因此而减少。记得之前提到过说很多公司仅仅使用了被分配资源的10%么?Kubernetes将会拯救你。别急,还有更多惊喜。Kubernetes有一个通常被遗忘或被忽略的杀手级功能。Kubernetes作为数据中心的API层 你在Kubernetes所做的一切都是你的一个API调用。你需要部署一个容器吗?有一个REST端点。也许你想配置负载均衡器?不是问题。只需调用此API即可。你想配置存储吗?请发送POST请求到此URL。你在Kubernetes所做的一切都是调用API。你肯定会因此而感到兴奋: 你可以创建以编程方式与API交互的脚本和守护程序 API已版本化; 升级群集时,您可以继续使用旧API并逐步迁移; 你可以在任何云提供商或数据中心安装Kubernetes,您可以使用相同的API。 你可以把Kubernetes当作是基础架构之上的一层。由于这层是通用的,可以安装在任何地方,你可以随意迁移。亚马逊网络服务太贵了?没问题。您可以在Google Cloud Platform上安装Kubernetes并把您的工作负载迁移过去。或者也许你可以保留两者,因为高可用性策略总是会派上用场。但也许你不相信我。因为这件事听上去太美好了以致于不真实。让我演示给你看。使用Kubernetes节省您的云账单 Netlify是一个用于构建,部署和管理静态网站的平台。它有自己的CI管道,因此你对代码库中的代码进行更改时,您的网站都会重新构建。Netlify成功迁移到Kubernetes,用户数量增加了一倍,但成本仍维持不变。这真是个好消息!想象一下,节省50%的Google Cloud Platform花销!但Netlify不是唯一的一个受益者。Qbox——一家专注于托管弹性搜索的公司——设法每月在AWS账单上再次节省50%!在此过程中,他们还开源了他们在多云运维方面的实践。如果你仍然没有印象,你应该看看关于OpenAI的新闻。OpenAI是一家非营利性研究公司,专注于人工智能和机器学习。他们写了一个算法来让机器玩一个多人在线游戏Dota,就像任何人类玩家一样。但他们做了一些额外的操作,他们训练了一组机器一起玩。他们使用Kubernetes在云中扩展他们的机器学习模型。想知道他们的集群的细节?128000个vCPU那是大约16000台MacBook Pro。256 Nvidia Tesla P100这是2100 Teraflops 16位浮点性能。就像你运行525 PlayStation 4s一样。你能猜出每小时的费用吗?猜不到?128000 vCPU仅为1280美元/小时,256个Nvidia P100仅为400美元。考虑到赢得Dota锦标赛可以为您赢得数百万美元的奖金,这并不是很多。你还在等什么?准备好采用Kubernetes并节省您的云账单!最后的笔记 Kubernetes和容器将继续流行。在谷歌,微软,红帽,Pivotal,甲骨文,IBM等公司的支持下,你很难相信它不会流行起来。许多公司正在开始使用Kubernetes并加入这场革命。不只是创业公司和中小企业,而是像银行,金融机构和保险公司这样的大公司都在押注容器,而Kubernetes则是未来。甚至公司也将这项技术应用于物联网和嵌入式系统。现在还处于早期阶段,社区仍需时间成熟,但你应该密切关注这个领域的创新。 本文转自DockOne-什么是Kubernetes?科普文
Kubernetes现在似乎已经成了管理和部署基于微服务和容器的应用的事实标准了,而且我们也很容易理解。要知道,Kubernetes是由CNCF支持,目前是最大的开源社区。它是DevOps友好的,它提供了混合云的优势。大家为什么不喜爱它呢? 但在最近的一项调查中,69%的受访者表示虽然Kubernetes是他们使用容器架构的首选,但是部署和管理Kubernetes却没有那么轻松。尽管Kubernetes相当灵活,但在操作工作流程上还是比较复杂,对应用程序性能管理(APM)必须进行有效管理才能发挥出Kubernetes所承诺的好处来。 重新考虑下你的Kubernetes监控策略最近的一项CNCF调查显示,38%的受访者认为监控是其应用Kubernetes最大的的挑战之一,随着企业规模的增长,这个比例甚至达到了46%。那么,现代IT领导者如何在优化性能的同时又能简化Kubernetes监控,从而提高效率呢? 对于目前的Kubernetes监控手段而言,由于缺乏端到端可视性以及面临着容易出错的迁移,其实是存在不足的。以下就是监控Kubernetes时我们可能遇到的四个常见挑战以及如何解决这些挑战的建议。挑战1:缺乏端到端的可视性在Kubernetes传统监控中,最常见挑战之一就是缺乏对客户触点和分布式应用的端到端可视性。 结果,IT团队对最终用户体验以及应用程序性能如何影响业务的KPI毫不知情,也就无法知道哪些地方需要修复或者改进。 为了解决这个问题,使用基于正常性能的Kubernetes监控解决方案非常重要,并且,通过机器学习的强大功能,可以在出现问题时智能地向IT团队发出警报。挑战2:告警风暴虽然全面了解所有的应用问题看起来是个不错的选择,但是当多个问题同时出现时,它可能会迅速失控并且变成了工作的阻力。毕竟,您真的需要每次工作完成时或者在新的容器就位时被提醒一下吗? 如果没有对报警进行优先级分类,IT团队通常必须对每个问题的根本原因进行响应和归类- 这将导致糟糕的用户体验和收入损失。 强大的Kubernetes监控解决方案可以帮助您识别和解决确切的潜在问题,从代码行、单台设备、Kubernetes服务一直到单个容器。挑战3:故障排除应用停机的成本是十分惊人的 ,要知道,关键应用程序故障带来的损失可能高达每小时100万美元。时间就是金钱,IT团队在查找故障的根本原因的时候就不应该浪费时间。 我们所面临的问题是,现在大量监控工具缺乏在Kubernetes环境下进行自动化排障分析的能力,这就使故障排除成为耗时的噩梦,导致了高MTTR和减少停机时间。 为避免这种情况,请确保您的Kubernetes监控方案能够通过比较迁移前后的用户体验,提供对应用程序依赖性和验证迁移成功性的可视化能力。挑战4:迁移至Kubernetes的易错性将传统应用程序迁移到Kubernetes可能容易出错而且十分耗时。将现有的整体应用迁移到部署在Kubernetes上的微服务架构的这些公司缺乏对Kubernetes环境的可视化管理,是无法得知每个微服务或传统应用程序间是如何实时交互的。 借助通过统一平台提供集成化安装和统一监控的这种解决方案,IT团队就可以充分利用其现有的技能,流程和工具了。在Kubernetes上提供完美的应用性能使用Kubernetes在分布式多云环境中部署和运行应用程序的方式已经越来越流行,而且也没有放缓的迹象。但对于在Kubernetes上运行传统或微服务的应用厂家来说,传统监控方法是存在着明显的缺点的。 因此,各组织必须重新考虑他们在Kubernetes中的监控手段,用来简化复杂的企业工作流程,提高效率并提高生产力。通过对整个Kubernetes栈和Kubernetes编排应用的端到端的统一可视化,IT团队可以提供完美的应用体验,并确保他们的Kubernetes投资能够带来更好的收入。 本文转自DockOne-4个需要避免的常见Kubernetes监控陷阱
最近我们澄清了一些大家在进行Kubernetes实验的时候所见到的常见的误解。其中最大的一个误解就是:在生产环境中运行Kubernetes和开发测试环境并无两样。 答案:是不一样的。 Avi Network公司的联合创始人兼首席技术官Ranga Rajagopalan认为:“对于Kubernetes,容器和微服务来说,实验环境和生产环境有巨大的不同。简单的运行和安全、可靠的运行是不一样的”。 Ranga Rajagopalan的意见中有一个重要的论点:上述问题不仅仅只是存在于Kubernetes,同样也存在于容器和微服务。部署容器相对简单;而在生产环境运维和缩放容器(包括容器化的微服务)是问题复杂的原因。 容器和容器的编排工具通常都是成套出现的。New Stack公司之前进行了一项调查,调查发现当组织为了解决运维所面对的挑战,去寻找更强大的技术的时候,容器反过来推动了Kubernetes的普及。虽然也有其他的工具,Kubernetes还是快速成为了编排工具和选择的代名词。像Rajagopalan说的那样,在沙箱里运行Kubernetes和在生产环境中运行Kubernetes有巨大的不同。 通常IT专业人士和团队运维小规模的Kubernetes环境,当这个环境转向生产环境部署的时候,这些人将有很多东西需要面对和学习。“你肯定想在上生产环境之前扫清这些常见的误解,这里有IT领导者和团队需要了解的知识”。误区1:在开发测试环境运行Kubernetes能帮你全面了解运维的需求现实:在开发测试环境运行Kubernetes能帮你走一些捷径,你可以不必面对生产环境所带来的运营负载。 Wei Lien Dang, StackRox的产品副总裁认为“开发测试环境和生产环境的最大不同源自于运维和安全,在运维测试环境你根本不用在乎集群宕机。” Portworx的联合创始人兼CEO Murli Thirumale将开发测试环境和生产环境的不同与敏捷和可靠,高性能的敏捷做了类比。 “开发团队的目标是在开发和测试新应用和代码的时候实现应用的敏捷;而同时运维人员的目标是应用和数据的可靠性,可伸缩性,安全性和性能。后者需要一个强大的,企业级的,经过测试和验证的平台。” 自动化已经成为了在生产环境中采用Kubernetes(或者通俗所说的容器)的迫切需求。 Coda Global的架构师Ranjan Bhagitrathan认为“生产集群必须通过自动化部署。生产集群一定要具备可复制性,从而实现整体生产环境的一致性,同时可复制性也对灾难恢复有所帮助”。 Bhagitrathan同时也认为版本控制对生产环境运维至关重要,“对所有事情进行版本控制,比如服务部署的配置文件,策略等等,如果可能也包括实现基础架构即代码的代码命令。 这能确保你的环境是相同的。同时也要确保容器镜像有版本控制,不要只是用“最新镜像”这样的名字来个镜像打标签,这样很容易引起混乱”。误区2:你已经得到了可靠性和安全性现实:如果你只在非生产环境试验过Kubernetes。你可能没获得可靠性和安全性,或者目前没有。 好消息是你最终会做到,因为上生产环境之前,需要做规划和架构设计。 AquaSecurity的联合创始人兼 CTO Amir Jerbi认为“很显然,在生产环境,性能,可伸缩性,高可用性和安全性方面所面对的挑战更严峻。所以在架构阶段规划生产环境需求,并且把安全,伸缩控制,Helm charts集成在Kubernetes部署的定义中是极为重要的”。 Dang分享了一个测试开发环境如何导致过度自信的例子: “在测试开发环境中开放所有的网络端口是非常不错的,很可能所有的服务都能互相连接。Kubernetes默认设置所有网络连接开放。但这不是一个成熟生产环境所应有的设置,一旦步入生产阶段,停机和大规模攻击会给业务带来风险“。 “当工作转移到容器和微服务的时候,构建高可靠,高可用的系统是非常有价值的工作。编排工具帮你实现这部分工作,但这并不举手可得。安全性也是一样。” “我们要做很多事情来减少针对Kubernetes的攻击,通过加强网络策略来实施最低权限模型,并且限制服务只和需要的服务连接是非常关键的。” 在生产环境中,容器镜像的安全漏洞可以很快变的很严重,威胁可能是受限的或者根本不在本地。(whereas the threat may be limited or non-existent locally.) Bhagirathan说,“要注意你的容器镜像采用的基础镜像是什么,尽可能用受信任的官方镜像或者自己动手制作。用未知的镜像也许可以帮你很快的跑起服务,但也带来了安全问题。比如,你肯定不想你的Kubernetes系统给别人挖比特币做贡献。” 红帽公司的安全策略师Kirsten Newcomer鼓励人们通过10个层次来考虑容器安全——包括容器堆栈层(比如容器主机和注册),容器生命周期(API管理)。有关10层的细节信息以及如何应用在Kubernetes这样的容器编排工具,请参阅Newcomer的播客或者白皮书:https://www.redhat.com/en/enga ... yaAAA。误区3:编排工具让缩放变得易如反掌事实:虽然很多软件专业人士都认为Kubernetes这样的容器编排引擎对于容器扩展性来说是必不可少的,但是认为编排工具能立即让缩放变得简单是错误的,特别是当你第一次在生产环境中使用它时。 Sumo Logic公司的高级技术产品经理Frank Reno认为“自动缩放改变了一切,数据越来越大,你的监控系统需要根据数据量来扩展。在生产环境运行之前,Kubernetes的所有组件都不能被很好的了解。毕竟确保Kubernetes系统的健康运行,API服务器和其他控制组件能根据需求缩放而不是自动的。” 开发和测试环境让事情变得过于简单,而真正的环境需要这样,或那样的需求,并且需要一直维护。 “在开发测试环境可以非常简单跳过一些基础设置,比如确保你能用到特定的资源,并且限制请求,”Reno说,“如果在生产环境不这样设置,你的好日子就算到头了。” 向上或向下扩展群集是一个很好的例子,当你在本地进行实验时,它可能看起来很简单,但在生产环境中缩放变得明显更具挑战性。 “生产集群,与开发或模拟集群相反,在扩展方面会遇到更多的痛点,”WhiteSource的首席执行官Rami Sass说。 “尽管应用程序的横向扩展在Kubernetes中非常简单,但DevOps[团队]还需要考虑其它方面,特别是保持服务在线的同时扩展基础架构。重要的是确保主要服务,负责漏洞和安全的报警系统分布在群集的节点上,并使之有状态,这样在向下扩展时不会丢失任何数据。” 和其它挑战一样,这是合理规划和利用资源的问题。 “了解你的缩放需求,为它们做计划,更重要的是:做测试!” Coda Global公司的Bhagirathan提出了建议。“你的生产环境应该能承担更高的负荷”。误区4:Kubernetes在哪运行都没区别现实:正如同在开发人员的笔记本上运行Kubernetes和生产环境中运行Kubernetes有区别一样,环境会导致差别。 “一个常见的误解是(假设)Kubernetes在本地运行正常,那么它可以在任何地方正常工作,”Bitnami的首席执行官兼联合创始人Daniel Lopez说道,他用云平台之间的差异来应证这个假设的是错误的。 “虽然Kubernetes如果能够有效地提供一致的环境,但供应商之间仍存在显著差异 。” Lopez还指出,生产环境的部署需要那些不只作用于本地的组件,比如监控,日志和证书管理,以及凭据。你需要考虑这些因素,这也是导致开发/测试和生产环境之间差距扩大的关键问题之一。 这也是综合性问题,不仅Kubernetes需要考虑,也包括容器和微服务,更广泛的来说,在混合云和多云环境也需要考虑。 “公共 - 私有部署比纸面上看起来更棘手,因为许多必要的服务,如负载平衡和防火墙,都是专有的。 在实验室中运行良好的容器在具有不同工具的云环境中运行可能根本不工作——或者至少不安全 ,”Avi Networks的Rajagopalan说。 “这就是为什么像Istio这样的服务网格技术受到如此多关注的原因。 不管容器运行在哪,他们都提供相同的应用程序服务,因此你不必考虑基础架构——这是容器最重要的一点”。本文转自DockOne-生产环境 VS 开发环境,关于Kubernetes的四大认识误区
介绍 域名系统(DNS)是一种用于将各种类型的信息(例如IP地址)与易于记忆的名称相关联的系统。 默认情况下,大多数Kubernetes群集会自动配置内部DNS服务,以便为服务发现提供轻量级机制。 内置的服务发现使应用程序更容易在Kubernetes集群上相互查找和通信,即使在节点之间创建,删除和移动Pod和服务时也是如此。 最近版本的Kubernetes中Kubernetes DNS服务的实现细节已经改变。 在本文中,我们将介绍Kubernetes DNS服务的kube-dns和CoreDNS几个不同的实现版本。 我们一起来看看它们的运作方式以及Kubernetes生成的DNS记录。如果你想在开始之前就更全面地了解DNS,请阅读“DNS术语,组件和概念简介”。 对于您可能不熟悉的任何Kubernetes主题,可以阅读“Kubernetes简介。”Kubernetes DNS服务提供什么? 在Kubernetes版本1.11之前,Kubernetes DNS服务基于kube-dns。 1.11版引入了CoreDNS来解决kube-dns的一些安全性和稳定性问题。无论处理实际DNS记录的软件如何,两种实现都以类似的方式工作: 创建名为kube-dns的服务和一个或多个Pod。 kube-dns服务监听来自Kubernetes API的服务service和端点endpoint事件,并根据需要更新其DNS记录。 创建,更新或删除Kubernetes服务及其关联的pod时会触发这些事件。 kubelet将每个新Pod的/etc/resolv.conf名称服务器选项设置为kube-dns服务的集群IP,并使用适当的搜索选项以允许使用更短的主机名: resolve.confnameserver 10.32.0.10 search namespace.svc.cluster.local svc.cluster.local cluster.local options ndots:5 然后,在容器中运行的应用程序可以将主机名(例如example-service.namespace)解析为正确的群集IP地址。 Kubernetes DNS记录示例 Kubernetes服务的完整DNS A记录类似于以下示例:service.namespace.svc.cluster.local 一个Pod会有这种格式的记录,反映了Pod的实际IP地址:10.32.0.125.namespace.pod.cluster.local 此外,为Kubernetes服务的命名端口创建SRV记录:_port-name._protocol.service.namespace.svc.cluster.local 所有这些的结果是内置的,基于DNS的服务发现机制,你的应用程序或微服务可以在其中定位一个简单一致的主机名,并可以访问群集上的其他服务或Pod。搜索域和解析较短的主机名 由于resolv.conf文件中列出了搜索域后缀,所以您通常不需要使用完整主机名来访问其他服务。 如果要在同一命名空间中寻址其它服务,则只需使用服务名称即可:other-service 如果服务位于不同的命名空间中,请将其添加:other-service.other-namespace 如果您要定位Pod,则至少需要像以下所示使用:pod-ip.other-namespace.pod 正如我们在默认的resolv.conf文件中看到的那样,只有.svc后缀会自动完成,因此请确保指定.pod之前的所有内容。现在我们已经了解了Kubernetes DNS服务的实际用途,让我们来看看两个不同实现的一些细节。Kubernetes DNS实现细节 如上一节所述,Kubernetes 1.11版引入了处理kube-dns服务的新版本CoreDNS。 这样做的动机是提高服务的性能和安全性。 我们先来看看原始的kube-dns实现。kube-dns Kubernetes 1.11之前的kube-dns服务由在kube-system命名空间中的kube-dns pod中运行的三个容器组成。 这三个容器是: kube-dns:运行SkyDNS的容器,用于执行DNS查询解析 dnsmasq:流行的轻量级DNS解析器和缓存,用于缓存SkyDNS的响应 sidecar:一个sidecar容器,用于处理指标报告并响应服务的运行状况检查 Dnsmasq中的安全漏洞以及SkyDNS的扩展性能问题导致了被CoreDNS所替换。CoreDNS 从Kubernetes 1.11开始,新的Kubernetes DNS服务,CoreDNS已升级为GA。 这意味着它已准备好用于生产,并且将成为许多安装工具和托管Kubernetes提供商的默认集群DNS服务。CoreDNS,用Go编写且单一进程,它涵盖了以前系统的所有功能: 单个容器解析并缓存DNS查询,响应运行状况检查并提供指标。除了解决与性能和安全相关的问题之外,CoreDNS还修复了一些其他小错误并添加了一些新功能: 修复了使用stubDomains和外部服务之间不兼容的一些问题 CoreDNS可以通过随机化返回某些记录的顺序来增强基于DNS的round-robin负载平衡 autopath功能可以在解析外部主机名时提高DNS响应时间,方法是更好地遍历resolv.conf中列出的每个搜索域后缀 如果使用kube-dns的话,10.32.0.125.namespace.pod.cluster.local将始终解析为10.32.0.125,即使pod实际上不存在。 CoreDNS具有“已验证的pod”模式,只有当存在具有正确IP且位于右侧命名空间的pod时,才会成功解析。 有关CoreDNS及其与kube-dns的不同之处的更多信息,可以阅读Kubernetes CoreDNS GA公告。其他配置选项 kubernetes运营商通常希望自定义其Pod和容器如何解析某些自定义域,或者需要调整上游名称服务器或搜索resolv.conf中配置的域后缀。 您可以使用Pod规范的dnsConfig选项执行此操作:example_pod.yamlyaml apiVersion: v1 kind: Pod metadata: namespace: example name: custom-dns spec: containers: - name: example image: nginx dnsPolicy: "None" dnsConfig: nameservers: - 203.0.113.44 searches: - custom.dns.local 更新此配置将重写Pod的resolv.conf以启用更改。 配置直接映射到标准的resolv.conf选项,因此上面的配置将新增nameserver 203.0.113.44和search custom.dns.local这几行。结论 在本文中,我们介绍了Kubernetes DNS为开发人员提供了哪些服务的基础知识,展示了Service和Pod的一些DNS记录示例,讨论了不同的Kubernetes版本上的不同实现,并突出显示了一些可用于自定义Pod解析DNS查询的其他配置选项。 本文转自DockOne-Kubernetes DNS服务简介
北美时间11月26日,Kubernetes爆出严重安全漏洞,该漏洞由Rancher Labs联合创始人及首席架构师Darren Shepherd发现。该漏洞CVE-2018-1002105(又名Kubernetes特权升级漏洞,https://github.com/kubernetes/ ... 71411)被确认为严重性9.8分(满分10分),恶意用户可以使用Kubernetes API服务器连接到后端服务器以发送任意请求,并通过API服务器的TLS凭证进行身份验证。这一安全漏洞的严重性更在于它可以远程执行,攻击并不复杂,不需要用户交互或特殊权限。漏洞被发现并验证后,Kubernetes快速响应并已经发布了修补版本v1.10.11、v1.11.5、v1.12.3和v1.13.0-rc.1。仍在使用Kubernetes v1.0.x至Kubernetes v1.9.x版本的用户,被建议即刻停止并升级到修补版本。本文由该漏洞的发现者、Rancher Labs联合创始人及首席架构师Darren Shepherd所写。他描述了自己发现这一漏洞的完整经过,剖析了问题的机制与原理,并分享了相应的解决方案以及他本人对Kubernetes、对开源社区的看法。 Rancher Labs联合创始人及首席架构师 Darren Shepherd,同时也是Docker生态核心组织Docker治理委员会(DGAB)的全球仅有的四位个人顶级贡献者之一。 Amazon ALB的问题这一切都始于2016年,当时Rancher Labs刚发布了Rancher 1.6。2016年年中的时候,亚马逊发布了ALB,这是一个新的HTTP(7层)负载均衡器。ALB的设置比ELB容易得多,因此我们会建议用户使用ALB。随后很快,我们开始收到有关ALB后端设置失败的报告,很多随机请求只会得到401、403、404、503的报错。然而,Rancher Labs的团队无法重现这些错误,我们从社区成员那里得到的日志都没有没法成为参考。我们看到了HTTP请求和响应,但无法将其与代码相关联。那个时候,我们只好认为是因为ALB发布不久、产品本身可能存在些错误。除了ALB,我们之前从未遇到任何其他负载均衡器的问题。因此那时,我们只得最终告诉用户不要使用ALB。时间到了今年8月,又有Rancher社区成员向Rancher 2.1提交了同样的问题(https://github.com/rancher/rancher/issues/14931)。仍和以前一样,使用ALB会导致奇数401和403错误。这极大地引起了我的关注,因为Rancher 1.x和2.x之间没有共同的代码,而且ALB现在应该也已经相当成熟了。反复深入研究后,我发现问题与不处理非101响应和反向代理缓存TCP连接有关。若您想要真正理解这个问题,您必须了解TCP连接重用、websockets如何使用TCP连接以及HTTP反向代理。TCP连接重用在一种非常天真的HTTP方法中,客户端将打开TCP socket,发送HTTP请求,读取HTTP响应,然后关闭TCP socket。很快你就会发现你花了太多时间打开和关闭TCP连接。因此,HTTP协议具有内置的机制,以便客户端可以跨请求重用TCP连接。WebSocketsWebsockets是双向通信,其工作方式与HTTP请求/响应流不同。为了使用websockets,客户端首先会发送HTTP升级请求,服务器会以HTTP 101 Switch Protocols响应来响应这一请求。收到101之后,TCP连接将专用于websocket。在TCP连接的剩余生命周期中,它是被认为是专用于该websocket连接的。这就意味着此TCP连接永远不会被重新使用。HTTP反向代理HTTP反向代理(负载均衡器是一种反向代理)从客户端接收请求,然后将它们发送到不同的服务器。对于标准HTTP请求,它只写入请求,读取响应,然后将响应发送到客户端。这种逻辑相当直接,而且Go也包含一个内置的反向代理:https://golang.org/pkg/net/htt ... Proxy。相比之下Websockets就复杂一点。对于websocket,你必须查看请求,看到它是一个升级请求,然后发送请求,读取101响应,然后劫持TCP连接,然后开始来回复制字节。对于反向代理,它不会在此之后查看连接的内容,它只是创建一个“废弃管道”。标准Go库中不存在此逻辑,许多开源项目都编写了代码来执行此操作。错误所在关于错误所在,太长不看版的解释是,Kubernetes在启动“废弃管道”之前没有检查101响应。在代码的防御中,不检查101是挺常见的。(这也是我们上文所说的Rancher用户会发现的Rancher 1.x和Rancher 2.x的问题的原因,即使Rancher 1.x和Rancher 2.x使用的是完成不同的代码。)错误的场景如下: 客户端发送websocket升级请求 反向代理向后端服务器发送升级请求 后端服务器以404响应 反向代理启动复制循环并将404写入客户端 客户端看到404响应并将TCP连接添加到“空闲连接池” 在这种情况下,如果客户端重新使用TCP连接,它将向TCP连接写入请求,它将通过反向代理中的“废弃管道”并将其发送到前一个后端。通常这不会很糟糕,例如在负载均衡器的情况下,因为所有请求都会转到同一组同类后端。但是,当反向代理是智能的,并且是由其执行身份验证、授权和路由(即Kubernetes所做的全部工作)时,就会出现此问题。安全漏洞因为101未被处理,所以客户端最终使用TCP连接,该连接是对某些先前访问的后端服务的“废弃管道”。这将导致特权升级。问题是,Kubernetes将仅在反向代理中执行许多请求的授权。这意味着如果我执行一个授权失败的websocket请求路由到一个kubelet,我可以保持与该kubelet的持久连接,然后运行我选择的任何API命令,无论我是否被授权。例如,您可以在任何pod上运行exec并复制出secrets。因此,在这种情况下,已经授权的用户基本上可以获得对kubelet的完全API访问(同样的事情适用于通过kube-aggregation运行的服务)。当您添加另一个反向代理时,会出现另一个问题。在这种情况下,您将HTTP负载均衡器放在Kubernetes API(非4层负载均衡器)之前。如果执行此操作,那个通过了身份验证的、运行着“废弃管道” 的TCP连接,将会被添加到一个任何用户都可以访问的空闲池中。那么,用户A创建了TCP连接,之后用户B仍可重新使用该连接。这样一来,未经过身份验证的用户就可以访问您的Kubernetes集群了。此时你可能会感到恐慌,因为当然每个人都会在kube-apiserver前放置一个负载均衡器。唔……首先,您必须运行HTTP负载均衡器,而不是TCP负载均衡器。负载均衡器必须了解HTTP语义才能产生此问题。其次,幸运的是大多数反向代理并不关心101个回复。这就是为什么这个问题其实(在不少开源项目中)存在已久而未被发现的原因。大多数负载均衡器在看到升级请求而非101响应后不会重用TCP连接。所以,如果您会受到这一漏洞的影响,那么您的Kubernetes设置应该已经不可靠了,您应该能看到随机失败或无法完成的请求。至少我知道ALB就是这样工作的,所以你升级到了已修补该漏洞的Kubernetes版本之前,不要使用ALB。简而言之,Kubernetes的这一安全漏洞会允许具有正确权限的任何经过身份验证的用户获得更多权限。如果您正在运行硬件多租户集群(内含不受信任的用户),您确实应该担心并且及时应对。如果您不担心用户主动互相攻击(大多数多租户集群都是这样),那么不要惊慌,只需升级到已修补该漏洞的Kubernetes版本即可。最坏的情况,如果真的有未经身份验证的用户可以进入您的集群,您的负载均衡器也有可能会阻止这种情况。只要不是将API暴露给世界,并且有在其上放置一些适当的ACL,也许你的集群也还是安全的。Rancher为Kubernetes保驾护航对于使用Rancher Kubernetes平台的用户,你们更无须紧张。对于把集群部署在内网的用户,完全不需要过于担心此问题,因为外部无法直接入侵。通过Rancher2.0或RKE部署的kubernetes集群的用户同样不用过于担心。因为通过Ranche2.0或RKE部署的集群默认是禁止和匿名用户访问。针对通过pod exec/attach/portforward权限提权问题,目前Kubernetes发布通用的修复方法是通过升级到指定Kubernetes版本来修复,针对此Rancher也已经发布修复程序,具体修复方法请参考:https://forums.rancher.com/t/r ... 12598感谢开源我深刻地感到,这次Kubernetes的这个安全漏洞的最初发现、修复和最终交付,证明了开源社区强大的生命力。我第一次发现这个问题,也是因为Rancher的非付费开源用户给予我们的反馈。事实上,我们已经确认了这一问题并没有影响Rancher 2.x的付费客户,因为Rancher的HA架构恰好否定了ALB的行为,但我们还是去研究并解决了这个问题,因为我们太爱我们的开源用户了。也正是在研究和修复这个问题的过程中,我发现了Kubernetes自身存在的安全隐患,并通过已建立的安全公开流程向Kubernetes社区反馈了该问题。 本文转自DockOne-Kubernetes首个严重安全漏洞发现者,谈发现过程及原理机制
1、基础架构 1.1 Master Master节点上面主要由四个模块组成:APIServer、scheduler、controller manager、etcd。 APIServer。APIServer负责对外提供RESTful的Kubernetes API服务,它是系统管理指令的统一入口,任何对资源进行增删改查的操作都要交给APIServer处理后再提交给etcd。如架构图中所示,kubectl(Kubernetes提供的客户端工具,该工具内部就是对Kubernetes API的调用)是直接和APIServer交互的。 schedule。scheduler的职责很明确,就是负责调度pod到合适的Node上。如果把scheduler看成一个黑匣子,那么它的输入是pod和由多个Node组成的列表,输出是Pod和一个Node的绑定,即将这个pod部署到这个Node上。Kubernetes目前提供了调度算法,但是同样也保留了接口,用户可以根据自己的需求定义自己的调度算法。 controller manager。如果说APIServer做的是“前台”的工作的话,那controller manager就是负责“后台”的。每个资源一般都对应有一个控制器,而controller manager就是负责管理这些控制器的。比如我们通过APIServer创建一个pod,当这个pod创建成功后,APIServer的任务就算完成了。而后面保证Pod的状态始终和我们预期的一样的重任就由controller manager去保证了。 etcd。etcd是一个高可用的键值存储系统,Kubernetes使用它来存储各个资源的状态,从而实现了Restful的API。 1.2 Node 每个Node节点主要由三个模块组成:kubelet、kube-proxy、runtime。 runtime。runtime指的是容器运行环境,目前Kubernetes支持docker和rkt两种容器。 kube-proxy。该模块实现了Kubernetes中的服务发现和反向代理功能。反向代理方面:kube-proxy支持TCP和UDP连接转发,默认基于Round Robin算法将客户端流量转发到与service对应的一组后端pod。服务发现方面,kube-proxy使用etcd的watch机制,监控集群中service和endpoint对象数据的动态变化,并且维护一个service到endpoint的映射关系,从而保证了后端pod的IP变化不会对访问者造成影响。另外kube-proxy还支持session affinity。 kubelet。Kubelet是Master在每个Node节点上面的agent,是Node节点上面最重要的模块,它负责维护和管理该Node上面的所有容器,但是如果容器不是通过Kubernetes创建的,它并不会管理。本质上,它负责使Pod得运行状态与期望的状态一致。 至此,Kubernetes的Master和Node就简单介绍完了。下面我们来看Kubernetes中的各种资源/对象。 2、Pod Pod 是Kubernetes的基本操作单元,也是 应用运行的载体。整个Kubernetes系统都是围绕着Pod展开的,比如如何部署运行Pod、如何保证Pod的数量、如何访问Pod等。另外,Pod是一个或多个机关容器的集合,这可以说是一大创新点,提供了一种容器的组合的模型。 2.1 基本操作 创建 kubectl create -f xxx.yaml 查询 kubectl get pod yourPodName kubectl describe pod yourPodName 删除 kubectl delete pod yourPodName 更新 kubectl replace /path/to/yourNewYaml.yaml 2.2 Pod与容器 在Docker中,容器是最小的处理单元,增删改查的对象是容器,容器是一种虚拟化技术,容器之间是隔离的,隔离是基于Linux Namespace实现的。而在Kubernetes中,Pod包含一个或者多个相关的容器,Pod可以认为是容器的一种延伸扩展,一个Pod也是一个隔离体,而Pod内部包含的一组容器又是共享的(包括PID、Network、IPC、UTS)。除此之外,Pod中的容器可以访问共同的数据卷来实现文件系统的共享。 2.3 镜像 在kubernetes中,镜像的下载策略为: Always:每次都下载最新的镜像 Never:只使用本地镜像,从不下载 IfNotPresent:只有当本地没有的时候才下载镜像 Pod被分配到Node之后会根据镜像下载策略进行镜像下载,可以根据自身集群的特点来决定采用何种下载策略。无论何种策略,都要确保Node上有正确的镜像可用。 2.4 其他设置 通过yaml文件,可以在Pod中设置: 启动命令,如:spec-->containers-->command; 环境变量,如:spec-->containers-->env-->name/value; 端口桥接,如:spec-->containers-->ports-->containerPort/protocol/hostIP/hostPort(使用hostPort时需要注意端口冲突的问题,不过Kubernetes在调度Pod的时候会检查宿主机端口是否冲突,比如当两个Pod均要求绑定宿主机的80端口,Kubernetes将会将这两个Pod分别调度到不同的机器上); Host网络,一些特殊场景下,容器必须要以host方式进行网络设置(如接收物理机网络才能够接收到的组播流),在Pod中也支持host网络的设置,如:spec-->hostNetwork=true; 数据持久化,如:spec-->containers-->volumeMounts-->mountPath; 重启策略,当Pod中的容器终止退出后,重启容器的策略。这里的所谓Pod的重启,实际上的做法是容器的重建,之前容器中的数据将会丢失,如果需要持久化数据,那么需要使用数据卷进行持久化设置。Pod支持三种重启策略:Always(默认策略,当容器终止退出后,总是重启容器)、OnFailure(当容器终止且异常退出时,重启)、Never(从不重启); 2.5 Pod生命周期 Pod被分配到一个Node上之后,就不会离开这个Node,直到被删除。当某个Pod失败,首先会被Kubernetes清理掉,之后ReplicationController将会在其它机器上(或本机)重建Pod,重建之后Pod的ID发生了变化,那将会是一个新的Pod。所以,Kubernetes中Pod的迁移,实际指的是在新Node上重建Pod。以下给出Pod的生命周期图。 生命周期回调函数:PostStart(容器创建成功后调研该回调函数)、PreStop(在容器被终止前调用该回调函数)。以下示例中,定义了一个Pod,包含一个JAVA的web应用容器,其中设置了PostStart和PreStop回调函数。即在容器创建成功后,复制/sample.war到/app文件夹中。而在容器终止之前,发送HTTP请求到http://monitor.com:8080/waring,即向监控系统发送警告。具体示例如下: ……….. containers: - image: sample:v2 name: war posrStart: lifecycle: exec: command: - “cp” - “/sample.war” - “/app” prestop: httpGet: host: monitor.com psth: /waring port: 8080 scheme: HTTP 3、Replication Controller Replication Controller(RC)是Kubernetes中的另一个核心概念,应用托管在Kubernetes之后,Kubernetes需要保证应用能够持续运行,这是RC的工作内容,它会确保任何时间Kubernetes中都有指定数量的Pod在运行。在此基础上,RC还提供了一些更高级的特性,比如滚动升级、升级回滚等。 3.1 RC与Pod的关联——Label RC与Pod的关联是通过Label来实现的。Label机制是Kubernetes中的一个重要设计,通过Label进行对象的弱关联,可以灵活地进行分类和选择。对于Pod,需要设置其自身的Label来进行标识,Label是一系列的Key/value对,在Pod-->metadata-->labeks中进行设置。 Label的定义是任一的,但是Label必须具有可标识性,比如设置Pod的应用名称和版本号等。另外Lable是不具有唯一性的,为了更准确的标识一个Pod,应该为Pod设置多个维度的label。如下: "release" : "stable", "release" : "canary" "environment" : "dev", "environment" : "qa", "environment" : "production" "tier" : "frontend", "tier" : "backend", "tier" : "cache" "partition" : "customerA", "partition" : "customerB" "track" : "daily", "track" : "weekly" 举例,当你在RC的yaml文件中定义了该RC的selector中的label为app:my-web,那么这个RC就会去关注Pod-->metadata-->labeks中label为app:my-web的Pod。修改了对应Pod的Label,就会使Pod脱离RC的控制。同样,在RC运行正常的时候,若试图继续创建同样Label的Pod,是创建不出来的。因为RC认为副本数已经正常了,再多起的话会被RC删掉的。 3.2 弹性伸缩 弹性伸缩是指适应负载变化,以弹性可伸缩的方式提供资源。反映到Kubernetes中,指的是可根据负载的高低动态调整Pod的副本数量。调整Pod的副本数是通过修改RC中Pod的副本是来实现的,示例命令如下: 扩容Pod的副本数目到10 $ kubectl scale relicationcontroller yourRcName --replicas=10 缩容Pod的副本数目到1 $ kubectl scale relicationcontroller yourRcName --replicas=1 3.3 滚动升级 滚动升级是一种平滑过渡的升级方式,通过逐步替换的策略,保证整体系统的稳定,在初始升级的时候就可以及时发现、调整问题,以保证问题影响度不会扩大。Kubernetes中滚动升级的命令如下: $ kubectl rolling-update my-rcName-v1 -f my-rcName-v2-rc.yaml --update-period=10s 升级开始后,首先依据提供的定义文件创建V2版本的RC,然后每隔10s(--update-period=10s)逐步的增加V2版本的Pod副本数,逐步减少V1版本Pod的副本数。升级完成之后,删除V1版本的RC,保留V2版本的RC,及实现滚动升级。 升级过程中,发生了错误中途退出时,可以选择继续升级。Kubernetes能够智能的判断升级中断之前的状态,然后紧接着继续执行升级。当然,也可以进行回退,命令如下: $ kubectl rolling-update my-rcName-v1 -f my-rcName-v2-rc.yaml --update-period=10s --rollback 回退的方式实际就是升级的逆操作,逐步增加V1.0版本Pod的副本数,逐步减少V2版本Pod的副本数。 3.4 新一代副本控制器replica set 这里所说的replica set,可以被认为 是“升级版”的Replication Controller。也就是说。replica set也是用于保证与label selector匹配的pod数量维持在期望状态。区别在于,replica set引入了对基于子集的selector查询条件,而Replication Controller仅支持基于值相等的selecto条件查询。这是目前从用户角度肴,两者唯一的显著差异。 社区引入这一API的初衷是用于取代vl中的Replication Controller,也就是说.当v1版本被废弃时,Replication Controller就完成了它的历史使命,而由replica set来接管其工作。虽然replica set可以被单独使用,但是目前它多被Deployment用于进行pod的创建、更新与删除。Deployment在滚动更新等方面提供了很多非常有用的功能,关于DeplOymCn的更多信息,读者们可以在后续小节中获得。 4、Job 从程序的运行形态上来区分,我们可以将Pod分为两类:长时运行服务(jboss、mysql等)和一次性任务(数据计算、测试)。RC创建的Pod都是长时运行的服务,而Job创建的Pod都是一次性任务。 在Job的定义中,restartPolicy(重启策略)只能是Never和OnFailure。Job可以控制一次性任务的Pod的完成次数(Job-->spec-->completions)和并发执行数(Job-->spec-->parallelism),当Pod成功执行指定次数后,即认为Job执行完毕。 5、Service 为了适应快速的业务需求,微服务架构已经逐渐成为主流,微服务架构的应用需要有非常好的服务编排支持。Kubernetes中的核心要素Service便提供了一套简化的服务代理和发现机制,天然适应微服务架构。 5.1 原理 在Kubernetes中,在受到RC调控的时候,Pod副本是变化的,对于的虚拟IP也是变化的,比如发生迁移或者伸缩的时候。这对于Pod的访问者来说是不可接受的。Kubernetes中的Service是一种抽象概念,它定义了一个Pod逻辑集合以及访问它们的策略,Service同Pod的关联同样是居于Label来完成的。Service的目标是提供一种桥梁, 它会为访问者提供一个固定访问地址,用于在访问时重定向到相应的后端,这使得非 Kubernetes原生应用程序,在无须为Kubemces编写特定代码的前提下,轻松访问后端。 Service同RC一样,都是通过Label来关联Pod的。当你在Service的yaml文件中定义了该Service的selector中的label为app:my-web,那么这个Service会将Pod-->metadata-->labeks中label为app:my-web的Pod作为分发请求的后端。当Pod发生变化时(增加、减少、重建等),Service会及时更新。这样一来,Service就可以作为Pod的访问入口,起到代理服务器的作用,而对于访问者来说,通过Service进行访问,无需直接感知Pod。 需要注意的是,Kubernetes分配给Service的固定IP是一个虚拟IP,并不是一个真实的IP,在外部是无法寻址的。真实的系统实现上,Kubernetes是通过Kube-proxy组件来实现的虚拟IP路由及转发。所以在之前集群部署的环节上,我们在每个Node上均部署了Proxy这个组件,从而实现了Kubernetes层级的虚拟转发网络。 5.2 Service代理外部服务 Service不仅可以代理Pod,还可以代理任意其他后端,比如运行在Kubernetes外部Mysql、Oracle等。这是通过定义两个同名的service和endPoints来实现的。示例如下: redis-service.yaml apiVersion: v1 kind: Service metadata: name: redis-service spec: ports: - port: 6379 targetPort: 6379 protocol: TCP redis-endpoints.yaml apiVersion: v1 kind: Endpoints metadata: name: redis-service subsets: - addresses: - ip: 10.0.251.145 ports: - port: 6379 protocol: TCP 基于文件创建完Service和Endpoints之后,在Kubernetes的Service中即可查询到自定义的Endpoints。 [root@k8s-master demon]# kubectl describe service redis-service Name: redis-service Namespace: default Labels: <none> Selector: <none> Type: ClusterIP IP: 10.254.52.88 Port: <unset> 6379/TCP Endpoints: 10.0.251.145:6379 Session Affinity: None No events. [root@k8s-master demon]# etcdctl get /skydns/sky/default/redis-service {"host":"10.254.52.88","priority":10,"weight":10,"ttl":30,"targetstrip":0} 5.3 Service内部负载均衡 当Service的Endpoints包含多个IP的时候,及服务代理存在多个后端,将进行请求的负载均衡。默认的负载均衡策略是轮训或者随机(有kube-proxy的模式决定)。同时,Service上通过设置Service-->spec-->sessionAffinity=ClientIP,来实现基于源IP地址的会话保持。 5.4 发布Service Service的虚拟IP是由Kubernetes虚拟出来的内部网络,外部是无法寻址到的。但是有些服务又需要被外部访问到,例如web前段。这时候就需要加一层网络转发,即外网到内网的转发。Kubernetes提供了NodePort、LoadBalancer、Ingress三种方式。 NodePort,在之前的Guestbook示例中,已经延时了NodePort的用法。NodePort的原理是,Kubernetes会在每一个Node上暴露出一个端口:nodePort,外部网络可以通过(任一Node)[NodeIP]:[NodePort]访问到后端的Service。 LoadBalancer,在NodePort基础上,Kubernetes可以请求底层云平台创建一个负载均衡器,将每个Node作为后端,进行服务分发。该模式需要底层云平台(例如GCE)支持。 Ingress,是一种HTTP方式的路由转发机制,由Ingress Controller和HTTP代理服务器组合而成。Ingress Controller实时监控Kubernetes API,实时更新HTTP代理服务器的转发规则。HTTP代理服务器有GCE Load-Balancer、HaProxy、Nginx等开源方案。 5.5 servicede 自发性机制 Kubernetes中有一个很重要的服务自发现特性。一旦一个service被创建,该service的service IP和service port等信息都可以被注入到pod中供它们使用。Kubernetes主要支持两种service发现 机制:环境变量和DNS。 环境变量方式 Kubernetes创建Pod时会自动添加所有可用的service环境变量到该Pod中,如有需要.这些环境变量就被注入Pod内的容器里。需要注意的是,环境变量的注入只发送在Pod创建时,且不会被自动更新。这个特点暗含了service和访问该service的Pod的创建时间的先后顺序,即任何想要访问service的pod都需要在service已经存在后创建,否则与service相关的环境变量就无法注入该Pod的容器中,这样先创建的容器就无法发现后创建的service。 DNS方式 Kubernetes集群现在支持增加一个可选的组件——DNS服务器。这个DNS服务器使用Kubernetes的watchAPI,不间断的监测新的service的创建并为每个service新建一个DNS记录。如果DNS在整个集群范围内都可用,那么所有的Pod都能够自动解析service的域名。Kube-DNS搭建及更详细的介绍请见:基于Kubernetes集群部署skyDNS服务 5.6 多个service如何避免地址和端口冲突 此处设计思想是,Kubernetes通过为每个service分配一个唯一的ClusterIP,所以当使用ClusterIP:port的组合访问一个service的时候,不管port是什么,这个组合是一定不会发生重复的。另一方面,kube-proxy为每个service真正打开的是一个绝对不会重复的随机端口,用户在service描述文件中指定的访问端口会被映射到这个随机端口上。这就是为什么用户可以在创建service时随意指定访问端口。 5.7 service目前存在的不足 Kubernetes使用iptables和kube-proxy解析service的人口地址,在中小规模的集群中运行良好,但是当service的数量超过一定规模时,仍然有一些小问题。首当其冲的便是service环境变量泛滥,以及service与使用service的pod两者创建时间先后的制约关系。目前来看,很多使用者在使用Kubernetes时往往会开发一套自己的Router组件来替代service,以便更好地掌控和定制这部分功能。 6、Deployment Kubernetes提供了一种更加简单的更新RC和Pod的机制,叫做Deployment。通过在Deployment中描述你所期望的集群状态,Deployment Controller会将现在的集群状态在一个可控的速度下逐步更新成你所期望的集群状态。Deployment主要职责同样是为了保证pod的数量和健康,90%的功能与Replication Controller完全一样,可以看做新一代的Replication Controller。但是,它又具备了Replication Controller之外的新特性: Replication Controller全部功能:Deployment继承了上面描述的Replication Controller全部功能。 事件和状态查看:可以查看Deployment的升级详细进度和状态。 回滚:当升级pod镜像或者相关参数的时候发现问题,可以使用回滚操作回滚到上一个稳定的版本或者指定的版本。 版本记录: 每一次对Deployment的操作,都能保存下来,给予后续可能的回滚使用。 暂停和启动:对于每一次升级,都能够随时暂停和启动。 多种升级方案:Recreate----删除所有已存在的pod,重新创建新的; RollingUpdate----滚动升级,逐步替换的策略,同时滚动升级时,支持更多的附加参数,例如设置最大不可用pod数量,最小升级间隔时间等等。 6.1 滚动升级 相比于RC,Deployment直接使用kubectl edit deployment/deploymentName 或者kubectl set方法就可以直接升级(原理是Pod的template发生变化,例如更新label、更新镜像版本等操作会触发Deployment的滚动升级)。操作示例——首先 我们同样定义一个nginx-deploy-v1.yaml的文件,副本数量为2: apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 3 template: metadata: app: nginx labels: spec: - name: nginx containers: image: nginx:1.7.9 ports: - containerPort: 80 创建deployment: $ kubectl create -f nginx-deploy-v1.yaml --record deployment "nginx-deployment" created $ kubectl get deployments NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx-deployment 3 0 0 0 1s $ kubectl get deployments NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx-deployment 3 3 3 3 18s 正常之后,将nginx的版本进行升级,从1.7升级到1.9。第一种方法,直接set镜像: $ kubectl set image deployment/nginx-deployment2 nginx=nginx:1.9 deployment "nginx-deployment2" image updated 第二种方法,直接edit: $ kubectl edit deployment/nginx-deployment deployment "nginx-deployment2" edited 查看Deployment的变更信息(以下信息得以保存,是创建时候加的“--record”这个选项起的作用): $ kubectl rollout history deployment/nginx-deployment deployments "nginx-deployment": REVISION CHANGE-CAUSE 1 kubectl create -f docs/user-guide/nginx-deployment.yaml --record 2 kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1 3 kubectl set image deployment/nginx-deployment nginx=nginx:1.91 $ kubectl rollout history deployment/nginx-deployment --revision=2 deployments "nginx-deployment" revision 2 Labels: app=nginx pod-template-hash=1159050644 Annotations: kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1 Containers: nginx: Image: nginx:1.9.1 Port: 80/TCP QoS Tier: cpu: BestEffort memory: BestEffort Environment Variables: <none> No volumes. 最后介绍下Deployment的一些基础命令。 $ kubectl describe deployments #查询详细信息,获取升级进度 $ kubectl rollout pause deployment/nginx-deployment2 #暂停升级 $ kubectl rollout resume deployment/nginx-deployment2 #继续升级 $ kubectl rollout undo deployment/nginx-deployment2 #升级回滚 $ kubectl scale deployment nginx-deployment --replicas 10 #弹性伸缩Pod数量 关于多重升级,举例,当你创建了一个nginx1.7的Deployment,要求副本数量为5之后,Deployment Controller会逐步的将5个1.7的Pod启动起来;当启动到3个的时候,你又发出更新Deployment中Nginx到1.9的命令;这时Deployment Controller会立即将已启动的3个1.7Pod杀掉,然后逐步启动1.9的Pod。Deployment Controller不会等到1.7的Pod都启动完成之后,再依次杀掉1.7,启动1.9。 7、Volume 在Docker的设计实现中,容器中的数据是临时的,即当容器被销毁时,其中的数据将会丢失。如果需要持久化数据,需要使用Docker数据卷挂载宿主机上的文件或者目录到容器中。在Kubernetes中,当Pod重建的时候,数据是会丢失的,Kubernetes也是通过数据卷挂载来提供Pod数据的持久化的。Kubernetes数据卷是对Docker数据卷的扩展,Kubernetes数据卷是Pod级别的,可以用来实现Pod中容器的文件共享。目前,Kubernetes支持的数据卷类型如下: 1) EmptyDir 2) HostPath 3) GCE Persistent Disk 4) AWS Elastic Block Store 5) NFS 6) iSCSI 7) Flocker 8) GlusterFS 9) RBD 10) Git Repo 11) Secret 12) Persistent Volume Claim 13) Downward API 7.1本地数据卷 EmptyDir、HostPath这两种类型的数据卷,只能最用于本地文件系统。本地数据卷中的数据只会存在于一台机器上,所以当Pod发生迁移的时候,数据便会丢失。该类型Volume的用途是:Pod中容器间的文件共享、共享宿主机的文件系统。 7.1.1 EmptyDir 如果Pod配置了EmpyDir数据卷,在Pod的生命周期内都会存在,当Pod被分配到 Node上的时候,会在Node上创建EmptyDir数据卷,并挂载到Pod的容器中。只要Pod 存在,EmpyDir数据卷都会存在(容器删除不会导致EmpyDir数据卷丟失数据),但是如果Pod的生命周期终结(Pod被删除),EmpyDir数据卷也会被删除,并且永久丢失。 EmpyDir数据卷非常适合实现Pod中容器的文件共享。Pod的设计提供了一个很好的容器组合的模型,容器之间各司其职,通过共享文件目录来完成交互,比如可以通过一个专职日志收集容器,在每个Pod中和业务容器中进行组合,来完成日志的收集和汇总。 7.1.2 HostPath HostPath数据卷允许将容器宿主机上的文件系统挂载到Pod中。如果Pod需要使用宿主机上的某些文件,可以使用HostPath。 7.2网络数据卷 Kubernetes提供了很多类型的数据卷以集成第三方的存储系统,包括一些非常流行的分布式文件系统,也有在IaaS平台上提供的存储支持,这些存储系统都是分布式的,通过网络共享文件系统,因此我们称这一类数据卷为网络数据卷。 网络数据卷能够满足数据的持久化需求,Pod通过配置使用网络数据卷,每次Pod创建的时候都会将存储系统的远端文件目录挂载到容器中,数据卷中的数据将被水久保存,即使Pod被删除,只是除去挂载数据卷,数据卷中的数据仍然保存在存储系统中,且当新的Pod被创建的时候,仍是挂载同样的数据卷。网络数据卷包含以下几种:NFS、iSCISI、GlusterFS、RBD(Ceph Block Device)、Flocker、AWS Elastic Block Store、GCE Persistent Disk 7.3 Persistent Volume和Persistent Volume Claim 理解每个存储系统是一件复杂的事情,特别是对于普通用户来说,有时候并不需要关心各种存储实现,只希望能够安全可靠地存储数据。Kubernetes中提供了Persistent Volume和Persistent Volume Claim机制,这是存储消费模式。Persistent Volume是由系统管理员配置创建的一个数据卷(目前支持HostPath、GCE Persistent Disk、AWS Elastic Block Store、NFS、iSCSI、GlusterFS、RBD),它代表了某一类存储插件实现;而对于普通用户来说,通过Persistent Volume Claim可请求并获得合适的Persistent Volume,而无须感知后端的存储实现。Persistent Volume和Persistent Volume Claim的关系其实类似于Pod和Node,Pod消费Node资源,Persistent Volume Claim则消费Persistent Volume资源。Persistent Volume和Persistent Volume Claim相互关联,有着完整的生命周期管理: 1) 准备:系统管理员规划或创建一批Persistent Volume; 2) 绑定:用户通过创建Persistent Volume Claim来声明存储请求,Kubernetes发现有存储请求的时候,就去查找符合条件的Persistent Volume(最小满足策略)。找到合适的就绑定上,找不到就一直处于等待状态; 3) 使用:创建Pod的时候使用Persistent Volume Claim; 4) 释放:当用户删除绑定在Persistent Volume上的Persistent Volume Claim时,Persistent Volume进入释放状态,此时Persistent Volume中还残留着上一个Persistent Volume Claim的数据,状态还不可用; 5) 回收:是否的Persistent Volume需要回收才能再次使用。回收策略可以是人工的也可以是Kubernetes自动进行清理(仅支持NFS和HostPath) 7.4信息数据卷 Kubernetes中有一些数据卷,主要用来给容器传递配置信息,我们称之为信息数据卷,比如Secret(处理敏感配置信息,密码、Token等)、Downward API(通过环境变量的方式告诉容器Pod的信息)、Git Repo(将Git仓库下载到Pod中),都是将Pod的信息以文件形式保存,然后以数据卷方式挂载到容器中,容器通过读取文件获取相应的信息。 8、Pet Sets/StatefulSet K8s在1.3版本里发布了Alpha版的PetSet功能。在云原生应用的体系里,有下面两组近义词;第一组是无状态(stateless)、牲畜(cattle)、无名(nameless)、可丢弃(disposable);第二组是有状态(stateful)、宠物(pet)、有名(having name)、不可丢弃(non-disposable)。RC和RS主要是控制提供无状态服务的,其所控制的Pod的名字是随机设置的,一个Pod出故障了就被丢弃掉,在另一个地方重启一个新的Pod,名字变了、名字和启动在哪儿都不重要,重要的只是Pod总数;而PetSet是用来控制有状态服务,PetSet中的每个Pod的名字都是事先确定的,不能更改。PetSet中Pod的名字的作用,是用来关联与该Pod对应的状态。 对于RC和RS中的Pod,一般不挂载存储或者挂载共享存储,保存的是所有Pod共享的状态,Pod像牲畜一样没有分别;对于PetSet中的Pod,每个Pod挂载自己独立的存储,如果一个Pod出现故障,从其他节点启动一个同样名字的Pod,要挂在上原来Pod的存储继续以它的状态提供服务。 适合于PetSet的业务包括数据库服务MySQL和PostgreSQL,集群化管理服务Zookeeper、etcd等有状态服务。PetSet的另一种典型应用场景是作为一种比普通容器更稳定可靠的模拟虚拟机的机制。传统的虚拟机正是一种有状态的宠物,运维人员需要不断地维护它,容器刚开始流行时,我们用容器来模拟虚拟机使用,所有状态都保存在容器里,而这已被证明是非常不安全、不可靠的。使用PetSet,Pod仍然可以通过漂移到不同节点提供高可用,而存储也可以通过外挂的存储来提供高可靠性,PetSet做的只是将确定的Pod与确定的存储关联起来保证状态的连续性。 9、ConfigMap 很多生产环境中的应用程序配置较为复杂,可能需要多个config文件、命令行参数和环境变量的组合。并且,这些配置信息应该从应用程序镜像中解耦出来,以保证镜像的可移植性以及配置信息不被泄露。社区引入ConfigMap这个API资源来满足这一需求。 ConfigMap包含了一系列的键值对,用于存储被Pod或者系统组件(如controller)访问的信息。这与secret的设计理念有异曲同工之妙,它们的主要区别在于ConfigMap通常不用于存储敏感信息,而只存储简单的文本信息。 10、Horizontal Pod Autoscaler 自动扩展作为一个长久的议题,一直为人们津津乐道。系统能够根据负载的变化对计算资源的分配进行自动的扩增或者收缩,无疑是一个非常吸引人的特征,它能够最大可能地减少费用或者其他代价(如电力损耗)。自动扩展主要分为两种,其一为水平扩展,针对于实例数目的增减;其二为垂直扩展,即单个实例可以使用的资源的增减。Horizontal Pod Autoscaler(HPA)属于前者。 10.1 Horizontal Pod Autoscaler如何工作 Horizontal Pod Autoscaler的操作对象是Replication Controller、ReplicaSet或Deployment对应的Pod,根据观察到的CPU实际使用量与用户的期望值进行比对,做出是否需要增减实例数量的决策。controller目前使用heapSter来检测CPU使用量,检测周期默认是30秒。 10.2 Horizontal Pod Autoscaler的决策策略 在HPA Controller检测到CPU的实际使用量之后,会求出当前的CPU使用率(实际使用量与pod 请求量的比率)。然后,HPA Controller会通过调整副本数量使得CPU使用率尽量向期望值靠近.另外,考虑到自动扩展的决策可能需要一段时间才会生效,甚至在短时间内会引入一些噪声. 例如当pod所需要的CPU负荷过大,从而运行一个新的pod进行分流,在创建的过程中,系统的CPU使用量可能会有一个攀升的过程。所以,在每一次作出决策后的一段时间内,将不再进行扩展决策。对于ScaleUp而言,这个时间段为3分钟,Scaledown为5分钟。再者HPA Controller允许一定范围内的CPU使用量的不稳定,也就是说,只有当aVg(CurrentPodConsumption/Target低于0.9或者高于1.1时才进行实例调整,这也是出于维护系统稳定性的考虑。 本文转自开源中国-Kubernetes核心概念总结
1: 前言 kubernetes的基本运行单元是pod,而pod是无状态的,pod可能会遇到挂掉,重启等情况.而tomcat运行在pod上,日志信息也存储在pod上。一旦pod挂了,存储在pod上的日志也就丢失了。而如果只是简单的将tomcat的日志目录进行数据卷的映射,在一个主机上运行着多个相同的tomcat pod时,日志信息又会非常混乱。 2: 目标 实现tomcat的日志持久化,并且实现一个主机 多个tomcat pod的日志不混淆。 3: 步骤1 制作自己的tomcat镜像。 Dockerfile: FROM tomcat:8-jre8 MAINTAINER "miao bainian <miaobainian36@163.com>" ADD logging.properties /usr/local/tomcat/conf/ ADD server.xml /usr/local/tomcat/conf/ ADD catalina.sh /usr/local/tomcat/bin/ logging.properties: 通过pod名和pod命名空间变量,唯一标识输出文件名。 给所有的log前缀加上${my.pod.name}_${my.pod.namespace} 比如:1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina. 改成 1catalina.org.apache.juli.AsyncFileHandler.prefix = ${my.pod.name}_${my.pod.namespace}_catalina. # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. handlers = 1catalina.org.apache.juli.AsyncFileHandler, 2localhost.org.apache.juli.AsyncFileHandler, 3manager.org.apache.juli.AsyncFileHandler, 4host-manager.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler .handlers = 1catalina.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler ############################################################ # Handler specific properties. # Describes specific configuration info for Handlers. ############################################################ 1catalina.org.apache.juli.AsyncFileHandler.level = FINE 1catalina.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs 1catalina.org.apache.juli.AsyncFileHandler.prefix = ${my.pod.name}_${my.pod.namespace}_catalina. 2localhost.org.apache.juli.AsyncFileHandler.level = FINE 2localhost.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs 2localhost.org.apache.juli.AsyncFileHandler.prefix = ${my.pod.name}_${my.pod.namespace}_localhost. 3manager.org.apache.juli.AsyncFileHandler.level = FINE 3manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs 3manager.org.apache.juli.AsyncFileHandler.prefix = ${my.pod.name}_${my.pod.namespace}_manager. 4host-manager.org.apache.juli.AsyncFileHandler.level = FINE 4host-manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs 4host-manager.org.apache.juli.AsyncFileHandler.prefix = ${my.pod.name}_${my.pod.namespace}_host-manager. java.util.logging.ConsoleHandler.level = FINE java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter ############################################################ # Facility specific properties. # Provides extra control for each logger. ############################################################ org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.AsyncFileHandler org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = 3manager.org.apache.juli.AsyncFileHandler org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level = INFO org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers = 4host-manager.org.apache.juli.AsyncFileHandler # For example, set the org.apache.catalina.util.LifecycleBase logger to log # each component that extends LifecycleBase changing state: #org.apache.catalina.util.LifecycleBase.level = FINE # To see debug messages in TldLocationsCache, uncomment the following line: #org.apache.jasper.compiler.TldLocationsCache.level = FINE # To see debug messages for HTTP/2 handling, uncomment the following line: #org.apache.coyote.http2.level = FINE # To see debug messages for WebSocket handling, uncomment the following line: #org.apache.tomcat.websocket.level = FINE server.xml : 也是通过pod名和pod命名空间变量,唯一标识输出文件名,这里标识的是localhost-access日志文件。 找到 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"这个节点,改成 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="${my.pod.name}_${my.pod.namespace}_localhost-access-log" suffix=".log" pattern="%h %l %u %t &quot;%r&quot; %s %b" /> <?xml version='1.0' encoding='utf-8'?> <!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!-- Note: A "Server" is not itself a "Container", so you may not define subcomponents such as "Valves" at this level. Documentation at /docs/config/server.html --> <Server port="8005" shutdown="SHUTDOWN"> <Listener className="org.apache.catalina.startup.VersionLoggerListener" /> <!-- Security listener. Documentation at /docs/config/listeners.html <Listener className="org.apache.catalina.security.SecurityListener" /> --> <!--APR library loader. Documentation at /docs/apr.html --> <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> <!-- Prevent memory leaks due to use of particular java/javax APIs--> <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /> <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> <!-- Global JNDI resources Documentation at /docs/jndi-resources-howto.html --> <GlobalNamingResources> <!-- Editable user database that can also be used by UserDatabaseRealm to authenticate users --> <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" /> </GlobalNamingResources> <!-- A "Service" is a collection of one or more "Connectors" that share a single "Container" Note: A "Service" is not itself a "Container", so you may not define subcomponents such as "Valves" at this level. Documentation at /docs/config/service.html --> <Service name="Catalina"> <!--The connectors can use a shared executor, you can define one or more named thread pools--> <!-- <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/> --> <!-- A "Connector" represents an endpoint by which requests are received and responses are returned. Documentation at : Java HTTP Connector: /docs/config/http.html (blocking & non-blocking) Java AJP Connector: /docs/config/ajp.html APR (HTTP/AJP) Connector: /docs/apr.html Define a non-SSL/TLS HTTP/1.1 Connector on port 8080 --> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <!-- A "Connector" using the shared thread pool--> <!-- <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> --> <!-- Define a SSL/TLS HTTP/1.1 Connector on port 8443 This connector uses the NIO implementation that requires the JSSE style configuration. When using the APR/native implementation, the OpenSSL style configuration is required as described in the APR/native documentation --> <!-- <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" SSLEnabled="true" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" /> --> <!-- Define an AJP 1.3 Connector on port 8009 --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> <!-- An Engine represents the entry point (within Catalina) that processes every request. The Engine implementation for Tomcat stand alone analyzes the HTTP headers included with the request, and passes them on to the appropriate Host (virtual host). Documentation at /docs/config/engine.html --> <!-- You should set jvmRoute to support load-balancing via AJP ie : <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1"> --> <Engine name="Catalina" defaultHost="localhost"> <!--For clustering, please take a look at documentation at: /docs/cluster-howto.html (simple how to) /docs/config/cluster.html (reference documentation) --> <!-- <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/> --> <!-- Use the LockOutRealm to prevent attempts to guess user passwords via a brute-force attack --> <Realm className="org.apache.catalina.realm.LockOutRealm"> <!-- This Realm uses the UserDatabase configured in the global JNDI resources under the key "UserDatabase". Any edits that are performed against this UserDatabase are immediately available for use by the Realm. --> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <!-- SingleSignOn valve, share authentication between web applications Documentation at: /docs/config/valve.html --> <!-- <Valve className="org.apache.catalina.authenticator.SingleSignOn" /> --> <!-- Access log processes all example. Documentation at: /docs/config/valve.html Note: The pattern used is equivalent to using pattern="common" --> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="${my.pod.name}_${my.pod.namespace}_localhost-access-log" suffix=".log" pattern="%h %l %u %t &quot;%r&quot; %s %b" /> </Host> </Engine> </Service> </Server> catalina.sh: 通过容器传入的变量在tomcat中并不能直接使用,需要在tomcat的启动脚本中,在java的启动命令中加上-D选项。找到所有有-Dcatalina.base="\"$CATALINA_BASE\"" 的地方加上 -Dmy.pod.name="$MY_POD_NAME" -Dmy.pod.namespace="$MY_POD_NAMESPACE" #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ----------------------------------------------------------------------------- # Control Script for the CATALINA Server # # Environment Variable Prerequisites # # Do not set the variables in this script. Instead put them into a script # setenv.sh in CATALINA_BASE/bin to keep your customizations separate. # # CATALINA_HOME May point at your Catalina "build" directory. # # CATALINA_BASE (Optional) Base directory for resolving dynamic portions # of a Catalina installation. If not present, resolves to # the same directory that CATALINA_HOME points to. # # CATALINA_OUT (Optional) Full path to a file where stdout and stderr # will be redirected. # Default is $CATALINA_BASE/logs/catalina.out # # CATALINA_OPTS (Optional) Java runtime options used when the "start", # "run" or "debug" command is executed. # Include here and not in JAVA_OPTS all options, that should # only be used by Tomcat itself, not by the stop process, # the version command etc. # Examples are heap size, GC logging, JMX ports etc. # # CATALINA_TMPDIR (Optional) Directory path location of temporary directory # the JVM should use (java.io.tmpdir). Defaults to # $CATALINA_BASE/temp. # # JAVA_HOME Must point at your Java Development Kit installation. # Required to run the with the "debug" argument. # # JRE_HOME Must point at your Java Runtime installation. # Defaults to JAVA_HOME if empty. If JRE_HOME and JAVA_HOME # are both set, JRE_HOME is used. # # JAVA_OPTS (Optional) Java runtime options used when any command # is executed. # Include here and not in CATALINA_OPTS all options, that # should be used by Tomcat and also by the stop process, # the version command etc. # Most options should go into CATALINA_OPTS. # # JAVA_ENDORSED_DIRS (Optional) Lists of of colon separated directories # containing some jars in order to allow replacement of APIs # created outside of the JCP (i.e. DOM and SAX from W3C). # It can also be used to update the XML parser implementation. # Defaults to $CATALINA_HOME/endorsed. # # JPDA_TRANSPORT (Optional) JPDA transport used when the "jpda start" # command is executed. The default is "dt_socket". # # JPDA_ADDRESS (Optional) Java runtime options used when the "jpda start" # command is executed. The default is localhost:8000. # # JPDA_SUSPEND (Optional) Java runtime options used when the "jpda start" # command is executed. Specifies whether JVM should suspend # execution immediately after startup. Default is "n". # # JPDA_OPTS (Optional) Java runtime options used when the "jpda start" # command is executed. If used, JPDA_TRANSPORT, JPDA_ADDRESS, # and JPDA_SUSPEND are ignored. Thus, all required jpda # options MUST be specified. The default is: # # -agentlib:jdwp=transport=$JPDA_TRANSPORT, # address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND # # JSSE_OPTS (Optional) Java runtime options used to control the TLS # implementation when JSSE is used. Default is: # "-Djdk.tls.ephemeralDHKeySize=2048" # # CATALINA_PID (Optional) Path of the file which should contains the pid # of the catalina startup java process, when start (fork) is # used # # LOGGING_CONFIG (Optional) Override Tomcat's logging config file # Example (all one line) # LOGGING_CONFIG="-Djava.util.logging.config.file=$CATALINA_BASE/conf/logging.properties" # # LOGGING_MANAGER (Optional) Override Tomcat's logging manager # Example (all one line) # LOGGING_MANAGER="-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager" # # USE_NOHUP (Optional) If set to the string true the start command will # use nohup so that the Tomcat process will ignore any hangup # signals. Default is "false" unless running on HP-UX in which # case the default is "true" # ----------------------------------------------------------------------------- # OS specific support. $var _must_ be set to either true or false. cygwin=false darwin=false os400=false hpux=false case "`uname`" in CYGWIN*) cygwin=true;; Darwin*) darwin=true;; OS400*) os400=true;; HP-UX*) hpux=true;; esac # resolve links - $0 may be a softlink PRG="$0" while [ -h "$PRG" ]; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`/"$link" fi done # Get standard environment variables PRGDIR=`dirname "$PRG"` # Only set CATALINA_HOME if not already set [ -z "$CATALINA_HOME" ] && CATALINA_HOME=`cd "$PRGDIR/.." >/dev/null; pwd` # Copy CATALINA_BASE from CATALINA_HOME if not already set [ -z "$CATALINA_BASE" ] && CATALINA_BASE="$CATALINA_HOME" # Ensure that any user defined CLASSPATH variables are not used on startup, # but allow them to be specified in setenv.sh, in rare case when it is needed. CLASSPATH= if [ -r "$CATALINA_BASE/bin/setenv.sh" ]; then . "$CATALINA_BASE/bin/setenv.sh" elif [ -r "$CATALINA_HOME/bin/setenv.sh" ]; then . "$CATALINA_HOME/bin/setenv.sh" fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$JRE_HOME" ] && JRE_HOME=`cygpath --unix "$JRE_HOME"` [ -n "$CATALINA_HOME" ] && CATALINA_HOME=`cygpath --unix "$CATALINA_HOME"` [ -n "$CATALINA_BASE" ] && CATALINA_BASE=`cygpath --unix "$CATALINA_BASE"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi # Ensure that neither CATALINA_HOME nor CATALINA_BASE contains a colon # as this is used as the separator in the classpath and Java provides no # mechanism for escaping if the same character appears in the path. case $CATALINA_HOME in *:*) echo "Using CATALINA_HOME: $CATALINA_HOME"; echo "Unable to start as CATALINA_HOME contains a colon (:) character"; exit 1; esac case $CATALINA_BASE in *:*) echo "Using CATALINA_BASE: $CATALINA_BASE"; echo "Unable to start as CATALINA_BASE contains a colon (:) character"; exit 1; esac # For OS400 if $os400; then # Set job priority to standard for interactive (interactive - 6) by using # the interactive priority - 6, the helper threads that respond to requests # will be running at the same priority as interactive jobs. COMMAND='chgjob job('$JOBNAME') runpty(6)' system $COMMAND # Enable multi threading export QIBM_MULTI_THREADED=Y fi # Get standard Java environment variables if $os400; then # -r will Only work on the os400 if the files are: # 1. owned by the user # 2. owned by the PRIMARY group of the user # this will not work if the user belongs in secondary groups . "$CATALINA_HOME"/bin/setclasspath.sh else if [ -r "$CATALINA_HOME"/bin/setclasspath.sh ]; then . "$CATALINA_HOME"/bin/setclasspath.sh else echo "Cannot find $CATALINA_HOME/bin/setclasspath.sh" echo "This file is needed to run this program" exit 1 fi fi # Add on extra jar files to CLASSPATH if [ ! -z "$CLASSPATH" ] ; then CLASSPATH="$CLASSPATH": fi CLASSPATH="$CLASSPATH""$CATALINA_HOME"/bin/bootstrap.jar if [ -z "$CATALINA_OUT" ] ; then CATALINA_OUT="$CATALINA_BASE"/logs/catalina.out fi if [ -z "$CATALINA_TMPDIR" ] ; then # Define the java.io.tmpdir to use for Catalina CATALINA_TMPDIR="$CATALINA_BASE"/temp fi # Add tomcat-juli.jar to classpath # tomcat-juli.jar can be over-ridden per instance if [ -r "$CATALINA_BASE/bin/tomcat-juli.jar" ] ; then CLASSPATH=$CLASSPATH:$CATALINA_BASE/bin/tomcat-juli.jar else CLASSPATH=$CLASSPATH:$CATALINA_HOME/bin/tomcat-juli.jar fi # Bugzilla 37848: When no TTY is available, don't output to console have_tty=0 if [ "`tty`" != "not a tty" ]; then have_tty=1 fi # For Cygwin, switch paths to Windows format before running java if $cygwin; then JAVA_HOME=`cygpath --absolute --windows "$JAVA_HOME"` JRE_HOME=`cygpath --absolute --windows "$JRE_HOME"` CATALINA_HOME=`cygpath --absolute --windows "$CATALINA_HOME"` CATALINA_BASE=`cygpath --absolute --windows "$CATALINA_BASE"` CATALINA_TMPDIR=`cygpath --absolute --windows "$CATALINA_TMPDIR"` CLASSPATH=`cygpath --path --windows "$CLASSPATH"` JAVA_ENDORSED_DIRS=`cygpath --path --windows "$JAVA_ENDORSED_DIRS"` fi if [ -z "$JSSE_OPTS" ] ; then JSSE_OPTS="-Djdk.tls.ephemeralDHKeySize=2048" fi JAVA_OPTS="$JAVA_OPTS $JSSE_OPTS" # Set juli LogManager config file if it is present and an override has not been issued if [ -z "$LOGGING_CONFIG" ]; then if [ -r "$CATALINA_BASE"/conf/logging.properties ]; then LOGGING_CONFIG="-Djava.util.logging.config.file=$CATALINA_BASE/conf/logging.properties" else # Bugzilla 45585 LOGGING_CONFIG="-Dnop" fi fi if [ -z "$LOGGING_MANAGER" ]; then LOGGING_MANAGER="-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager" fi # Uncomment the following line to make the umask available when using the # org.apache.catalina.security.SecurityListener #JAVA_OPTS="$JAVA_OPTS -Dorg.apache.catalina.security.SecurityListener.UMASK=`umask`" if [ -z "$USE_NOHUP" ]; then if $hpux; then USE_NOHUP="true" else USE_NOHUP="false" fi fi unset _NOHUP if [ "$USE_NOHUP" = "true" ]; then _NOHUP=nohup fi # ----- Execute The Requested Command ----------------------------------------- # Bugzilla 37848: only output this if we have a TTY if [ $have_tty -eq 1 ]; then echo "Using CATALINA_BASE: $CATALINA_BASE" echo "Using CATALINA_HOME: $CATALINA_HOME" echo "Using CATALINA_TMPDIR: $CATALINA_TMPDIR" if [ "$1" = "debug" ] ; then echo "Using JAVA_HOME: $JAVA_HOME" else echo "Using JRE_HOME: $JRE_HOME" fi echo "Using CLASSPATH: $CLASSPATH" if [ ! -z "$CATALINA_PID" ]; then echo "Using CATALINA_PID: $CATALINA_PID" fi fi if [ "$1" = "jpda" ] ; then if [ -z "$JPDA_TRANSPORT" ]; then JPDA_TRANSPORT="dt_socket" fi if [ -z "$JPDA_ADDRESS" ]; then JPDA_ADDRESS="localhost:8000" fi if [ -z "$JPDA_SUSPEND" ]; then JPDA_SUSPEND="n" fi if [ -z "$JPDA_OPTS" ]; then JPDA_OPTS="-agentlib:jdwp=transport=$JPDA_TRANSPORT,address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND" fi CATALINA_OPTS="$JPDA_OPTS $CATALINA_OPTS" shift fi if [ "$1" = "debug" ] ; then if $os400; then echo "Debug command not available on OS400" exit 1 else shift if [ "$1" = "-security" ] ; then if [ $have_tty -eq 1 ]; then echo "Using Security Manager" fi shift exec "$_RUNJDB" "$LOGGING_CONFIG" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="$JAVA_ENDORSED_DIRS" -classpath "$CLASSPATH" \ -sourcepath "$CATALINA_HOME"/../../java \ -Djava.security.manager \ -Djava.security.policy=="$CATALINA_BASE"/conf/catalina.policy \ -Dmy.pod.name="$MY_POD_NAME" \ -Dmy.pod.namespace="$MY_POD_NAMESPACE" \ -Dcatalina.base="$CATALINA_BASE" \ -Dcatalina.home="$CATALINA_HOME" \ -Djava.io.tmpdir="$CATALINA_TMPDIR" \ org.apache.catalina.startup.Bootstrap "$@" start else exec "$_RUNJDB" "$LOGGING_CONFIG" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="$JAVA_ENDORSED_DIRS" -classpath "$CLASSPATH" \ -sourcepath "$CATALINA_HOME"/../../java \ -Dmy.pod.name="$MY_POD_NAME" \ -Dmy.pod.namespace="$MY_POD_NAMESPACE" \ -Dcatalina.base="$CATALINA_BASE" \ -Dcatalina.home="$CATALINA_HOME" \ -Djava.io.tmpdir="$CATALINA_TMPDIR" \ org.apache.catalina.startup.Bootstrap "$@" start fi fi elif [ "$1" = "run" ]; then shift if [ "$1" = "-security" ] ; then if [ $have_tty -eq 1 ]; then echo "Using Security Manager" fi shift eval exec "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Djava.security.manager \ -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \ -Dmy.pod.name="$MY_POD_NAME" \ -Dmy.pod.namespace="$MY_POD_NAMESPACE" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start else eval exec "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Dmy.pod.name="$MY_POD_NAME" \ -Dmy.pod.namespace="$MY_POD_NAMESPACE" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start fi elif [ "$1" = "start" ] ; then if [ ! -z "$CATALINA_PID" ]; then if [ -f "$CATALINA_PID" ]; then if [ -s "$CATALINA_PID" ]; then echo "Existing PID file found during start." if [ -r "$CATALINA_PID" ]; then PID=`cat "$CATALINA_PID"` ps -p $PID >/dev/null 2>&1 if [ $? -eq 0 ] ; then echo "Tomcat appears to still be running with PID $PID. Start aborted." echo "If the following process is not a Tomcat process, remove the PID file and try again:" ps -f -p $PID exit 1 else echo "Removing/clearing stale PID file." rm -f "$CATALINA_PID" >/dev/null 2>&1 if [ $? != 0 ]; then if [ -w "$CATALINA_PID" ]; then cat /dev/null > "$CATALINA_PID" else echo "Unable to remove or clear stale PID file. Start aborted." exit 1 fi fi fi else echo "Unable to read PID file. Start aborted." exit 1 fi else rm -f "$CATALINA_PID" >/dev/null 2>&1 if [ $? != 0 ]; then if [ ! -w "$CATALINA_PID" ]; then echo "Unable to remove or write to empty PID file. Start aborted." exit 1 fi fi fi fi fi shift touch "$CATALINA_OUT" if [ "$1" = "-security" ] ; then if [ $have_tty -eq 1 ]; then echo "Using Security Manager" fi shift eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Djava.security.manager \ -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \ -Dmy.pod.name="$MY_POD_NAME" \ -Dmy.pod.namespace="$MY_POD_NAMESPACE" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start \ >> "$CATALINA_OUT" 2>&1 "&" else eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Dmy.pod.name="$MY_POD_NAME" \ -Dmy.pod.namespace="$MY_POD_NAMESPACE" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start \ >> "$CATALINA_OUT" 2>&1 "&" fi if [ ! -z "$CATALINA_PID" ]; then echo $! > "$CATALINA_PID" fi echo "Tomcat started." elif [ "$1" = "stop" ] ; then shift SLEEP=5 if [ ! -z "$1" ]; then echo $1 | grep "[^0-9]" >/dev/null 2>&1 if [ $? -gt 0 ]; then SLEEP=$1 shift fi fi FORCE=0 if [ "$1" = "-force" ]; then shift FORCE=1 fi if [ ! -z "$CATALINA_PID" ]; then if [ -f "$CATALINA_PID" ]; then if [ -s "$CATALINA_PID" ]; then kill -0 `cat "$CATALINA_PID"` >/dev/null 2>&1 if [ $? -gt 0 ]; then echo "PID file found but no matching process was found. Stop aborted." exit 1 fi else echo "PID file is empty and has been ignored." fi else echo "\$CATALINA_PID was set but the specified file does not exist. Is Tomcat running? Stop aborted." exit 1 fi fi eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Dmy.pod.name="$MY_POD_NAME" \ -Dmy.pod.namespace="$MY_POD_NAMESPACE" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" stop # stop failed. Shutdown port disabled? Try a normal kill. if [ $? != 0 ]; then if [ ! -z "$CATALINA_PID" ]; then echo "The stop command failed. Attempting to signal the process to stop through OS signal." kill -15 `cat "$CATALINA_PID"` >/dev/null 2>&1 fi fi if [ ! -z "$CATALINA_PID" ]; then if [ -f "$CATALINA_PID" ]; then while [ $SLEEP -ge 0 ]; do kill -0 `cat "$CATALINA_PID"` >/dev/null 2>&1 if [ $? -gt 0 ]; then rm -f "$CATALINA_PID" >/dev/null 2>&1 if [ $? != 0 ]; then if [ -w "$CATALINA_PID" ]; then cat /dev/null > "$CATALINA_PID" # If Tomcat has stopped don't try and force a stop with an empty PID file FORCE=0 else echo "The PID file could not be removed or cleared." fi fi echo "Tomcat stopped." break fi if [ $SLEEP -gt 0 ]; then sleep 1 fi if [ $SLEEP -eq 0 ]; then echo "Tomcat did not stop in time." if [ $FORCE -eq 0 ]; then echo "PID file was not removed." fi echo "To aid diagnostics a thread dump has been written to standard out." kill -3 `cat "$CATALINA_PID"` fi SLEEP=`expr $SLEEP - 1 ` done fi fi KILL_SLEEP_INTERVAL=5 if [ $FORCE -eq 1 ]; then if [ -z "$CATALINA_PID" ]; then echo "Kill failed: \$CATALINA_PID not set" else if [ -f "$CATALINA_PID" ]; then PID=`cat "$CATALINA_PID"` echo "Killing Tomcat with the PID: $PID" kill -9 $PID while [ $KILL_SLEEP_INTERVAL -ge 0 ]; do kill -0 `cat "$CATALINA_PID"` >/dev/null 2>&1 if [ $? -gt 0 ]; then rm -f "$CATALINA_PID" >/dev/null 2>&1 if [ $? != 0 ]; then if [ -w "$CATALINA_PID" ]; then cat /dev/null > "$CATALINA_PID" else echo "The PID file could not be removed." fi fi echo "The Tomcat process has been killed." break fi if [ $KILL_SLEEP_INTERVAL -gt 0 ]; then sleep 1 fi KILL_SLEEP_INTERVAL=`expr $KILL_SLEEP_INTERVAL - 1 ` done if [ $KILL_SLEEP_INTERVAL -lt 0 ]; then echo "Tomcat has not been killed completely yet. The process might be waiting on some system call or might be UNINTERRUPTIBLE." fi fi fi fi elif [ "$1" = "configtest" ] ; then eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Dmy.pod.name="$MY_POD_NAME" \ -Dmy.pod.namespace="$MY_POD_NAMESPACE" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap configtest result=$? if [ $result -ne 0 ]; then echo "Configuration error detected!" fi exit $result elif [ "$1" = "version" ] ; then "$_RUNJAVA" \ -classpath "$CATALINA_HOME/lib/catalina.jar" \ org.apache.catalina.util.ServerInfo else echo "Usage: catalina.sh ( commands ... )" echo "commands:" if $os400; then echo " debug Start Catalina in a debugger (not available on OS400)" echo " debug -security Debug Catalina with a security manager (not available on OS400)" else echo " debug Start Catalina in a debugger" echo " debug -security Debug Catalina with a security manager" fi echo " jpda start Start Catalina under JPDA debugger" echo " run Start Catalina in the current window" echo " run -security Start in the current window with security manager" echo " start Start Catalina in a separate window" echo " start -security Start in a separate window with security manager" echo " stop Stop Catalina, waiting up to 5 seconds for the process to end" echo " stop n Stop Catalina, waiting up to n seconds for the process to end" echo " stop -force Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running" echo " stop n -force Stop Catalina, wait up to n seconds and then use kill -KILL if still running" echo " configtest Run a basic syntax check on server.xml - check exit code for result" echo " version What version of tomcat are you running?" echo "Note: Waiting for the process to end and use of the -force option require that \$CATALINA_PID is defined" exit 1 fi ok, 制作自己的镜像并上传自己的私有仓库: docker build -t 10.10.50.161:5000/mbn_containers/tomcat8-jre8:v1.0.0 . docker push 10.10.50.161:5000/mbn_containers/tomcat8-jre8:v1.0.0 4: 步骤2 pod定义文件传入pod名称和pod命名空间 在rc的yaml中传入pod名称和pod命名空间变量,并对tomcat的logs目录进行主机数据卷映射: tomcat-controller.yaml: apiVersion: v1 kind: ReplicationController metadata: name: mbn-tomcat namespace: mbn spec: replicas: 2 selector: mbn-app: mbn-tomcat template: metadata: name: mbn-tomcat labels: mbn-app: mbn-tomcat spec: containers: - name: tomcat image: 10.10.50.161:5000/mbn_containers/tomcat8-jre8:v1.0.0 volumeMounts: - name: varlogtomcat8 mountPath: /usr/local/tomcat/logs readOnly: false env: - name: MY_POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: MY_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: MY_POD_IP valueFrom: fieldRef: fieldPath: status.podIP ports: - containerPort: 8080 volumes: - name: varlogtomcat8 hostPath: path: /var/log/tomcat8 ok,结束。运行起来后,在各个k8s的节点主机的/var/log/tomcat8目录下,看到的就是各个pod名称和pod命名空间唯一标识的日志文件了。 本文转自开源中国-在kubernetes上实现tomcat日志的持久化
Kubeflow(https://github.com/kubeflow)是基于Kubernetes(https://kubernets.io,容器编排与管理服务软件)和TensorFlow(https://tensorflow.org,深度学习库)的机器学习流程工具,使用Ksonnet进行应用包的管理。 本文简要介绍Kubeflow的部署和交互操作的基本概念和方法,对于Kubernetes、Tensorflow和Ksonnet 的了解对于本文内容的理解将 很有帮助,点击下面的链接查看相关的内容。 Kubernetes Tensorflow Ksonnet 对部署Kubeflow和运行一个简单的训练任务的手把手教的例子,可以看这个教程( tutorial)。 环境要求 Kubernetes>=1.8, see here,安装攻略 https://my.oschina.net/u/2306127/blog/1628082 ksonnet version 0.9.2. (查看 below 有对为什么使用ksonnet的解释) 部署 Kubeflow 我们将使用Ksonnet来部署kubeflow到Kubernetes集群上,支持本地的和GKE、Azure中的集群。 初始化一个目录,包含有ksonnet application。 ks init my-kubeflow 安装Kubeflow packages到Ksonnet application中。 这里有一些安装脚本,https://github.com/openthings/kubernetes-tools/tree/master/kubeflow # For a list of releases see: # https://github.com/kubeflow/kubeflow/releases VERSION=v0.1.2 cd my-kubeflow ks registry add kubeflow github.com/kubeflow/kubeflow/tree/${VERSION}/kubeflow ks pkg install kubeflow/core@${VERSION} ks pkg install kubeflow/tf-serving@${VERSION} ks pkg install kubeflow/tf-job@${VERSION} 创建Kubeflow core component. 这个core component 包括: JupyterHub TensorFlow job controller ks generate core kubeflow-core --name=kubeflow-core # Enable collection of anonymous usage metrics # Skip this step if you don't want to enable collection. # Or set reportUsage to false (the default). ks param set kubeflow-core reportUsage true ks param set kubeflow-core usageId $(uuidgen) Ksonnet 允许参数化 Kubeflow的部署,可以按照需求设定。我们定义两个环境变量:nocloud和cloud。 ks env add nocloud ks env add cloud 环境变量 nocloud 用于 minikube和其它的标准 k8s clusters,环境变量 cloud 用于GKE和Azure。 如果使用 GKE, 我们配置云计算环境的参数来使用 GCP的特征,如下: ks param set kubeflow-core cloud gke --env=cloud 如果集群创建在 Azure 上,使用 AKS/ACS: ks param set kubeflow-core cloud aks --env=cloud 如果创建时使用acs-engine来代替: ks param set kubeflow-core cloud acsengine --env=cloud 然后我们设置 ${KF_ENV} 为 cloud 或 nocloud ,从而反映我们在本教程中使用的环境。 $ KF_ENV=cloud|nocloud 缺水情况下,Kubeflow没有持久化我们在Jupyter notebook所做的工作。 如果容器被销毁或重新创建,所有的内容,包括 notebooks 和其它的文件都会被删除。 为了持久化这些文件,用户需要一个缺省的 StorageClass,在 persistent volumes 中定义。 可以运行下面的命令来检查是否有了一个 storage class。 kubectl get storageclass 有了缺省的storage class定义的用户,可以使用jupyterNotebookPVCMount参数去创建一个volume,将被挂载到notebook之中。 ks param set kubeflow-core jupyterNotebookPVCMount /home/jovyan/work 这里我们挂载卷到 /home/jovyan/work ,因为notebook一直以用户jovyan来执行。 选中的目录将被存储到集群的缺省存储上(典型地是永久磁盘)。 创建部署的命名空间(namespace)并且设为换的一部分。可以将namespace设为更适合你自己的kubernetes cluster的名称,如下。 NAMESPACE=kubeflow kubectl create namespace ${NAMESPACE} ks env set ${KF_ENV} --namespace ${NAMESPACE} 然后应用该components到我们的Kubernetes cluster。 ks apply ${KF_ENV} -c kubeflow-core 任何时候,可以使用 ks show 探查特定的 ksonnet component在kubernetes的对象定义。 ks show ${KF_ENV} -c kubeflow-core 用法报告(Usage Reporting) 当启用时,Kubeflow将使用 spartakus 报告匿名数据,这是Kubernetes的一个汇报工具。Spartakus不会报告任何个人信息。查看 here 得到更多细节。这是完全志愿的行为,也可以可选将其关闭,如下所示: ks param set kubeflow-core reportUsage false # Delete any existing deployments of spartakus kubectl delete -n ${NAMESPACE} deploy spartakus-volunteer 为了明确开启用法报告,设置 reportUsage 为 true,如下所示: ks param set kubeflow-core reportUsage true # Delete any existing deployments of spartakus kubectl delete -n ${NAMESPACE} deploy spartakus-volunteer 报告数据是你对Kubeflow的显著贡献之一,所以请考虑将其开启。这些数据允许我们改善Kubeflow项目并且帮助Kubeflow上开展工作的企业评估其持续的投资。 你可以改进数据质量,通过给每一个Kubeflow deployment 一个单独的ID。 ks param set kubeflow-core usageId $(uuidgen) 打开 Jupyter Notebook 这里的 kubeflow-core component 部署JupyterHub和对应的load balancer service,查看状态使用下面的 kubectl 命令行: kubectl get svc -n=${NAMESPACE} NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ... tf-hub-0 ClusterIP None <none> 8000/TCP 1m tf-hub-lb ClusterIP 10.11.245.94 <none> 80/TCP 1m ... 缺水情况下,我们使用ClusterIPs来访问JupyterHub UI,在下面情况会有所改变: NodePort (for non-cloud) ,通过指示: ks param set kubeflow-core jupyterHubServiceType NodePort ks apply ${KF_ENV} LoadBalancer (for cloud) ,通过指示: ks param set kubeflow-core jupyterHubServiceType LoadBalancer ks apply ${KF_ENV} 但是,这将使 Jupyter notebook 开放予Internet网络(有潜在的安全风险)。 本地连接到 Jupyter Notebook 可以使用: PODNAME=`kubectl get pods --namespace=${NAMESPACE} --selector="app=tf-hub" --output=template --template="{{with index .items 0}}{{.metadata.name}}{{end}}"` kubectl port-forward --namespace=${NAMESPACE} $PODNAME 8000:8000 然后,到浏览器中打开 http://127.0.0.1:8000,如果设置了代理,需要对该地址关闭 。 将看到一个提示窗口。 使用任何username/password登录。 点击 "Start My Server" 按钮,将会打开一个对话框。 选择镜像为CPU 或 GPU 类型,在 Image一项有菜单列出预构建的Docker镜像。也可以直接输入Tensorflow的镜像名称,用于运行。 分配内存、CPU、GPU和其他的资源,根据需求而定。 (1 CPU 和 2Gi 内存已经是一个好的起点,可以满足初始练习的需要。) 分配 GPUs, 需要确认你的集群中有可用数量的 GPUs,GPU将会被容器实例独占使用,如果资源不够,该实例将会一直挂起,处于Pending状态。 检查是否有足够的nvidia gpus可用: kubectl get nodes "-o=custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu" 如果 GPUs 可用,你可以调度你的服务器到 GPU node,通过指定下面的json, 在 Extra Resource Limits section: {"nvidia.com/gpu": "1"} 点击 Spawn 该镜像将近 10 GBs,下载需要比较长的时间,取决于网络情况。 检查 pod 的状态,通过: kubectl -n ${NAMESPACE} describe pods jupyter-${USERNAME} 这里 ${USERNAME} 是你 login时用到的名称。 GKE users,如果你有 IAP turned on the pod,将会名称有所不同: 如果登陆为像 USER@DOMAIN.EXT, pod 被命名为: jupyter-accounts-2egoogle-2ecom-3USER-40DOMAIN-2eEXT 完成后,将会打开 Jupyter Notebook 初始界面。 上面提供的容器镜像可以用于 Tensorflow models的训练,使用Jupyter即可操作。该镜像包含所有需要的plugins, 包括 Tensorboard,可以用于对模型进行丰富的可视化和探查分析。 未来测试安装情况,我们运行一个基本的hello world应用 (来自 mnist_softmax.py ) from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) import tensorflow as tf x = tf.placeholder(tf.float32, [None, 784]) W = tf.Variable(tf.zeros([784, 10])) b = tf.Variable(tf.zeros([10])) y = tf.nn.softmax(tf.matmul(x, W) + b) y_ = tf.placeholder(tf.float32, [None, 10]) cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1])) train_step = tf.train.GradientDescentOptimizer(0.05).minimize(cross_entropy) sess = tf.InteractiveSession() tf.global_variables_initializer().run() for _ in range(1000): batch_xs, batch_ys = mnist.train.next_batch(100) sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys}) correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})) 粘贴上面的例子到新的 Python 3 Jupyter notebook,然后 shift+enter 执行代码。这里会得到基于测试数据的 0.9014 结果精度。 需要注意的是,运行大多数 cloud providers时,public IP address 将会暴露到internet,并且缺省是没有安全控制的endpoint。为了产品级部署,需要使用 SSL 和 authentication, 参考 documentation。 使用TensorFlow Serving提供model服务 我们将每一个部署的模型都作为APP中的 component 。 在云计算中创建一个模型的component: MODEL_COMPONENT=serveInception MODEL_NAME=inception MODEL_PATH=gs://kubeflow-models/inception ks generate tf-serving ${MODEL_COMPONENT} --name=${MODEL_NAME} ks param set ${MODEL_COMPONENT} modelPath ${MODEL_PATH} (或者) 创建一个model的component 在 nfs 上,了解和参考 components/k8s-model-server,如下: MODEL_COMPONENT=serveInceptionNFS MODEL_NAME=inception-nfs MODEL_PATH=/mnt/var/nfs/general/inception MODEL_STORAGE_TYPE=nfs NFS_PVC_NAME=nfs ks generate tf-serving ${MODEL_COMPONENT} --name=${MODEL_NAME} ks param set ${MODEL_COMPONENT} modelPath ${MODEL_PATH} ks param set ${MODEL_COMPONENT} modelStorageType ${MODEL_STORAGE_TYPE} ks param set ${MODEL_COMPONENT} nfsPVC ${NFS_PVC_NAME} 部署model component。Ksonnet将选择你的环境中的已存在的参数 (e.g. cloud, nocloud),然后定制化结果部署为合适的: ks apply ${KF_ENV} -c ${MODEL_COMPONENT} 之前, 一些pods和services已经创建在你的集群中。你可以查询kubernetes得到服务endpoint: kubectl get svc inception -n=${NAMESPACE} NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ... inception LoadBalancer 10.35.255.136 ww.xx.yy.zz 9000:30936/TCP 28m ... 在这里,你可以使用的 inception_client 为 ww.xx.yy.zz:9000 在gs://kubeflow-models/inception 的model 是可以公开访问的。但是,如果你的环境没有配置google cloud credential,TF serving 将无法读取model,查看 issue 获取样本。为了设置google cloud credential,你需要环境变量 GOOGLE_APPLICATION_CREDENTIALS 指向credential 文件,或者运行 gcloud auth login. 查看 doc 获取更多详细说明。 通过 Seldon 服务 model Seldon-core 提供了任意机器学习运行时的部署,将其 packaged in a Docker container。 安装seldon package: ks pkg install kubeflow/seldon 创建 core components: ks generate seldon seldon Seldon 允许复杂的 runtime graphs用于模型推理的部署。一个 end-to-end整合的例子参见 kubeflow-seldon example。更多的细节参考 seldon-core documentation。 提交一个TensorFlow训练任务 注意:在提交训练任务之前,你首先需要有一个 deployed kubeflow to your cluster。提交训练任务时,首先确认 TFJob custom resource 是可用的。 我们将每一个TensorFlow job作为APP中的 component 看待。 A、创建工作任务 为训练任务创建 component: JOB_NAME=myjob ks generate tf-job ${JOB_NAME} --name=${JOB_NAME} 为了配置这个 job需要设置一系列的参数。为了查看参数列表,运行: ks prototype describe tf-job 参数设置使用 ks param ,设置 Docker image 使用: IMAGE=gcr.io/tf-on-k8s-dogfood/tf_sample:d4ef871-dirty-991dde4 ks param set ${JOB_NAME} image ${IMAGE} 你可以编辑 params.libsonnet 文件,直接设置参数。 警告 由于escaping序列问题,目前命令行的设置参数不能工作 (参见 ksonnet/ksonnet/issues/235)。因此,设置参数需要直接编辑 params.libsonnet 文件。 B、运行工作任务 ks apply ${KF_ENV} -c ${JOB_NAME} C、监视工作任务 监视任务执行情况,参见 TfJob docs. D、删除工作任务 ks delete ${KF_ENV} -c ${JOB_NAME} 运行例程-TfCnn Kubeflow 附带了一个 ksonnet prototype ,适合运行 TensorFlow CNN Benchmarks。 创建component: CNN_JOB_NAME=mycnnjob ks generate tf-cnn ${CNN_JOB_NAME} --name=${CNN_JOB_NAME} 提交任务: ks apply ${KF_ENV} -c ${CNN_JOB_NAME} 查看运行情况 (注意 tf-cnn job 也是 tfjobs. 参考 TfJob docs) kubectl get -o yaml tfjobs ${CNN_JOB_NAME} 删除任务: ks delete ${KF_ENV} -c ${CNN_JOB_NAME} 该 prototype提供了一系列参数控制任务的运行 (如使用 GPUs,分布式运行等...)。查看参数运行: ks prototype describe tf-cnn 提交 PyTorch 训练任务 注意:在提交任务之前,你需要有一个部署好Kubeflow的集群(参见 deployed kubeflow to your cluster)。提交,确保 PyTorchJob custom resource 可用。 我们将每一个PyTorch任务看作为APP中的 component 。 为工作任务创建一个 component。 JOB_NAME=myjob ks generate pytorch-job ${JOB_NAME} --name=${JOB_NAME} 为了配置工作任务,需要设置一系列的参数。 显示参数使用: ks prototype describe pytorch-job 参数设置使用 ks param ,设置Docker image 使用: IMAGE=<your pytorch image> ks param set ${JOB_NAME} image ${IMAGE} 也可以编辑文件 params.libsonnet 来直接设置参数。 警告 由于escaping序列问题,目前命令行的设置参数不能工作 (参见 ksonnet/ksonnet/issues/235)。因此,设置参数需要直接编辑 params.libsonnet 文件。 运行工作任务: ks apply ${KF_ENV} -c ${JOB_NAME} 删除工作任务: ks delete ${KF_ENV} -c ${JOB_NAME} 高级定制 数据科学家经常要求一个 POSIX 兼容的文件系统: 例如,大多数HDF5 libraries 要求 POSIX,对于GCS或S3的object store无法工作。 当共享 POSIX 文件系统被挂载到 notebook 环境,数据科学家可以在同一数据集上协同工作。 这里将展示如何部署Kubeflow来达到这个要求。 设置磁盘参数,以分号隔开,设置你想要挂载的 Google persistent disks。 这些磁盘必须在你的集群的同一个 zone 上。 这些磁盘需要通过 gcloud 或 Cloud console手动创建。 这些磁盘不能被引用到任何已存在的 VM 或 POD上。 创建磁盘: gcloud --project=${PROJECT} compute disks create --zone=${ZONE} ${PD_DISK1} --description="PD to back NFS storage on GKE." --size=1TB gcloud --project=${PROJECT} compute disks create --zone=${ZONE} ${PD_DISK2} --description="PD to back NFS storage on GKE." --size=1TB 配置环境来使用这些磁盘: ks param set --env=cloud kubeflow-core disks ${PD_DISK1},${PD_DISK2} 部署环境。 ks apply cloud 启动Juptyer,你将可以看见你的 NFS volumes 挂载为 /mnt/${DISK_NAME}。在Juptyer cell中运行: !df 将看到如下的输出: https://github.com/jlewi/deepvariant_on_k8s Filesystem 1K-blocks Used Available Use% Mounted on overlay 98884832 8336440 90532008 9% / tmpfs 15444244 0 15444244 0% /dev tmpfs 15444244 0 15444244 0% /sys/fs/cgroup 10.11.254.34:/export/pvc-d414c86a-e0db-11e7-a056-42010af00205 1055841280 77824 1002059776 1% /mnt/jlewi-kubeflow-test1 10.11.242.82:/export/pvc-33f0a5b3-e0dc-11e7-a056-42010af00205 1055841280 77824 1002059776 1% /mnt/jlewi-kubeflow-test2 /dev/sda1 98884832 8336440 90532008 9% /etc/hosts shm 65536 0 65536 0% /dev/shm tmpfs 15444244 0 15444244 0% /sys/firmware 这里 jlewi-kubeflow-test1 和 jlewi-kubeflow-test2 是 PDs的名称。 问题解决 Minikube 在 Minikube ,Virtualbox/VMware drivers是已知在 KVM/KVM2 driver 和 TensorFlow Serving之间的问题. 该问题跟踪在 kubernetes/minikube#2377。 我们建议增加 Minikube分配的资源总量,如下: minikube start --cpus 4 --memory 8096 --disk-size=40g Minikube 缺省分配 2048Mb RAM给虚拟机,对于 JupyterHub是不够的。 最大的磁盘容量需要满足 Kubeflow's Jupyter images,包含额外的库超过10G以上。 如果遇到jupyter-xxxx pod 进入Pending 状态,获取描述信息: Warning FailedScheduling 8s (x22 over 5m) default-scheduler 0/1 nodes are available: 1 Insufficient memory. 然后尝试重新创建 Minikube cluster (重新使用Ksonnet应用 Kubeflow) ,并指定更多的资源。 RBAC clusters 如果你运行的集群开启了RBAC(参考 RBAC enabled),,运行Kubeflow可能遇到如下的错误: ERROR Error updating roles kubeflow-test-infra.jupyter-role: roles.rbac.authorization.k8s.io "jupyter-role" is forbidden: attempt to grant extra privileges: [PolicyRule{Resources:["*"], APIGroups:["*"], Verbs:["*"]}] user=&{your-user@acme.com [system:authenticated] map[]} ownerrules=[PolicyRule{Resources:["selfsubjectaccessreviews"], APIGroups:["authorization.k8s.io"], Verbs:["create"]} PolicyRule{NonResourceURLs:["/api" "/api/*" "/apis" "/apis/*" "/healthz" "/swagger-2.0.0.pb-v1" "/swagger.json" "/swaggerapi" "/swaggerapi/*" "/version"], Verbs:["get"]}] ruleResolutionErrors=[] 该错误指示没有足够的权限。在大多数情况下,解决这个问题通过创建合适的 clusterrole binding 然后重新部署kubeflow: kubectl create clusterrolebinding default-admin --clusterrole=cluster-admin --user=your-user@acme.com 替换 your-user@acme.com 为在错误信息提示的用户名。 如果你使用 GKE, 你可以参考 GKE's RBAC docs 去了解如何设置 RBAC,通过 IAM on GCP来实现。 spawning Jupyter pods的问题 如果你 spawning jupyter notebooks遇到麻烦,检查该 pod 是否已经被调度运行: kubectl -n ${NAMESPACE} get pods 查看启动juypter的pod名称。 如果使用username/password auth,Jupyter pod 将被命名: jupyter-${USERNAME} 如果你使用 IAP on GKE,pod 将被命名为: jupyter-accounts-2egoogle-2ecom-3USER-40DOMAIN-2eEXT 这里 USER@DOMAIN.EXT 是在使用IAP时的Google account。 一旦你知道pod的名称: kubectl -n ${NAMESPACE} describe pods ${PODNAME} 查看events ,可以看到试图schedule pod的错误原因。 无法schedule pod的常见原因是集群上没有足够的资源可用。 OpenShift 如果部署 Kubeflow 在 OpenShift 环境( 是对 Kubernetes的封装),你需要调整 security contexts,为了ambassador 和 jupyter-hub 部署的运行。 oc adm policy add-scc-to-user anyuid -z ambassador oc adm policy add-scc-to-user anyuid -z jupyter-hub 一旦安全策略设置好,你需要删除失败的pods 然后允许在 project deployment时可以重新创建。 你需要调整 tf-job-operator service 张好的权限,以使TFJobs能够运行。如下运行一个TFJobs: oc adm policy add-role-to-user cluster-admin -z tf-job-operator Docker for Mac Docker for Mac 社区版带有Kubernetes支持 (1.9.2) ,可以从edge channel启用。如果决定私用 Kubernetes environment on Mac,部署 Kubeflow时可能遇到如下的问题: ks apply default -c kubeflow-core ERROR Attempting to deploy to environment 'default' at 'https://127.0.0.1:8443', but cannot locate a server at that address 该错误是因为Docker for Mac安装时设置的缺省集群为 https://localhost:6443.,一个选项是直接编辑创建的 environments/default/spec.json 文件设置 "server" 变量为正确的位置,然后重试部署。不过,更好的方式是使用希望的kube config来创建Ksonnet app。 kubectl config use-context docker-for-desktop ks init my-kubeflow 403 API rate limit 超出错误 因为 ksonnet 使用 Github 拉取 kubeflow,除非用户指定Github API token,将会快速消耗最大的 API 匿名调用限量,为了解决该问题,可以创建 Github API token,参考这里 guide,,然后将该token 赋给GITHUB_TOKEN 环境变量。 export GITHUB_TOKEN=<< token >> ks apply 产生错误 "Unknown variable: env" Kubeflow 要求版本 0.9.2 或更高,查看 see here。如果你运行 ks apply 使用的老版本ksonnet,将得到错误 Unknown variable: env ,如下所示: ks apply ${KF_ENV} -c kubeflow-core ERROR Error reading /Users/xxx/projects/devel/go/src/github.com/kubeflow/kubeflow/my-kubeflow/environments/nocloud/main.jsonnet: /Users/xxx/projects/devel/go/src/github.com/kubeflow/kubeflow/my-kubeflow/components/kubeflow-core.jsonnet:8:49-52 Unknown variable: env namespace: if params.namespace == "null" then env.namespace else params.namespace 检查ksonnet 版本,如下: ks version 如果 ksonnet版本低于 v0.9.2, 请升级并按照 user_guide 重新创建app。 为什么 Kubeflow 要使用 Ksonnet? Ksonnet 是一个命令行工具,使管理包含多个部件的复杂部署变得更为容易,设计为与kubectl各自完成特定的操作。 Ksonnet 允许我们从参数化的模版中创建 Kubernetes manifests。这使参数化 Kubernetes manifests 用于特定的场景变得容易。在上面的例子中,我们为 TfServing 创建了manifests,为model提供用户化的URI。 我蛮喜欢ksonnet的一个原因是,对待 environment (如dev, test, staging, prod) 作为头等的概念。对于每一个环境,我们部署同样的 components只需要对特定环境有一些很小的定制化修改。我们认为这对于通常的工作流来说是非常友好的一种映射。例如,该特征让在本地没有GPU的情况下运行任务,使代码运行通过,然后将其移到带有大量GPU可规模伸缩的云计算环境之中。 本文转自开源中国-Kubeflow 使用指南
条分缕析带你充分理解Kubernetes的各个细节与部分:它是什么,它如何解决 容器编排问题,它包含哪些你必须掌握的关键对象,以及如何快速上手部署使用Kubernetes。 容器的好处不胜枚举:一致的运行时环境、节省磁盘空间、低开销、良好的隔离性,等等。了解完这些优势,您以及您的同事可能都开始跃跃欲试要把应用程序打包到容器中并准备运行它。然后突然之间或许您会发现,容器运行起来之后有一些问题也接踵而来,您需要一种方法来管理所有正在运行的容器及其生命周期:它们如何相互连接,它们应该运行在什么硬件之上,它们如何获取数据存储,容器因各种原因停止运行的话您该如何处理错误······ 这就是Kubernetes大显身手的地方了。 在本文中,我们将了解Kubernetes是什么,它如何解决容器编排问题,它背后是由哪些理论支撑,如何将该理论直接与实际操作绑定,最终帮助您充分理解Kubernetes的各个细节与部分。 Kubernetes: 历史 Kubernetes,也被称为k8s(k... 8个字母...和s)或kube,是希腊语中的单词,意为州长、舵手或船长。拿真正的航海的情景来理解,大型船舶装载着大量现实生活的容器,而船长或舵手则是负责船舶的人。因此,在信息技术的语境下,Kubernetes就是Docker容器的船长、编排者。 Kubernetes最初是谷歌公司在内部使用的,基于谷歌运行容器15年的经验,Kubernetes于2014年开始作为Google的开源项目提供给社区。四年过去,Kubernetes飞速发展,下载及使用量惊人,被大量的中小型企业用户用于开发或生产环境,并已成为业界公认的容器编排管理的标准框架。 Kubernetes的发展势头 Kubernetes的增长态势惊人,在GitHub上拥有超过40,000颗星,在2018年拥有超过60,000个commit,并且比GitHub上的任何其他项目都有更多的pull request和issue。其增长背后的部分原因是其不凡的可扩展性和强大的设计模式,这些我们将在后文中进一步解读。您可以在此链接了解一些大型软件公司的Kubernetes应用案例: https://kubernetes.io/case-studies/ Kubernetes提供的服务 让我们来看看是什么功能及特性让Kubernetes吸引到业界的如此关注。 Kubernetes的核心是以容器为中心的管理环境。它代表用户的工作负载来编排计算、网络和存储基础架构。这提供了平台即服务(PaaS)的简单性和基础架构即服务(IaaS)的灵活性,并实现了跨基础架构提供商的可移植性。Kubernetes不仅仅是一个编排系统。实际上,它让用户不再需要“编排”。“编排”的技术定义是“执行定义的工作流程”:首先执行A,然后运行B,然后运行C。而有了Kubernetes之后,Kubernetes由一组独立的、可组合的控制流程组成,这些流程可将当前状态持续推向所需状态。而你是如何从A进行到C的,在此无关紧要。而且,用户也不再需要集中控制,整个系统也变得更易于使用、功能更强大、可扩展性更佳。 Kubernetes的基本概念 要使用Kubernetes,您可以使用Kubernetes API对象来描述集群的所需状态:您想要运行的应用程序或服务,它们使用的容器镜像、副本数量,您希望提供的网络和磁盘资源等等。您可以通过使用Kubernetes API——kubectl(通常通过命令行界面)创建对象来设置所需的状态。您还可以直接使用Kubernetes API与集群交互,并设置或修改所需的状态。 设置完所需状态后,Kubernetes控制面板会让集群的当前状态与所需状态相匹配。为此,Kubernetes会自动执行各种任务,例如启动或重新启动容器、扩展给定应用程序的副本数量等等。 基本的Kubernetes对象包括: - 节点 - Pod - 服务 - 卷 - 命名空间 此外,Kubernetes包含许多称为controller的高级抽象。controller基于基本对象构建,并提供其他功能和便利的特性。它们包括: ReplicaSet Deployment StatefulSet DaemonSet Job 下文中我们会逐个介绍这些概念,然后再尝试一些动手练习。 节 点 节点( Node)是Kubernetes中的worker machine,以前称为minion。节点可以是虚拟机(VM)或物理机(具体取决于集群)。每个节点都包含运行pod所需的服务,并由主组件管理。你可以这样理解节点:节点对于pod就像是Hypervisor对于虚拟机。 Pod Pod是Kubernetes的基本构建块,它是您创建或部署的Kubernetes对象模型中最小和最简单的单元。一个Pod代表着一个部署单元:Kubernetes中的单个应用程序实例,可能包含单个容器或少量紧密组合并共享资源的容器。 Docker是Kubernetes Pod中最常用的容器运行时,但Pods也支持其他容器运行时。 Kubernetes集群中的Pod主要以两种方式使用:第一种是运行单个容器的Pod 。“one-container-per-Pod”模式是最常见的Kubernetes用例; 在这种情况下,您可以将Pod视为单个容器的打包,由Kubernetes而非容器来管理 Pods。第二种是运行多个需要协同工作的容器的Pod。一个Pod可能包含了一个应用程序,这个应用程序是由多个紧密耦合并且需要共享资源的容器组成的。这些共存的容器可能形成一个单一的内聚服务单元——一个容器负责从共享卷将文件公开,而另一个单独的“sidecar”容器负责刷新或更新这些文件。该Pod 将这些容器和存储资源一起封装为一个可管理的实体。 Pod为其组成容器提供两种共享资源:网络和存储。 网络:每个Pod都分配了一个唯一的IP地址。一个Pod中的所有容器都共享网络命名空间,包括IP地址和网络端口。Pod内的容器可以使用localhost相互通信。当Pod内的容器与Pod外的实体通信时,它们必须协调如何使用共享网络资源(例如端口)。 存储:Pod可以指定一组共享存储卷。Pod中的所有容器都可以访问共享卷,允许这些容器共享数据。如果需要重新启动其中一个容器,卷还可以让Pod中的持久数据一直保存着。 服 务 Kubernetes Pods不是不变的,它们被创建又被杀死后并不会重生。即使每个Pod都有自己的IP地址,你也不能完全指望它会随着时间推移却永不改变。这会产生一个问题,如果一组Pods(比如说后端)为Kubernetes集群中的另一组Pods(比如说前端)提供了功能,那些前端pod如何能够与后端pod保持可靠的通信? 这就是服务发挥作用的地方。 Kubernetes服务定义了一个逻辑集Pods和访问它们的策略(有时称为微服务),通常由Label Selector确定。 例如,如果你有一个带有3个Pod的后端应用程序,那些pod是可替代的,前端并不关心它们使用哪个后端。虽然Pods组成后端集的实际情况可能会发生变化,但前端客户端不应该知道这一点,也不需要跟踪后端列表本身。该Service抽象可实现这种分离。 对那些处于同样的Kubernetes集群中的应用,Kubernetes提供了一个简单的Endpoints API,每当服务中的一套Pods改变时,API就会相应地更新。对于集群外的应用程序,Kubernetes提供基于虚拟IP的桥接器,Services可将其重定向到后端Pods。 卷 容器中的磁盘文件是不是永久的,这会给运行在容器中的应用程序带来一些问题。首先,当容器崩溃时,它将由Kubernetes重新启动,但文件会丢失,因为容器总是以干净状态启动的。其次,当在一个pod中运行多个容器时,通常需要在这些容器之间共享文件。Kubernetes Volume就是来解决这两个问题的。 从本质上讲,卷只是一个目录,可能包含一些数据,在Pod中,它可以访问容器。该目录是如何形成的、支持它的介质是什么以及它的内容,是由所使用的特定卷类型决定的。 Kubernetes卷具有明确的生命周期,与创建它的Pod的生命周期相同。总而言之,一个卷超过了在Pod中运行的任何容器,并且在容器重启时保留了数据。通常,当一个Pod不再存在时,卷也将不复存在。Kubernetes支持多种类型的卷,并且Pod可以同时使用任意数量的卷。 命名空间 Kubernetes支持由同一物理集群支持的多个虚拟集群。这些虚拟集群称为命名空间。 命名空间提供了名称范围。在一个命名空间内,每个资源名称都需要是唯一的,不过跨命名空间时就没有这种要求了。 没有必要只是为了分离略有不同的资源而使用多个命名空间,比如说同一软件的不同版本:可以用标签来区分同一命名空间内的不同资源。 ReplicaSet ReplicaSet确保一次运行指定数量的pod副本。换句话说,ReplicaSet确保pod或同类pod组始终可用。但是,Deployment是一个更高级别的概念,它可以管理ReplicaSets,并为Pods提供声明性更新以及许多其他有用的功能。因此,除非您需要自定义更新编排或根本不需要更新,否则我建议您使用Deployments而不是直接使用ReplicaSets。 这实际上意味着您可能永远不需要直接操纵ReplicaSet对象,而是可以使用Deployment作为替代。 Deployment Deployment controller为Pods和ReplicaSets提供声明性更新。 您在Deployment对象中描述了所需状态,Deployment controller就将以受控速率将现阶段实际状态更改为所需状态。您可以定义Deployments来创建新的ReplicaSets,或删除现有的Deployments并使用新的Deployments来使用所有资源。 StatefulSets StatefulSet用于管理有状态应用程序,它管理一组Pods的部署和扩展,并提供有关这些Pod 的排序和唯一性的保证。 StatefulSet的运行模式和controller相同。您可以在StatefulSet对象中定义所需的状态,StatefulSet controller就会进行各种必要的更新以从当前状态到达更新为所需状态。和Deployment类似,StatefulSet管理那些基于相同容器规范的Pods。与Deployment不同的是,StatefulSet为每个Pods保留一个粘性身份。这些Pods是根据相同的规范创建的,但不可互换:每个都有一个永久的标识符,不论如何重新调度,这个标识都保持不变。 DaemonSet DaemonSet确保所有(或某些)节点运行Pod的副本。随着节点添加到群集中,Pod会随之添加。当将节点从集群中删除后,Pods就成为了垃圾。此时,删除一个DaemonSet就将清理它所创建的Pods。 DaemonSet的一些典型用途有: 在每个节点上运行集群存储daemon,例如glusterd、ceph。 在每个节点上运行日志收集daemon,例如fluentd或logstash。 在每个节点上运行节点监控daemon,例如Prometheus Node Exporter或collectd。 Job job创建一个或多个pod,并确保在需要的时候成功终止指定数量的pod。Pods成功完成后,job会追踪顺利完成的情况。当达到指定数量时,job本身的任务就完成了。删除Job将清除它所创建的Pods。 一个简单的例子是创建一个Job对象,以便可靠地运行一个对象Pod。如果第一个Pod失败或被删除(例如由于节点硬件故障或节点重启),那么该Job对象将启动一个新Pod。 实际操作中的挑战 现在您已经了解了Kubernetes中的关键对象及概念了,很明显,想要玩转Kubernetes需要了解大量的信息。当你尝试使用Kubernetes时,可能会遇到如下挑战: 如何在不同的基础架构中一致地部署? 如何跨多个集群(和名称空间)实现和管理访问控制? 如何与中央身份验证系统集成? 如何分区集群以更有效地使用资源? 如何管理多租户、多个专用和共享集群? 如何创建高可用集群? 如何跨集群/命名空间实施安全策略? 如何良好地进行监控,以确保有足够的可见性来检测和解决问题? 如何跟上Kubernetes快速发展的步伐? 这就是Rancher可以帮助您的地方。Rancher是一个100%开源的容器管理平台,用于在生产中运行Kubernetes。通过Rancher,你可以: 拥有易于使用的kubernetes配置和部署界面; 跨多个集群和云的基础架构管理; 自动部署最新的kubernetes版本; 工作负载、RBAC、政策和项目管理; 24x7企业级支持。 Rancher可以成为一个单一控制点,而您可以在多种、多个基础架构上运行多个Kubernetes集群: 上手Rancher和Kubernetes 现在让我们看看如何在Rancher的帮助下轻松使用前文描述的Kubernetes对象。首先,您需要一个Rancher实例。按照本指南,在几分钟之内即可启动一个Rancher实例并使用它创建一个Kubernetes集群: https://rancher.com/docs/rancher/v2.x/en/quick-start-guide/deployment/quickstart-manual-setup/ 启动集群后,您应该在Rancher中看到Kubernetes集群的资源: 要从第一个Kubernetes对象——节点开始,请单击顶部菜单上的Nodes。你应该可以组成了你的Kubernetes集群的Nodes的整体情况: 在那里,您还可以看到已从Kubernetes集群部署到每个节点的pod的数量。这些pod由Kubernetes和Rancher内部系统使用。通常情况下你不需要处理那些。 下面让我们继续Pod的例子。要做到这一点,转到Kubernetes集群的Default项目,然后进入Workloads选项卡。下面让我们部署一个工作负载。点击Deploy并将Name和Docker image设置为nginx,剩下的一切都使用默认值,然后点击Launch。 创建后,Workloads选项卡会显示nginx工作负载。 如果单击nginx工作负载,您将会看到Rancher实际创建了一个Deployment,就像Kubernetes推荐的那样用来管理ReplicaSet,您还将看到该ReplicaSet创建的Pod: 现在你有一个Deployment,它将确保我们所需的状态在集群中正确显示。现在,点击Scale附近+号,将此Workload扩容到3。一旦你这样做,你应该能立即看到另外2个Pods被创建出来,另外还多了2个ReplicaSet来缩放事件。使用Pod右侧菜单,尝试删除其中一个pod,并注意ReplicaSet是如何重新创建它以匹配所需状态的。 现在,您的应用程序已启动并运行,并且已经扩展到3个实例。下一个问题是,您如何访问它?在这里,我们将尝试使用下一个Kubernetes对象——服务。为了暴露我们的nginx工作负载,我们需要先编辑它,从Workload右侧菜单中选择Edit。您将看到Deploy Workload页面,且已填好了您的nginx工作负载的详细信息: 请注意,现在您有3个pod在Scalable Deployment旁边,但是当您启动时,默认值为1。这是因为你的扩展工作刚完成不久。 现在单击Add Port,并按如下所示填充值: 将Publish the container port值设置为80; Protocol仍为TCP; 将As a值设置为Layer-4 Load Balancer; 将On listening port值设置为80。 然后自信地点击Upgrade吧!这将在您的云提供商中创建一个外部负载均衡器,并将流量引至您的Kubernetes集群内的nginx Pods中。要对此进行测试,请再次访问nginx工作负载概述页面,现在您应该看到Endpoints旁的80/tcp链接: 如果点击80/tcp,它会导向您刚刚创建的负载均衡器的外部IP,并且向您展示一个默认的nginx页面,确认一切都按预期正常工作。 至此,你已经搞定了上半篇文章中介绍的大多数Kubernetes对象。你可以在Rancher中好好玩玩卷和命名空间,相信您一定能很快掌握如何通过Rancher更简单快捷地使用它们。至于StatefulSet、DaemonSet和Job,它们和Deployments非常类似,你同样可以在Workloads选项卡中通过选择Workload type来创建它们。 结 语 让我们回顾一下你在上面的动手练习中所做的一切。你已经创建了我们描述的大多数Kubernetes对象: 1、最开始,你在Rancher中创建了kubernetes集群; 2、然后你浏览了集群的Nodes; 3、你创造了一个Workload; 4、你已经看到了一个Workload实际上创建了3个独立的Kubernetes对象:一个Deployment管理着 ReplicaSet,同时按需保持着所需数量的Pods正常运行; 5、在那之后你扩展了你的Deployment数量,并观察它是如何改变了ReplicaSet并相应地扩展Pods的数量的; 6、最后你创建了一个Load Balancer类型的Service类型,用于平衡Pods之间的客户端请求。 所有这些都可以通过Rancher轻松完成,您只需进行一些点击的操作,无需在本地安装任何软件来复制身份验证配置或在终端中运行命令行。您只需一个浏览器,玩转Kubernetes唾手可得。 本文转自开源中国-零基础入门│带你理解Kubernetes
常不释放资源,造成高CPU占用;比如进程结束异常,不停的重启相同的进程;比如日志级别设置过低,大量日志输出,影响进程性能和占用大量磁盘空间。所以做监控时一定要遵循有自我安全控制的能力。监控工具在拿到生产环境中运行前,一定要先在测试环境中进行一段时间的试运行 。 3、触发式的数据采集 需要关注异常点的现场数据采集,比如threaddump,heapdump,主机的性能数据等。这些故障点的数据重启后就会失去,有些故障不能重现时,相关的分析数据就很重要了,所以对于这些数据,需要进行触发式的数 据采集。当满足某些条件时触发采集,而在平常不运行。 容器的监控方案 传统的监控系统大多是针对物理机或虚拟机设计的,物理机和虚拟机的特点是静态的,生命周期长,一个环境安装配置好后可能几年都不会去变动,那么对监控系统来说,监控对像是静态的,对监控对象做的监控配置也是静态的,系统上线部署好监控后基本就不再需要管理。 虽然物理机,虚拟机,容器对于应用进程来说都是host环境,容器也是一个轻量级的虚拟机, 但容器是动态的, 生命周期短,特别是在微服务的分布式架构下,容器的个数,IP地址随时可能变化。如果还采用原来传统监控的方案,则会增加监控的复杂度。比如对于一个物理机或虚拟机,我们只要安装一个监控工具的agent就可以了,但如果在一个物理机上运行了无数个容器,也采用安装agent的方式,就会增加agent对资源的占用,但因为容器是与宿主机是共享资源,所以在容器内采集的性能数据会是宿主机的数据,那就失去在容器内采集数据的意义了。 而且往往容器的数量比较多,那么采集到的数量也会非常多,容器可能启动几分钟就停止了,那么原来采集的数据就没有价值了,则会产生大量这样没有价值的监控数据,维护起来也会非常的复杂。那么应该如何对容器进行监控呢?答案是在容器外,宿主机上进行监控。这样不仅可以监控到每个容器的资源使用情况,还可以监控到容器的状态,数量等数据。 单台主机上容器的监控 单台主机上容器的监控实现最简单的方法就是使用命令Docker stats,就可以显示所有容器的资源使用情况,如下输出: 虽然可以很直观地看到每个容器的资源使用情况,但是显示的只是一个当前值,并不能看到变化趋势。而谷歌提供的图形化工具不仅可以看到每个容器的资源使用情况,还可以看到主机的资源使用情况,并且可以设置显示一段时间内的越势。以下是cAdvisor的面板: 而且cAdivsor的安装非常简单,下载一个cAdvisor的容器启动后,就可以使用主机IP加默认端口8080进行访问了。 跨多台主机上容器的监控 cAdivsor虽然能采集到监控数据,也有很好的界面展示,但是并不能显示跨主机的监控数据,当主机多的情况,需要有一种集中式的管理方法将数据进行汇总展示,最经典的方案就是 cAdvisor+ Influxdb+grafana,可以在每台主机上运行一个cAdvisor容器负责数据采集,再将采集后的数据都存到时序型数据库influxdb中,再通过图形展示工具grafana定制展示面板。结构如下: 这三个工具的安装也非常简单,可以直接启动三个容器快速安装。如下所示: 在上面的安装步骤中,先是启动influxdb容器,然后进行到容器内部配置一个数据库给cadvisor专用,然后再启动cadvisor容器,容器启动的时候指定把数据存储到influxdb中,最后启动grafana容器,在展示页面里配置grafana的数据源为influxdb,再定制要展示的数据,一个简单的跨多主机的监控系统就构建成功了。下图为Grafana的界面: Kubernetes上容器的监控 在Kubernetes的新版本中已经集成了cAdvisor,所以在Kubernetes架构下,不需要单独再去安装cAdvisor,可以直接使用节点的IP加默认端口4194就可以直接访问cAdvisor的监控面板。而Kubernetes还提供一个叫heapster的组件用于聚合每个node上cAdvisor采集的数据,再通过Kubedash进行展示,结构如下: 在Kubernetes的框架里,master复杂调度后有的node,所以在heapster启动时,当heapster配合k8s运行时,需要指定kubernetes_master的地址,heapster通过k8s得到所有node节点地址,然后通过访问对应的node ip和端口号(10250)来调用目标节点Kubelet的HTTP接口,再由Kubelet调用cAdvisor服务获取该节点上所有容器的性能数据,并依次返回到heapster进行数据聚合。再通过kubedash进行展示,界面如下: Mesos的监控方案 而Mesos提供一个mesos-exporter工具,用于导出mesos集群的监控数据prometheus,而prometheus是个集 db、graph、statistic、alert 于一体的监控工具,安装也非常简单,下载包后做些参数的配置,比如监控的对象就可以运行了,默认通过9090端口访问。而mesos-exporter工具只需要在每个slave节点上启动一个进程,再mesos-exporter监控配置到prometheus server的监控目标中就可以获取到相关的数据。架构如下: 在Prometheus的面板上我们可以看到Prometheus的监控对象可以为mesos-export,也可以为cAdvisor。 下面为Prometheus的展示界面: 采集工具的对比 cAdvisor 可以采集本机以及容器的资源监控数据,如CPU、 memory、filesystem and network usage statistics)。还可以展示Docker的信息及主机上已下载的镜像情况。因为cAdvisor默认是将数据缓存在内存中,在显示界面上只能显示1分钟左右的趋势,所以历史的数据还是不能看到,但它也提供不同的持久化存储后端,比如influxdb等。 Heapster的前提是使用cAdvisor采集每个node上主机和容器资源的使用情况,再将所有node上的数据进行聚合,这样不仅可以看到整个Kubernetes集群的资源情况,还可以分别查看每个node/namespace及每个node/namespace下pod的资源情况。这样就可以从cluster,node,pod的各个层面提供详细的资源使用情况。默认也是存储在内存中,也提供不同的持久化存储后端,比如influxdb等。 mesos-exporter的特点是可以采集 task 的监控数据。mesos在资源调度时是在每个slave上启动task executor,这些task executor可以是容器,也可以不是容器。而mesos-exporter则可以从task的角度来了解资源的使用情况,而不是一个一个没有关联关系的容器。 以上从几个典型的架构上介绍了一些监控,但都不是最优实践。需要根据生产环境的特点结合每个监控产品的优势来达到监控的目的。比如Grafana的图表展示能力强,但是没有告警的功能,那么可以结合Prometheus在数据处理能力改善数据分析的展示。下面列了一些监控产品,但并不是严格按表格进行分类,比如Prometheus和Zabbix都有采集,展示,告警的功能。都可以了解一下,各取所长。 本文转自开源中国-分享Docker监控体系(Kubernetes Mesos监控)