Kubernetes 容器编排技术

Kubernetes 容器编排

前言

知识扩展

早在 2015 年 5 月,Kubernetes 在 Google 上的搜索热度就已经超过了 Mesos 和 Docker Swarm,从那儿之后更是一路飙升,将对手甩开了十几条街,容器编排引擎领域的三足鼎立时代结束。

目前,AWS、Azure、Google、阿里云、腾讯云等主流公有云提供的是基于  Kubernetes  的容器服务;Rancher、CoreOS、IBM、Mirantis、Oracle、Red Hat、VMWare 等无数厂商也在大力研发和推广基于  Kubernetes 的容器 CaaS 或 PaaS 产品。可以说,Kubernetes 是当前容器行业最炙手可热的明星。

Google 的数据中心里运行着超过 20 亿个容器,而且 Google 十年前就开始使用容器技术。最初,Google 开发了一个叫 Borg 的系统(现在命名为 Omega)来调度如此庞大数量的容器和工作负载。在积累了这么多年的经验后,Google 决定重写这个容器管理系统,并将其贡献到开源社区,让全世界都能受益。这个项目就是 Kubernetes。简单的讲,Kubernetes 是 Google Omega 的开源版本。跟很多基础设施领域先有工程实践、后有方法论的发展路线不同,Kubernetes 项目的理论基础则要比工程实践走得靠前得多,这当然要归功于 Google 公司在 20154 月发布的 Borg 论文了。Borg 系统,一直以来都被誉为 Google 公司内部最强大的"秘密武器"。虽然略显夸张,但这个说法倒不算是吹牛。因为,相比于 Spanner、BigTable 等相对上层的项目,Borg 要承担的责任,是承载 Google 公司整个基础设施的核心依赖。在 Google 公司已经公开发表的基础设施体系论文中,Borg 项目当仁不让地位居整个基础设施技术栈的最底层。

由于这样的定位,Borg 可以说是 Google 最不可能开源的一个项目。而幸运地是,得益于 Docker 项目和容器技术的风靡,它却终于得以以另一种方式与开源社区见面,这个方式就是 Kubernetes 项目。

所以,相比于"小打小闹"的 Docker 公司、"旧瓶装新酒"的 Mesos 社区,Kubernetes 项目从一开始就比较幸运地站上了一个他人难以企及的高度:在它的成长阶段,这个项目每一个核心特性的提出,几乎都脱胎于 Borg/Omega 系统的设计与经验。更重要的是,这些特性在开源社区落地的过程中,又在整个社区的合力之下得到了极大的改进,修复了很多当年遗留在 Borg 体系中的缺陷和问题。

所以,尽管在发布之初被批评是"曲高和寡",但是在逐渐觉察到 Docker 技术栈的"稚嫩"和 Mesos 社区的"老迈"之后,这个社区很快就明白了:k8s 项目在 Borg 体系的指导下,体现出了一种独有的"先进性""完备性",而这些特质才是一个基础设施领域开源项目赖以生存的核心价值。

什么是编排

一个正在运行的 Linux 容器,可以分成两部分看待:

1. 容器的静态视图
一组联合挂载在 /var/lib/docker/aufs/mnt 上的 rootfs,这一部分称为"容器镜像"(Container Image)

2. 容器的动态视图
一个由 Namespace+Cgroups 构成的隔离环境,这一部分称为"容器运行时"(Container Runtime)

 作为一名开发者,其实并不关心容器运行时的差异。在整个"开发 - 测试 - 发布"的流程中,真正承载着容器信息进行传递的,是容器镜像,而不是容器运行时。这正是容器技术圈在 Docker 项目成功后不久,就迅速走向了"容器编排"这个"上层建筑"的主要原因:作为一家云服务商或者基础设施提供商,我只要能够将用户提交的 Docker 镜像以容器的方式运行起来,就能成为这个非常热闹的容器生态图上的一个承载点,从而将整个容器技术栈上的价值,沉淀在我的这个节点上。

 更重要的是,只要从这个承载点向 Docker 镜像制作者和使用者方向回溯,整条路径上的各个服务节点,比如 CI/CD、监控、安全、网络、存储等等,都有可以发挥和盈利的余地。这个逻辑,正是所有云计算提供商如此热衷于容器技术的重要原因:通过容器镜像,它们可以和潜在用户(即,开发者)直接关联起来。

 从一个开发者和单一的容器镜像,到无数开发者和庞大的容器集群,容器技术实现了从"容器""容器云"的飞跃,标志着它真正得到了市场和生态的认可。这样,容器就从一个开发者手里的小工具,一跃成为了云计算领域的绝对主角;而能够定义容器组织和管理规范的"容器编排"技术,则当仁不让地坐上了容器技术领域的"头把交椅"。

最具代表性的容器编排工具:	
1. Docker 公司的 Compose+Swarm 组合
2. Google 与 RedHat 公司共同主导的 Kubernetes 项目

google与k8s

20146 月,基础设施领域的翘楚 Google 公司突然发力,正式宣告了一个名叫 Kubernetes 项目的诞生。这个项目,不仅挽救了当时的 CoreOS 和 RedHat,还如同当年 Docker 项目的横空出世一样,再一次改变了整个容器市场的格局。这段时间,也正是 Docker 生态创业公司们的春天,大量围绕着 Docker 项目的网络、存储、监控、CI/CD,甚至 UI 项目纷纷出台,也涌现出了很多 Rancher、Tutum 这样在开源与商业上均取得了巨大成功的创业公司。

在 2014~2015 年间,整个容器社区可谓热闹非凡。这令人兴奋的繁荣背后,却浮现出了更多的担忧。这其中最主要的负面情绪,是对 Docker 公司商业化战略的种种顾虑。事实上,很多从业者也都看得明白,Docker 项目此时已经成为 Docker 公司一个商业产品。而开源,只是 Docker 公司吸引开发者群体的一个重要手段。不过这么多年来,开源社区的商业化其实都是类似的思路,无非是高不高调、心不心急的问题罢了。
  而真正令大多数人不满意的是,Docker 公司在 Docker 开源项目的发展上,始终保持着绝对的权威和发言权,并在多个场合用实际行动挑战到了其他玩家(比如,CoreOS、RedHat,甚至谷歌和微软)的切身利益。那么,这个时候,大家的不满也就不再是在 GitHub 上发发牢骚这么简单了。

 相信很多容器领域的老玩家们都听说过,Docker 项目刚刚兴起时,Google 也开源了一个在内部使用多年、经历过生产环境验证的 Linux 容器:lmctfy(Let Me Container That For You)。然而,面对 Docker 项目的强势崛起,这个对用户没那么友好的 Google 容器项目根本没有招架之力。所以,知难而退的 Google 公司,向 Docker 公司表示了合作的愿望:关停这个项目,和 Docker 公司共同推进一个中立的容器运行时(container runtime)库作为 Docker 项目的核心依赖。不过,Docker 公司并没有认同这个明显会削弱自己地位的提议,还在不久后,自己发布了一个容器运行时库 Libcontainer。这次匆忙的、由一家主导的、并带有战略性考量的重构,成了 Libcontainer 被社区长期诟病代码可读性差、可维护性不强的一个重要原因。

 至此,Docker 公司在容器运行时层面上的强硬态度,以及 Docker 项目在高速迭代中表现出来的不稳定和频繁变更的问题,开始让社区叫苦不迭。这种情绪在 2015 年达到了一个高潮,容器领域的其他几位玩家开始商议"切割"Docker 项目的话语权。而"切割"的手段也非常经典,那就是成立一个中立的基金会。于是,2015 年 622 日,由 Docker 公司牵头,CoreOS、Google、RedHat 等公司共同宣布,Docker 公司将 Libcontainer 捐出,并改名为 RunC 项目,交由一个完全中立的基金会管理,然后以 RunC 为依据,大家共同制定一套容器和镜像的标准和规范。这套标准和规范,就是 OCI( Open Container Initiative )。OCI 的提出,意在将容器运行时和镜像的实现从 Docker 项目中完全剥离出来。这样做,一方面可以改善 Docker 公司在容器技术上一家独大的现状,另一方面也为其他玩家不依赖于 Docker 项目构建各自的平台层能力提供了可能。
不过,OCI 的成立更多的是这些容器玩家出于自身利益进行干涉的一个妥协结果。尽管 Docker 是 OCI 的发起者和创始成员,它却很少在 OCI 的技术推进和标准制定等事务上扮演关键角色,也没有动力去积极地推进这些所谓的标准。这也是迄今为止 OCI 组织效率持续低下的根本原因。OCI 并没能改变 Docker 公司在容器领域一家独大的现状,Google 和 RedHat 等公司于是把第二把武器摆上了台面。

 Docker 之所以不担心 OCI 的威胁,原因就在于它的 Docker 项目是容器生态的事实标准,而它所维护的 Docker 社区也足够庞大。可是,一旦这场斗争被转移到容器之上的平台层,或者说 PaaS 层,Docker 公司的竞争优势便立刻捉襟见肘了。在这个领域里,像 Google 和 RedHat 这样的成熟公司,都拥有着深厚的技术积累;而像 CoreOS 这样的创业公司,也拥有像 Etcd 这样被广泛使用的开源基础设施项目。可是 Docker 公司却只有一个 Swarm。

 所以这次,Google、RedHat 等开源基础设施领域玩家们,共同牵头发起了一个名为 CNCF(Cloud Native Computing Foundation)的基金会。这个基金会的目的其实很容易理解:它希望,以 Kubernetes 项目为基础,建立一个由开源基础设施领域厂商主导的、按照独立基金会方式运营的平台级社区,来对抗以 Docker 公司为核心的容器商业生态。

 为了打造出一个围绕 Kubernetes 项目的"护城河",CNCF 社区就需要至少确保两件事情:Kubernetes 项目必须能够在容器编排领域取得足够大的竞争优势;CNCF 社区必须以 Kubernetes 项目为核心,覆盖足够多的场景。CNCF 社区如何解决 Kubernetes 项目在编排领域的竞争力的问题:
   在容器编排领域,Kubernetes 项目需要面对来自 Docker 公司和 Mesos 社区两个方向的压力。Swarm 和 Mesos 实际上分别从两个不同的方向讲出了自己最擅长的故事:Swarm 擅长的是跟 Docker 生态的无缝集成,而 Mesos 擅长的则是大规模集群的调度与管理。

 这两个方向,也是大多数人做容器集群管理项目时最容易想到的两个出发点。也正因为如此,Kubernetes 项目如果继续在这两个方向上做文章恐怕就不太明智了。

 Kubernetes 选择的应对方式是:Borg

k8s 项目大多来自于 Borg 和 Omega 系统的内部特性,这些特性落到 k8s 项目上,就是 Pod、Sidecar 等功能和设计模式。

 这就解释了,为什么 Kubernetes 发布后,很多人"抱怨"其设计思想过于"超前"的原因:Kubernetes 项目的基础特性,并不是几个工程师突然"拍脑袋"想出来的东西,而是 Google 公司在容器化基础设施领域多年来实践经验的沉淀与升华。这正是 Kubernetes 项目能够从一开始就避免同 Swarm 和 Mesos 社区同质化的重要手段。

 CNCF 接下来的任务是如何把这些先进的思想通过技术手段在开源社区落地,并培育出一个认同这些理念的生态?

  RedHat 发挥了重要作用。当时,Kubernetes 团队规模很小,能够投入的工程能力十分紧张,这恰恰是 RedHat 的长处。RedHat 更是世界上为数不多、能真正理解开源社区运作和项目研发真谛的合作伙伴。RedHat 与 Google 联盟的成立,不仅保证了 RedHat 在 Kubernetes 项目上的影响力,也正式开启了容器编排领域"三国鼎立"的局面。

 Mesos 社区与容器技术的关系,更像是"借势",而不是这个领域真正的参与者和领导者。这个事实,加上它所属的 Apache 社区固有的封闭性,导致了 Mesos 社区虽然技术最为成熟,却在容器编排领域鲜有创新。

 一开始,Docker 公司就把应对 Kubernetes 项目的竞争摆在首要位置:  一方面,不断强调"Docker Native""重要性"一方面,与 k8s 项目在多个场合进行了直接的碰撞。这次竞争的发展态势,很快就超过了 Docker 公司的预期。 Kubernetes 项目并没有跟 Swarm 项目展开同质化的竞争所以 "Docker Native"的说辞并没有太大的杀伤力相反 k8s 项目让人耳目一新的设计理念和号召力,很快就构建出了一个与众不同的容器编排与管理的生态。

 Kubernetes 项目在 GitHub 上的各项指标开始一骑绝尘,将 Swarm 项目远远地甩在了身后。

 CNCF 社区如何解决第二个问题:

在已经囊括了容器监控事实标准的 Prometheus 项目后,CNCF 社区迅速在成员项目中添加了 Fluentd、OpenTracing、CNI 等一系列容器生态的知名工具和项目。而在看到了 CNCF 社区对用户表现出来的巨大吸引力之后,大量的公司和创业团队也开始专门针对 CNCF 社区而非 Docker 公司制定推广策略。

 2016 年,Docker 公司宣布了一个震惊所有人的计划:放弃现有的 Swarm 项目,将容器编排和集群管理功能全部内置到 Docker 项目当中。Docker 公司意识到了 Swarm 项目目前唯一的竞争优势,就是跟 Docker 项目的无缝集成。那么,如何让这种优势最大化呢?那就是把 Swarm 内置到 Docker 项目当中。从工程角度来看,这种做法的风险很大。内置容器编排、集群管理和负载均衡能力,固然可以使得 Docker 项目的边界直接扩大到一个完整的 PaaS 项目的范畴,但这种变更带来的技术复杂度和维护难度,长远来看对 Docker 项目是不利的。不过,在当时的大环境下,Docker 公司的选择恐怕也带有一丝孤注一掷的意味。

 k8s 的应对策略:
  是反其道而行之,开始在整个社区推进"民主化"架构,即:从 API 到容器运行时的每一层,Kubernetes 项目都为开发者暴露出了可以扩展的插件机制,鼓励用户通过代码的方式介入到 Kubernetes 项目的每一个阶段。

 Kubernetes 项目的这个变革的效果立竿见影,很快在整个容器社区中催生出了大量的、基于 Kubernetes API 和扩展接口的二次创新工作,比如:
  目前热度极高的微服务治理项目 Istio;被广泛采用的有状态应用部署框架 Operator;还有像 Rook 这样的开源创业项目,它通过 Kubernetes 的可扩展接口,把 Ceph 这样的重量级产品封装成了简单易用的容器存储插件。

 在鼓励二次创新的整体氛围当中,k8s 社区在 2016 年后得到了空前的发展。更重要的是,不同于之前局限于"打包、发布"这样的 PaaS 化路线,这一次容器社区的繁荣,是一次完全以 Kubernetes 项目为核心的"百花争鸣"。面对 Kubernetes 社区的崛起和壮大,Docker 公司也不得不面对自己豪赌失败的现实。但在早前拒绝了微软的天价收购之后,Docker 公司实际上已经没有什么回旋余地,只能选择逐步放弃开源社区而专注于自己的商业化转型。

 所以,从 2017 年开始,Docker 公司先是将 Docker 项目的容器运行时部分 Containerd 捐赠给 CNCF 社区,标志着 Docker 项目已经全面升级成为一个 PaaS 平台;紧接着,Docker 公司宣布将 Docker 项目改名为 Moby,然后交给社区自行维护,而 Docker 公司的商业产品将占有 Docker 这个注册商标。

 Docker 公司这些举措背后的含义非常明确:它将全面放弃在开源社区同 Kubernetes 生态的竞争,转而专注于自己的商业业务,并且通过将 Docker 项目改名为 Moby 的举动,将原本属于 Docker 社区的用户转化成了自己的客户。2017 年 10 月,Docker 公司出人意料地宣布,将在自己的主打产品 Docker 企业版中内置 Kubernetes 项目,这标志着持续了近两年之久的"编排之争"至此落下帷幕。

2018130 日,RedHat 宣布斥资 2.5 亿美元收购 CoreOS。

2018328 日,这一切纷争的始作俑者,Docker 公司的 CTO Solomon Hykes 宣布辞职,曾经纷纷扰扰的容器技术圈子,到此尘埃落定。

    容器技术圈子在短短几年里发生了很多变数,但很多事情其实也都在情理之中。就像 Docker 这样一家创业公司,在通过开源社区的运作取得了巨大的成功之后,就不得不面对来自整个云计算产业的竞争和围剿。而这个产业的垄断特性,对于 Docker 这样的技术型创业公司其实天生就不友好。在这种局势下,接受微软的天价收购,在大多数人看来都是一个非常明智和实际的选择。可是 Solomon Hykes 却多少带有一些理想主义的影子,既然不甘于"寄人篱下",那他就必须带领 Docker 公司去对抗来自整个云计算产业的压力。
  只不过,Docker 公司最后选择的对抗方式,是将开源项目与商业产品紧密绑定,打造了一个极端封闭的技术生态。而这,其实违背了 Docker 项目与开发者保持亲密关系的初衷。相比之下,Kubernetes 社区,正是以一种更加温和的方式,承接了 Docker 项目的未尽事业,即:以开发者为核心,构建一个相对民主和开放的容器生态。这也是为何,Kubernetes 项目的成功其实是必然的。

    很难想象如果 Docker 公司最初选择了跟 Kubernetes 社区合作,如今的容器生态又将会是怎样的一番景象。不过我们可以肯定的是,Docker 公司在过去五年里的风云变幻,以及 Solomon Hykes 本人的传奇经历,都已经在云计算的长河中留下了浓墨重彩的一笔。

总结:
容器技术的兴起源于 PaaS 技术的普及;
Docker 公司发布的 Docker 项目具有里程碑式的意义;
Docker 项目通过"容器镜像",解决了应用打包这个根本性难题。
容器本身没有价值,有价值的是"容器编排"。也正因为如此,容器技术生态才爆发了一场关于"容器编排""战争"。而这次战争,最终以 Kubernetes 项目和 CNCF 社区的胜利而告终。

容器编排之战

一. kubernetes简要概述

Kubernetes 是用于自动部署, 扩展和管理容器化应用程序的开源系统. 它将组成应用程序的容器组合成逻辑单元, 以便于管理和服务发现. Kubernetes 源自Google 15 年生产环境的运维经验, 同时凝聚了社区的最佳创意和实践

在容器化应用的大趋势下, 越来越多的单体应用被划分成了无数个微小的服务, 从之前的裸机部署单体服务到现今使用容器承载服务运行; 容器越来越贴近研发、运维、测试等IT职能部门, 越来越多的人也渐渐转向了云进行开发, 而不再单独强调单体一致性问题; 这一切的功臣就是 — 容器;

其实容器这个产物, 早在Linux内核诞生后就已经有了它, 那时候程序员创建一个容器需要编写代码, 调用Linux内核中的Cgroup 和 NameSpace用作资源限制, 而创建出来的容器所观看到的底层文件系统却是和宿主机是同一套文件系统, 导致在容器中操作文件就相当于更改了宿主机的文件系统, 这就大大暴露了容器的应用性差的特性; 后来横空出世的docker解决了这个问题, docker创造了一个叫做镜像的东西, 并将其进行分层, 使得用户在启动容器的时候将镜像拷贝一份读写层挂载到容器中, 让容器看到的底层文件系统只是镜像中的内容从而避免了上述的问题, 镜像带来的好处远不止这一个, 比如在生产、测试、研发三个环境中总是因为环境不统一造成上线后的应用很快崩溃的局面, 而有了镜像就不需要考虑环境的问题, 因为所有的服务运行在容器中, 而提供给容器运行的底层文件系统是镜像, 只要使用的是同一个镜像那么不管在什么环境中, 都是一样的效果, 这对于运维、测试、研发三类人而言就能彻底的和应用运行环境要考虑的事情甩手拜拜了; 而docker这个引擎又提供了很多易于使用的控制容器的指令, 大大降低了程序员操作的难度, 又提供相应的镜像和存储镜像的公共仓库, 使得容器技术飞速发展起来;

就在容器飞速发展的第五年里, Google发布了容器编排系统 kubernetes, 它解决了容器在多个宿主机间调度的问题, 同时提供可靠的API接口来控制容器的运行, 并时刻保持容器启动的状态, 融合了故障自愈、服务发现等新功能; 从而占领了容器市场的高地; 从此容器圈内又向容器编排发起了猛烈的攻势;

1.1 kubernetes 功能简介
  • 服务发现和负载均衡
      Kubernetes 可以使用 DNS 名称或自己的 IP 地址公开容器, 如果进入容器的流量很大,  Kubernetes 可以负载均衡并分配网络流量, 从而使部署稳定. 常用的DNS插件为coreDNS, 用作服务发现和集群中容器通讯; 负载均衡器常使用集群内的service资源进行对外暴露服务, 同时也提供ingress插件接口对外暴露服务
  • 存储编排
      Kubernetes 允许你自动挂载你选择的存储系统, 例如本地存储、公共云提供商等. 对于集群内部的应用有时会需要持久化数据, 这时容器如果被删除数据也会随之消失, 所以一个稳定的外部存储集群就显得很重要了; 常见的对k8s提供存储能力的集群有: heketi + gluesterfs 、Rook + Ceph、阿里云OSS等, 配合集群内的storageclass可直接向存储集群申请存储空间
  • 自动部署和回滚
      你可以使用 Kubernetes 描述已部署容器的所需状态, 它可以以受控的速率将实际状态 更改为期望状态. 例如, 你可以自动化 Kubernetes 来为你的部署创建新容器,  删除现有容器并将它们的所有资源用于新容器. 控制k8s集群中容器的状态大多数时候都会采用 yaml 资源配置清单的形式, 方便记忆也易于识别
  • 自动完成装箱计算
      Kubernetes 允许你指定每个容器所需 CPU 和内存(RAM).  当容器指定了资源请求时, Kubernetes 可以做出更好的决策来管理容器的资源. 这就与Linux操作系统中的资源限额有关系了, 有些容器在接收高并发访问的时候, 往往会无限制的占用宿主机资源, 从而导致其他服务容器争抢不到应有的资源进行运行和服务, 从而导致大面积瘫痪
  • 自我修复
      Kubernetes 重新启动失败的容器、替换容器、杀死不响应用户定义的 运行状况检查的容器, 并且在准备好服务之前不将其通告给客户端. 在创建pod的时候, 只有当设置在pod中的探针全部探测正常后才会将pod连接到集群内网络中对外进行服务, 否则将通过监控告警给运维人员; 同样的对于k8s中的容器会受到kubelet和kube-apiserver的双重管理, kubelet上报容器运行状态, api-server向etcd中请求期望状态, 比较后让其达到期望的状态, 从而保证其功能/服务容器永久保持在线
  • 密钥与配置管理
      Kubernetes 允许你存储和管理敏感信息, 例如密码、OAuth 令牌和 ssh 密钥.  你可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置, 也无需在堆栈配置中暴露密钥. 对于无状态应用(exam: nginx)的配置文件, 可以使用k8s中的configmap资源进行存储, 但对于密码和某些私密信息而言就可以使用secret资源进行存储, 从而避免频繁更改和私密信息泄漏的风险
1.2 Kubernetes架构及组件

一个 Kubernetes 集群由一组被称作节点的机器组成。这些节点上运行 Kubernetes 所管理的容器化应用。集群具有至少一个工作节点。工作节点托管作为应用负载的组件的 Pod 。控制平面管理集群中的工作节点和 Pod 。 为集群提供故障转移和高可用性,这些控制平面一般跨多主机运行,集群跨多个节点运行。

  • kube-apiserver
    API 服务器是 Kubernetes 控制面的组件, 该组件公开了 Kubernetes API。 API 服务器是 Kubernetes 控制面的前端。Kubernetes API 服务器的主要实现是 kube-apiserver。 kube-apiserver 设计上考虑了水平伸缩,也就是说,它可通过部署多个实例进行伸缩。 你可以运行 kube-apiserver 的多个实例,并在这些实例之间平衡流量。
  • etcd
    etcd 是兼具一致性和高可用性的键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库。你的 Kubernetes 集群的 etcd 数据库通常需要有个备份计划。要了解 etcd 更深层次的信息,请参考 etcd 文档
  • kube-scheduler
    控制平面组件,负责监视新创建的、未指定运行节点(node)Pods,选择节点让 Pod 在上面运行。调度决策考虑的因素包括单个 Pod 和 Pod 集合的资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰和最后时限。

/etc/kubernetes/manifests/kube-scheduler.yaml 中添加参数

  • –node-monitor-grace-period=10s:该参数指定了 Node 节点的监控宽限期(Grace Period),即在节点变为不可用之前的时间间隔。在这段时间内,调度器不会将 Pod 从该节点上驱逐,以便给应用程序一些时间来完成正在进行的任务并正常关闭。在这个例子中,宽限期为 10 秒。
  • –node-monitor-period=3s:该参数指定了 Node 节点的监控周期,即调度器检查节点状态的时间间隔。在这个例子中,监控周期为 3 秒。
  • –node-startup-grace-period=20s:该参数指定了新节点加入集群后的启动宽限期(Grace Period),即在节点完全准备好之前的时间间隔。在这段时间内,调度器不会将 Pod 调度到该节点上,以便给节点一些时间来初始化和准备运行 Pod。在这个例子中,启动宽限期为 20 秒。
  • –pod-eviction-timeout=10s:该参数指定了 Pod 的驱逐超时时间,即在调度器决定驱逐一个 Pod 之后,等待该 Pod 关闭的时间间隔。如果 Pod 在这段时间内没有正常关闭,调度器将强制终止该 Pod。在这个例子中,驱逐超时时间为 10 秒。
  • kube-controller-manager
    运行控制器进程的控制平面组件。从逻辑上讲,每个控制器都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在一个进程中运行。这些控制器包括:
    • 节点控制器(Node Controller):负责在节点出现故障时进行通知和响应
    • 任务控制器(Job controller):监测代表一次性任务的 Job 对象,然后创建 Pods 来运行这些任务直至完成
    • 端点控制器(Endpoints Controller):填充端点(Endpoints)对象(即加入 Service 与 Pod)
    • 服务帐户和令牌控制器(Service Account & Token Controllers):为新的命名空间创建默认帐户和 API 访问令牌
  • cloud-controller-manager
    云控制器管理器是指嵌入特定云的控制逻辑的 控制平面组件。 云控制器管理器使得你可以将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。cloud-controller-manager 仅运行特定于云平台的控制回路。 如果你在自己的环境中运行 Kubernetes,或者在本地计算机中运行学习环境, 所部署的环境中不需要云控制器管理器。与 kube-controller-manager 类似,cloud-controller-manager 将若干逻辑上独立的 控制回路组合到同一个可执行文件中,供你以同一进程的方式运行。 你可以对其执行水平扩容(运行不止一个副本)以提升性能或者增强容错能力。下面的控制器都包含对云平台驱动的依赖:
    • 节点控制器(Node Controller):用于在节点终止响应后检查云提供商以确定节点是否已被删除
    • 路由控制器(Route Controller):用于在底层云基础架构中设置路由
    • 服务控制器(Service Controller):用于创建、更新和删除云提供商负载均衡器
  • Node 组件: 节点组件在每个节点上运行,维护运行的 Pod 并提供 Kubernetes 运行环境。
  • kubelet
    一个在集群中每个节点(node)上运行的代理。 它保证容器(containers)都 运行在 Pod 中。kubelet 接收一组通过各类机制提供给它的 PodSpecs,确保这些 PodSpecs 中描述的容器处于运行状态且健康。 kubelet 不会管理不是由 Kubernetes 创建的容器。
  • kube-proxy
    kube-proxy 是集群中每个节点上运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。kube-proxy 维护节点上的网络规则。这些网络规则允许从集群内部或外部的网络会话与 Pod 进行网络通信。如果操作系统提供了数据包过滤层并可用的话,kube-proxy 会通过它来实现网络规则。否则, kube-proxy 仅转发流量本身。
  • 容器运行时(Container Runtime)
    容器运行环境是负责运行容器的软件。Kubernetes 支持容器运行时,例如 DockercontainerdCRI-O 以及 Kubernetes CRI (容器运行环境接口) 的其他任何实现。
  • 插件(Addons)
    插件使用 Kubernetes 资源实现集群功能。 因为这些插件提供集群级别的功能,插件中命名空间域的资源属于 kube-system 命名空间。下面描述众多插件中的几种。有关可用插件的完整列表,请参见 插件(Addons)
    • DNS
      尽管其他插件都并非严格意义上的必需组件,但几乎所有 Kubernetes 集群都应该 有集群 DNS, 因为很多示例都需要 DNS 服务。集群 DNS 是一个 DNS 服务器,和环境中的其他 DNS 服务器一起工作,它为 Kubernetes 服务提供 DNS 记录。Kubernetes 启动的容器自动将此 DNS 服务器包含在其 DNS 搜索列表中。
    • Web 界面(仪表盘)
      Dashboard 是 Kubernetes 集群的通用的、基于 Web 的用户界面。 它使用户可以管理集群中运行的应用程序以及集群本身并进行故障排除。
    • 容器资源监控
      容器资源监控 将关于容器的一些常见的时间序列度量值保存到一个集中的数据库中,并提供用于浏览这些数据的界面。
    • 集群层面日志
      集群层面日志 机制负责将容器的日志数据 保存到一个集中的日志存储中,该存储能够提供搜索和浏览接口 ELK/Loki+grafana
1.3 Kubernetes核心概念

Master

Master节点主要负责资源调度(Scheduler),控制副本(Replication Controller),和提供统一访问集群的入口(API Server)。---核心节点也是管理节点

Node

Node是Kubernetes集群架构中运行Pod的服务节点(亦叫agent或minion)。Node是Kubernetes集群操作的单元,用来承载被分配Pod的运行,是Pod运行的宿主机,由Master管理,并汇报容器状态给Master,当Node节点宕机(NotReady状态)时,该节点上运行的Pod会被自动地转移到其他节点上。

Kubelet组件---Kubelet会从API Server接收Pod的创建请求,启动和停止容器,监控容器运行状态并汇报给Kubernetes API Server。

KuberProxy组件--实现将客户端请求流量转发到内部的pod资源。

Docker Engine(docker)组件,Docker引擎,负责本机的容器创建和管理工作;

Node IP

   Node节点的IP地址,是Kubernetes集群中每个节点的物理网卡的IP地址,是真是存在的物理网络,所有属于这个网络的服务器之间都能通过这个网络直接通信;

Pod

Pod直译是豆荚,可以把容器想像成豆荚里的豆子,把一个或多个关系紧密的豆子包在一起就是豆荚(一个Pod)。处于同一个Pod中的容器共享同样的存储空间(Volume,卷或存储卷)、网络命名空间IP地址和Port端口。在k8s中我们不会直接操作容器,而是把容器包装成Pod再进行管理

运行于Node节点上,Pod是k8s进行创建、调度和管理的最小单位.一个Pod可以包含一个容器或者多个相关容器。
 
Pod 就是 k8s 世界里的"应用";而一个应用,可以由多个容器组成。

pause容器

每个Pod中都有一个pause容器,pause容器做为Pod的网络接入点,Pod中其他的容器会使用容器映射模式启动并接入到这个pause容器。

Pod IP

 Pod的IP地址,是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络,位于不同Node上的Pod能够彼此通信,需要通过Pod IP所在的虚拟二层网络进行通信,而真实的TCP流量则是通过Node IP所在的物理网卡流出的

资源限制:

每个Pod可以设置限额的计算机资源有CPU和Memory;

Pod Volume:

Docker Volume对应Kubernetes中的Pod Volume;数间共享数据。

Event

是一个事件记录,记录了事件最早产生的时间、最后重复时间、重复次数、发起者、类型,以及导致此事件的原因等信息。Event通常关联到具体资源对象上,是排查故障的重要参考信息

Endpoint(pod IP+容器Port)

标识服务进程的访问点;

ReplicaSet

确保任何指定的Pod副本数量,并提供声明式更新等功能。

Deployment

Deployment是一个更高层次的API对象,它管理ReplicaSets和Pod,并提供声明式更新等功能。

官方建议使用Deployment管理ReplicaSets,而不是直接使用ReplicaSets,这就意味着可能永远不需要直接操作ReplicaSet对象,因此Deployment将会是使用最频繁的资源对象。

RC-Replication Controller

Replication  Controller用来管理Pod的副本,保证集群中存在指定数量的Pod副本。集群中副本的数量大于指定数量,则会停止指定数量之外的多余pod数量,反之,则会启动少于指定数量个数的容器,保证数量不变。Replication  Controller是实现弹性伸缩、动态扩容和滚动升级的核心。

Service

Service定义了Pod的逻辑集合和访问该集合的策略,是真实服务的抽象。它有暴露内部服务的端口到外网的能力。Service提供了一个统一的服务访问入口以及服务代理和发现机制,用户不需要了解后台Pod是如何运行。

一个service定义了访问pod的方式,就像单个固定的IP地址和与其相对应的DNS名之间的关系。
service分为两种ip地址:
一种是对内的叫Cluster IP ,一种是对外的叫external ip:
实现对外ip方式分为:
1.NodePort:通过每个 Node 上的 IP 和内部服务暴露出来的静态端口实现暴露服务。
2.LoadBalancer:使用云提供商的负载均衡器.
3.ingress:使用负载均衡软件实现负载均衡,向外部暴露服务。

Cluster IP

Service的IP地址,特性:
专门给集群内部资源访问的。在k8s集群里面部署的容器,用内部的容器可以访问这个ip地址。但是集群外面的机器不能访问。

1.无法被Ping通;
2.Node IP、Pod IP、Cluster IP之间的通信,采用的是Kubernetes自己设计的一种编程方式的特殊的路由规则,与IP路由有很大的不同

Label—标签与Selectors—选择器

 Kubernetes中的任意API对象都是通过Label进行标识,Label的实质是一系列的K/V键值对。Label是Replication Controller和Service运行的基础,二者通过Label来进行关联Node上运行的Pod。
 比如,你可能创建了一个"tier"和“app”标签,通过Label(tier=frontend, app=myapp)来标记前端Pod容器,使用 Label(tier=backend, app=myapp)标记后台Pod。然后可以使用 Selectors 选择带有特定Label的Pod,并且将 Service 或者 Replication Controller 应用到上面。

 一个label是一个被附加到资源上的键/值对,譬如附加到一个Pod上,为它传递一个用户自定的并且可识别的属性。

注:Node、Pod、Replication Controller和Service等都可以看作是一种"资源对象",几乎所有的资源对象都可以通过Kubernetes提供的kubectl工具执行增、删、改、查等操作并将其保存在etcd中持久化存储。

二. 常用镜像库

  1. 官方 Docker Hub 仓库:
    • 官方 Kubernetes 镜像通常存放在 Docker Hub 上。地址通常是 k8s.gcr.io/<镜像名>:<标签>,但实际上你需要通过 Docker Hub 访问这些镜像,例如 docker pull k8s.gcr.io/<镜像名>:<标签>
  2. 阿里云容器镜像服务(Aliyun Docker Registry):
    • 阿里云的 Kubernetes 镜像仓库地址格式一般为 registry.cn-hangzhou.aliyuncs.com/google_containers/<镜像名>:<标签>
    • 例如,对于 kube-apiserver 镜像,地址可能是 registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:<标签>
  3. 腾讯云容器镜像服务(Tencent Cloud Container Registry):
    • 腾讯云的 Kubernetes 镜像仓库地址格式可能类似于 ccr.ccs.tencentyun.com/tke-market/<镜像名>:<标签>
  4. 华为云容器镜像服务(Huawei Cloud Container Registry):
    • 华为云的 Kubernetes 镜像仓库地址格式可能类似于 swr.cn-north-4.myhuaweicloud.com/<镜像名>:<标签>

三.集群部署方式

  • 方式1. minikube
Minikube是一个工具,可以在本地快速运行一个单点的Kubernetes,尝试Kubernetes或日常开发的用户使用。不能用于生产环境。

官方地址:https://kubernetes.io/docs/setup/minikube/
  • 方式2. kubeadm
Kubeadm也是一个工具,提供kubeadm init和kubeadm join,用于快速部署Kubernetes集群。

官方地址:https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/
  • 方式3. 直接使用epel-release yum源,缺点就是版本较低 1.5
  • 方式4. 二进制包

四.Kubeadm 方式部署集群

kubeadm部署官方文档
kubeadm部署k8s高可用集群的官方文档
主节点CPU核数必须是 ≥2核且内存要求必须≥2G,否则k8s无法启动

主机名地址角色配置
kub-k8s-master1192.168.75.100主节点2核2G 50G
kub-k8s-master2192.168.75.101主节点2核2G 50G
kub-k8s-master3192.168.75.102主节点2核2G 50G
kub-k8s-node1192.168.75.103工作节点1核2G 50G
kub-k8s-node2192.168.75.104工作节点/haproxy1核2G 50G
4.1 安装docker[集群]

过程请查看docker安装部分

# 一键安装docker脚本
curl -L https://gitea.beyourself.org.cn/newrain001/shell-project/raw/branch/master/os/get-docker-latest.sh | sh
4.2 获取镜像[集群]

获取镜像版本方法

kubeadm config images list --kubernetes-version=<版本号>

谷歌镜像[由于国内网络原因,无法下载,后续将采用阿里云镜像代替]

# 官方,不采用
docker pull k8s.gcr.io/kube-apiserver:v1.22.0
docker pull k8s.gcr.io/kube-proxy:v1.22.0
docker pull k8s.gcr.io/kube-controller-manager:v1.22.0
docker pull k8s.gcr.io/kube-scheduler:v1.22.0
docker pull k8s.gcr.io/etcd:3.5.0-0
docker pull k8s.gcr.io/pause:3.5
docker pull k8s.gcr.io/coredns/coredns:v1.8.4
4.3 阿里仓库下载[集群]

在线下载

docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.22.0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.22.0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.22.0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.22.0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:v1.8.4
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.5.0-0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.5

# 下载完了之后需要将aliyun下载下来的所有镜像打成k8s.gcr.io/kube-controller-manager:v1.22.0这样的tag
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.22.0 k8s.gcr.io/kube-controller-manager:v1.22.0
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.22.0 k8s.gcr.io/kube-proxy:v1.22.0
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.22.0 k8s.gcr.io/kube-apiserver:v1.22.0
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.22.0 k8s.gcr.io/kube-scheduler:v1.22.0
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:v1.8.4 k8s.gcr.io/coredns/coredns:v1.8.4
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.5.0-0 k8s.gcr.io/etcd:3.5.0-0
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.5 k8s.gcr.io/pause:3.5	

# 可以清理掉aliyun的镜像标签
docker rmi -f `docker images --format {{.Repository}}:{{.Tag}} | grep aliyun`

离线安装[适用于当前网络环境不佳的情况]

[root@localhost ~]# ls
anaconda-ks.cfg  application.tar.xz  kube-1.22.0.tar.xz
[root@localhost ~]# tar xvf application.tar.xz  
[root@localhost ~]# tar xvf kube-1.22.0.tar.xz 
[root@localhost ~]# cd application/images && ls *.tar | xargs -i docker load -i {} && cd ../.. && cd kube-1.22.0/images && ls *.tar | xargs -i docker load -i {} && cd ../.. && docker images | wc -l
4.4 集群部署[集群]
cat >> /etc/hosts <<EOF
192.168.75.100   kub-k8s-master1
192.168.75.101   kub-k8s-master2
192.168.75.102   kub-k8s-master3
192.168.75.103   kub-k8s-node1
192.168.75.104   kub-k8s-node2
EOF
制作本地解析,修改主机名。相互解析
4.5 集群环境配置[集群]
1.关闭防火墙:
# systemctl disable firewalld --now
2.禁用SELinux:
# setenforce 0
3.编辑文件/etc/selinux/config,将SELINUX修改为disabled,如下:
# sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/sysconfig/selinux
SELINUX=disabled
4.时间同步
# timedatectl set-timezone Asia/Shanghai
# yum install -y ntpsec
# ntpdate time.windows.com
# hwclock --systohc
5.配置静态ip
4.6 关闭系统Swap[集群]

Kubernetes 1.8开始要求关闭系统的Swap,如果不关闭,默认配置下kubelet将无法启动。

  • 方法一: 通过kubelet的启动参数–fail-swap-on=false更改这个限制。
  • 方法二: 关闭系统的Swap。
1.关闭swap分区
# swapoff -a
修改/etc/fstab文件,注释掉SWAP的自动挂载,使用free -m确认swap已经关闭。
2.注释掉swap分区:
# sed -i 's/.*swap.*/#&/' /etc/fstab
# free -m
             total        used        free      shared  buff/cache   available
Mem:           3935         144        3415           8         375        3518
Swap:             0           0           0
4.7 安装Kubeadm包[集群]
配置官方源[需翻墙]
# cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
EOF

配置阿里云源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
所有节点:

1.安装依赖包及常用软件包
# yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wget vim net-tools git iproute lrzsz bash-completion tree bridge-utils unzip bind-utils gcc


2.安装对应版本
# yum install -y kubelet-1.22.0-0.x86_64 kubeadm-1.22.0-0.x86_64 kubectl-1.22.0-0.x86_64

3.加载ipvs相关内核模块
# cat <<EOF > /etc/modules-load.d/ipvs.conf 
ip_vs
ip_vs_lc
ip_vs_wlc
ip_vs_rr
ip_vs_wrr
ip_vs_lblc
ip_vs_lblcr
ip_vs_dh
ip_vs_sh
ip_vs_nq
ip_vs_sed
ip_vs_ftp
ip_vs_sh
nf_conntrack_ipv4
ip_tables
ip_set
xt_set
ipt_set
ipt_rpfilter
ipt_REJECT
ipip
EOF

4.配置:
配置转发相关参数,否则可能会出错
# cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
net.ipv4.tcp_tw_recycle=0
vm.swappiness=0
vm.overcommit_memory=1
vm.panic_on_oom=0
fs.inotify.max_user_instances=8192
fs.inotify.max_user_watches=1048576
fs.file-max=52706963
fs.nr_open=52706963
net.ipv6.conf.all.disable_ipv6=1
net.netfilter.nf_conntrack_max=2310720
EOF

5.使配置生效
# sysctl --system

6.如果net.bridge.bridge-nf-call-iptables报错,加载br_netfilter模块
# modprobe br_netfilter
# modprobe ip_conntrack
# sysctl -p /etc/sysctl.d/k8s.conf


7.查看是否加载成功
# lsmod | grep ip_vs
4.8 配置启动kubelet[集群]
1.配置kubelet使用pause镜像
获取docker的cgroups
配置变量:
[root@k8s-master ~]# DOCKER_CGROUPS=`docker info |grep 'Cgroup' | awk ' NR==1 {print $3}'`
[root@k8s-master ~]# echo $DOCKER_CGROUPS
cgroupfs

2.配置kubelet的cgroups
# cat >/etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--cgroup-driver=$DOCKER_CGROUPS --pod-infra-container-image=k8s.gcr.io/pause:3.5"
EOF
启动
# systemctl daemon-reload
# systemctl enable kubelet && systemctl restart kubelet
在这里使用 # systemctl status kubelet,你会发现报错误信息;

10月 11 00:26:43 node1 systemd[1]: kubelet.service: main process exited, code=exited, status=255/n/a
10月 11 00:26:43 node1 systemd[1]: Unit kubelet.service entered failed state.
10月 11 00:26:43 node1 systemd[1]: kubelet.service failed.

运行 # journalctl -xefu kubelet 命令查看systemd日志才发现,真正的错误是:
    unable to load client CA file /etc/kubernetes/pki/ca.crt: open /etc/kubernetes/pki/ca.crt: no such file or directory
#这个错误在运行kubeadm init 生成CA证书后会被自动解决,此处可先忽略。
#简单地说就是在kubeadm init 之前kubelet会不断重启。
4.9 配置master节点[master]

! 4.9.1 和 4.9.2 所有master都执行,4.9.3 只有master1执行

4.9.1 安装haproxy

也可以选择lvs、nginx等

[root@localhost ~]# yum install -y haproxy
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg 
# Global settings section
global
    log         127.0.0.1 local2     # 设置日志记录,使用本地系统日志服务

    chroot      /var/lib/haproxy     # 设置 HAProxy 的运行环境为隔离模式
    pidfile     /var/run/haproxy.pid # 指定 HAProxy 进程的 PID 文件位置
    maxconn     4000                 # 设置最大并发连接数
    user        haproxy              # 指定 HAProxy 运行的用户
    group       haproxy              # 指定 HAProxy 运行的用户组
    daemon                           # 以守护进程模式运行

    # 开启状态统计的 UNIX 套接字
    stats socket /var/lib/haproxy/stats

# Default settings section
defaults
    mode                    tcp      # 默认使用 TCP 模式,适用于非 HTTP 流量
    log                     global   # 使用全局日志设置
    option                  tcplog   # 开启 TCP 日志记录
    timeout connect         10s      # 连接超时设置
    timeout client          1m       # 客户端超时
    timeout server          1m       # 服务器超时
    retries                 3        # 连接重试次数

frontend stats
    mode http
    bind *:9000                # 监听 9000 端口,用于访问统计页面
    stats enable               # 启用统计报告
    stats uri /haproxy_stats   # 设置统计报告的 URI,可以通过 http://<your-ip>:9000/haproxy_stats 访问
    stats realm HAProxy\ Statistics  # 设置认证弹窗的标题
    stats auth admin:password  # 设置访问统计页面的用户名和密码,这里为 admin 和 password,您应该设置一个更安全的密码
    stats admin if TRUE        # 如果设置为 TRUE,允许在页面上进行管理操作

# Kubernetes Master 节点的前端配置
frontend kubernetes-frontend
    bind *:6443                        # 监听 8443 端口,用于 Kubernetes API
    default_backend kubernetes-backend # 默认后端设置为 kubernetes-backend

# Kubernetes Master 节点的后端配置
backend kubernetes-backend
    balance roundrobin                 # 使用轮询算法进行负载均衡
    option tcp-check                   # 开启 TCP 检查以检测服务器健康状态
    server master1 kub-k8s-master1:6443 check # Kubernetes Master 节点1
    server master2 kub-k8s-master2:6443 check # Kubernetes Master 节点2
    server master3 kub-k8s-master3:6443 check # Kubernetes Master 节点3

[root@localhost ~]# systemctl start haproxy

image.png

4.9.2 配置keepalived
[root@kub-k8s-master1 ~]# yum install -y keepalived
! Configuration File for keepalived

global_defs {
   router_id directory1  # master2、3记得修改
}

vrrp_instance VI_1 {
    state MASTER  # master2、3记得修改
    interface ens33
    virtual_router_id 80
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.75.104/24 # 网段的vip,也是4.9.3初始化步骤中的 --control-plane-endpoint
    }
}

启动并开机启动
4.9.3 初始化集群

注意事项:

  • 1、–control-plane-endpoint “192.168.75.104:8443” 指的是vip的地址和haproxy的端口
  • 2、–pod-network-cidr=10.244.0.0/16 可以默认,如果修改,所有pod的网段均会修改
  • 3、–apiserver-advertise-address=192.168.75.100 指的是当前master1 的ip地址
  • 4、–apiserver-cert-extra-sans=后面指的是所有master的ip和vip地址
运行初始化过程如下:
[root@kub-k8s-master1 ~]# kubeadm init --kubernetes-version=v1.22.0 --control-plane-endpoint "192.168.75.104:8443" --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.75.100 --apiserver-cert-extra-sans=192.168.75.104,192.168.75.100,192.168.75.101,192.168.75.102,192.168.75.101 --upload-certs
[init] Using Kubernetes version: v1.22.0
[preflight] Running pre-flight checks
        [WARNING SystemVerification]: this Docker version is not on the list of validated versions: 25.0.0. Latest validated version: 20.10
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kub-k8s-master1 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.75.100 192.168.75.104 192.168.75.101 192.168.75.102]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [kub-k8s-master1 localhost] and IPs [192.168.75.100 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [kub-k8s-master1 localhost] and IPs [192.168.75.100 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
[kubeconfig] Writing "admin.conf" kubeconfig file
[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 5.013483 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.22" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
[upload-certs] Using certificate key:
1d241f4bdfbfb37444221397b0e3522c8096f54d4b301cc49b69ddc735bee9c1
[mark-control-plane] Marking the node kub-k8s-master1 as control-plane by adding the labels: [node-role.kubernetes.io/master(deprecated) node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node kub-k8s-master1 as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: hxyvvg.bzvpv2h1ag28g79m
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of the control-plane node running the following command on each as root:
  # 其他控制节点执行
  kubeadm join 192.168.75.104:8443 --token hxyvvg.bzvpv2h1ag28g79m \
        --discovery-token-ca-cert-hash sha256:19a140207276157f923d7f962b5a51e93006f282180b439ad5c1390acaea722c \
        --control-plane --certificate-key 1d241f4bdfbfb37444221397b0e3522c8096f54d4b301cc49b69ddc735bee9c1

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.

Then you can join any number of worker nodes by running the following on each as root:
 # 其他node节点执行
kubeadm join 192.168.75.104:8443 --token hxyvvg.bzvpv2h1ag28g79m \
        --discovery-token-ca-cert-hash sha256:19a140207276157f923d7f962b5a51e93006f282180b439ad5c1390acaea722c 
上面记录了完成的初始化输出的内容,根据输出的内容基本上可以看出手动初始化安装一个Kubernetes集群所需要的关键步骤。
其中有以下关键内容:
    [kubelet] 生成kubelet的配置文件”/var/lib/kubelet/config.yaml”
    [certificates]生成相关的各种证书
    [kubeconfig]生成相关的kubeconfig文件
    [bootstraptoken]生成token记录下来,后边使用kubeadm join往集群中添加节点时会用到
  
配置使用kubectl
如下操作在master节点操作
[root@kub-k8s-master1 ~]# rm -rf $HOME/.kube
[root@kub-k8s-master1 ~]# mkdir -p $HOME/.kube
[root@kub-k8s-master1 ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@kub-k8s-master1 ~]# chown $(id -u):$(id -g) $HOME/.kube/config

查看node节点
[root@k8s-master ~]# kubectl get nodes
NAME         STATUS     ROLES    AGE     VERSION
k8s-master   NotReady   master   2m41s   v1.22.0
4.10 配置使用网络插件[master]
# 版本差异 https://projectcalico.docs.tigera.io/archive/v3.22/getting-started/kubernetes/requirements
#> 部署calico网络插件
[root@kub-k8s-master1 ~]# curl -L https://docs.projectcalico.org/v3.22/manifests/calico.yaml -O
# 修改 文件
4205             - name: IP_AUTODETECTION_METHOD
4206               value: "interface=ens33"

[root@kub-k8s-master1 ~]# kubectl apply -f  calico.yaml

[root@kub-k8s-master1 ~]# kubectl get pod -A -w
NAMESPACE     NAME                                       READY   STATUS    RESTARTS       AGE
kube-system   calico-kube-controllers-7c87c5f9b8-q2vth   1/1     Running   0              36s
kube-system   calico-node-5th7f                          1/1     Running   0              36s
kube-system   calico-node-g996t                          1/1     Running   0              36s
kube-system   calico-node-n94v5                          1/1     Running   0              36s
kube-system   calico-node-qjmth                          1/1     Running   0              36s
kube-system   calico-node-rfzf7                          1/1     Running   0              36s
kube-system   coredns-78fcd69978-5sg7p                   1/1     Running   0              11m
kube-system   coredns-78fcd69978-btws4                   1/1     Running   0              11m
kube-system   etcd-kub-k8s-master1                       1/1     Running   6              11m
kube-system   etcd-kub-k8s-master2                       1/1     Running   0              10m
kube-system   etcd-kub-k8s-master3                       1/1     Running   0              10m
kube-system   kube-apiserver-kub-k8s-master1             1/1     Running   6              11m
kube-system   kube-apiserver-kub-k8s-master2             1/1     Running   2 (10m ago)    11m
kube-system   kube-apiserver-kub-k8s-master3             1/1     Running   0              10m
kube-system   kube-controller-manager-kub-k8s-master1    1/1     Running   10 (10m ago)   11m
kube-system   kube-controller-manager-kub-k8s-master2    1/1     Running   0              11m
kube-system   kube-controller-manager-kub-k8s-master3    1/1     Running   0              10m
kube-system   kube-proxy-4bsm2                           1/1     Running   0              8m2s
kube-system   kube-proxy-bb5rd                           1/1     Running   0              10m
kube-system   kube-proxy-bnxj5                           1/1     Running   0              8m4s
kube-system   kube-proxy-cfm5m                           1/1     Running   0              11m
kube-system   kube-proxy-smhd6                           1/1     Running   0              11m
kube-system   kube-scheduler-kub-k8s-master1             1/1     Running   10 (10m ago)   11m
kube-system   kube-scheduler-kub-k8s-master2             1/1     Running   0              11m
kube-system   kube-scheduler-kub-k8s-master3             1/1     Running   0              10m

calico 原理

  • 基于 BGP 的网络连接:Calico 使用 BGP 协议在 Kubernetes 集群中的节点之间建立网络连接。每个节点运行一个 Calico 代理【daemonset】,该代理负责与其他节点建立 BGP 连接,并将容器的网络流量路由到正确的目的地。
  • 网络策略:Calico 提供了一种灵活的网络策略模型,允许定义容器之间的访问规则。可以使用标签来标识容器,并使用网络策略来控制容器之间的流量。
  • IP 地址管理:Calico 自动管理 Kubernetes 集群中的 IP 地址分配。它使用 IPIP 隧道技术将容器的 IP 地址映射到节点的 IP 地址,以确保容器之间的通信。
  • 网络隔离:Calico 提供了网络隔离功能,允许将不同的容器和应用程序隔离到不同的网络中。

flannel 原理

  • 网络覆盖:Flannel 在 Kubernetes 集群中的每个节点上运行一个守护进程,该守护进程使用 VXLAN。
  • 网络配置:Flannel 守护进程负责配置虚拟网络,并将容器的网络流量路由到正确的目的地。
  • IP 地址分配:Flannel 自动管理 Kubernetes 集群中的 IP 地址分配。它使用 IPAM(IP Address Management)模块来分配 IP 地址,并将它们分配给容器。
  • 网络策略:Flannel 支持网络策略,允许定义容器之间的访问规则。可以使用网络策略来控制容器之间的流量,以确保应用程序的安全性。
4.11 加入集群[node]

注意,如果丢失了初始化命令,可以通过一下命令重新生成

kubeadm token create --print-join-command

master 节点[master2、master3]

[root@kub-k8s-master1 ~]# kubeadm join 192.168.75.104:8443 --token hxyvvg.bzvpv2h1ag28g79m         --discovery-token-ca-cert-hash sha256:19a140207276157f923d7f962b5a51e93006f282180b439ad5c1390acaea722c         --control-plane --certificate-key 1d241f4bdfbfb37444221397b0e3522c8096f54d4b301cc49b69ddc735bee9c1

node 节点

配置node节点加入集群:
如果报错开启ip转发:
# sysctl -w net.ipv4.ip_forward=1

在所有node节点操作,此命令为初始化master成功后返回的结果
# kubeadm join 192.168.75.104:8443 --token hxyvvg.bzvpv2h1ag28g79m \
        --discovery-token-ca-cert-hash sha256:19a140207276157f923d7f962b5a51e93006f282180b439ad5c1390acaea722c
4.12 后续检查[master]
各种检测:
1.查看pods:
[root@kub-k8s-master1 ~]# kubectl get pod -A -w
NAMESPACE     NAME                                       READY   STATUS    RESTARTS       AGE
kube-system   calico-kube-controllers-7c87c5f9b8-q2vth   1/1     Running   0              36s
kube-system   calico-node-5th7f                          1/1     Running   0              36s
kube-system   calico-node-g996t                          1/1     Running   0              36s
kube-system   calico-node-n94v5                          1/1     Running   0              36s
kube-system   calico-node-qjmth                          1/1     Running   0              36s
kube-system   calico-node-rfzf7                          1/1     Running   0              36s
kube-system   coredns-78fcd69978-5sg7p                   1/1     Running   0              11m
kube-system   coredns-78fcd69978-btws4                   1/1     Running   0              11m
kube-system   etcd-kub-k8s-master1                       1/1     Running   6              11m
kube-system   etcd-kub-k8s-master2                       1/1     Running   0              10m
kube-system   etcd-kub-k8s-master3                       1/1     Running   0              10m
kube-system   kube-apiserver-kub-k8s-master1             1/1     Running   6              11m
kube-system   kube-apiserver-kub-k8s-master2             1/1     Running   2 (10m ago)    11m
kube-system   kube-apiserver-kub-k8s-master3             1/1     Running   0              10m
kube-system   kube-controller-manager-kub-k8s-master1    1/1     Running   10 (10m ago)   11m
kube-system   kube-controller-manager-kub-k8s-master2    1/1     Running   0              11m
kube-system   kube-controller-manager-kub-k8s-master3    1/1     Running   0              10m
kube-system   kube-proxy-4bsm2                           1/1     Running   0              8m2s
kube-system   kube-proxy-bb5rd                           1/1     Running   0              10m
kube-system   kube-proxy-bnxj5                           1/1     Running   0              8m4s
kube-system   kube-proxy-cfm5m                           1/1     Running   0              11m
kube-system   kube-proxy-smhd6                           1/1     Running   0              11m
kube-system   kube-scheduler-kub-k8s-master1             1/1     Running   10 (10m ago)   11m
kube-system   kube-scheduler-kub-k8s-master2             1/1     Running   0              11m
kube-system   kube-scheduler-kub-k8s-master3             1/1     Running   0              10m


2.查看节点:
[root@kub-k8s-master1 ~]# kubectl get nodes
NAME             STATUS   ROLES    AGE     VERSION
kub-k8s-master   Ready    master   43m     v1.22.0
kub-k8s-node1    Ready    <none>   6m46s   v1.22.0
kub-k8s-node2    Ready    <none>   6m37s   v1.22.0

到此集群配置完成
4.13 集群数据库相关操作

由k8s集群数据库文档单独记录

kubernetes命令自动补全

[root@kub-k8s-master1 ~]# yum install -y epel-release bash-completion
[root@kub-k8s-master1 ~]# source /usr/share/bash-completion/bash_completion
[root@kub-k8s-master1 ~]# source <(kubectl completion bash)
[root@kub-k8s-master1 ~]# echo "source <(kubectl completion bash)" >> ~/.bashrc
# 输入kubectl, 双击Tab
[root@kub-k8s-master1 ~]# kubectl
alpha       apply       certificate    convert      delete       edit        get       options      proxy        scale          uncordon
annotate    attach      cluster-info   cordon       describe     exec        kustomize patch        replace      set            version
api-resources  auth     completion     cp           diff         explain     label     plugin       rollout      taint          wait
api-versions   autoscale config        create       drain        expose      logs      port-forward   run        top
[root@kub-k8s-master1 ~]# kubectl

错误整理

#> 如果集群初始化失败:(每个节点都要执行)
$ kubeadm reset -f; ipvsadm --clear; rm -rf ~/.kube
$ systemctl restart kubelet

#> 如果忘记token值
$ kubeadm token create --print-join-command
$ kubeadm init phase upload-certs --upload-certs

五.可视化部署

5.1 官方Dashboard
5.1.1 部署Dashboard
# kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.4.0/aio/deploy/recommended.yaml
# kubectl edit svc kubernetes-dashboard -n kubernetes-dashboard
# 注意将 type: ClusterIP 改为 type: NodePort

# kubectl get svc -A |grep kubernetes-dashboard
kubernetes-dashboard   dashboard-metrics-scraper   ClusterIP   10.107.179.10   <none>        8000/TCP                 38s
kubernetes-dashboard   kubernetes-dashboard        NodePort    10.110.18.72    <none>        443:32231/TCP            38s
5.1.2 创建访问账号
#创建访问账号,准备一个yaml文件; vi dash.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
  

# kubectl apply -f dash.yaml
5.1.3 获取访问令牌
#获取访问令牌
# kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"
eyJhbGciOiJSUzI1NiIsImtpZCI6ImhRa2Q3UDFGempzb3VneVdUS0R0dk50SHlwUHExc0tuT21SOTdWQkczaG8ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLXBmbGxyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJlNDk0MTFhYS0zOTVhLTRkYzUtYmZmYS04MDZiODE3M2VmZWEiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.k3Gd9vRF6gP3Zxy89x14y4I2RCGn232bGLo9A5iEmeMl6BRPdJXZPbwy9fm3OT6ZjVc7LHiRgArczjZuCU3Sis4tIA_24A55h74WQE_JTeiZ5XnSGRknYQRHFSqyBrTaTxgDJb-O-DHol8GQLQjr6gIPzppHc-RhWhUFFNnPVP1nr2MLFBkvIT_qAcbHP6McFf2N6IsYwVFuvyIO77qWcmyFlgSr8a3A0INEJYB2bFPRL82rNc41c0TsUwOguQbJYrDA9lBqVpSff_7Uk_-7ycabZclbZX1HPz2-F59LQW7NWQy7biZw5b25AZaXAG3kL3SDuRRBoMNS92MmDFsVyA
5.1.4 浏览器访问
任意节点ip+端口[上面查看到为32231]
url https://192.168.246.216:32231/
使用token登录

5.2 kuboard 部署
[root@kube-master ~]# kubectl apply -f https://addons.kuboard.cn/kuboard/kuboard-v3.yaml

[root@kube-master ~]# kubectl get pod -n kuboard
NAME                               READY   STATUS    RESTARTS         AGE
kuboard-agent-2-5c54dcb98f-4vqvc   1/1     Running   24 (7m50s ago)   16d
kuboard-agent-747b97fdb7-j42wr     1/1     Running   24 (7m34s ago)   16d
kuboard-etcd-ccdxk                 1/1     Running   16 (8m58s ago)   16d
kuboard-etcd-k586q                 1/1     Running   16 (8m53s ago)   16d
kuboard-questdb-bd65d6b96-rgx4x    1/1     Running   10 (8m53s ago)   16d
kuboard-v3-5fc46b5557-zwnsf        1/1     Running   12 (8m53s ago)   16d

[root@kube-master ~]# kubectl get svc -n kuboard
NAME         TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                                        AGE
kuboard-v3   NodePort   10.96.25.125   <none>        80:30080/TCP,10081:30081/TCP,10081:30081/UDP   16d

image.png

5.3 kubesphere 部署

官方文档:

在 Kubernetes 上最小化安装 KubeSphere

# 运行安装
[root@kube-master ~]# kubectl apply -f https://github.com/kubesphere/ks-installer/releases/download/v3.4.0/kubesphere-installer.yaml
[root@kube-master ~]# kubectl apply -f https://github.com/kubesphere/ks-installer/releases/download/v3.4.0/cluster-configuration.yaml

# 查看日志
[root@kube-master ~]# kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l 'app in (ks-install, ks-installer)' -o jsonpath='{.items[0].metadata.name}') -f

卸载kubesphere

[root@kube-master ~]# curl https://raw.githubusercontent.com/kubesphere/ks-installer/release-3.4/scripts/kubesphere-delete.sh | sh
5.4 rainbond 部署

Rainbond
image.png

[root@kube-master ~]# curl -o install.sh https://get.rainbond.com && bash ./install.sh
RO
[root@kube-master ~]# helm repo add rainbond https://openchart.goodrain.com/goodrain/rainbond
[root@kube-master ~]# helm repo update
[root@kube-master ~]# kubectl create namespace rbd-system

卸载

如果是基于helm创建的rainbond
[root@kube-master ~]# helm uninstall rainbond -n rbd-system

如果是基于官方脚本创建的rainbond
# Delete PVC
[root@kube-master ~]# kubectl get pvc -n rbd-system | grep -v NAME | awk '{print $1}' | xargs kubectl delete pvc -n rbd-system

# Delete PV
[root@kube-master ~]# kubectl get pv | grep rbd-system | grep -v NAME | awk '{print $1}' | xargs kubectl delete pv

# Delete CRD
[root@kube-master ~]# kubectl delete crd componentdefinitions.rainbond.io \
helmapps.rainbond.io \
rainbondclusters.rainbond.io \
rainbondpackages.rainbond.io \
rainbondvolumes.rainbond.io \
rbdcomponents.rainbond.io \
servicemonitors.monitoring.coreos.com \
thirdcomponents.rainbond.io \
rbdabilities.rainbond.io \
rbdplugins.rainbond.io \
servicemeshclasses.rainbond.io \
servicemeshes.rainbond.io \
-n rbd-system

# Delete NAMESPACE
[root@kube-master ~]# kubectl delete ns rbd-system

六.集群常用指令

6.1 基础控制指令
# 查看对应资源: 状态
$ kubectl get <SOURCE_NAME> -n <NAMESPACE> -o wide

# 查看对应资源: 事件信息
$ kubectl describe <SOURCE_NAME> <SOURCE_NAME_RANDOM_ID> -n <NAMESPACE>

# 查看pod资源: 日志
$ kubectl logs -f <SOURCE_NAME_RANDOM_ID> [CONTINER_NAME] -n <NAMESPACE>

# 创建资源: 根据资源清单
$ kubectl apply[or create] -f <SOURCE_FILENAME>.yaml

# 删除资源: 根据资源清单
$ kubectl delete -f <SOURCE_FILENAME>.yaml

# 修改资源: 根据反射出的etcd中的配置内容, 生产中不允许该项操作, 且命令禁止
$ kubectl edit <SOURCE_NAME> <SOURCE_NAME_RANDOM_ID> -n <NAMESPACE>
6.2 命令实践
# 查看node状态
$ kubectl get node # -o wide 显示更加详细的信息

# 查看service对象
$ kubectl get svc

# 查看kube-system名称空间内的Pod
$ kubectl get pod -n kube-system

# 查看所有名称空间内的pod
$ kubectl get pod -A

# 查看集群信息
$ kubectl cluster-info

# 查看各组件信息
$ kubectl -s https://api-server:6443 get componentstatuses

# 查看各资源对象对应的api版本
$ kubectl explain pod

# 查看帮助信息
$ kubectl explain deployment
$ kubectl explain deployment.spec
$ kubectl explain deployment.spec.replicas
6.3 备注
问题一 查看各组件信息,可能会发现错误
$ kubectl -s https://192.168.96.143:6443 get componentstatuses
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME                 STATUS      MESSAGE                                                                                       ERROR
scheduler            Unhealthy   Get "http://127.0.0.1:10251/healthz": dial tcp 127.0.0.1:10251: connect: connection refused   
controller-manager   Unhealthy   Get "http://127.0.0.1:10252/healthz": dial tcp 127.0.0.1:10252: connect: connection refused   
etcd-0               Healthy     {"health":"true"}                                                           

问题一解决
$ vim /etc/kubernetes/manifests/kube-scheduler.yaml
 10 spec:
 11   containers:
 12   - command:
 13     - kube-scheduler
 14     - --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
 15     - --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
 16     - --bind-address=127.0.0.1
 17     - --kubeconfig=/etc/kubernetes/scheduler.conf
 18     - --leader-elect=true
 19     - --port=0   # 将此行注释或删除
 
 $ vim /etc/kubernetes/manifests/kube-controller-manager.yaml
  10 spec:
 11   containers:
 12   - command:
 13     - kube-controller-manager
 14     - --allocate-node-cidrs=true
 15     - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
 16     - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
 17     - --bind-address=127.0.0.1
 18     - --client-ca-file=/etc/kubernetes/pki/ca.crt
 19     - --cluster-cidr=10.244.0.0/16
 20     - --cluster-name=kubernetes
 21     - --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt
 22     - --cluster-signing-key-file=/etc/kubernetes/pki/ca.key
 23     - --controllers=*,bootstrapsigner,tokencleaner
 24     - --kubeconfig=/etc/kubernetes/controller-manager.conf
 25     - --port=0 # 将此行注释或删除
 
$ systemctl restart kubelet
 
$ kubectl -s https://192.168.96.143:6443 get componentstatuses
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok                  
controller-manager   Healthy   ok                  
etcd-0               Healthy   {"health":"true"}

七.Yaml语法解析

YAML是一个类似 XML、JSON 的标记性语言。它强调以数据为中心,并不是以标识语言为重点。因而YAML本身的定义比较简单,号称"一种人性化的数据格式语言"。

YAML的语法比较简单,主要有下面几个:
1、大小写敏感
2、使用缩进表示层级关系
3、缩进不允许使用tab,只允许空格( 低版本限制 )
4、缩进的空格数不重要,只要相同层级的元素左对齐即可
5、'#'表示注释

YAML支持以下几种数据类型:
1、纯量:单个的、不可再分的值
2、对象:键值对的集合,又称为映射(mapping)/ 哈希(hash) / 字典(dictionary)
3、数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)

补充说明:
1、书写yaml切记: 后面要加一个空格
2、如果需要将多段yaml配置放在一个文件中,中间要使用---分隔

举个例子,通过声明式配置yaml 创建名称空间

$ vim namespace.yaml
apiVersion: v1
	kind: Namespace
	metadata:
  		name: webserver
  
$ kubectl apply -f namespace.yaml

# 如果通过命令行创建
$ kubectl create namespace webserver
# 删除名称空间[注意,这将删除名称空间下的所有资源]
$ kubectl delete namespace webserver

八.k8s中的Pod

Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元; Pod 中会启动一个或一组紧密相关的业务容器, 各个业务容器相当于_Pod_ 中的各个进程, 此时就可以将_Pod_ 作为虚拟机看待; 在创建 Pod 时会启动一个init容器, 用来初始化存储和网络, 其余的业务容器都将在init容器启动后启动, 业务容器共享init容器的存储和网络; Pod 只是一个逻辑单元, 并不是真实存在的“主机”, 这种类比主机的概念可以更好的符合现有互联网中几乎所有的虚拟化设计; 像之前运行在虚拟机中的 nginx、mysql、php均可以使用对应的镜像运行出对应的容器在Pod中, 来类比虚拟机中运行这三者;

对于 Pod 而言, 在运行的过程中, k8s为了控制其生命周期的状态, 增加了_容器探测指针_、资源限额期望状态保持多容器结合安全策略设定控制器受管故障处理策略 等; Pod在平时是不能够被单独创建的, 而是需要使用控制器对其创建, 这样可以时刻保持Pod的期望状态;

在Kubernetes中所有的资源均可通过命令行参数或者资源清单[yaml/json]的方式进行创建和修改, 但由于Kubernetes属于声明式资源控制集群, 故大多管理Kubernetes集群的方式采用资源配置清单; 资源配置清单可以很好的追溯资源的原型和详细的配置, 且配合版本控制能够完成更好的溯源、回滚、发布等操作; 所以课程中所有的资源创建和修改都会采用资源配置清单的方式, 除部分命令行操作以外;

Pod API 对象

问题:

通过yaml文件创建pod的时候里面有容器,这个文件里面到底哪些属性属于 Pod 对象,哪些属性属于 Container?

解决:

共同特征是,它们描述的是"机器"这个整体,而不是里面运行的"程序"。

容器可选的设置属性包括:
Pod是 k8s 项目中的最小编排单位。将这个设计落实到 API 对象上,容器(Container)就成了 Pod 属性里一个普通的字段。
把 Pod 看成传统环境里的"虚拟机机器"、把容器看作是运行在这个"机器"里的"用户程序",那么很多关于 Pod 对象的设计就非常容易理解了。
凡是调度、网络、存储,以及安全相关的属性,基本上是 Pod 级别的
比如:

配置这个"机器"的网卡(即:Pod 的网络定义)

配置这个"机器"的磁盘(即:Pod 的存储定义)

配置这个"机器"的防火墙(即:Pod 的安全定义)

这台"机器"运行在哪个服务器之上(即:Pod 的调度)
kind:指定了这个 API 对象的类型(Type),是一个 Pod,根据实际情况,此处资源类型可以是Deployment、Job、Ingress、Service等。

metadata:包含Pod的一些meta信息,比如名称、namespace、标签等信息.

spec:specification of the resource content 指定该资源的内容,包括一些container,storage,volume以及其他Kubernetes需要的参数,以及诸如是否在容器失败时重新启动容器的属性。可在特定Kubernetes API找到完整的Kubernetes Pod的属性。
ˌ
specification----->[spesɪfɪˈkeɪʃn]
"name": 容器名称
"image": 容器镜像
"command": 容器启动指令
"args": 指令参数
"workingDir": 工作目录
"ports": 容器端口
"env": 环境变量
"resource": 资源限制
"volumeMounts": 卷挂载
"livenessProbe": 存活探针
"readinessProbe": 就绪探针
"startupProbe": 启动探针
"livecycle": 钩子函数
"terminationMessagePath": 容器的异常终止消息的路径,默认在 /dev/termination-log
"imagePullPolicy": 镜像拉去策略
"securityContext": 安全上下文
"stdin": 给容器分配标准输入,类似docker run -i
"tty": 分配终端
8.1 创建一个pod
$ vim nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.16.1
      ports:
        - containerPort: 80
        
$ kubectl apply -f nginx.yaml
8.2 pod是如何被创建的
  • step1: kubectl 向 k8s api server 发起一个create pod 请求
  • step2: k8s api server接收到pod创建请求后,不会去直接创建pod;而是生成一个包含创建信息的yaml。
  • step3: apiserver 将刚才的yaml信息写入etcd数据库。
  • step4: scheduler 查看 k8s api,判断:pod.spec.Node == null,若为null,表示这个Pod请求是新来的,需要创建;因此先进行调度计算,找到最适合的node。并更新数据库
  • step5: node节点上的Kubelet通过监听数据库更新,发现有新的任务与自己的node编号匹配,则进行任务创建
8.3 创建一个单容器pod
$ vim mysql.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mysql
  labels:
    name: mysql
spec:
  restartPolicy: OnFailure
  containers:
  - name: mysql
    image: mysql:5.7
    imagePullPolicy: IfNotPresent
    env:
      - name: MYSQL_ROOT_PASSWORD
        value: "123456"
    resources:
      limits:
        memory: "1024Mi"
        cpu: "1000m"
    ports:
      - containerPort: 3306
  nodeSelector:
    kubernetes.io/hostname: kub-k8s-node2
  # nodeName: kub-k8s-node2

$ kubectl apply -f mysql.yaml

# 字段解析
restartPolicy:
pod 重启策略,可选参数有:
1、Always:Pod中的容器无论如何停止都会自动重启
2、OnFailure: Pod中的容器非正常停止会自动重启
3、Never: Pod中的容器无论怎样都不会自动重启

imagePullPolicy:
镜像拉取策略,可选参数有:
1、Always:总是重新拉取
2、IfNotPresent:默认,如果本地有,则不拉取
3、Never:只是用本地镜像,从不拉取

nodeSelector:
节点选择器:可以指定node的标签,查看标签指令:
nodeName:
节点名称: 可以指定node的名称进行调度
$ kubectl get node --show-labels
8.4 创建一个多容器pod
#vim lnmp.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-many
  namespace: default
  labels:
  	app: nginx
spec:
  restartPolicy: OnFailure
  containers:
  - name: nginx
    image: nginx:1.16.1
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 80
  - name: php-fpm
    image: php:8.0-fpm-alpine
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 9000

$ kubectl apply -f lnmp.yaml
    
# 字段解析
nodeSelector:
节点选择器:可以指定node的标签,查看标签指令:

$ kubectl get node --show-labels
8.4.1 配置节点标签
添加标签
kubectl label nodes node3 name=value
删除标签
kubectl label nodes node3 name-
8.5 Pod容器的交互
8.5.1 创建pod,并做本地解析
vim host-alias.yaml

---
apiVersion: v1
kind: Pod
metadata:
  name: centos
  labels:
    name: centos
spec:
  containers:
  - name: centos
    image: centos:7
    command:
      - "tail"
      - "-f"
      - "/dev/null"
  hostAliases:
    - ip: "192.168.100.128"
      hostnames:
        - "master"
        - "k8s-master"
        - "apiserver"

# 字段解析
command:
启动容器时执行的指令,类似于docker run -it 镜像 tail -f /dev/null

hostAliases:
在容器中的/etc/hosts文件中配置本地解析
8.5.2 pod共享进程
[root@kub-k8s-master prome]# kubectl delete -f pod.yml
pod "website" deleted
[root@kub-k8s-master prome]# vim pod.yml   #修改如下。最好是提前将镜像pull下来。
---
apiVersion: v1
kind: Pod
metadata:
  name: website
  labels:
    app: website
spec:
  shareProcessNamespace: true  #共享进程名称空间
  containers:
    - name: test-web
      image: daocloud.io/library/nginx
      ports:
        - containerPort: 80
    - name: busybox
      image: daocloud.io/library/busybox
      stdin: true
      tty: true
      
2.创建
[root@kub-k8s-master prome]# kubectl apply -f pod.yml 
pod/website created
1. 定义了 shareProcessNamespace=true
表示这个 Pod 里的容器要共享进程(PID Namespace)如果是false则为不共享。
2. 定义了两个容器:
一个 nginx 容器
一个开启了 tty 和 stdin 的 busybos 容器

在 Pod 的 YAML 文件里声明开启它们俩,等同于设置了 docker run 里的 -it(-i 即 stdin,-t 即 tty)参数。此 Pod 被创建后,就可以使用 shell 容器的 tty 跟这个容器进行交互了。
我们通过kubectl可以查看到
# kubectl exec -it website -c busybox -- ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    7 root      0:00 nginx: master process nginx -g daemon off;
   35 101       0:00 nginx: worker process
   36 root      0:00 sh
   66 root      0:00 ps aux
   
在busybox中可以查看到nginx的进程
8.5.2 pod共用宿主机namespace
刚才的都是pod里面容器的Namespace,并没有和本机的Namespace做共享,接下来我们可以做与本机的Namespace共享,可以在容器里面看到本机的进程。

[root@kub-k8s-master prome]# kubectl delete -f pod.yml 
pod "website" deleted
[root@kub-k8s-master prome]# vim pod.yml #修改如下
---
apiVersion: v1
kind: Pod
metadata:
  name: website
  labels:
    app: website
spec:
  hostNetwork: true  #共享宿主机网络
  hostIPC: true  #共享ipc通信
  hostPID: true  #共享宿主机的pid
  containers:
    - name: test-web
      image: daocloud.io/library/nginx
      ports:
        - containerPort: 80
    - name: busybos
      image: daocloud.io/library/busybox
      stdin: true
      tty: true
创建pod
[root@kub-k8s-master prome]# kubectl apply -f pod.yml 
pod/website created

定义了共享宿主机的 Network、IPC 和 PID Namespace。这样,此 Pod 里的所有容器,会直接使用宿主机的网络、直接与宿主机进行 IPC 通信、看到宿主机里正在运行的所有进程。

8.6 钩子函数lifecycle

kubernetes 在主容器启动之后和删除之前提供了两个钩子函数:

  • post start:容器创建之后执行,如果失败会重启容器
  • pre stop:容器删除之前执行,执行完成之后容器将成功删除,在其完成之前会阻塞删除容器的操作
  • 钩子函数有三种定义方式
  • 第一种 exec 执行指令
  • 第二种 在容器中请求端口
  • 第三种 向容器发起http请求
lifecycle:
  postStart: 
    exec:
      command:
        - cat
        - /etc/hosts
lifecycle:
  postStart:
    tcpSocket:
      port: 8080
   lifecycle:
     postStart:
       httpGet:
         path: /                  # URI地址
         port: 80                 # 端口号
         host: 192.168.75.100     # 主机地址  
         scheme: HTTP
一个钩子函数的示例
$ cat nginx-lifecycle.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx-lifecycle
  namespace: default
  labels:
    app: nginx
spec:
  containers:
    - name: nginx-lifecycle
      image: nginx:1.16.1
      ports:
        - containerPort: 80
          protocol: TCP
      lifecycle:
        postStart:
          exec:
            command: ["/bin/sh", "-c", "echo '<h1>this is a nginx-lifecycle test page</h1>' > /usr/share/nginx/html/index.html"]
        preStop:
          exec:
            command: ["/usr/sbin/nginx", "-s", "quit"]

$ kubectl apply -f nginx-lifecycle.yaml 
pod/pod-lifecycle created

九.容器监控检查及恢复机制

在 k8s 中,可以为 Pod 里的容器定义一个健康检查"探针"(Probe)。kubelet 就会根据这个 Probe 的返回值决定这个容器的状态,而不是直接以容器是否运行(来自 Docker 返回的信息)作为依据。这种机制,是生产环境中保证应用健康存活的重要手段。

9.1 命令模式探针

Kubernetes 文档中的例子:

[root@kub-k8s-master1 ~]# cd prome/
[root@kub-k8s-master prome]# vim test-liveness-exec.yaml
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: test-liveness-exec
spec:
  containers:
  - name: liveness
    image: daocloud.io/library/nginx
    args:
    - /bin/sh
    - -c  
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 50
    livenessProbe:    #探针,健康检查
      exec:    #类型
        command:  #命令
        - cat 
        - /tmp/healthy
      initialDelaySeconds: 5   #健康检查,在容器启动5s后开始执行
      periodSeconds: 5   #每5s执行一次
它在启动之后做的第一件事是在/tmp目录下创建了一个healthy文件,以此作为自己已经正常运行的标志。而30s过后,它会把这个文件删除掉。
与此同时,定义了一个这样的 livenessProbe(健康检查)。它的类型是 exec,它会在容器启动后,在容器里面执行一句我们指定的命令,比如:"cat /tmp/healthy"。这时,如果这个文件存在,这条命令的返回值就是 0,Pod就会认为这个容器不仅已经启动,而且是健康的。这个健康检查,在容器启动5s后开始执行(initialDelaySeconds: 5),每5s执行一次(periodSeconds: 5)。

创建Pod:

[root@kub-k8s-master prome]# kubectl apply -f test-liveness-exec.yaml 
pod/test-liveness-exec created

查看 Pod 的状态:

[root@kub-k8s-master prome]# kubectl get pod 
NAME                    READY   STATUS    RESTARTS   AGE
nginx-configmap         1/1     Running   0          16h
nginx-pod               1/1     Running   0          12h
test-liveness-exec      1/1     Running   0          75s

由于已经通过了健康检查,这个 Pod 就进入了 Running 状态。

然后30 s 之后,再查看一下 Pod 的 Events:

[root@kub-k8s-master prome]# kubectl describe pod test-liveness-exec

发现,这个 Pod 在 Events 报告了一个异常:

Events:
  Type     Reason     Age                  From                    Message
  ----     ------     ----                 ----                    -------
Warning  Unhealthy  54s (x9 over 3m34s)  kubelet, kub-k8s-node1  Liveness probe failed: cat: /tmp/healthy: No such file or directory

这个健康检查探查到 /tmp/healthy 已经不存在了,所以它报告容器是不健康的。那么接下来会发生什么呢?

再次查看一下这个 Pod 的状态:

[root@kub-k8s-master prome]# kubectl get pod test-liveness-exec
NAME                 READY   STATUS    RESTARTS   AGE
test-liveness-exec   1/1     Running   4          5m19s

这时发现,Pod 并没有进入 Failed 状态,而是保持了 Running 状态。这是为什么呢?

RESTARTS 字段从 0 到 1 的变化,就明白原因了:这个异常的容器已经被 Kubernetes 重启了。在这个过程中,Pod 保持 Running 状态不变。
#注
k8s 中并没有 Docker 的 Stop 语义。所以如果容器被探针检测到有问题,查看状态虽然看到的是 Restart,但实际却是重新创建了容器。

这个功能就是 Kubernetes 里的Pod 恢复机制,也叫 restartPolicy。它是 Pod 的 Spec 部分的一个标准字段(pod.spec.restartPolicy),默认值是 Always,即:任何时候这个容器发生了异常,它一定会被重新创建。

小提示:

Pod 的恢复过程,永远都是发生在当前节点上,而不会跑到别的节点上去。事实上,一旦一个 Pod 与一个节点(Node)绑定,除非这个绑定发生了变化(pod.spec.node 字段被修改),否则它永远都不会离开这个节点。这也就意味着,如果这个宿主机宕机了,这个 Pod 也不会主动迁移到其他节点上去。
9.2 http get方式探针
[root@kub-k8s-master prome]# vim liveness-httpget.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: liveness-httpget-pod
  namespace: default
spec:
  containers:
    - name: liveness-exec-container
      image: daocloud.io/library/nginx
      imagePullPolicy: IfNotPresent
      ports:
        - name: http
          containerPort: 80
      livenessProbe:  #探针,健康检查
        httpGet:
          port: http
          path: /index.html
        initialDelaySeconds: 1
        periodSeconds: 3

创建该pod

[root@kub-k8s-master prome]# kubectl create -f liveness-httpget.yaml 
pod/liveness-httpget-pod created

查看当前pod的状态

[root@kub-k8s-master prome]# kubectl describe pod liveness-httpget-pod
...
Liveness:       http-get http://:http/index.html delay=1s timeout=1s period=3s #success=1 #failure=3
...

登陆容器

测试将容器内的index.html删除掉
[root@kub-k8s-master prome]# kubectl exec -it liveness-httpget-pod /bin/bash
root@liveness-httpget-pod:/# mv /usr/share/nginx/html/index.html index.html
root@liveness-httpget-pod:/# command terminated with exit code 137
可以看到,当把index.html移走后,这个容器立马就退出了。

此时,查看pod的信息

[root@kub-k8s-master prome]# kubectl describe pod liveness-httpget-pod
...
Normal   Killing    49s                  kubelet, kub-k8s-node2  Container liveness-exec-container failed liveness probe, will be restarted
  Normal   Pulled     49s                  kubelet, kub-k8s-node2  Container image "daocloud.io/library/nginx" already present on machine
...

看输出,容器由于健康检查未通过,pod会被杀掉,并重新创建

[root@kub-k8s-master prome]#  kubectl get pods
NAME                    READY   STATUS             RESTARTS   AGE
lifecycle-demo          1/1     Running            1          34h
liveness-httpget-pod    1/1     Running            1          5m42s

#restarts 为 1

重新登陆容器,发现index.html又出现了,证明容器是被重拉了。

[root@kub-k8s-master prome]# kubectl exec -it liveness-httpget-pod /bin/bash
root@liveness-httpget-pod:/# cat /usr/share/nginx/html/index.html
9.3 POD 的恢复策略
Pod  恢复策略:
可以通过设置 restartPolicy,改变 Pod 的恢复策略。一共有3种:
    1. Always:      在任何情况下,只要容器不在运行状态,就自动重启容器;
    2. OnFailure:    只在容器 异常时才自动重启容器;
    3. Never:         从来不重启容器。
实际使用时,需要根据应用运行的特性,合理设置这三种恢复策略。

十.投射数据卷 Projected Volume

注:Projected Volume 是 Kubernetes v1.11 之后的新特性

10.1 什么是Projected Volume
在 k8s 中,有几种特殊的 Volume,它们的意义不是为了存放容器里的数据,"而是为容器提供预先定义好的数据。"
从容器的角度来看,这些 Volume 里的信息仿佛是被 k8s "投射"(Project)进入容器当中的。
10.2 k8s 支持的 Projected Volume 方式
Secret
ConfigMap
Downward API

十一.Secret 实现

11.1 secret  详解
secret用来保存小片敏感数据的k8s资源,例如密码,token,或者秘钥。这类数据当然也可以存放在Pod或者镜像中,但是放在Secret中是为了更方便的控制如何使用数据,并减少暴露的风险。
 
 用户可以创建自己的secret,系统也会有自己的secret。
 Pod需要先引用才能使用某个secret

Pod使用secret方式:

作为volume的一个域被一个或多个容器挂载

內建的Secrets:

由ServiceAccount创建的API证书附加的秘钥k8s自动生成的用来访问apiserver的Secret,所有Pod会默认使用这个Secret与apiserver通信

创建自己的Secret:

方式1:使用kubectl create secret命令
方式2:yaml文件创建Secret
11.2 secret 使用

创建Secret

假如某个Pod要访问数据库,需要用户名密码,现在我们分别设置这个用户名和密码
Secret 对象要求这些数据必须是经过 Base64 转码的,以免出现明文密码显示的安全隐患。

  创建一个secret.yaml文件,内容用base64编码:明文显示容易被别人发现,这里先转码。
  [root@kub-k8s-master1 ~]# echo -n 'admin' | base64
  YWRtaW4=
  [root@kub-k8s-master1 ~]# echo -n '1f2d1e2e67df' | base64
  MWYyZDFlMmU2N2Rm

创建一个secret.yaml文件,内容用base64编码

[root@kub-k8s-master prome]# vim secret.yml
---
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque  #模糊
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

创建:

[root@kub-k8s-master prome]# kubectl apply -f secret.yml 
secret/mysecret created

解析Secret中内容,还是经过编码的—需要解码

查看secret
[root@kub-k8s-master1 ~]# kubectl get secrets
NAME                  TYPE                                  DATA   AGE
default-token-7vc82   kubernetes.io/service-account-token   3      30h
mysecret              Opaque                                2      6s

查看secret详细信息
[root@kub-k8s-master prome]# kubectl get secret mysecret -o yaml
apiVersion: v1
data:
  password: MWYyZDFlMmU2N2Rm
  username: YWRtaW4=
kind: Secret
metadata:
  creationTimestamp: "2019-10-21T03:07:56Z"
  name: mysecret
  namespace: default
  resourceVersion: "162855"
  selfLink: /api/v1/namespaces/default/secrets/mysecret
  uid: 36bcd07d-92eb-4755-ac0a-a5843ed986dd
type: Opaque

手动base64解码方式:

[root@kub-k8s-master1 ~]# echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
11.3 使用Secret
secret可以作为数据卷挂载或者作为环境变量暴露给Pod中的容器使用,也可以被系统中的其他资源使用。

一个Pod中引用Secret的列子:

创建一个Secret,多个Pod可以引用同一个Secret

修改Pod的定义,在spec.volumes[]加一个volume,给这个volume起个名字,spec.volumes[].secret.secretName记录的是要引用的Secret名字
[root@kub-k8s-master prome]# vim pod_use_secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: testredis
    image: daocloud.io/library/redis
    volumeMounts:    #挂载一个卷
    - name: foo     #这个名字需要与定义的卷的名字一致
      mountPath: "/etc/foo"  #挂载到容器里哪个目录下,随便写
      readOnly: true
  volumes:     #数据卷的定义
  - name: foo   #卷的名字这个名字自定义
    secret:    #卷是直接使用的secret。
      secretName: mysecret   #调用刚才定义的secret
      
创建:
[root@kub-k8s-master prome]# kubectl apply -f pod_use_secret.yaml 
pod/mypod created
[root@kub-k8s-master prome]# kubectl exec -it mypod /bin/bash
root@mypod:/data# cd /etc/foo/
root@mypod:/etc/foo# ls
password  username
root@mypod:/etc/foo# cat password 
1f2d1e2e67df
结果中看到,保存在 Etcd 里的用户名和密码信息,已经以文件的形式出现在了容器的 Volume 目录里。
而这个文件的名字,就是 kubectl create secret 指定的 Key,或者说是 Secret 对象的 data 字段指定的 Key。
每一个被引用的Secret都要在spec.volumes中定义
 如果Pod中的多个容器都要引用这个Secret那么每一个容器定义中都要指定自己的volumeMounts,但是Pod定义中声明一次spec.volumes就好了。

映射secret key到指定的路径

可以控制secret key被映射到容器内的路径,利用spec.volumes[].secret.items来修改被映射的具体路径

[root@kub-k8s-master prome]# kubectl delete -f pod_use_secret.yaml 
pod "mypod" deleted
[root@kub-k8s-master prome]# vim pod_use_secret.yaml
---
apiVersion: v1
kind: Pod
metadata:
 name: mypod
spec:
 containers:
 - name: testredis
   image: daocloud.io/library/redis
   volumeMounts:
   - name: foo
     mountPath: "/etc/foo"
     readOnly: true
 volumes:
 - name: foo
   secret:
     secretName: mysecret
     items:   #定义一个items
      - key: username   #将那个key重新定义到那个目录下
        path: my-group/my-username  #相对路径,相对于/etc/foo的路径
       
2.创建
[root@kub-k8s-master prome]# kubectl apply -f pod_use_secret.yaml 
pod/mypod created
3.从volume中读取secret的值
[root@kub-k8s-master prome]# kubectl exec -it mypod /bin/bash                         
root@mypod:/data# cd /etc/foo/my-group
root@mypod:/etc/foo/my-group# ls
my-username
root@mypod:/etc/foo/my-group# cat my-username 
admin
root@mypod:/etc/foo/my-group#

username被映射到了文件/etc/foo/my-group/my-username而不是/etc/foo/username,而password没有被使用,这种方式每个key的调用需要单独用key像username一样调用

被挂载的secret内容自动更新

也就是如果修改一个Secret的内容,那么挂载了该Secret的容器中也将会取到更新后的值,但是这个时间间隔是由kubelet的同步时间决定的。

1.设置base64加密
[root@kub-k8s-master prome]# echo qianfeng | base64
cWlhbmZlbmcK
2.将admin替换成qianfeng
[root@kub-k8s-master prome]# vim secret.yml
---
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: cWlhbmZlbmcK   #修改为qianfeng的base64加密后的
  password: MWYyZDFlMmU2N2Rm
  
1.创建
[root@kub-k8s-master prome]# kubectl apply -f secret.yml 
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
secret/mysecret configured
2.连接pod容器
[root@kub-k8s-master prome]# kubectl exec -it mypod /bin/bash
root@mypod:/data# cd /etc/foo/my-group
root@mypod:/etc/foo/my-group# ls
my-username
root@mypod:/etc/foo/my-group# cat my-username 
qianfeng

以环境变量的形式使用Secret

[root@kub-k8s-master prome]# kubectl delete -f pod_use_secret.yaml 
pod "mypod" deleted
[root@kub-k8s-master prome]# vim pod_use_secret.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: testredis
    image: daocloud.io/library/redis
    env:  #定义环境变量
     - name: SECRET_USERNAME   #创建新的环境变量名称
       valueFrom:
        secretKeyRef:     #调用的key是什么
         name: mysecret       #变量的值来自于mysecret
         key: username       #username里面的值

2.创建使用secret的pod容器
[root@kub-k8s-master prome]# kubectl apply -f pod_use_secret.yaml 
pod/mypod created
3.连接
[root@kub-k8s-master prome]# kubectl exec -it mypod /bin/bash 
root@mypod:/data# echo $SECRET_USERNAME   #打印一下定义的变量
qianfeng
11.4 实战案例
1.创建数据库用户的密码secret
[root@kub-k8s-master test]# echo -n 'QianFeng@123!' | base64
UWlhbkZlbmdAMTIzIQ==
[root@kub-k8s-master test]# cat secret.yml 
apiVersion: v1
kind: Secret
metadata:
 name: mysql-secret
type: Opaque
data:
 password: UWlhbkZlbmdAMTIzIQ==
 
[root@kub-k8s-master test]# kubectl apply -f secret.yml

2.创建数据库并使用secret
[root@kub-k8s-master test]# cat mysql.yaml 
apiVersion: v1
kind: Pod
metadata:
 name: my-mysql
spec:
 containers:
 - name: mysql
   image: daocloud.io/library/mysql:5.7
   ports:
    - containerPort: 3306
   env:
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
       secretKeyRef:
        name: mysql-secret
        key: password
[root@kub-k8s-master test]# kubectl apply -f myslq.yaml
[root@kub-k8s-master test]# kubectl get pod -o wide 
NAME       READY   STATUS    RESTARTS   AGE     IP            NODE    NOMINATED NODE   READINESS GATES
my-mysql   1/1     Running   0          2m47s   10.244.2.13   node2   <none>           <none>

测试:
[root@kub-k8s-master test]# mysql -uroot -p'QianFeng@123!' -h 10.244.2.13 -P3306

十二.ConfigMap祥解

ConfigMap与 Secret 类似,用来存储配置文件的kubernetes资源对象,所有的配置内容都存储在etcd中。

与 Secret 的区别:

ConfigMap 保存的是不需要加密的、应用所需的配置信息。

ConfigMap 的用法几乎与 Secret 完全相同:可以使用 kubectl create configmap 从文件或者目录创建 ConfigMap,也可以直接编写 ConfigMap 对象的 YAML 文件。
12.1 创建ConfigMap的方式
创建ConfigMap的方式有4种:

命令行方式
方式1:通过直接在命令行中指定configmap参数创建,即--from-literal
方式2:通过指定文件创建,即将一个配置文件创建为一个ConfigMap,--from-file=<文件>
方式3:通过指定目录创建,即将一个目录下的所有配置文件创建为一个ConfigMap,--from-file=<目录>
配置文件方式
方式4:事先写好标准的configmap的yaml文件,然后kubectl create -f 创建
12.2 通过命令行参数创建

创建命令:

[root@kub-k8s-master prome]# kubectl create configmap test-configmap --from-literal=user=admin --from-literal=pass=1122334
configmap/test-configmap created

结果如下面的data内容所示:

[root@kub-k8s-master prome]# kubectl get configmap test-configmap -o yaml
apiVersion: v1
data:
  pass: "1122334"
  user: admin
kind: ConfigMap
metadata:
  creationTimestamp: "2019-10-21T07:48:15Z"
  name: test-configmap
  namespace: default
  resourceVersion: "187590"
  selfLink: /api/v1/namespaces/default/configmaps/test-configmap
  uid: 62a8a0d0-fab9-4159-86f4-a06aa213f4b1
12.3 通过指定文件创建

编辑文件server.conf内容如下:

[root@kub-k8s-master prome]# vim server.conf
server {
listen 80;
server_name localhost;
location / {
root /var/www/html;
index index.html index.htm;
}
}

创建(可以有多个–from-file):

[root@kub-k8s-master prome]# kubectl create configmap test-config2 --from-file=server.conf
configmap/test-config2 created

结果如下面data内容所示:

[root@kub-k8s-master prome]# kubectl get configmap test-config2 -o yaml
apiVersion: v1
data:
  server.conf: |
    server {
    listen 80;
    server_name localhost;
    localtion / {
    root /var/www/html;
    index index.html index.htm;
    }
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2019-10-21T08:01:43Z"
  name: test-config2
  namespace: default
  resourceVersion: "188765"
  selfLink: /api/v1/namespaces/default/configmaps/test-config2
  uid: 790fca12-3900-4bf3-a017-5af1070792e5

通过指定文件创建时,configmap会创建一个key/value对,key是文件名,value是文件内容。

12.4 指定目录创建

configs 目录下的config-1和config-2内容如下所示:

[root@kub-k8s-master prome]# mkdir config
[root@kub-k8s-master prome]# cd config/
[root@kub-k8s-master config]# vim config1
aaa
bbb
c=d
[root@kub-k8s-master config]# vim config2
eee
fff
h=k

创建:

[root@kub-k8s-master config]# cd ..
[root@kub-k8s-master prome]# kubectl create configmap test-config3 --from-file=./config
configmap/test-config3 created

结果下面data内容所示:

[root@kub-k8s-master prome]# kubectl get configmap test-config3 -o yaml
apiVersion: v1
data:
  config1: |
    aaa
    bbb
    c=d
  config2: |
    eee
    fff
    h=k
kind: ConfigMap
metadata:
  creationTimestamp: "2019-10-21T08:20:42Z"
  name: test-config3
  namespace: default
  resourceVersion: "190420"
  selfLink: /api/v1/namespaces/default/configmaps/test-config3
  uid: 6e00fded-80a8-4297-aeb3-4c48795e6eb9

指定目录创建时,configmap内容中的各个文件会创建一个key/value对,key是文件名,value是文件内容。

12.5 通过yaml文件创建

yaml文件内容如下: 注意其中一个key的value有多行内容时的写法

[root@kub-k8s-master prome]# vim configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-config4
  namespace: default
data:
  cache_host: memcached-gcxt
  cache_port: "11211"
  cache_prefix: gcxt
  my.cnf: |
   [mysqld]
   log-bin = mysql-bin
   haha = hehe

创建:

[root@kub-k8s-master prome]# kubectl apply -f configmap.yaml 
configmap/test-config4 created

结果如下面data内容所示:

[root@kub-k8s-master prome]# kubectl get configmap test-config4 -o yaml
apiVersion: v1
data:
  cache_host: memcached-gcxt
  cache_port: "11211"
  cache_prefix: gcxt
  my.cnf: |
    [mysqld]
    log-bin = mysql-bin
    haha = hehe
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"cache_host":"memcached-gcxt","cache_port":"11211","cache_prefix":"gcxt","my.cnf":"[mysqld]\nlog-bin = mysql-bin\nhaha = hehe\n"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test-config4","namespace":"default"}}
  creationTimestamp: "2019-10-21T08:30:24Z"
  name: test-config4
  namespace: default
  resourceVersion: "191270"
  selfLink: /api/v1/namespaces/default/configmaps/test-config4
  uid: 2a8cd6e7-db2c-4781-b005-e0b76d26394b

查看configmap的详细信息:

# kubectl describe configmap
12.6 使用ConfigMap
使用ConfigMap的方式,一种是通过环境变量的方式,直接传递pod,另一种是使用volume的方式挂载入到pod内

示例ConfigMap文件:

[root@kub-k8s-master prome]# vim config-map.yml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: config-map
  namespace: default
data:
  special.how: very
  special.type: charm  

创建  
[root@kub-k8s-master prome]# kubectl apply -f config-map.yml 
configmap/config-map created
12.6.1 通过变量使用

(1) 使用valueFrom、configMapKeyRef、name、key指定要用的key:

1.设置指定变量的方式
[root@kub-k8s-master prome]# vim testpod.yml
---
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: daocloud.io/library/nginx
      env:   #专门在容器里面设置变量的关键字
        - name: SPECIAL_LEVEL_KEY   #这里的-name,是容器里设置的新变量的名字
          valueFrom:
            configMapKeyRef:
              name: config-map   #这里是来源于哪个configMap
              key: special.how      #configMap里的key
        - name: SPECIAL_TYPE_KEY
          valueFrom:
            configMapKeyRef:
              name: config-map
              key: special.type
  restartPolicy: Never

创建pod
[root@kub-k8s-master prome]# kubectl apply -f testpod.yml 
pod/dapi-test-pod created

测试:

[root@kub-k8s-master prome]# kubectl exec -it dapi-test-pod /bin/bash
root@dapi-test-pod:/# echo $SPECIAL_TYPE_KEY
charm

(2) 通过envFrom、configMapRef、name使得configmap中的所有key/value对儿  都自动变成环境变量:

[root@kub-k8s-master prome]# kubectl delete -f testpod.yml 
pod "dapi-test-pod" deleted
[root@kub-k8s-master prome]# cp testpod.yml testpod.yml.bak
[root@kub-k8s-master prome]# vim testpod.yml
---
apiVersion: v1
kind: Pod
metadata:
 name: dapi-test-pod
spec:
 containers:
   - name: test-container
     image: daocloud.io/library/nginx
     envFrom:
     - configMapRef:
         name: config-map
 restartPolicy: Never

这样容器里的变量名称直接使用configMap里的key名:

[root@kub-k8s-master prome]# kubectl apply -f testpod.yml
pod/dapi-test-pod created.
[root@kub-k8s-master prome]# kubectl exec -it dapi-test-pod /bin/bash
root@dapi-test-pod:/# env
HOSTNAME=dapi-test-pod
NJS_VERSION=0.3.3
NGINX_VERSION=1.17.1
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PKG_RELEASE=1~stretch
KUBERNETES_PORT=tcp://10.96.0.1:443
PWD=/
special.how=very
HOME=/root
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
TERM=xterm
SHLVL=1
KUBERNETES_SERVICE_PORT=443
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
special.type=charm
KUBERNETES_SERVICE_HOST=10.96.0.1
_=/usr/bin/env
12.6.2 作为volume挂载使用

(1) 把1.4中test-config4所有key/value挂载进来:

[root@kub-k8s-master prome]# kubectl delete -f testpod.yml
pod "dapi-test-pod" deleted
[root@kub-k8s-master prome]# vim volupod.yml
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-configmap
spec:
  containers:
  - name: nginx-configmap
    image: daocloud.io/library/nginx
    volumeMounts:
    - name: config-volume4
      mountPath: "/tmp/config4"
  volumes:
  - name: config-volume4
    configMap:
      name: test-config4
          
创建pod
[root@kub-k8s-master prome]# kubectl apply -f volupod.yml 
pod/nginx-configmap created

进入容器中/tmp/config4查看:

[root@kub-k8s-master prome]#  kubectl  exec -it nginx-configmap /bin/bash
root@nginx-configmap:/# ls /tmp/config4/
cache_host  cache_port	cache_prefix  my.cnf
root@nginx-configmap:/# cat /tmp/config4/cache_host 
memcached-gcxt
root@nginx-configmap:/#

可以看到,在config4文件夹下以每一个key为文件名,value为内容,创建了多个文件。

12.7 实战案例
创建configmap
[root@kub-k8s-master configmap]# vim configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
 name: nginx-server-conf
 namespace: default
data:
 index.html: |
  Hello, cloud computing
  Hello, Mr. Wang
  
[root@kub-k8s-master configmap]# kubectl get configmap
NAME                DATA   AGE
nginx-server-conf   2      7s
[root@kub-k8s-master configmap]# kubectl get configmap nginx-server-conf -o yaml

使用configmap
[root@kub-k8s-master configmap]# vim pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
 name: test-webapp
spec:
 containers:
 - name: nginx-app
   image: daocloud.io/library/nginx
   ports:
    - containerPort: 80
   volumeMounts:
   - name: nginx-volume
     mountPath: "/usr/share/nginx/html"
 volumes:
 - name: nginx-volume
   configMap:
    name: nginx-server-conf
[root@kub-k8s-master configmap]# kubectl apply -f pod.yaml
[root@kub-k8s-master configmap]# kubectl get pod 
NAME          READY   STATUS    RESTARTS   AGE
test-webapp   1/1     Running   0          6s
[root@kub-k8s-master configmap]# kubectl exec -it test-webapp /bin/bash
root@test-webapp:/# cd /usr/share/nginx/html/
root@test-webapp:/usr/share/nginx/html# ls
index.html
root@test-webapp:/usr/share/nginx/html# cat index.html 
Hello, cloud computing
Hello, Mr. Wang

[root@kub-k8s-master configmap]# curl 10.244.2.25
Hello, cloud computing
Hello, Mr. Wang

十三.Downward API

Downward API
用于在容器中获取 POD 的基本信息,kubernetes原生支持

Downward API提供了两种方式用于将 POD 的信息注入到容器内部:
1.环境变量:用于单个变量,可以将 POD 信息直接注入容器内部。
2.Volume挂载:将 POD 信息生成为文件,直接挂载到容器内部中去。
13.1 目前 Downward API 支持的字段
1. 使用 fieldRef 可以声明使用:
spec.nodeName - 宿主机名字
status.hostIP - 宿主机 IP
metadata.name - Pod 的名字
metadata.namespace - Pod 的 Namespace
status.podIP - Pod 的 IP
spec.serviceAccountName - Pod 的 Service Account 的名字
metadata.uid - Pod 的 UID
metadata.labels['<KEY>'] - 指定 <KEY> 的 Label 值
metadata.annotations['<KEY>'] - 指定 <KEY> 的 Annotation 值
metadata.labels - Pod 的所有 Label
metadata.annotations - Pod 的所有 Annotation

上面这个列表的内容,随着 Kubernetes 项目的发展肯定还会不断增加。所以这里列出来的信息仅供参考,在使用 Downward API 时,还是要记得去查阅一下官方文档。

所有基本信息可以使用下面的方式去查看(describe方式看不出来):
[root@kub-k8s-master configmap]# kubectl  get pod test-webapp -o yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"test-webapp","namespace":"default"},"spec":{"containers":[{"image":"daocloud.io/library/nginx","name":"nginx-app","volumeMounts":[{"mountPath":"/usr/share/nginx/html","name":"nginx-volume"}]}],"volumes":[{"configMap":{"name":"nginx-server-conf"},"name":"nginx-volume"}]}}
  creationTimestamp: "2021-02-21T09:44:51Z"
  name: test-webapp
  namespace: default
  resourceVersion: "270687"
  selfLink: /api/v1/namespaces/default/pods/test-webapp
  uid: ed92d685-f800-464f-95dc-d6aa5f92fc9c
......
13.2 实战案例

使用fieldRef获取 POD 的基本信息,以环境变量的方式实现

[root@kub-k8s-master prome]# vim test-env-pod.yml
---
apiVersion: v1
kind: Pod
metadata:
    name: test-env-pod
    namespace: kube-system
spec:
    containers:
    - name: test-env-pod
      image: daocloud.io/library/nginx
      env:
      - name: POD_NAME   #第一个环境变量的名字
        valueFrom:      #使用valueFrom方式设置
          fieldRef:    #关联一个字段metadata.name
            fieldPath: metadata.name  #这个字段从当前运行的pod详细信息查看
      - name: POD_NAMESPACE
        valueFrom:
          fieldRef:
            fieldPath: metadata.namespace
      - name: POD_IP
        valueFrom:
          fieldRef:
            fieldPath: status.podIP
注意: POD 的 name 和 namespace 属于元数据,是在 POD 创建之前就已经定下来了的,所以使用 metadata 获取就可以了,但是对于 POD 的 IP 则不一样,因为POD IP 是不固定的,POD 重建了就变了,它属于状态数据,所以使用 status 去获取。

创建上面的 POD:

[root@kub-k8s-master prome]#  kubectl apply -f test-env-pod.yml
pod/test-env-pod created

POD 创建成功后,查看:

[root@kub-k8s-master prome]# kubectl exec -it test-env-pod /bin/bash -n kube-system
root@test-env-pod:/# env | grep POD
POD_NAME=test-env-pod
POD_NAMESPACE=kube-system
POD_IP=10.244.1.35
root@test-env-pod:/#

Volume挂载

通过Downward API将 POD 的 Label、等信息通过 Volume 以文件的形式挂载到容器的某个文件中去,然后在容器中打印出该文件的值来验证。

[root@kub-k8s-master prome]# vim test-volume-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
   name: test-volume-pod
   namespace: kube-system
   labels:
       k8s-app: test-volume
       node-env: test
spec:
   containers:
   - name: test-volume-pod-container
     image: daocloud.io/library/nginx
     volumeMounts:
     - name: podinfo
       mountPath: /etc/podinfo
   volumes:
   - name: podinfo
     downwardAPI:
       items:
       - path: "labels"
         fieldRef:
           fieldPath: metadata.labels

创建上面的 POD :

[root@kub-k8s-master prome]# kubectl apply -f test-volume-pod.yaml pod/test-volume-pod created
[root@kub-k8s-master prome]# kubectl get pod -n kube-system
[root@k8s-master prome]# kubectl exec -it test-volume-pod /bin/bash -n kube-system
Secret、ConfigMap,以及 Downward API 这三种 Projected Volume 定义的信息,大多还可以通过环境变量的方式出现在容器里。但是,通过环境变量获取这些信息的方式,不具备自动更新的能力。一般情况下,建议使用 Volume 文件的方式获取这些信息。

十四.ServiceAccount详解

官方文档地址:https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/

k8s中提供了良好的多租户认证管理机制,如RBAC、ServiceAccount还有各种Policy等。

什么是 Service Account ?

Pod里的进程也可以与 apiserver 联系。 当它们在联系 apiserver 的时候,它们就会被认证为一个特定的 Service Account。

使用场景:

    Service Account它并不是给kubernetes集群的用户使用的,而是给pod里面的进程使用的,它为pod提供必要的身份认证。----专门为pod里面的进程和apiserver通信提供认证的。

Service account与User account区别:

1. User account是为人设计的,而service account则是为Pod中的进程调用Kubernetes API或其他外部服务而设计的

2. User account是跨namespace的,而service account则是仅局限它所在的namespace;

3. 每个namespace都会自动创建一个default service account    

4. Token controller检测service account的创建,并为它们创建secret
14.1 Service Account应用示例

Service Account(服务账号)示例

因为平时系统会使用默认service account,我们不需要自己创建,感觉不到service account的存在,本实验是使用自己手动创建的service account

1、创建serviceaccount
[root@kub-k8s-master1 ~]# kubectl create serviceaccount mysa
serviceaccount/mysa created
2、查看mysa
[root@kub-k8s-master1 ~]# kubectl describe sa mysa
Name:                mysa
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   mysa-token-cknwf
Tokens:              mysa-token-cknwf
Events:              <none>
3、查看mysa自动创建的secret
[root@kub-k8s-master1 ~]# kubectl  get secret
NAME                  TYPE                                  DATA   AGE
db-user-pass          Opaque                                2      11h
default-token-6svwp   kubernetes.io/service-account-token   3      4d23h
mysa-token-cknwf      kubernetes.io/service-account-token   3      76s
mysecret              Opaque                                2      11h
mysecret-01           Opaque                                2      6h58m
pass                  Opaque                                1      7h6m
user                  Opaque                                1      7h7m

4、使用mysa的sa资源配置pod
[root@kub-k8s-master1 ~]# cd prome/
[root@kub-k8s-master prome]# vim mysa-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
 name: nginx-pod
 labels:
   app: my-pod
spec:
 containers:
 - name: my-pod
   image: daocloud.io/library/nginx
   ports:
   - name: http
     containerPort: 80
 serviceAccountName: mysa   #指定serviceaccount的名称
 
5、导入
[root@kub-k8s-master prome]# kubectl apply -f  mysa-pod.yaml
pod/nginx-pod created
6、查看
[root@kub-k8s-master prome]# kubectl describe pod nginx-pod
7、查看使用的token和secret(使用的是mysa的token)
[root@kub-k8s-master prome]# kubectl get pod nginx-pod -o jsonpath={".spec.volumes"}
[map[name:mysa-token-cknwf secret:map[defaultMode:420 secretName:mysa-token-cknwf]]]
[root@kub-k8s-master prome]#

十五.RBAC 详解(基于角色的访问控制)

在Kubernetes中,授权有ABAC(基于属性的访问控制)、RBAC(基于角色的访问控制)、Webhook、Node、AlwaysDeny(一直拒绝)和AlwaysAllow(一直允许)这6种模式。

RBAC基于角色的访问控制--全拼Role-Based Access Control----做权限控制的,顾名思义就是通过给角色赋予相应的权限,从而使得该角色具有访问相关资源的权限。

k8s里面有两种用户,一种是User,一种就是service account(服务使用的账号)。
User account是为人设计的属于用户账户(个人使用的账号),此外User Account是跨Namespace的,而ServiceAccount则是仅局限它所在的Namespace。

在RABC API中,通过如下的步骤进行授权:
1)定义角色:在定义角色时会指定此角色对于资源的访问控制的规则;
2)绑定角色:将主体与角色进行绑定,对用户进行访问授权。
在K8s中这些资源分属于两个级别,名称空间(role/rolebinding)和集群级别(clusterrole/clusterrolebinding)这两个都是标准的K8s资源,可以直接定义。

Role与ClusterRole
 Role普通角色:一个Role对象只能用于授予对某一单一命名空间中资源的访问权限,普通角色只是在当前的名称空间生效。
 ClusterRole集群角色:整个Kubernetes集群范围内有效的角色则通过ClusterRole对象实现,可以访问整个集群资源。
 简介
role/ClusterRole:
    1、允许的操作,如get,list,update,create,delete等权限
    2、允许操作的对象,如pod,svc等资源
rolebinding:将哪个用户绑定到哪个role上
clusterrolebinding:绑定到集群角色上
   如果使用clusterrolebinding绑定到clusterrole上,表示绑定的用户拥有所有namespace的权限
   
 #这里面有哪些重要的东西:role,clusterrole,binding,账号。
15.1 创建k8s账号与RBAC授权使用
创建账号
1、创建私钥
[root@kub-k8s-master1 ~]# (umask 077; openssl genrsa -out soso.key 2048)
Generating RSA private key, 2048 bit long modulus
...............................+++
..........................+++
e is 65537 (0x10001)

用此私钥创建一个csr(证书签名请求)文件
[root@kub-k8s-master1 ~]# openssl  req -new -key soso.key -out soso.csr -subj  "/CN=soso" # 这个地方是用户名

拿着私钥和请求文件生成证书
[root@kub-k8s-master1 ~]# openssl x509 -req -in soso.csr -CA  /etc/kubernetes/pki/ca.crt  -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out soso.crt -days 365
Signature ok
subject=/CN=soso
Getting CA Private Key

生成账号
[root@kub-k8s-master1 ~]# kubectl config set-credentials soso --client-certificate=soso.crt --client-key=soso.key --embed-certs=true
User "soso" set.

3、设置上下文环境--指的是创建这个账号的环境在当前名称空间中
[root@kub-k8s-master1 ~]# kubectl config set-context soso@kubernetes --cluster=kubernetes --user=soso
Context "soso@kubernetes" created.
查看当前的工作上下文
[root@kub-k8s-master1 ~]# kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.75.100:6443
....
4、切换用户(切换上下文)
[root@kub-k8s-master1 ~]# kubectl  config use-context soso@kubernetes
Switched to context "soso@kubernetes".
验证是否已经切换到了新的上下文
[root@kub-k8s-master1 ~]# kubectl config current-context
soso@kubernetes
5.测试(还未赋予权限)
[root@kub-k8s-master1 ~]# kubectl  get pod
Error from server (Forbidden): pods is forbidden: User "soso" cannot list resource "pods" in API group "" in the namespace "default"
创建一个角色(role)---设置权限
1.切回管理帐号先
[root@kub-k8s-master1 ~]# kubectl  config use-context kubernetes-admin@kubernetes
Switched to context "kubernetes-admin@kubernetes".

创建角色(命令):
[root@kub-k8s-master1 ~]# kubectl  create role  role-reader  --verb=get,list,watch --resource=pod,svc
role.rbac.authorization.k8s.io/role-reader created
--verb: 相当于是权限
--resource:给什么资源使用

yaml文件方式:
[root@kub-k8s-master1 ~]# vim role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
 name: role-reader
rules: #定义规则
 - apiGroups: [""]  #表示当前pod使用核心的APIserver组,默认用""表示就可以
   resources: ["pods","svc"]
   verbs: ["get", "list", "watch", "create", "update", "delete"] #["*"]表示所有权限

[root@kub-k8s-master1 ~]# kubectl apply -f role.yaml 
role.rbac.authorization.k8s.io/role-reader created

[root@kub-k8s-master1 ~]# kubectl get roles
NAME          AGE
role-reader   30s

[root@kub-k8s-master1 ~]# kubectl describe role role-reader
Name:         role-reader
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"rbac.authorization.k8s.io/v1beta1","kind":"Role","metadata":{"annotations":{},"name":"role-reader","namespace":"default"},"...
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  pods       []                 []              [get list watch create update delete]
  svc        []                 []              [get list watch create update delete]

2.绑定用户soso(上面创建的用户),绑定用户到role-reader
[root@kub-k8s-master1 ~]# kubectl  create  rolebinding myrole-binding  --role=role-reader  --user=soso
rolebinding.rbac.authorization.k8s.io/myrole-binding created

yaml文件方式:
[root@k8s-master ~]# vim role-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
 name: myrolebind
subjects:  #定义对那个主体进行操作,有三种Subjects:Service Account、User Account、Groups
- kind: User
  name: soso
  apiGroup: rbac.authorization.k8s.io
roleRef:  #定义使用哪个角色
  kind: Role
  name: role-reader
  apiGroup: rbac.authorization.k8s.io

[root@k8s-master ~]# kubectl apply -f role-binding.yaml 
rolebinding.rbac.authorization.k8s.io/myrolebind created

[root@k8s-master ~]# kubectl get rolebinding 
NAME         AGE
myrolebind   25s

3.切换用户
[root@kub-k8s-master1 ~]# kubectl  config use-context soso@kubernetes
Switched to context "soso@kubernetes".

4.查看权限(只授权了default名称空间pod和svc的get,list,watch权限)
[root@kub-k8s-master1 ~]# kubectl  get pod
NAME                    READY   STATUS    RESTARTS   AGE
lifecycle-demo          1/1     Running   1          22h
mypod                   1/1     Running   0          8h
nginx-configmap         1/1     Running   0          4h29m
nginx-pod               1/1     Running   0          39m
[root@kub-k8s-master1 ~]#  kubectl  get pod -n kube-system  #无权访问kube-system
Error from server (Forbidden): pods is forbidden: User "soso" cannot list resource "pods" in API group "" in the namespace "kube-system"

[root@kub-k8s-master1 ~]# kubectl  delete pod nginx-pod   #无权限删除
Error from server (Forbidden): pods "nginx-pod" is forbidden: User "soso" cannot delete resource "pods" in API group "" in the namespace "default"

5.切换用户
[root@kub-k8s-master1 ~]# kubectl  config use-context kubernetes-admin@kubernetes
Switched to context "kubernetes-admin@kubernetes".



实验二,绑定用户到集群角色

6.删除soso账号之前绑定的rolebinding
[root@kub-k8s-master1 ~]# kubectl  delete rolebinding myrolebind
rolebinding.rbac.authorization.k8s.io "myrolebind" deleted

7.创建clusterrole #可以访问全部的namespace
[root@kub-k8s-master1 ~]# kubectl create clusterrole myclusterrole --verb=get,list,watch --resource=pod,svc
clusterrole.rbac.authorization.k8s.io/myclusterrole created

yaml文件方式:
[root@kub-k8s-master1 ~]# vim clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: myclusterrole
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
  - list
  - watch
[root@kub-k8s-master1 ~]# kubectl apply -f clusterrole.yaml
[root@kub-k8s-master1 ~]# kubectl get clusterrole

8.绑定集群角色到用户soso
[root@kub-k8s-master1 ~]# kubectl  create clusterrolebinding my-cluster-rolebinding   --clusterrole=myclusterrole --user=soso
clusterrolebinding.rbac.authorization.k8s.io/my-cluster-rolebinding created

yaml文件方式:
[root@kub-k8s-master1 ~]# vim clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: my-cluster-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: myclusterrole
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: soso
[root@kub-k8s-master1 ~]# kubectl apply -f clusterrolebinding.yaml
[root@kub-k8s-master1 ~]# kubectl get clusterrolebinding

9.切换账号
[root@kub-k8s-master1 ~]# kubectl  config use-context soso@kubernetes
Switched to context "soso@kubernetes".

10.查看权限 查看kube-system空间的pod
[root@kub-k8s-master1 ~]# kubectl  get pod -n kube-system
NAME                                     READY   STATUS    RESTARTS   AGE
coredns-5644d7b6d9-sm8hs                 1/1     Running   0          5d
coredns-5644d7b6d9-vddll                 1/1     Running   0          5d
etcd-kub-k8s-master                      1/1     Running   0          5d
... 

注意:11.切换为管理员用户
[root@kub-k8s-master1 ~]# kubectl  config use-context kubernetes-admin@kubernetes
15.2 设置上下文和账户切换

设置工作上下文(前提得有用户)

[root@kub-k8s-master1 ~]# kubectl  config   set-context  soso@kubernetes --cluster=kubernetes --user=soso
Context "soso@kubernetes" created.

查看当前的工作上下文

[root@kub-k8s-master1 ~]# kubectl config view
apiVersion: v1
clusters:
- cluster:
....

切换上下文(切换用户)

[root@kub-k8s-master1 ~]# kubectl config use-context soso@kubernetes
Switched to context "soso@kubernetes".

切换为管理员用户

[root@kub-k8s-master prome]# kubectl  config use-context kubernetes-admin@kubernetes
Switched to context "kubernetes-admin@kubernetes".

查看某个资源类型是由哪个apiserver版本提供

[root@kub-k8s-master1 ~]# kubectl explain ClusterRole

十六.Deployment 资源详解

如果Pod出现故障,对应的服务也会挂掉,所以Kubernetes提供了一个Deployment的概念 ,目的是让Kubernetes去管理一组Pod的副本,也就是副本集 ,这样就能够保证一定数量的副本一直可用,不会因为某一个Pod挂掉导致整个服务挂掉。

Deployment 还负责在 Pod 定义发生变化时,对每个副本进行滚动更新(Rolling Update)。

使用yaml创建Deployment
k8s deployment资源创建流程:
1. 用户通过 kubectl 创建 Deployment。
2. Deployment 创建 ReplicaSet。
3. ReplicaSet 创建 Pod。
对象的命名方式是:子对象的名字 = 父对象名字 + 随机字符串或数字

Deployment是一个定义及管理多副本应用(即多个副本 Pod)的新一代对象,与Replication Controller相比,它提供了更加完善的功能,使用起来更加简单方便。

16.1 deployment 实例
例1:
apiVersion: apps/v1
kind: Deployment
metadata:
 name: nginx-deployment
spec:
 selector:
   matchLabels:
     app: nginx
 replicas: 2
 template:
   metadata:
     labels:
       app: nginx
   spec:
     containers:
     - name: nginx
       image: nginx:1.7.9
       ports:
       - containerPort: 80
例2:在上面yaml的基础上添加了volume
[root@kub-k8s-master prome]# vim deployment.yaml
apiVersion: apps/v1  #注意版本号
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:  #属性,选择器
  matchLabels:
    app: nginx
replicas: 2  #管理的副本个数
template:  #模板属性
  metadata: #对pod的描述
    labels:
      app: nginx
  spec:
    volumes:   #定义共享卷
    - name: nginx-vol
      emptyDir: {}
    containers:
    - name: nginx
      image: daocloud.io/library/nginx
      ports:
      - containerPort: 80
      volumeMounts:  #定义挂载卷
      - mountPath: "/usr/share/nginx/html"
        name: nginx-vol
16.2 文件说明
创建Deployment:
将上述的YAML文件保存为deployment.yaml,然后创建Deployment:
[root@kub-k8s-master prome]# kubectl apply -f deployment.yaml
deployment.apps/nginx-deployment created

检查Deployment的列表:启动之后需要创建时间比较长
通过 kubectl get 命令检查这个 YAML 运行起来的状态:
[root@kub-k8s-master prome]# kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2/2     2            2           2m22s

[root@kub-k8s-master prome]# kubectl get pods -l app=nginx
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-59c4b86474-2llrt   1/1     Running   0          2m51s
nginx-deployment-59c4b86474-n2r2m   1/1     Running   0          2m51s

在这里加上了一个 -l 参数,即获取所有匹配 app: nginx 标签的 Pod。需要注意的是,在命令行中,所有 key-value 格式的参数,都使用"="而非":"表示。

删除Deployment:
[root@k8s-master ~]# kubectl delete deployments nginx-deployment
deployment "nginx-deployment" deleted
或者
[root@k8s-master ~]# kubectl delete -f deployment.yaml
16.3 deployment可用字段
replicas: 1 # 声明副本数目
revisionHistoryLimit: 3 # 保留历史版本
selector: # 选择器

十七.Service 服务

k8s 内部域名访问方式
…svc.cluster.local

17.1 创建service
1.创建一个depl
[root@kub-k8s-master prome]# vim nginx-depl.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dep01
spec:
  selector:
    matchLabels:
      app: web
  replicas: 2
  template:
      metadata:
        labels:
          app: web
      spec:
        containers:
          - name: testnginx9
            image: daocloud.io/library/nginx
            ports:
              - containerPort: 80
[root@kub-k8s-master prome]# kubectl apply -f nginx-depl.yml 
deployment.apps/nginx-deployment created
2. 创建service并且以nodePort的方式暴露端口给外网:
[root@kub-k8s-master prome]# vim nginx_svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysvc
spec:
  type: NodePort  #类型
  ports:
    - port: 8080
      nodePort: 30001
      targetPort: 80
  selector:   #选择器
    app: web
    
    
[root@kub-k8s-master prome]# kubectl apply -f nginx_svc.yaml 
service/mysvc created

3.测试
[root@kub-k8s-master prome]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP          5d18h
mysvc        NodePort    10.100.166.208   <none>        8080:30001/TCP   21s
17.2 页面请求测试

17.3 pod内部请求测试
# 进入docker容器
[root@kube-node1 ~]# docker exec -it b4 /bin/bash
# 请求
root@dep01-694c5dbcd-ccdsv:/# curl mysvc.default.svc.cluster.local:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
17.4 端口解析
# 解析
port
port是暴露在cluster ip上的端口,port提供了集群内部客户端访问service的入口,即clusterIP:port。

nodeport
nodePort 提供了集群外部客户端访问 Service 的一种方式,nodePort 提供了集群外部客户端访问 Service 的端口,通过 nodeIP:nodePort 提供了外部流量访问k8s集群中service的入口。

targetPort
targetPort是pod的端口,从port和nodePort来的流量经过kube-proxy流入到后端pod的targetPort上,最后进入容器。

containerPort
containerPort是pod内部容器的端口,targetPort映射到containerPort。
17.5 kube-proxy 使用ipvs
[root@master ~]# kubectl get configmap kube-proxy -n kube-system -o yaml > kube-proxy-configmap.yaml
[root@master ~]# sed -i 's/mode: ""/mode: "ipvs"/' kube-proxy-configmap.yaml
[root@master ~]# kubectl apply -f kube-proxy-configmap.yaml
[root@master ~]# rm -f kube-proxy-configmap.yaml
[root@master ~]# kubectl get pod -n kube-system | grep kube-proxy | awk '{system("kubectl delete pod "$1" -n kube-system")}' 

后续请求时,可以发现已经通过了算法进行调度
[root@kube-master ~]# ipvsadm -L -n
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  172.17.0.1:30001 rr
  -> 10.244.1.34:80               Masq    1      0          0         
  -> 10.244.2.31:80               Masq    1      0          0         
TCP  192.168.96.143:30001 rr
  -> 10.244.1.34:80               Masq    1      0          12        
  -> 10.244.2.31:80               Masq    1      0          12

十八.k8s服务暴露

18.1 ClusterIP

此类型会提供一个集群内部的虚拟IP(与Pod不在同一网段),以供集群内部的pod之间通信使用。ClusterIP也是Kubernetes service的默认类型。

18.2 NodePort

外网client—>nodeIP+nodePort—>podIP+PodPort

为每个节点暴露一个端口,通过nodeip + nodeport可以访问这个服务,同时服务依然会有cluster类型的ip+port。内部通过clusterip方式访问,外部通过nodeport方式访问。

18.3 loadbalance

LoadBalancer在NodePort基础上,K8S可以请求底层云平台创建一个负载均衡器,将每个Node作为后端,进行服务分发。

18.4 Ingress

Ingress是一种HTTP方式的路由转发机制,为K8S服务配置HTTP负载均衡器,通常会将服务暴露给K8S群集外的客户端。

十九.Ingress 暴露服务

要理解ingress,需要区分两个概念,ingress和ingress-controller:

ingress对象:
指的是k8s中的一个api对象,一般用yaml配置。作用是定义请求如何转发到service的规则,可以理解为配置模板。

ingress-controller:
具体实现反向代理及负载均衡的程序,对ingress定义的规则进行解析,根据配置的规则来实现请求转发。

简单来说,ingress-controller才是负责具体转发的组件,通过各种方式将它暴露在集群入口,外部对集群的请求流量会先到ingress-controller,而ingress对象是用来告诉ingress-controller该如何转发请求,比如哪些域名哪些path要转发到哪些服务等等。

service 的表现形式为IP:PORT,即工作在第四层传输层(TCP/IP层),对于不同的URL地址经常对应用不同的后端服务或者虚拟服务器,这些应用层的转发机制仅通过kubernetes的service机制是无法实现的,这种情况我么可以使用ingress策略定义和一个具体的ingress Controller.

Ingress提供七层负载均衡能力,可以通过 Ingress 配置提供外部可访问的 URL、负载均衡、SSL、基于名称的虚拟主机等。作为集群流量接入层,Ingress 的高可靠性显得尤为重要。

19.1 ingress详解
  • 这个负载均衡是基于nginx七层反向代理来实现的,ingress工作原理如下图:
  • 外部客户端通过访问负载均衡器,然后调度到service上,然后在调度到IngressController,IngressController通过Ingress规则(域名或虚拟主机)访问到后端pod,而在Ingress规则当中对应的主机是由service分组来设定的,可以看到,这幅图有2种service,最上面的service是用来对外提供服务的,而下面2个service仅仅是用来分pod组的
Kubernetes 并没有自带 Ingress Controller,实际上ingress-controller只是一个统称,具体实现有多种,需要自己单独安装,目前,由k8s维护的ingress-controller只有google云的GCE与ingress-nginx两个,常用的是 Ingress-nginx Controller.
Ingress 一般由三个组件组成:

1. Nginx 反向代理负载均衡器
2. Ingress Controller 可以理解为控制器,它通过不断的跟 Kubernetes API 交互,实时获取后端 Service、Pod 等的变化,比如新增、删除等,然后结合 Ingress 定义的规则生成配置,然后动态更新上边的 Nginx 负载均衡器,并刷新使配置生效,来达到服务自动发现的作用。
3. Ingress 则是定义规则,通过它定义某个域名的请求过来之后转发到集群中指定的 Service。它可以通过 Yaml 文件定义,可以给一个或多个 Service 定义一个或多个 Ingress 规则。
19.2 如何创建 Ingress 资源
  • Ingress 中的spec字段是Ingress资源的核心组成部分,主要包含以下3个字段:
    • rules:用于定义当前Ingress资源的转发规则列表;由rules定义规则,或没有匹配到规则时,所有的流量会转发到由backend定义的默认后端。
    • backend:默认的后端,用于服务那些没有匹配到任何规则的请求;定义Ingress资源时,必须要定义backend或rules两者之一,该字段用于让负载均衡器指定一个全局默认的后端。
    • tls:TLS配置,目前仅支持通过默认端口443提供服务,如果要配置指定的列表成员指向不同的主机,则需要通过SNI TLS扩展机制来支持该功能。
19.3 部署 Ingress 控制器(Nginx)
19.3.1 下载ingress controller
[root@k8s-master ~]# cd /mnt/
[root@k8s-master mnt]# wget https://codeload.github.com/kubernetes/ingress-nginx/tar.gz/refs/tags/controller-v1.3.1
[root@k8s-master mnt]# tar xf ingress-nginx-controller-v1.3.1.tar.gz
[root@k8s-master mnt]# cd ingress-nginx-controller-v1.3.1/deploy/static/provider/cloud
[root@k8s-master cloud]# ls
deploy.yaml  kustomization.yaml
[root@k8s-master cloud]# cd
[root@k8s-master ~]# vim deploy.yaml #修改配置文件
找到已下apiserver的版本:
# 390行修改
kind: DaemonSet  #将原来的Deployment修改为DaemonSet

# 415行下边添加
hostNetwork: true  #添加此配置

需要修改的地方:

kind: DaemonSet:

官方原始文件使用的是deployment,replicate 为 1,这样将会在某一台节点上启动对应的nginx-ingress-controller pod。外部流量访问至该节点,由该节点负载分担至内部的service。测试环境考虑防止单点故障,改为DaemonSet然后删掉replicate ,配合亲和性部署在制定节点上启动nginx-ingress-controller pod,确保有多个节点启动nginx-ingress-controller pod,后续将这些节点加入到外部硬件负载均衡组实现高可用性。

hostNetwork: true:

添加该字段,暴露nginx-ingress-controller pod的服务端口(80)

19.3.2 创建ingress-controller
[root@k8s-master ~]# kubectl apply -f deploy.yaml

查看ingress-controller资源
[root@k8s-master ~]# kubectl get pods -n ingress-nginx
NAME                             READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-s8vnl   1/1     Running   0          98m
nginx-ingress-controller-ztxz4   1/1     Running   0          97m

[root@k8s-master cloud]# kubectl get ingressclass
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       38m

测试ingress

创建两个应用和service

[root@k8s-master ~]# vim my-apache.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
 name: my-apache
spec:
 selector:
   matchLabels:
     run: my-apache
 replicas: 2
 template:
  metadata:
   labels:
    run: my-apache
  spec:
   containers:
   - name: my-apache
     image: daocloud.io/library/httpd:2.4
     ports:
     - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
 name: my-apache
 labels:
  run: my-apache
spec:
 #type: NodePort
 ports:
 - port: 80
   targetPort: 80
   #nodePort: 30002
 selector:
  run: my-apache
  
  
[root@k8s-master ~]# cat my-nginx.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
 name: my-nginx
spec:
 selector:
   matchLabels:
     run: my-nginx
 replicas: 2
 template:
  metadata:
   labels:
    run: my-nginx
  spec:
   containers:
   - name: my-nginx
     image: daocloud.io/library/nginx:1.7.9
     ports:
     - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
 name: my-nginx
 labels:
  run: my-nginx
spec:
 #type: NodePort
 ports:
 - port: 80
   targetPort: 80
   #nodePort: 30001
 selector:
  run: my-nginx
  
  
创建pod和service
[root@k8s-master ~]# kubectl apply -f my-apache.yaml
[root@k8s-master ~]# kubectl apply -f my-nginx.yaml

查看资源
[root@k8s-master ~]# kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
my-apache-d49c8b95c-8z8l9   1/1     Running   0          125m
my-apache-d49c8b95c-d9q5s   1/1     Running   0          125m
my-nginx-5fdc96f9b4-bmf6s   1/1     Running   0          124m
my-nginx-5fdc96f9b4-qfw8c   1/1     Running   0          124m
[root@k8s-master ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        20d
my-apache    NodePort    10.99.178.186   <none>        80/TCP   125m
my-nginx     NodePort    10.97.171.188   <none>        80/TCP   124m

配置ingress转发文件

[root@k8s-master ~]# cat ingress-test.yaml 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: test-ingress
 namespace: default
 annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
 ingressClassName: nginx
 rules:  #定义转发规则
 - host: test.apache.ingress  #指定域名方式
   http:
    paths:
    - path: /  #指定访问的路径
      pathType: Prefix  #定义路径的类型
      backend:   #定义转发后端的服务
       service:  #定义转发的service
         name: my-apache
         port:
          number: 80
 - host: test.nginx.ingress
   http:
    paths:
    - path: /
      pathType: Prefix
      backend:
       service:
         name: my-nginx
         port:
          number: 80
          
[root@k8s-master ~]# kubectl apply -f ingress-test.yaml
[root@k8s-master ~]# kubectl get ingress
NAME           CLASS    HOSTS                                    ADDRESS   PORTS   AGE
test-ingress   <none>   test.apache.ingress,test.nginx.ingress             80      119m
19.3.3 修改ingress转发类型
[root@k8s-master ~]# kubectl edit svc ingress-nginx-controller -n ingress-nginx
# 将type修改为NodePort

nginx-ingress-controller运行在node1,node2两个节点上。

如果网络中有dns服务器,在dns中把这两个域名映射到nginx-ingress-controller运行的任意一个节点上,如果没有dns服务器只能修改host文件了。

任意一个节点上操作:(客户端解析)

我这里有两个节点部署了控制器,ip分别为172.16.229.5,172.16.229.6 ,如果有多个,可以随便选。

在wind电脑设置本地解析

172.16.229.5 test.nginx.ingress
172.16.229.6 test.apache.ingress

二十.企业级镜像仓库Harbor

如果资源不足可以在3个节点中的任意节点部署,也可以在单独的一台中部署。

20.1 上传harbor安装包并安装
$ tar xf harbor-offline-installer-v2.5.3.tgz
$ cp harbor.yml.tmpl harbor.yml
$ vim harbor.yml
hostname: 192.168.246.217

# http related config
http:
  # port for http, default is 80. If https enabled, this port will redirect to https port
  port: 80
# 注释所有https的内容

$ ./install.sh
20.2 浏览器访问

默认账号:admin 默认密码:Harbor12345

20.3 k8s使用harbor仓库
# 两台node节点执行
[root@kub-k8s-node1 ~]# vim /etc/docker/daemon.json    #不存在则创建
{ "insecure-registries":["192.168.246.168"] }

重启docker:
[root@kub-k8s-node1 ~]# systemctl restart docker
20.4 上传镜像到仓库
$ docker login http://192.168.246.168
页面中创建用户及仓库
$ docker pull newrain857/show
$ docker tag newrain857/show 192.168.246.168/os-image/show:latest
20.5 创建secret.yaml文件
$ cat ~/.docker/config.json |base64 -w 0
ewoJImF1dGhzIjogewoJCSIxOTIuMTY4Ljk2LjIxNyI6IHsKCQkJImF1dGgiOiAiZUdsaGIyMXBibWM2VVhFeE1URXhNVEU9IgoJCX0KCX0KfQ==

创建 secret.yaml 文件
apiVersion: v1
kind: Secret
metadata:
  name: login
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: ewoJImF1dGhzIjogewoJCSIxOTIuMTY4Ljk2LjIxNyI6IHsKCQkJImF1dGgiOiAiZUdsaGIyMXBibWM2VVhFeE1URXhNVEU9IgoJCX0KCX0KfQ==
  
$ kubectl apply -f secret.yaml

或者 
kubectl create secret docker-registry  --docker-server=192.168.246.168 --docker-username=admin --docker-password=Harbor12345 --docker-email=admin@newrain.com
20.6 k8s-pod 使用镜像
vim harbor-pod.yml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: show
spec:
  replicas: 2
  selector:
    matchLabels:
      app: show
  template:
    metadata:
      labels:
        app: show
    spec:
      containers:
      - name: show
        image: 192.168.246.168/os-image/show:latest
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: login

---
apiVersion: v1
kind: Service
metadata:
  name: show-service
spec:
  type: NodePort
  selector:
    app: show
  ports:
  - port: 80
    targetPort: 80
    nodePort: 32000

# 浏览器访问最终的结果

二十一.水平扩展/收缩与滚动更新

21.1 水平扩展/收缩
21.1.1 创建一个deployment
---
apiVersion: v1
kind: Namespace
metadata:
  name: dep01
  labels:
    name: dep01
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: nginx-dep
  name: myapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.16.1
        ports:
        - containerPort: 80
21.1.2 通过声明方式扩展
[root@kub-k8s-master prome]# kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
dep01              2/2     2            2           4h41m

我们将dep01的副本数量变成4个,现在2个
[root@kub-k8s-master prome]# vim deployment.yaml  #修改如下内容
将replicas: 2
修改为:
replicas: 4

创建上节儿的:dep01

[root@kub-k8s-master prome]# kubectl apply -f deployment.yaml --record
deployment.apps/dep01 configured

--record  kubectl apply 每次更新应用时 Kubernetes 都会记录下当前的配置,保存为一个 revision(版次),这样就可以回滚到某个特定 revision。

检查nginx-deployment 创建后的状态信息:

[root@kub-k8s-master prome]# kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
dep01              4/4     4            4           4h53m

返回结果中四个状态字段含义:

DESIRED: 
如果有就表示用户期望的 Pod 副本个数(spec.replicas 的值);

CURRENT:
当前处于 Running 状态的 Pod 的个数;

UP-TO-DATE:
当前处于最新版本的 Pod 的个数,所谓最新版本指的是 Pod 的 Spec 部分与 Deployment 里 Pod 模板里定义的完全一致;

AVAILABLE:
当前已经可用的 Pod 的个数,即:既是 Running 状态,又是最新版本,并且已经处于 Ready(健康检查正确)状态的 Pod 的个数。只有这个字段,描述的才是用户所期望的最终状态。
21.1.3 通过edit方式收缩
[root@kub-k8s-master prome]# kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
dep01              4/4     4            4           4h59m

将dep01的副本将4变为3个
[root@kub-k8s-master prome]# kubectl edit deployment/dep01

# reopened with the relevant failures.
#
apiVersion: apps/v1
...
spec:
  progressDeadlineSeconds: 600
  replicas: 3   #将这里原来的4改为3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
...
保存退出,vim的方式
[root@kub-k8s-master prome]# kubectl edit deployment/dep01
deployment.apps/dep01 edited
21.2 滚动更新

概念:

将一个集群中正在运行的多个 Pod 版本,交替地逐一升级的过程,就是"滚动更新"
21.2.1 进行版本的升级
创建一个新的deploy
[root@kub-k8s-master prome]# cp nginx-depl.yml nginx-depl02.yml
[root@kub-k8s-master prome]# vim nginx-depl02.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dep02 #注意修改
spec:
  selector:
    matchLabels:
      app: web1
  replicas: 2
  template:
      metadata:
        name: testnginx9
        labels:
          app: web1
      spec:
        containers:
          - name: testnginx9
            image: daocloud.io/library/nginx:1.14 #注意修改
            ports:
              - containerPort: 80
[root@kub-k8s-master prome]# kubectl apply -f nginx-depl02.yml 
deployment.apps/dep02 created
[root@kub-k8s-master prome]# kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
dep01-58f6d4d4cb-997jw              1/1     Running   0          16m
dep01-58f6d4d4cb-g6vtg              1/1     Running   0          5h32m
dep01-58f6d4d4cb-k6z47              1/1     Running   0          5h32m
dep02-78dbd944fc-47czr              1/1     Running   0          44s
dep02-78dbd944fc-4snsj              1/1     Running   0          25s

将nginx的版本从1.14升级到1.16
[root@kub-k8s-master prome]# kubectl edit deployment/dep02
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
...
spec:
      containers:
      - image: daocloud.io/library/nginx:1.16  #将这里原来的nginx:1.14修改为nginx:1.16
        imagePullPolicy: Always
        name: testnginx9
        ports:
        - containerPort: 80
...
保存退出,vim的方式
[root@kub-k8s-master prome]# kubectl edit deployment/dep02
deployment.apps/dep01 edited

这时可以通过查看 Deployment 的 Events,看到这个"滚动更新"的流程

[root@kub-k8s-master prome]# kubectl describe deployment dep02
...
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  50s   deployment-controller  Scaled up replica set dep02-846bf8775b to 2
  Normal  ScalingReplicaSet  9s    deployment-controller  Scaled up replica set dep02-58f8d5678 to 1
  Normal  ScalingReplicaSet  8s    deployment-controller  Scaled down replica set dep02-846bf8775b to 1
  Normal  ScalingReplicaSet  8s    deployment-controller  Scaled up replica set dep02-58f8d5678 to 2
  Normal  ScalingReplicaSet  5s    deployment-controller  Scaled down replica set dep02-846bf8775b to 0
如此交替进行,新 ReplicaSet 管理的 Pod 副本数,从 0 个变成 1 个,再变成 2 个,最后变成 3 个。而旧的 ReplicaSet 管理的 Pod 副本数则从 3 个变成 2 个,再变成 1 个,最后变成 0 个。这样,就完成了这一组 Pod 的版本升级过程。
21.2.2 验证
[root@kub-k8s-master prome]# kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
dep02-78dbd944fc-69t8x              1/1     Running   0          11h
dep02-78dbd944fc-7cn86              1/1     Running   0          11h
[root@kub-k8s-master prome]# kubectl exec -it dep02-78dbd944fc-69t8x /bin/bash 
root@dep02-78dbd944fc-69t8x:/# nginx -v 
nginx version: nginx/1.16.1
root@dep02-78dbd944fc-69t8x:/# exit
21.2.3 滚动更新的好处
在升级刚开始的时候,集群里只有 1 个新版本的 Pod。如果这时,新版本 Pod 有问题启动不起来,那么"滚动更新"就会停止,从而允许开发和运维人员介入。而在这个过程中,由于应用本身还有两个旧版本的 Pod 在线,所以服务并不会受到太大的影响。
21.3 版本回滚
21.3.1 查看版本历史
[root@kub-k8s-master prome]# kubectl rollout history deployment/dep02
deployment.apps/dep02 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
21.3.2 回滚到以前的旧版本:

把整个 Deployment 回滚到上一个版本:

[root@kub-k8s-master prome]# kubectl rollout undo deployment/dep02
deployment.apps/dep02 rolled back

查看回滚状态

[root@kub-k8s-master prome]# kubectl rollout status deployment/dep02
deployment "dep02" successfully rolled out

验证:

[root@kub-k8s-master prome]# kubectl get pods
NAME                                READY   STATUS             RESTARTS   AGE
dep02-8594cd6447-pqtxk              1/1     Running            0          55s
dep02-8594cd6447-tt4h4              1/1     Running            0          51s
[root@kub-k8s-master prome]# kubectl exec -it dep02-8594cd6447-tt4h4 /bin/bash 
root@dep02-8594cd6447-tt4h4:/# nginx -v 
nginx version: nginx/1.14.2
21.3.3 回滚到更早之前的版本
  1. 使用 kubectl rollout history 命令查看每次 Deployment 变更对应的版本。
[root@kub-k8s-master prome]# kubectl rollout history deployment/dep02
deployment.apps/dep02 
REVISION  CHANGE-CAUSE
2         <none>
3         <none>

#默认配置下,Kubernetes 只会保留最近的几个 revision,可以在 Deployment 配置文件中通过 revisionHistoryLimit: 属性增加 revision 数量。

由于在创建这个 Deployment 的时候,指定了–record 参数,会将创建这些版本时执行的 kubectl 时文件中的配置,都会记录下来。

查看每个版本对应的 Deployment 的 API 对象的细节:

[root@kub-k8s-master prome]# kubectl rollout history deployment/dep02 --revision=3
deployment.apps/dep02 with revision #3
Pod Template:
  Labels:	app=web1
	pod-template-hash=8594cd6447
  Containers:
   testnginx9:
    Image:	daocloud.io/library/nginx:1.14
    Port:	80/TCP
    Host Port:	0/TCP
    Environment:	<none>
    Mounts:	<none>
  Volumes:	<none>

2.在 kubectl rollout undo 命令行最后,加上要回滚到的指定版本的版本号,就可以回滚到指定版本了。

[root@kub-k8s-master prome]# kubectl rollout undo deployment/dep02 --to-revision=2
deployment.apps/dep02 rolled back

验证:

[root@kub-k8s-master prome]# kubectl get pods
NAME                                READY   STATUS             RESTARTS   AGE
dep02-78dbd944fc-8nvxl              1/1     Running            0          86s
dep02-78dbd944fc-sb9sj              1/1     Running            0          88s
[root@kub-k8s-master prome]# kubectl exec -it dep02-78dbd944fc-8nvxl /bin/bash 
root@dep02-78dbd944fc-8nvxl:/# nginx -v
nginx version: nginx/1.16.1

课后了解

你听说过金丝雀发布(Canary Deployment)和蓝绿发布(Blue-Green Deployment)吗?你能说出它们是什么意思吗?

二十二.DeamonSet详解

22.1 何为DaemonSet

介绍DaemonSet我们先来思考一个问题:相信大家都接触过监控系统比如zabbix,监控系统需要在被监控机安装一个agent,安装agent通常会涉及到以下几个场景:

- 所有节点都必须安装agent以便采集监控数据
- 新加入的节点需要配置agent,手动或者运行脚本

k8s中经常涉及到在node上安装部署应用,它是如何解决上述的问题的呢?答案是DaemonSet。DaemonSet守护进程简称DS,适用于在所有节点或部分节点运行一个daemon守护进程。

DaemonSet 的主要作用,是让你在 k8s 集群里,运行一个 Daemon Pod。

这个 Pod 有如下三个特征:

  1. 这个 Pod 运行在 k8s 集群里的每一个节点(Node)上;
  2. 每个节点上只有一个这样的 Pod 实例;
  3. 当有新的节点加入 Kubernetes 集群后,该 Pod 会自动地在新节点上被创建出来;而当旧节点被删除后,它上面的 Pod 也相应地会被回收掉。

举例:

各种网络插件的 Agent 组件,都必须运行在每一个节点上,用来处理这个节点上的容器网络;

各种存储插件的 Agent 组件,也必须运行在每一个节点上,用来在这个节点上挂载远程存储目录,操作容器的 Volume 目录;

各种监控组件和日志组件,也必须运行在每一个节点上,负责这个节点上的监控信息和日志搜集。

22.2 DaemonSet 的 API 对象的定义

所有node节点分别下载镜像

# docker pull daocloud.io/daocloud/fluentd-elasticsearch:1.20
# docker pull daocloud.io/daocloud/fluentd-elasticsearch:v2.2.0

fluentd-elasticsearch 镜像功能:

通过 fluentd 将每个node节点上面的Docker 容器里的日志转发到 ElasticSearch 中。

编写daemonset配置文件

Yaml文件内容如下

[root@k8s-master ~]# mkdir set
[root@k8s-master ~]# cd set/
[root@k8s-master set]# vim fluentd-elasticsearch.yaml # DaemonSet 没有 replicas 字段
apiVersion: apps/v1
kind: DaemonSet  #创建资源的类型
metadata:
 name: fluentd-elasticsearch
 namespace: kube-system
 labels:
  k8s-app: fluentd-logging
spec:
 selector:
  matchLabels:
   name: fluentd-elasticsearch
 template:
  metadata:
   labels:
    name: fluentd-elasticsearch
  spec:
   tolerations:  #容忍污点
   - key: node-role.kubernetes.io/master #污点
     effect: NoSchedule  #描述污点的效应
   containers:
   - name: fluentd-elasticsearch
     image: daocloud.io/daocloud/fluentd-elasticsearch:1.20
     resources:  #限制使用资源
      limits:  #定义使用内存的资源上限
       memory: 200Mi
      requests: #实际使用
       cpu: 100m
       memory: 200Mi
     volumeMounts:
     - name: varlog
       mountPath: /var/log
     - name: varlibdockercontainers
       mountPath: /var/lib/docker/containers
       readOnly: true
   volumes:
   - name: varlog
     hostPath:  #定义卷使用宿主机目录
      path: /var/log
   - name: varlibdockercontainers
     hostPath:
      path: /var/lib/docker/containers

DaemonSet 没有 replicas 字段

selector :

选择管理所有携带了 name=fluentd-elasticsearch 标签的 Pod。

Pod 的模板用 template 字段定义:

定义了一个使用 fluentd-elasticsearch:1.20 镜像的容器,而且这个容器挂载了两个 hostPath 类型的 Volume,分别对应宿主机的 /var/log 目录和 /var/lib/docker/containers 目录。

fluentd 启动之后,它会从这两个目录里搜集日志信息,并转发给 ElasticSearch 保存。这样,通过 ElasticSearch 就可以很方便地检索这些日志了。Docker 容器里应用的日志,默认会保存在宿主机的 /var/lib/docker/containers/{{. 容器 ID}}/{{. 容器 ID}}-json.log 文件里,这个目录正是 fluentd 的搜集目标。

DaemonSet 如何保证每个 Node 上有且只有一个被管理的 Pod ?

DaemonSet Controller,首先从 Etcd 里获取所有的 Node 列表,然后遍历所有的 Node。这时,它就可以很容易地去检查,当前这个 Node 上是不是有一个携带了 name=fluentd-elasticsearch 标签的 Pod 在运行。

检查结果有三种情况:

1. 没有这种 Pod,那么就意味着要在这个 Node 上创建这样一个 Pod;指定的 Node 上创建新 Pod 用 nodeSelector,选择 Node 的名字即可。
2. 有这种 Pod,但是数量大于 1,那就说明要把多余的 Pod 从这个 Node 上删除掉;删除节点(Node)上多余的 Pod 非常简单,直接调用 Kubernetes API 就可以了。
3. 正好只有一个这种 Pod,那说明这个节点是正常的。

tolerations:

DaemonSet 还会给这个 Pod 自动加上另外一个与调度相关的字段,叫作 tolerations。这个字段意思是这个 Pod,会"容忍"(Toleration)某些 Node 的"污点"(Taint)。

tolerations 字段,格式如下:

apiVersion: v1
kind: Pod
metadata:
 name: with-toleration
spec:
 tolerations:
 - key: node.kubernetes.io/unschedulable  #污点的key
   operator: Exists #将会忽略value;只要有key和effect就行
   effect: NoSchedule  #污点的作用

含义是:"容忍"所有被标记为 unschedulable"污点"的 Node;"容忍"的效果是允许调度。可以简单地把"污点"理解为一种特殊的 Label。

正常情况下,被标记了 unschedulable"污点"的 Node,是不会有任何 Pod 被调度上去的(effect: NoSchedule)。可是,DaemonSet 自动地给被管理的 Pod 加上了这个特殊的 Toleration,就使得这些 Pod 可以忽略这个限制,保证每个节点上都会被调度一个 Pod。如果这个节点有故障的话,这个 Pod 可能会启动失败,DaemonSet 会始终尝试下去,直到 Pod 启动成功。

DaemonSet 的"过人之处",其实就是依靠 Toleration 实现的

DaemonSet 是一个控制器。在它的控制循环中,只需要遍历所有节点,然后根据节点上是否有被管理 Pod 的情况,来决定是否要创建或者删除一个 Pod。

更多种类的Toleration

可以在 Pod 模板里加上更多种类的 Toleration,从而利用 DaemonSet 实现自己的目的。

比如,在这个 fluentd-elasticsearch DaemonSet 里,给它加上了这样的 Toleration:

tolerations:
- key: node-role.kubernetes.io/master
  effect: NoSchedule

这是因为在默认情况下,Kubernetes 集群不允许用户在 Master 节点部署 Pod。因为,Master 节点默认携带了一个叫作node-role.kubernetes.io/master的"污点"。所以,为了能在 Master 节点上部署 DaemonSet 的 Pod,就必须让这个 Pod"容忍"这个"污点"。

22.3 DaemonSet实践
22.3.1 创建 DaemonSet 对象
[root@k8s-master set] # kubectl create -f fluentd-elasticsearch.yaml

DaemonSet 上一般都加上 resources 字段,来限制它的 CPU 和内存使用,防止它占用过多的宿主机资源。

创建成功后,如果有 3 个节点,就会有 3 个 fluentd-elasticsearch Pod 在运行

[root@k8s-master set]# kubectl get pod -n kube-system -l name=fluentd-elasticsearch
NAME                          READY   STATUS    RESTARTS   AGE
fluentd-elasticsearch-6lmnb   1/1     Running   0          21m
fluentd-elasticsearch-9fd7k   1/1     Running   0          21m
fluentd-elasticsearch-vz4n4   1/1     Running   0          21m
22.3.2 查看 DaemonSet 对象
[root@k8s-master set]# kubectl get ds -n kube-system fluentd-elasticsearch
NAME                    DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
fluentd-elasticsearch   3         3         3       3            3           <none>          22m

注:k8s 里比较长的 API 对象都有短名字,比如 DaemonSet 对应的是 ds,Deployment 对应的是 deploy。

22.3.3 DaemonSet 版本管理
[root@k8s-master set]# kubectl rollout history daemonset fluentd-elasticsearch -n kube-system
daemonset.apps/fluentd-elasticsearch 
REVISION  CHANGE-CAUSE
1         <none>
22.3.4 DaemonSet 的容器镜像版本到 v2.2.0
[root@k8s-master set]# kubectl set image ds/fluentd-elasticsearch fluentd-elasticsearch=daocloud.io/daocloud/fluentd-elasticsearch:v2.2.0 --record -n=kube-system
daemonset.apps/fluentd-elasticsearch image updated

这个 kubectl set image 命令里,第一个 fluentd-elasticsearch 是 DaemonSet 的名字,第二个 fluentd-elasticsearch 是容器的名字。

–record 参数:

升级使用到的指令会自动出现在 DaemonSet 的 rollout history 里面,如下所示:

[root@k8s-master set]# kubectl rollout history daemonset fluentd-elasticsearch -n kube-system
daemonset.apps/fluentd-elasticsearch 
REVISION  CHANGE-CAUSE
1         <none>
2         kubectl set image ds/fluentd-elasticsearch fluentd-elasticsearch=daocloud.io/daocloud/fluentd-elasticsearch:v2.2.0 --record=true --namespace=kube-system

有了版本号,也就可以像 Deployment 一样,将 DaemonSet 回滚到某个指定的历史版本了。

二十三.离线业务编排详解

23.1 在线业务和离线业务

在线业务

Deployment、StatefulSet以及 DaemonSet 这三个编排概念的共同之处是:它们主要编排的对象,都是"在线业务",即:Long Running Task(长作业)。比如常用的 Nginx、Tomcat,以及 MySQL 等等。这些应用一旦运行起来,除非出错或者停止,它的容器进程会一直保持在 Running 状态。

离线业务

也可以叫做Batch Job(计算业务)。这种业务在计算完成后就直接退出了,如果用 Deployment 来管理这种业务,会发现 Pod 会在计算结束后退出,然后被 Deployment Controller 不断地重启

23.2 Job解析

什么是Job?

一个用来描述离线业务的 API 对象

Job API 对象的定义

# vim job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
     containers:
     - name: pi
       image: resouer/ubuntu-bc
       command: ["sh", "-c", "echo 'scale=10000; 4*a(1)' | bc -l "]
     restartPolicy: Never
  backoffLimit: 4

Job 对象不要求定义一个 spec.selector 来描述要控制哪些 Pod

spec.template 字段为Pod 模板

23.3 运行程序

echo “scale=10000; 4*a(1)” | bc -l

bc 命令:

是 Linux 里的"计算器"

-l 表示:是要使用标准数学库

a(1):  是调用数学库中的 arctangent 函数,计算 atan(1)。这是什么意思呢?

数学知识回顾:

  	tan(π/4) = 1。所以,4*atan(1)正好就是π,也就是 3.1415926…。

  	这是一个计算π值的容器。通过 scale=10000,指定了输出的小数点后的位数是 10000。在我的计算机上,这个计算大概用时 1 分 54 秒。
23.4 创建Job
# kubectl create -f job.yaml

查看 Job 对象

# kubectl describe jobs/pi
Name:       pi
Namespace:  default
Selector:     controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495
Labels:      controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495
         job-name=pi
Annotations:    <none>
Parallelism:    1
Completions:    1
..
Pods Statuses:   0 Running / 1 Succeeded / 0 Failed
Pod Template:
 Labels:    controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495
        job-name=pi
 Containers:
  ...
 Volumes:        <none>
Events:
 FirstSeen   LastSeen   Count   From       SubobjectPath   Type     Reason       Message
---------   --------   -----   ----       -------------   --------   ------       -------
 1m      1m      1     {job-controller }         Normal    SuccessfulCreate  Created pod: pi-rq5rl

为了避免不同 Job 对象所管理的 Pod 发生重合,Job 对象在创建后,它的 Pod 模板,被自动加上了一个 controller-uid=< 一个随机字符串 > 这样的 Label。而这个 Job 对象本身,则被自动加上了这个 Label 对应的 Selector,保证了 Job 与它所管理的 Pod 之间的匹配关系。

这种自动生成的 Label 对用户并不友好,不太适合推广到 Deployment 等长作业编排对象上。

Pod 进入了 Running 状态说明它正在计算 Pi 的值

# kubectl get pods
NAME                 READY   STATUS   RESTARTS  AGE
pi-rq5rl               1/1    Running  0      10s

几分钟后计算结束,这个 Pod 就会进入 Completed 状态

# kubectl get pods
NAME                  READY    STATUS    RESTARTS  AGE
pi-rq5rl               0/1     Completed  0      4m

离线计算的 Pod 永远都不应该被重启

实现方式是在 Pod 模板中定义 restartPolicy=Never

事实上restartPolicy 在 Job 对象里只允许被设置为 Never 和 OnFailure;而在 Deployment 对象里,restartPolicy 则只允许被设置为 Always。

查看 Pod 日志

# kubectl logs pi-rq5rl  //可以看到计算得到的 Pi 值已经被打印了出来
3.141592653589793238462643383279...

离线作业失败处理方式

离线作业失败后 Job Controller 就会不断地尝试创建一个新 Pod,这个尝试肯定不能无限进行下去。所以,在 Job 对象的 spec.backoffLimit 字段里定义了重试次数为 4(即,backoffLimit=4,默认值是 6)

Job Controller 重新创建 Pod 的间隔是呈指数增加的,即下一次重新创建 Pod 的动作会分别发生在 10 s、20 s、40 s …后。

如果restartPolicy=OnFailure,离线作业失败后,Job Controller 就不会去尝试创建新的 Pod。但是,它会不断地尝试重启 Pod 里的容器。

# kubectl get pods
NAME                 READY   STATUS        RESTARTS  AGE
pi-55h89               0/1    ContainerCreating  0      2s
pi-tqbcz               0/1    Error        0      5s

可以看到,这时候会不断地有新 Pod 被创建出来。

spec.activeDeadlineSeconds 字段:

当一个 Job 的 Pod 运行结束后,它会进入 Completed 状态。但是,如果这个 Pod 因为某种原因一直不肯结束呢?

在 Job 的 API 对象里,有一个 spec.activeDeadlineSeconds 字段可以设置最长运行时间,比如:

spec:

backoffLimit: 5

activeDeadlineSeconds: 100

一旦运行超过了 100 s,这个 Job 的所有 Pod 都会被终止。并且,你可以在 Pod 的状态里看到终止的原因是 reason: DeadlineExceeded。

以上,就是一个 Job API 对象最主要的概念和用法

23.5 并行作业

离线业务之所以被称为 Batch Job,是因为它们可以以"Batch",也就是并行的方式去运行。

负责并行控制的参数有两个:

spec.parallelism:

定义一个 Job 在任意时间最多可以启动多少个 Pod 同时运行;

spec.completions:

定义 Job 至少要完成的 Pod 数目,即 Job 的最小完成数。

在之前计算 Pi 值的 Job 里,添加这两个参数:

注意:本例只是为了演示 Job 的并行特性,实际用途不大。不过现实中确实存在很多需要并行处理的场景。比如批处理程序,每个副本(Pod)都会从任务池中读取任务并执行,副本越多,执行时间就越短,效率就越高。这种类似的场景都可以用 Job 来实现。

apiVersion: batch/v1
kind: Job
metadata:
 name: pi
spec:
 parallelism: 2
 completions: 4
 template:
  spec:
   containers:
   - name: pi
     image: resouer/ubuntu-bc
     command: ["sh", "-c", "echo 'scale=5000; 4*a(1)' | bc -l "]
   restartPolicy: Never
 backoffLimit: 4

这样,我们就指定了这个 Job 最大的并行数是 2,而最小的完成数是 4。

创建这个 Job 对象:

# kubectl create -f job.yaml

这个 Job 其实也维护了两个状态字段,即 DESIRED 和 SUCCESSFUL,如下所示:

# kubectl get job  //注意,最新版本的子段已经不一样了
NAME    DESIRED  SUCCESSFUL  AGE
pi        4     0       3s

其中,DESIRED 的值,正是 completions 定义的最小完成数。

这个 Job 首先创建了两个并行运行的 Pod 来计算 Pi:

# kubectl get pods
NAME    READY   STATUS   RESTARTS  AGE
pi-5mt88  1/1    Running  0      6s
pi-gmcq5  1/1    Running  0      6s

而在 40 s 后,这两个 Pod 相继完成计算。

这时可以看到,每当有一个 Pod 完成计算进入 Completed 状态时,就会有一个新的 Pod 被自动创建出来,并且快速地从 Pending 状态进入到 ContainerCreating 状态:

# kubectl get pods
NAME    READY   STATUS   RESTARTS  AGE
pi-gmcq5  0/1    Completed  0     40s
pi-84ww8  0/1    Pending  0     0s
pi-5mt88  0/1    Completed  0     41s
pi-62rbt  0/1    Pending  0     0s

# kubectl get pods
NAME    READY   STATUS   RESTARTS  AGE
pi-gmcq5  0/1    Completed  0     40s
pi-84ww8  0/1    ContainerCreating  0     0s
pi-5mt88  0/1    Completed  0     41s
pi-62rbt  0/1    ContainerCreating  0     0s

紧接着,Job Controller 第二次创建出来的两个并行的 Pod 也进入了 Running 状态:

# kubectl get pods 
NAME    READY    STATUS    RESTARTS  AGE
pi-5mt88  0/1    Completed  0        54s
pi-62rbt  1/1    Running    0        13s
pi-84ww8  1/1    Running    0        14s
pi-gmcq5  0/1    Completed  0        54s

最终,后面创建的这两个 Pod 也完成了计算,进入了 Completed 状态。

这时,由于所有的 Pod 均已经成功退出,这个 Job 也就执行完了,所以你会看到它的 SUCCESSFUL 字段的值变成了 4:

# kubectl get pods 
NAME      READY  STATUS    RESTARTS  	AGE
pi-5mt88  0/1    Completed  0      		5m
pi-62rbt  0/1    Completed  0      		4m
pi-84ww8  0/1    Completed  0      		4m
pi-gmcq5  0/1    Completed  0      		5m

# kubectl get job
NAME    DESIRED  SUCCESSFUL   AGE
pi         4          4       5m

Job Controller工作原理总结

  1. Job Controller 控制的对象,直接就是 Pod。
  2. Job Controller 在控制循环中进行的调谐(Reconcile)操作,是根据实际在 Running 状态 Pod 的数目、已经成功退出的 Pod 的数目,以及 parallelism、completions 参数的值共同计算出在这个周期里,应该创建或者删除的 Pod 数目,然后调用 Kubernetes API 来执行这个操作。

在上面计算 Pi 值的这个例子中,当 Job 一开始创建出来时,实际处于 Running 状态的 Pod 数目 =0,已经成功退出的 Pod 数目 =0,而用户定义的 completions,也就是最终用户需要的 Pod 数目 =4。

所以,在这个时刻,需要创建的 Pod 数目 = 最终需要的 Pod 数目 - 实际在 Running 状态 Pod 数目 - 已经成功退出的 Pod 数目 = 4 - 0 - 0= 4。也就是说,Job Controller 需要创建 4 个 Pod 来纠正这个不一致状态。

可是,又定义了这个 Job 的 parallelism=2 规定了每次并发创建的 Pod 个数不能超过 2 个。所以,Job Controller 会对前面的计算结果做一个修正,修正后的期望创建的 Pod 数目应该是:2 个。

这时候,Job Controller 就会并发地向 kube-apiserver 发起两个创建 Pod 的请求。

类似地,如果在这次调谐周期里,Job Controller 发现实际在 Running 状态的 Pod 数目,比 parallelism 还大,那么它就会删除一些 Pod,使两者相等。

综上所述,Job Controller 实际上控制了,作业执行的并行度,以及总共需要完成的任务数这两个重要参数。而在实际使用时,你需要根据作业的特性,来决定并行度(parallelism)和任务数(completions)的合理取值。

23.6 Job控制器CronJob

Job 对象:CronJob

CronJob 描述的是定时任务,CronJob 是一个 Job 对象的控制器(Controller)

CronJob的 API 对象,如下所示:

# vim ./cronjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: hello
              image: daocloud.io/library/busybox
              args:
                - /bin/sh
                - -c
                - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

CronJob 与 Job 的关系,同 Deployment 与 Pod 的关系一样。

CronJob 是一个专门用来管理 Job 对象的控制器。它创建和删除 Job 的依据,是 schedule 字段定义的、一个标准的Unix Cron格式的表达式。

比如:

*/1 * * * *
分钟、小时、日、月、星期

这个 Cron 表达式里 _/1 中的 _ 表示从 0 开始,/ 表示"每",1 表示偏移量。所以,它的意思就是:从 0 开始,每 1 个时间单位执行一次。

本例表示从当前开始,每分钟执行一次

这里要执行的内容,就是 jobTemplate 定义的 Job 。

这个 CronJob 对象在创建 1 分钟后,就会有一个 Job 产生了,如下所示:

# kubectl create -f ./cronjob.yaml
cronjob "hello" created

一分钟后

# kubectl get jobs
NAME        DESIRED  SUCCESSFUL  AGE
hello-4111706356  1     1     2s

此时,CronJob 对象会记录下这次 Job 执行的时间:新版本中在describe里显示执行时间

# kubectl get cronjob hello
NAME    SCHEDULE    SUSPEND  ACTIVE   LAST-SCHEDULE
hello     */1 * * * *     False   0     Thu, 6 Sep 2018 14:34:00 -070

spec.concurrencyPolicy 字段:

由于定时任务的特殊性,很可能某个 Job 还没有执行完,另外一个新 Job 就产生了。这时候,可以通过 spec.concurrencyPolicy 字段来定义具体的处理策略。

concurrencyPolicy=Allow

默认情况,表示这些 Job 可以同时存在;

concurrencyPolicy=Forbid

表示不会创建新的 Pod,该创建周期被跳过;

concurrencyPolicy=Replace

表示新产生的 Job 会替换旧的、没有执行完的 Job。

spec.startingDeadlineSeconds 字段:

如果某一次 Job 创建失败,这次创建就会被标记为"miss"。当在指定的时间窗口内,miss 的数目达到 100 时,那么 CronJob 会停止再创建这个 Job。

这个时间窗口,可以由 spec.startingDeadlineSeconds 字段指定。比如 startingDeadlineSeconds=200,意味着在过去 200 s 里,如果 miss 的数目达到了 100 次,那么这个 Job 就不会被创建执行了。

二十四.k8s之共享存储pv&pvc

24.1 存储资源管理

在基于k8s容器云平台上,对存储资源的使用需求通常包括以下几方面:

1.应用配置文件、密钥的管理;
2.应用的数据持久化存储;
3.在不同的应用间共享数据存储;

k8s的Volume抽象概念就是针对这些问题提供的解决方案,k8s的volume类型非常丰富,从临时目录、宿主机目录、ConfigMap、Secret、共享存储(PV和PVC)。

k8s支持Volume类型包括以下几类:

1.临时空目录(随着Pod的销毁而销毁)
emptyDir:

2.配置类(将配置文件以Volume的形式挂载到容器内)
ConfigMap:将保存在ConfigMap资源对象中的配置文件信息挂载到容器内的某个目录下

Secret:将保存在Secret资源对象中的密码密钥等信息挂载到容器内的某个文件中。

downwardAPI:将downwardAPI的数据以环境变量或文件的形式注入容器中。

gitErpo:将某Git代码库挂载到容器内的某个目录下

3.本地存储类
hostpath:将宿主机的目录或文件挂载到容器内进行使用。

local:Kubernetes从v1.9版本引入,将本地存储以PV的形式提供给容器使用,并能够实现存储空间的管理。

4.共享存储
PV(Persistent Volume):将共享存储定义为一种“持久存储卷”,可以被多个容器应用共享使用。

PVC(Persistent Volume Claim):用户对存储资源的一次“申请”,PVC申请的对象是PV,一旦申请成功,应用就能够像使用本地目录一样使用共享存储了。

共享存储主要用于多个应用都能够使用存储资源,例如NFS存储、光纤存储Glusterfs共享文件系统等,在k8s系统中通过PV/StorageClass和PVC来完成定义,并通过volumeMount挂载到容器的目录或文件进行使用。

ConfigMap、Secret、emptyDir、hostPath等属于临时性存储,当pod被调度到某个节点上时,它们随pod的创建而创建,临时占用节点存储资源,当pod离开节点时,存储资源被交还给节点,pod一旦离开这个节点,存储就失效,不具备持久化存储数据的能力。与此相反,持久化存储拥有独立的生命周期,具备持久化存储能力,其后端一般是独立的存储系统如NFS、iSCSI、cephfs、glusterfs等。

pv与pvc

Kubernetes中的node代表计算资源,而PersistentVolume(PV)则代表存储资源,它是对各种诸如NFS、iSCSI、云存储等各种存储后端所提供存储块的统一抽象从而提供网络存储,通过它屏蔽低层实现细节。与普通volume不同,PV拥有完全独立的生命周期。

因为PV表示的是集群能力,它是一种集群资源,所以用户(通常是开发人员)不能直接使用PV,就像不能直接使用内存一样,需要先向系统申请,而 PVC 就是请求存储资源的。
Kubernetes通过PersistentVolumeClaim(PVC)代理用户行为,用户通过对PVC的操作实现对PV申请、使用、释放等操作,PVC是用户层面的资源。

PV 和 PVC 可以将 pod 和数据卷解耦,pod 不需要知道确切的文件系统或者支持它的持久化引擎。

Kubernetes的共享存储供应模式包括静态和动态两种模式

静态模式
静态PV由系统管理员负责创建、提供、维护,系统管理员为用户屏蔽真正提供存储的后端及其实现细节,普通用户作为消费者,只需通过PVC申请、使用此类资源。

动态模式:
集群管理员无须手工创建PV,而是通过对Storage Class的设置对后端存储进行描述,“storage class”可以理解成某种具体的后端存储,标记为某种“类型(Class)”,此时要求PVC对存储的类型进行声明,系统将自动完成PV的创建与PVC的绑定。如果用户的PVC中“storage class”的值为"",则表示不能为此PVC动态创建PV。

PV与PVC的绑定

用户创建包含容量、访问模式等信息的PVC,向系统请求存储资源。系统查找已存在PV或者监控新创建PV,如果与PVC匹配则将两者绑定。如果PVC创建动态PV,则系统将一直将两者绑定。PV与PVC的绑定是一一对应关系,不能重复绑定。如果系统一直没有为PVC找到匹配PV,则PVC无限期维持在"unbound"状态,直到系统找到匹配PV。实际绑定的PV容量可能大于PVC中申请的容量。
24.2 持久卷pv的类型

PV 持久卷是用插件的形式来实现的。Kubernetes 目前支持以下插件

- GCEPersistentDisk
- AWSElasticBlockStore
- AzureFile
- AzureDisk
- FC (Fibre Channel)
- Flexvolume
- Flocker
- NFS
- iSCSI
- RBD (Ceph Block Device)
- CephFS
- Cinder (OpenStack block storage)
- Glusterfs
- VsphereVolume
- Quobyte Volumes
- HostPath (Single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster)
- Portworx Volumes
- ScaleIO Volumes
- StorageOS
24.5 实验mysql基于NFS共享存储实现持久化存储
24.5.1 安装NFS

机器准备

k8s-master  制作nfs服务端作为共享文件系统
[root@k8s-master ~]# yum install -y nfs-utils rpcbind
[root@k8s-master ~]# mkdir /mnt/data  #制作共享目录
[root@k8s-master ~]# vim /etc/exports
/mnt/data 192.168.122.0/24(rw,no_root_squash)
[root@k8s-master ~]# systemctl start rpcbind nfs-server #启动服务
# 使配置生效
[root@k8s-master ~]# exportfs -r

# 检查配置是否生效
[root@k8s-master ~]# exportfs

集群中的工作节点都需要安装客户端工具,主要作用是节点能够驱动 nfs 文件系统
只安装,不启动服务
# yum install -y nfs-utils
# showmount -e master_ip
# mount -t nfs master_ip:/mnt/data /mnt/data


master节点
制作pv.yaml(一般这个动作由 kubernetes 管理员完成,也就是我们运维人员)
[root@k8s-master ~]# mkdir /k8s/mysql -p 
[root@k8s-master ~]# cd /k8s/mysql/
[root@k8s-master mysql]# vim pv.yaml
apiVersion: v1
kind: PersistentVolume   #类型定义为pv
metadata: 
  name: my-pv  #pv的名字
  labels:  #定义标签
    type: nfs  #类型为nfs
spec:
  nfs:  # 存储类型,需要与底层实际的存储一致,这里采用 nfs
    server: 192.168.122.24 # NFS 服务器的 IP
    path: "/mnt/data" # NFS 上共享的目录
  capacity:   #定义存储能力
    storage: 3Gi  #指定存储空间
  accessModes:  #定义访问模式
    - ReadWriteMany   #读写权限,允许被多个Node挂载
  persistentVolumeReclaimPolicy: Retain  #定义数据回收策略,这里是保留
24.5.2 PV参数详解

1.存储能力(Capacity)

描述存储设备的能力,目前仅支持对存储空间的设置(storage=xx)。

2.访问模式(Access Modes)

对PV进行访问模式的设置,用于描述用户应用对存储资源的访问权限。访问模式如下:

ReadWriteOnce:读写权限,并且只能被单个pod挂载。
ReadOnlyMany:只读权限,允许被多个pod挂载。
ReadWriteMany:读写权限,允许被多个pod挂载。

某些PV可能支持多种访问模式,但PV在挂载时只能使用一种访问模式,多种访问模式不能同时生效。

3.persistentVolumeReclaimPolicy定义数据回收策略

目前支持如下三种回收策略:

保留(Retain):保留数据,需要手工处理。

回收空间(Recycle):警告: 回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态供应。如果下层的卷插件支持,回收策略 Recycle 会在卷上执行一些基本的 擦除(rm -rf /thevolume/*)操作,之后允许该卷用于新的 PVC 申领

删除(Delete):会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。 就是把云存储一起删了。

目前,只有 NFS 和 HostPath 两种类型的存储设备支持 “Recycle” 策略; AWS EBS、 GCE PD、Azure Disk 和 Cinder volumes 支持 “Delete” 策略。

4.storageClassName存储类别(Class)

PV可以设定其存储的类型(Class),通过 storageClassName参数指定一个 StorageClass 资源对象的名称。
具有特定“类别”的 PV 只能与请求了该“类别”的 PVC 进行绑定。未设定 “类别” 的 PV 则只能与不请求任何 “类别” 的 PVC 进行绑定。
相当于一个标签。
24.5.3 创建pv
[root@k8s-master mysql]# kubectl apply -f pv.yaml 
persistentvolume/my-pv created
[root@k8s-master mysql]# kubectl get pv

PV 生命周期的各个阶段(Phase)

某个 PV 在生命周期中,可以处于以下4个阶段之一:

- Available:可用状态,还未与某个 PVC 绑定。
- Bound:已与某个 PVC 绑定。
- Released:释放,绑定的 PVC 已经删除,但没有被集群回收存储空间 。
- Failed:自动资源回收失败。

pv创建成功目前属于可用状态,还没有与pvc绑定,那么现在创建pvc

创建pvc

[root@k8s-master mysql]# vim mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim   #定义类型为PVC
metadata:
  name: mypvc   #声明pvc的名称,当做pod的卷使用时会用到
spec:
  accessModes:  #定义访问pvc的模式,与pv拥有一样的模式
    - ReadWriteMany   #读写权限,允许被多个pod挂载
  resources:  #声明可以请求特定数量的资源,目前仅支持 request.storage 的设置,即存储空间大小。
   requests:
     storage: 3Gi  #定义空间大小
  selector:  #PV选择条件,标签选择器,通过标签选择
     matchLabels:
       type: "nfs" #选择pv类型的nfs
       

注意:当我们申请pvc的容量大于pv的容量是无法进行绑定的。

创建pvc
[root@k8s-master mysql]# kubectl apply -f mysql-pvc.yaml 
persistentvolumeclaim/mypvc created
[root@k8s-master mysql]# kubectl get pvc
NAME    STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc   Bound    my-pv    3Gi        RWX            pv-nfs         37s

status状态

- Available (可用): 表示可用状态,还未被任何PVC绑定
- Bound (已绑定):已经绑定到某个PVC
- Released (已释放):对应的PVC已经删除,但资源还没有被集群收回
- Failed:PV自动回收失败

Kubernetes中会自动帮我们查看pv状态为Available并且根据声明pvc容量storage的大小进行筛选匹配,同时还会根据AccessMode进行匹配。如果pvc匹配不到pv会一直处于pending状态。

24.5.4 mysql使用pvc持久卷
创建secret
[root@k8s-master mysql]# echo -n 'QianFeng@123!' | base64
UWlhbkZlbmdAMTIzIQ==
[root@k8s-master mysql]# vim mysql-secret.yaml
apiVersion: v1
data:
  password: UWlhbkZlbmdAMTIzIQ==
kind: Secret
metadata:
  annotations:
  name: my-pass
type: Opaque
[root@k8s-master mysql]# kubectl apply -f mysql-secret.yaml 
secret/my-pass created
[root@k8s-master mysql]# kubectl get secret
NAME                  TYPE                                  DATA   AGE
default-token-24c52   kubernetes.io/service-account-token   3      6d22h
my-pass               Opaque                                1      69s

创建myslq-pod文件
[root@k8s-master mysql]# cat mysql-deployment.yaml 
apiVersion:  apps/v1
kind: Deployment
metadata:
  name: my-mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: my-mysql
        image: daocloud.io/library/mysql:5.7
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: my-pass
              key: password
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-data
        persistentVolumeClaim:  #绑定pvc
          claimName: mypvc #指定对应的pvc名字
[root@k8s-master mysql]# kubectl apply -f mysql-deployment.yaml          
[root@k8s-master mysql]# kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
my-mysql-5474b6885f-c5dmp    1/1     Running   0          8s

[root@k8s-master mysql]# kubectl get pod -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
my-mysql-5474b6885f-c5dmp    1/1     Running   0          40s   10.244.2.5    k8s-node2   <none>           <none>

测试
[root@k8s-master ~]# cd /mnt/data/
[root@k8s-master data]# ll
总用量 188484
-rw-r----- 1 polkitd ssh_keys       56 11月  8 21:49 auto.cnf
-rw------- 1 polkitd ssh_keys     1680 11月  8 21:49 ca-key.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月  8 21:49 ca.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月  8 21:49 client-cert.pem
-rw------- 1 polkitd ssh_keys     1680 11月  8 21:49 client-key.pem
-rw-r----- 1 polkitd ssh_keys      688 11月  8 21:57 ib_buffer_pool
-rw-r----- 1 polkitd ssh_keys 79691776 11月  8 21:59 ibdata1
-rw-r----- 1 polkitd ssh_keys 50331648 11月  8 21:59 ib_logfile0
-rw-r----- 1 polkitd ssh_keys 50331648 11月  8 21:49 ib_logfile1
-rw-r----- 1 polkitd ssh_keys 12582912 11月  8 22:00 ibtmp1
drwxr-x--- 2 polkitd ssh_keys     4096 11月  8 21:49 mysql
drwxr-x--- 2 polkitd ssh_keys     8192 11月  8 21:49 performance_schema
-rw------- 1 polkitd ssh_keys     1680 11月  8 21:49 private_key.pem
-rw-r--r-- 1 polkitd ssh_keys      452 11月  8 21:49 public_key.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月  8 21:49 server-cert.pem
-rw------- 1 polkitd ssh_keys     1676 11月  8 21:49 server-key.pem
drwxr-x--- 2 polkitd ssh_keys     8192 11月  8 21:49 sys
24.6 动态绑定pv

StorageClass 相当于一个创建 PV 的模板,用户通过 PVC 申请存储卷,StorageClass 通过模板自动创建 PV,然后和 PVC 进行绑定。

StorageClass创建动态存储卷流程

  1. 集群管理员预先创建存储类(StorageClass);
  2. 用户创建使用存储类的持久化存储声明(PVC:PersistentVolumeClaim);
  3. 存储持久化声明通知系统,它需要一个持久化存储(PV: PersistentVolume);
  4. 系统读取存储类的信息;
  5. 系统基于存储类的信息,在后台自动创建PVC需要的PV;
  6. 用户创建一个使用PVC的Pod;
  7. Pod中的应用通过PVC进行数据的持久化;
  8. 而PVC使用PV进行数据的最终持久化处理。

StorageClass支持的动态存储插件:

NFS Provisioner 是一个自动配置卷程序,它使用现有的和已配置的 NFS 服务器来支持通过持久卷声明动态配置 Kubernetes 持久卷。

注意:k8s 1.21版本中创建pvc时nfs-provisioner会报错

E0903 08:00:24.858523 1 controller.go:1004] provision “default/test-claim” class “managed-nfs-storage”: unexpected error getting claim reference: selfLink was empty, can’t make reference

解决方法:
修改 /etc/kubernetes/manifests/kube-apiserver.yaml文件
增加 - --feature-gates=RemoveSelfLink=false

spec:
  containers:
  - command:
    - kube-apiserver
    - --feature-gates=RemoveSelfLink=false   # 增加这行
    - --advertise-address=172.24.0.5
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
24.6.1 配置nfs-provisioner授权

创建ServiceAccount、ClusterRole、ClusterRoleBinding等,为nfs-client-provisioner授权

# rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
    # replace with namespace where provisioner is deployed
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io
24.6.2 部署nfs-client-provisioner
# nfs-provisioner.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default  #与RBAC文件中的namespace保持一致
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: gxf-nfs-storage  #provisioner名称,请确保该名称与 nfs-StorageClass.yaml文件中的provisioner名称保持一致
            - name: NFS_SERVER
              value: 10.24.X.X    #NFS Server IP地址
            - name: NFS_PATH  
              value: /home/nfs/1    #NFS挂载卷
      volumes:
        - name: nfs-client-root
          nfs:
            server: 10.24.X.X  #NFS Server IP地址
            path: /home/nfs/1     #NFS 挂载卷
# 部署
[root@kube-master ~]# kubectl apply -f rbac.yaml 
[root@kube-master ~]# kubectl apply -f nfs-provisioner.yaml 
[root@kube-master ~]# kubectl get pod 
NAME                                     READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-75bfdbdcd8-5mqbv   1/1     Running   0          4m24s

24.6.3 创建StorageClass
# nfs-StorageClass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: gxf-nfs-storage #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致
reclaimPolicy: Retain # 默认为delete
parameters:
  archiveOnDelete: "true" # false表示pv被删除时,在nfs下面对应的文件夹也会被删除,true正相反
24.6.4 deployment 动态挂载

部署一个有2个副本的deployment,挂载共享目录

创建pvc,“storageClassName"为上面创建的"managed-nfs-storage”,即指定动态创建PV的模板文件的名字。

# test-pvclaim.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 100Mi
  storageClassName: managed-nfs-storage
[root@kube-master ~]# kubectl apply -f test-pvclaim.yaml

deployment部署

# test-deploy.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deploy
  labels:
    app: test-deploy
  namespace: default  #与RBAC文件中的namespace保持一致
spec:
  replicas: 2
  selector:
    matchLabels:
      app: test-deploy
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: test-deploy
  template:
    metadata:
      labels:
        app: test-deploy
    spec:
      containers:
      - name: test-pod
        image: busybox:1.24
        command:
          - "/bin/sh"
        args:
          - "-c"
          # - "touch /mnt/SUCCESS3 && exit 0 || exit 1"   #创建一个SUCCESS文件后退出
          - touch /mnt/SUCCESS5; sleep 50000
        volumeMounts:
          - name: nfs-pvc
            mountPath: "/mnt"
            # subPath: test-pod-3 # 子路径 (这路基代表存储卷下面的test-pod子目录)  
      volumes:
        - name: nfs-pvc
          persistentVolumeClaim:
            claimName: test-claim  #与PVC名称保持一致
24.6.5 statefulset 动态挂载
# test-sts-1.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: test-sts
  labels:
    k8s-app: test-sts
spec:
  serviceName: test-sts-svc
  replicas: 3
  selector:
    matchLabels:
      k8s-app: test-sts
  template:
    metadata:
      labels:
        k8s-app: test-sts
    spec:
      containers:
        - image: busybox:1.24
          name: test-pod
          command:
            - "/bin/sh"
          args:
            - "-c"
            # - "touch /mnt/SUCCESS3 && exit 0 || exit 1"   #创建一个SUCCESS文件后退出
            - touch /mnt/SUCCESS5; sleep 50000
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: nfs-pvc
              mountPath: "/mnt"
  volumeClaimTemplates:
  - metadata:
      name: nfs-pvc
    spec:
      accessModes: ["ReadWriteMany"]
      storageClassName: managed-nfs-storage
      resources:
        requests:
          storage: 20Mi
[root@kube-master ~]# kubectl apply -f test-sts-1.yaml
[root@kube-master ~]# kubectl get sts
NAME       READY   AGE
test-sts   3/3     4m46s

二十五.StatefulSet有状态的应用

在Kubernetes系统中,Pod的管理对象RC、Deployment、DaemonSet都是面向无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的。但现实中有很多服务是有状态的,特别是一些复杂的中间件集群。例如Mysql集群、MongoDB集群、Kafka集群、Zookeeper集群等,这些都有一个共同的特点:

1.每个节点都有固定的ID,通过这个ID,集群中的成员可以相互发现并且通信。
2.集群的规模是比较固定的,集群规模不能随意改动。
3.集群里的每个节点都是有状态的,通常会持久化数据到永久存储中。
4.如果磁盘损坏,集群里的某个节点就无法正常运行,集群功能受损。

StatefulSet本质上是Deployment的一种变体,在v1.9版本中已成为GA版本,它为了解决有状态服务的问题,它所管理的Pod拥有固定的Pod名称,启停顺序的,如果使用RC/Deployment控制副本的方式实现有状态的集群那么pod的名字是没有办法控制的因为是随机产生,同时也无法为每一个pod确定唯一不变的ID号,为了解决这个问题,在k8s中引用了新的资源对象即StatefulSet。在StatefulSet中,Pod名字称为网络标识(hostname),还必须要用到共享存储。

1.StatefulSet里的每个Pod都有稳定、唯一的网络标识,可以用来发现集群中的其他成员,比如说StatefulSet的名字是N,那么第一个pod的名字就是N-0,第二个就是N-1,以此类推
2.StatefulSet控制的pod副本的启停顺序是受控制的,操作第n个pod,前面的n-1的pod已经是运行且准备好的状态。
3.StatefulSet里的pod采用稳定的持久化存储卷,通过PV/PVC来实现,删除Pod时默认不会删除与StatefulSet相关的存储卷(目的是为了保证数据的安全性)

所以,StatefulSet的核心功能,就是通过某种方式,记录这些状态,然后在Pod被重新创建时,能够为新Pod恢复这些状态.

在Deployment中,与之对应的服务是service,而在StatefulSet中与之对应的headless service,headless service,即无头服务,与service的区别就是它没有Cluster IP,解析它的名称时将返回该Headless Service对应的全部Pod的Endpoint列表。

除此之外,StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod副本创建了一个DNS域名,这个域名的格式为:

$(podname).(headless server name)

25.1 StatefulSet实现Pod的存储状态

通过PVC机制来实现存储状态管理

在StatefulSet对象中除了定义PodTemplate还会定义一个volumeClaimTemplates凡是被这个StatefulSet管理的Pod都会声明一个对应的PVC,这个PVC的定义就来自于 volumeClaimTemplates这个模板字段,这个PVC的名字,会被分配一个与这个Pod完全一致的编号。

把一个Pod比如N-0删除之后,这个Pod对应的PVC和PV并不会被删除,而这个Volume 里已经写入的数据,也依然会保存在远程存储服务里

StatefulSet在重新创建N-0这个pod的时候.它声明使用的PVC的名字还是叫作:N-0 这个PVC的定义,还是来自于PVC模板(volumeClaimTemplates)这是StatefulSet创建 Pod的标准流程

Kubernetes为它查找名叫N-0的PVC时,就会直接找到旧Pod遗留下来的同名的 PVC进而找到跟这个PVC绑定在一起的PV.这样新的Pod就可以挂载到旧Pod对应的那个Volume并且获取到保存在Volume里的数据.通过这种方式Kubernetes的StatefulSet就实现了对应用存储状态的管理
25.2 StatefulSet 的应用特点
  • StatefulSet的核心功能就是,通过某种方式记录应用状态,在Pod被重建的时候,通过这种方式还可以恢复原来的状态。
  • StatefulSet由以下几个部分组成:
    • Headless Service 用于定义网络标识(DNS)
    • volumeClaimTemplates  用于创建PV
    • StatefulSet  用于定义具体应用
  • 稳定且有唯一的网络标识符 当节点挂掉,既pod重新调度后其PodName和HostName不变,基于Headless Service来实现
  • 稳定且持久的存储  当节点挂掉,既pod重新调度能访问到相同的持久化存储,基于PVC实现
  • 有序、平滑的扩展、部署 即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现。
  • 有序、平滑的收缩、删除 既Pod是有顺序的,在收缩或者删除的时候要依据定义的顺序依次进行(既从N-1到0,既倒序)。
  • **拓扑状态。**是应用多个实例之间的不完全对等的关系,这些应用实例是必须按照定义的顺序启动的。例如主应用A先于从应用B启动,如果把A和B删掉,应用还要按照先启动主应用A再启动从应用B,且创建的应用必须和原来的应用的网络标识一样(既PodName和HostName)。这样他们就可以按照原来的顺序创建了。
  • 存储状态。应用实例分别绑定了不同的数据存储,Pod A第一次读到的数据要和10分钟后读到的数据,是同一份。哪怕这期间Pod A被重建。这种典型的例子就是数据库应用的多个存储实例。
25.3 实战1-通过StatefulSet创建nginx

由于statefulSet在创建的时候使用的是动态绑定pvc,而动态绑定pvc是需要插件支持的,一下实验通过手动创建pv在通过StatefulSet自动实现pvc申请持久卷。

1.创建基于NFS的pv持久卷
[root@k8s-master ~]# mkdir /mnt/data-1  #创建共享目录
[root@k8s-master ~]# mkdir /mnt/data-2
[root@k8s-master ~]# vim /etc/exports
/mnt/data-1 172.16.229.*/24 (rw,sync,insecure,no_subtree_check,no_root_squash)
/mnt/data-2 172.16.229.*/24 (rw,sync,insecure,no_subtree_check,no_root_squash)
[root@k8s-master ~]# systemctl restart nfs

[root@k8s-master ~]# mkdir /k8s/nginx  #创建工作目录
[root@k8s-master ~]# cd /k8s/nginx/
[root@k8s-master nginx]# vim nginx-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata: 
  name: pv-0
  labels:
    type: pv-0
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    server: 172.16.229.143
    path: "/mnt/data-1"
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-1
  labels:
    type: pv-1
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    server: 172.16.229.143
    path: "/mnt/data-2"
    
2.创建pv
[root@k8s-master nginx]# kubectl apply -f nginx-pv.yaml 
persistentvolume/nginx-pv created
[root@k8s-master nginx]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM           STORAGECLASS   REASON   AGE
my-pv   3Gi        RWX            Recycle          Bound       default/mypvc   pv-nfs                  6d16h
pv-0    5Gi        RWX            Recycle          Available                                           43s
pv-1    5Gi        RWX            Recycle          Available                                           43s

3.创建 service的Headless Service无头服务
[root@k8s-master nginx]# vim nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx  #service的名字
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None   #采用什么类型的service,这里采用的是Headless Service
  selector:
    app: nginx
[root@k8s-master nginx]# kubectl apply -f nginx-service.yaml 
service/nginx created
[root@k8s-master nginx]# kubectl get svc 
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes        ClusterIP   10.96.0.1      <none>        443/TCP        12d
nginx             ClusterIP   None           <none>        80/TCP         5s

4.创建StatefulSet,通过StatefulSet创建pvc实现自动绑定已经创建好的pv
[root@k8s-master nginx]# cat nginx-web.yaml 
apiVersion: apps/v1  #选择apiserver的版本
kind: StatefulSet  #定义pod类型
metadata:
  name: nginx-web
spec:
  serviceName: "nginx" #定义属于哪个Headless Service
  replicas: 2  #定义pod的副本
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: daocloud.io/library/nginx
        ports:
        - containerPort: 80
          name: web
        volumeMounts:       #定义卷的挂载到pod的路径
        - name: nginx-data
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:  #pvc的申请模版,会自动创建pvc并与pv绑定。从而实现各pod有专用存储(我们使用的是静态创建pv)
  - metadata:
      name: nginx-data   #pvc的名字
    spec:
      accessModes: ["ReadWriteMany"]
      resources:
        requests:
          storage: 5Gi  #指定每一个持久卷的大小
[root@k8s-master nginx]# kubectl apply -f nginx-web.yaml

查看statefulset
[root@k8s-master nginx]# kubectl get statefulset nginx-web
NAME        READY   AGE
nginx-web   2/2     19m

查看service
[root@k8s-master nginx]# kubectl get svc 
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx             ClusterIP   None           <none>        80/TCP         18m
为什么需要volumeClaimTemplate?
对于有状态的副本集都会用到持久存储,对于分布式系统来讲,它的最大特点是数据是不一样的,所以各个节点不能使用同一存储卷,每个节点有自已的专用存储,但是如果在Deployment中的Pod template里定义的存储卷,是所有副本集共用一个存储卷,数据是相同的,因为是基于模板来的 ,而statefulset中每个Pod都要自已的专有存储卷,所以statefulset的存储卷就不能再用Pod模板来创建了,于是statefulSet使用volumeClaimTemplate,称为卷申请模板,它会为每个Pod生成不同的pvc,并绑定pv, 从而实现各pod有专用存储。这就是为什么要用volumeClaimTemplate的原因。
25.3.1 顺序创建 Pod

对于一个拥有 N 个副本的 StatefulSet,Pod 被部署时是按照 {0 …… N-1} 的序号顺序创建的。在终端中使用 kubectl get 检查输出。这个输出最终将看起来像下面的样子

[root@k8s-master nginx]# kubectl get -l app='nginx' pods
NAME          READY   STATUS    RESTARTS   AGE
nginx-web-0   1/1     Running   0          18m
nginx-web-1   1/1     Running   0          6m34s

请注意在 nginx-web-0 Pod 处于 Running和Ready 状态后 nginx-web-1 Pod 才会被启动

如同 StatefulSets 概念中所提到的,StatefulSet 中的 Pod 拥有一个具有黏性的、独一无二的身份标志。这个标志基于 StatefulSet 控制器分配给每个 Pod 的唯一顺序索引。Pod 的名称的形式为<statefulset name>-<ordinal index>webStatefulSet 拥有两个副本,所以它创建了两个 Pod:nginx-web-0nginx-web-1

25.3.2 使用稳定的网络身份标识

每个 Pod 都拥有一个基于其顺序索引的稳定的主机名。使用[kubectl exec](https://kubernetes.io/zh/docs/reference/generated/kubectl/kubectl-commands/#exec)在每个 Pod 中执行hostname

[root@k8s-master nginx]# for i in 0 1; do kubectl exec nginx-web-$i -- /bin/bash -c 'hostname'; done
nginx-web-0
nginx-web-1
25.3.3 查看statefulset创建的pod的存储

获取 nginx-web-0nginx-web-1 的 PersistentVolumeClaims。

[root@k8s-master nginx]# kubectl get pvc -l app='nginx'
NAME                     STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
nginx-data-nginx-web-0   Bound    pv-0     5Gi        RWX                           53m
nginx-data-nginx-web-1   Bound    pv-1     5Gi        RWX                           53m

StatefulSet 控制器创建了两个 PersistentVolumeClaims,绑定到两个 PersistentVolumes。由于这里我们使用的是手动创建pv,所有的 PersistentVolume 都是手动创建自动的绑定。

NGINX web 服务器默认会加载位于 /usr/share/nginx/html/index.html 的 index 文件。StatefulSets spec 中的 volumeMounts 字段保证了 /usr/share/nginx/html 文件夹由一个 PersistentVolume 支持。

将 Pod 的主机名写入它们的index.html文件并验证 NGINX web 服务器使用该主机名提供服务。

[root@k8s-master nginx]# kubectl exec -it nginx-web-0 /bin/bash
root@nginx-web-0:/# echo nginx-web-0 >> /usr/share/nginx/html/index.html 
root@nginx-web-0:/# exit  
exit
[root@k8s-master nginx]# kubectl exec -it nginx-web-1 /bin/bash
root@nginx-web-1:/# echo nginx-web-1 >> /usr/share/nginx/html/index.html
root@nginx-web-1:/# exit
exit

[root@k8s-master nginx]# kubectl get pods -l app='nginx' -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
nginx-web-0   1/1     Running   0          84m   10.244.2.24   k8s-node2   <none>           <none>
nginx-web-1   1/1     Running   0          84m   10.244.1.26   k8s-node1   <none>           <none>

[root@k8s-master nginx]# curl -s http://10.244.2.24
nginx-web-0
[root@k8s-master nginx]# curl -s http://10.244.1.26
nginx-web-1
25.3.4 说明
请注意,如果你看见上面的 curl 命令返回了 403 Forbidden 的响应,你需要像这样修复使用 `volumeMounts`挂载的目录的权限:
# for i in 0 1; do kubectl exec web-$i -- chmod 755 /usr/share/nginx/html; done

将StatefulSet创建的 所有 Pod删除

[root@k8s-master nginx]# kubectl delete -f nginx-web.yaml 
statefulset.apps "nginx-web" deleted

在使用StatefulSet重新创建的所有Pod

[root@k8s-master nginx]# kubectl apply -f nginx-web.yaml 
statefulset.apps/nginx-web created
[root@k8s-master nginx]# kubectl get pods -l app='nginx' -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
nginx-web-0   1/1     Running   0          14s   10.244.2.25   k8s-node2   <none>           <none>
nginx-web-1   1/1     Running   0          12s   10.244.1.27   k8s-node1   <none>           <none>

我们发现pod的IP地址发生了变化,接下来再次访问

[root@k8s-master nginx]# curl -s http://10.244.2.25
nginx-web-0
[root@k8s-master nginx]# curl -s http://10.244.1.27
nginx-web-1

虽然 nginx-web-0nginx-web-1 被重新调度了,但它们仍然继续监听各自的主机名,因为和它们的 PersistentVolumeClaim 相关联的 PersistentVolume 被重新挂载到了各自的 volumeMount 上。不管 nginx-web-0nginx-web-1 被调度到了哪个节点上,它们的 PersistentVolumes 将会被挂载到合适的挂载点上。

25.3.5 扩容/缩容 StatefulSet

扩容/缩容 StatefulSet 指增加或减少它的副本数。这通过更新 replicas 字段完成.如果是手动创建的pv,那么需要提前将pv创建好了。

25.4 实战2-通过StatefulSet来管理有状态的服务msyql
[root@k8s-master ~]# mkdir /mnt/data-3
[root@k8s-master ~]# vim /etc/exports
/mnt/data-3 172.16.229.*/24 (rw,sync,insecure,no_subtree_check,no_root_squash)
[root@k8s-master ~]# systemctl restart nfs
[root@k8s-master ~]# mkdir /k8s/application/mysql
[root@k8s-master mysql]# cat mysql.pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata: 
  name: mysql-pv
  labels:
    type: mysql-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    server: 172.16.229.143
    path: "/mnt/data-3"
    
[root@k8s-master mysql]# cat mysql-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  ports:
  - port: 3306
    name: mysql
  clusterIP: None
  selector:
    app: mysql
    
[root@k8s-master mysql]# cat mysql-server.yaml 
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: "mysql"
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: daocloud.io/library/mysql:5.7
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "Qfedu@123"
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: ["ReadWriteMany"]
      resources:
        requests:
          storage: 5Gi
          
[root@k8s-master mysql]# kubectl apply -f mysql.pv.yaml 
persistentvolume/mysql-pv created
[root@k8s-master mysql]# kubectl apply -f mysql-svc.yaml 
service/mysql created 
[root@k8s-master mysql]# kubectl apply -f mysql-server.yaml 
statefulset.apps/mysql created

[root@k8s-master mysql]# kubectl get pv | grep mysql
mysql-pv   5Gi        RWX            Recycle          Bound    default/mysql-data-mysql-0                               7m28s

[root@k8s-master mysql]# kubectl get pvc
NAME                     STATUS   VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mysql-data-mysql-0       Bound    mysql-pv   5Gi        RWX                           7m16s

[root@k8s-master mysql]# kubectl get pods -l app='mysql' -o wide
NAME                        READY   STATUS             RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES
mysql-0                     1/1     Running            0          6m22s   10.244.1.28   k8s-node1   <none>           <none>

[root@k8s-master mysql]# kubectl exec -it mysql-0 /bin/bash
root@mysql-0:/# mysql -uroot -p'Qfedu@123'
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
...
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.01 sec)

mysql> create database test1;
Query OK, 1 row affected (0.00 sec)

mysql> exit
Bye
root@mysql-0:/# exit
exit 
[root@k8s-master mysql]# cd /mnt/data-3/
[root@k8s-master data-3]# ll
总用量 188484
-rw-r----- 1 polkitd ssh_keys       56 11月 15 16:24 auto.cnf
-rw------- 1 polkitd ssh_keys     1676 11月 15 16:24 ca-key.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月 15 16:24 ca.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月 15 16:24 client-cert.pem
-rw------- 1 polkitd ssh_keys     1680 11月 15 16:24 client-key.pem
-rw-r----- 1 polkitd ssh_keys     1353 11月 15 16:25 ib_buffer_pool
-rw-r----- 1 polkitd ssh_keys 79691776 11月 15 16:25 ibdata1
-rw-r----- 1 polkitd ssh_keys 50331648 11月 15 16:25 ib_logfile0
-rw-r----- 1 polkitd ssh_keys 50331648 11月 15 16:24 ib_logfile1
-rw-r----- 1 polkitd ssh_keys 12582912 11月 15 16:25 ibtmp1
drwxr-x--- 2 polkitd ssh_keys     4096 11月 15 16:25 mysql
drwxr-x--- 2 polkitd ssh_keys     8192 11月 15 16:25 performance_schema
-rw------- 1 polkitd ssh_keys     1680 11月 15 16:24 private_key.pem
-rw-r--r-- 1 polkitd ssh_keys      452 11月 15 16:24 public_key.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月 15 16:24 server-cert.pem
-rw------- 1 polkitd ssh_keys     1680 11月 15 16:24 server-key.pem
drwxr-x--- 2 polkitd ssh_keys     8192 11月 15 16:25 sys
drwxr-x--- 2 polkitd ssh_keys       20 11月 15 16:29 test1


[root@k8s-master mysql]# kubectl delete -f mysql-server.yaml 
statefulset.apps "mysql" deleted
[root@k8s-master mysql]# cd /mnt/data-3/
[root@k8s-master data-3]# ll
总用量 176196
-rw-r----- 1 polkitd ssh_keys       56 11月 15 16:24 auto.cnf
-rw------- 1 polkitd ssh_keys     1676 11月 15 16:24 ca-key.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月 15 16:24 ca.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月 15 16:24 client-cert.pem
-rw------- 1 polkitd ssh_keys     1680 11月 15 16:24 client-key.pem
-rw-r----- 1 polkitd ssh_keys      688 11月 15 16:32 ib_buffer_pool
-rw-r----- 1 polkitd ssh_keys 79691776 11月 15 16:32 ibdata1
-rw-r----- 1 polkitd ssh_keys 50331648 11月 15 16:32 ib_logfile0
-rw-r----- 1 polkitd ssh_keys 50331648 11月 15 16:24 ib_logfile1
drwxr-x--- 2 polkitd ssh_keys     4096 11月 15 16:25 mysql
drwxr-x--- 2 polkitd ssh_keys     8192 11月 15 16:25 performance_schema
-rw------- 1 polkitd ssh_keys     1680 11月 15 16:24 private_key.pem
-rw-r--r-- 1 polkitd ssh_keys      452 11月 15 16:24 public_key.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月 15 16:24 server-cert.pem
-rw------- 1 polkitd ssh_keys     1680 11月 15 16:24 server-key.pem
drwxr-x--- 2 polkitd ssh_keys     8192 11月 15 16:25 sys
drwxr-x--- 2 polkitd ssh_keys       20 11月 15 16:29 test1

[root@k8s-master data-3]# cd -
/k8s/application/mysql
[root@k8s-master mysql]# kubectl apply  -f mysql-server.yaml 
statefulset.apps/mysql created
[root@k8s-master mysql]# kubectl exec -it mysql-0 /bin/bash
root@mysql-0:/# mysql -uroot -p'Qfedu@123'
...
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test1              |
+--------------------+
5 rows in set (0.01 sec)

mysql>
25.5 k8s中无状态服务和有状态服务部署的区别?

无状态

1. pod命名:pod名由资源名+随机的字符串组成;
2. 数据存储:多个实例pod可以共享相同的持久化数据,存储不是必要条件;
3. 扩缩容:可以随意扩缩容某个pod,不会指定某个pod进行扩缩容;
4. 启停顺序:因为pod名的序号是随机串,无启停顺序之分;
5. 无状态k8s资源:ReplicaSet、ReplicationController、Deployment、DaemonSet、Job等资源;
6. 无状态服务:tomcat、nginx等;

有状态
这里假设有N个pod;

1. pod命名:pod名由statefulset资源名+有序的数字组成(0,1,2...N-1),且pod有特定的网络标识;
2. 数据存储:有状态的服务对应实例需要有自己的独立持久卷存储;
3. 扩缩容:扩缩容不可随意,缩容是从数字最大的开始递减,扩容是在原有pod序号基础上递增1。
4. 启停顺序:pod启停是有顺序的,启动时,先启动pod序号为0的,然后依次递增至N-1;停止时,先停止pod序号为最大的N-1,然后依次递减至0;
5. 有状态k8s资源:StatefulSet资源;
6. 有状态服务:Kafka、ZooKeeper、MySql、MongoDB以及一些需要保存日志的应用等服务;
  • 20
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值