容器化技术之K8S - AI数据

时间:2024-02-23 09:41:12

容器化技术之K8S

Docker 本身非常适合管理单个容器。但随着您开始使用越来越多的容器和容器化应用程序,并把它们划分成数百个部分,很可能会导致管理和编排变得非常困难。最终,您需要后退一步,对容器实施分组,以便跨所有容器提供网络、安全、遥测等服务。于是,Kubernetes 应运而生。

 

一、简介

Kubernetes,又称为 k8s(首字母为 k、首字母与尾字母之间有 8 个字符、尾字母为 s,所以简称 k8s)或者简称为 “kube” ,是一种可自动实施 Linux 容器操作的开源平台。它可以帮助用户省去应用容器化过程的许多手动部署和扩展操作。也就是说,您可以将运行 Linux 容器的多组主机聚集在一起,由 Kubernetes 帮助您轻松高效地管理这些集群。而且,这些集群可跨公共云、私有云或混合云部署主机。因此,对于要求快速扩展的云原生应用而言(例如借助 Apache Kafka 进行的实时数据流处理),Kubernetes 是理想的托管平台。

Kubernetes 最初由 Google 的工程师基于 go 语言开发和设计出来并于2014年6月开源。Google 是最早研发 Linux 容器技术的企业之一,曾公开分享介绍 Google 如何将一切都运行于容器之中(这是 Google 云服务背后的技术)。Google 每周会启用超过 20 亿个容器——全都由内部平台 Borg 支撑。Borg 是 Kubernetes 的前身,多年来开发 Borg 的经验教训成了影响 Kubernetes 中许多技术的主要因素。

趣闻:Kubernetes 徽标的七个轮辐代表着项目最初的名称“九之七项目”(Project Seven of Nine)。

红帽是第一批与 Google 合作研发 Kubernetes 的公司之一,作为 Kubernetes 上游项目的第二大贡献者,我们甚至在这个项目启动之前就已参与其中。2015 年,Google 将 Kubernetes 项目捐赠给了新成立的云原生计算基金会。

为什么用k8s

docker很难在不同服务器之间互联。

真正的生产型应用会涉及多个容器。这些容器必须跨多个服务器主机进行部署。Kubernetes 可以提供所需的编排和管理功能,以便您针对这些工作负载大规模部署容器。借助 Kubernetes 编排功能,您可以构建跨多个容器的应用服务、跨集群调度、扩展这些容器,并长期持续管理这些容器的健康状况。

 

Kubernetes 还需要与联网、存储、安全性、遥测和其他服务集成整合,以提供全面的容器基础架构。

当然,这取决于您如何在您的环境中使用容器。Linux 容器中的基本应用将它们视作高效、快速的虚拟机。一旦把它部署到生产环境或扩展为多个应用,您显然需要许多托管在相同位置的容器来协同提供各种服务。随着这些容器的累积,您运行环境中容器的数量会急剧增加,复杂度也随之增长。

 

Kubernetes 通过将容器分类组成 “容器集” (pod),解决了容器增殖带来的许多常见问题容器集为分组容器增加了一个抽象层,可帮助您调用工作负载,并为这些容器提供所需的联网和存储等服务。Kubernetes 的其它部分可帮助您在这些容器集之间达成负载平衡,同时确保运行正确数量的容器,充分支持您的工作负载。

 容器分组:pod,pod下一组多个docker容器,最小操作单元。

 

如果能正确实施 Kubernetes,再辅以其它开源项目(例如 Atomic 注册表、Open vSwitch、heapster、OAuth 以及 SELinux),您就能够轻松编排容器基础架构的各个部分。

 

用途

在您生产环境中使用 Kubernetes 的主要优势在于,它提供了一个便捷有效的平台,让您可以在物理机和虚拟机集群上调度和运行容器。更广泛一点说,它可以帮助您在生产环境中,完全实施并依托基于容器的基础架构运营。由于 Kubernetes 的实质在于实现操作任务自动化,所以您可以将其它应用平台或管理系统分配给您的许多相同任务交给容器来执行。

 

利用 Kubernetes,您能够达成以下目标:

  • l  跨多台主机进行容器编排。
  • l  更加充分地利用硬件,最大程度获取运行企业应用所需的资源。
  • l  有效管控应用部署和更新,并实现自动化操作。
  • l  挂载和增加存储,用于运行有状态的应用。
  • l  快速、按需扩展容器化应用及其资源。
  • l  对服务进行声明式管理,保证所部署的应用始终按照部署的方式运行。
  • l  利用自动布局、自动重启、自动复制以及自动扩展功能,对应用实施状况检查和自我修复。

 

但是,Kubernetes 需要依赖其它项目来全面提供这些经过编排的服务。因此,借助其它开源项目可以帮助您将 Kubernetes 的全部功用发挥出来。这些功能包括:

  •  注册表,通过 Atomic 注册表或 Docker 注册表等项目实现。
  •  联网,通过 OpenvSwitch 和智能边缘路由等项目实现。
  •  遥测,通过 heapster、kibana、hawkular 和 elastic 等项目实现。
  •  安全性,通过 LDAP、SELinux、RBAC 和 OAUTH 等项目以及多租户层来实现。
  •  自动化,参照 Ansible 手册进行安装和集群生命周期管理。
  •  服务,可通过自带预建版常用应用模式的丰富内容目录来提供。

 

二、核心概念

Kubernetes 有各类资源对象来描述整个集群的运行状态(Node、Pod、Replication Controller、Service等都可以看作一种“资源对象”)。这些对象都需要通过调用 kubernetes api 来进行创建、修改、删除并将其保存在etcd中持久化存储,可以通过 kubectl 命令工具,也可以直接调用 k8s api,或者使用对象语言的客户端库(例如:golang , pythion )。

从这个角度来看,Kubernetes其实是一个高度自动化的资源控制系统,它通过跟踪对比etcd库里保存的“资源期望状态”与当前环境中的“实际资源状态”的差异来实现自动控制和自动纠错的高级功能。

每个 kubernetes 对象都会包含两个关键字段:Object Spec 和 Object Status。spec 描述了对象所期望达到的状态,status 描述了该对象的实际状态。

 

 

 

2.1  Master

 Kubernetes里的Master指的是集群控制节点,每个Kubernetes集群里需要有一个Master节点来负责整个集群的管理和控制,基本上Kubernetes的所有控制命令都发给它,它来负责具体的执行过程,我们后面执行的所有命令基本都是在Master节点上运行的。Master节点通常会占据一个独立的服务器(高可用部署建议用3台服务器),其主要原因是它太重要了,是整个集群的“首脑”,如果宕机或者不可用,那么对集群内容器应用的管理都将失效。

 

Master节点上运行着以下一组关键进程:

  • Kubernetes API Server (kube-apiserver):提供了HTTP Rest接口的关键服务进程,是Kubernetes里所有资源的增、删、改、查等操作的唯一入口,也是集群控制的入口进程。
  • Kubernetes Controller Manager (kube-controller-manager):Kubernetes里所有资源对象的自动化控制中心,可以理解为资源对象的“大总管”。
  • Kubernetes Scheduler (kube-scheduler):负责资源调度(Pod调度)的进程,相当于公交公司的“调度室”。

 另外,在Master节点上还需要启动一个etcd服务,因为Kubernetes里的所有资源对象的数据全部是保存在etcd中的。

 

 

比如,你在 bash中输入

[root@node01 ~]# kubectl get service

k8s的API Server会对其处理,实际调用了k8s的api,k8s操作的唯一入口

kube-controller-manager负责RC机制,当docker容器出错关闭后,会自动开启新的

资源调度器和管家联系在一起,管家查看etcd中的数据,调用资源调度器去进行资源调度,看哪些工作节点符合要求,下发命令给节点

 

2.2.  Node

除了Master,Kubernetes集群中的其他机器被称为Node节点,在较早的版本中也被称为Minion。与Master一样,Node节点可以是一台物理主机,也可以是一台虚拟机。Node节点才是Kubernetes集群中的工作负载节点,每个Node都会被Master分配一些工作负载(Docker容器),当某个Node宕机时,其上的工作负载会被Master自动转移到其他节点上去。

每个Node节点上都运行着以下一组关键进程:

  • kubelet:负责Pod对应的容器的创建、启停等任务,同时与Master节点密切协作,实现集群管理的基本功能。
  • kube-proxy:实现Kubernetes Service的通信与负载均衡机制的重要组件。
  • Docker Engine (docker):Docker引擎,负责本机的容器创建和管理工作。

fluentd

 Node节点可以在运行期间动态增加到Kubernetes集群中,前提是这个节点上已经正确安装、配置和启动了上述关键进程,在默认情况下kubelet会向Master注册自己,这也是Kubernetes推荐的Node管理方式。一旦Node被纳入集群管理范围,kubelet进程就会定时向Master节点汇报自身的情报,例如操作系统、Docker版本、机器的CPU和内存情况,以及当前有哪些Pod在运行等,存在etcd中,这样Master可以获知每个Node的资源使用情况,并实现高效均衡等资源调度策略。而某个Node超过指定时间不上报信息时,会被Master判断为“失联”,Node的状态被标记为不可用(Not Ready),随后Master会触发“工作负载大转移”的自动流程。

 

比如,要运行三台nginx,有一台挂了,该台的所有信息都已存在etcd当中了,controller manager会根据rc机制重启一台,让调度器去找可用的资源进行重启。

 

2.3.  Pod

Pod是Kubernetes的最重要也最基本的概念,如下图所示是Pod的组成示意图,我们看到每个Pod都有一个特殊的被成为“根容器”的Pause容器。Pause容器对应的镜像属于Kubernetes平台的一部分,除了Pause容器,每个Pod还包含一个或多个紧密相关的用户业务容器。

Pause是打不死的小强,不运行业务,Pod会检查该容器有没有挂

Pod的组成

为什么Kubernetes会设计出一个全新的Pod概念并且Pod有这样特殊的组成结构?

 原因之一:在一组容器作为一个单元的情况下,我们难以对“整体”简单地进行判断及有效地进行行动。比如,一个容器死亡了,此时算是整体死亡么?引入业务无关并且不易死亡的Pause容器作为Pod的根容器,以它的状态代表整体容器组的状态,就简单、巧妙地解决了这个难题。

 原因之二:Pod里的多个业务容器共享Pause容器的IP,共享Pause容器挂接的Volume,这样既简化了密切关联的业务容器之间的通信问题,也很好地解决了它们之间的文件共享问题。

 Kubernetes为每个Pod都分配了唯一的IP地址,称之为Pod IP,一个Pod里的多个容器共享Pod IP地址。Kubernetes要求底层网络支持集群内任意两个Pod之间的TCP/IP直接通信,这通常采用虚拟而层网络技术来实现,例如Flannel、Open vSwitch等,因此我们需要牢记一点:在Kubernetes里,一个Pod里的容器与另外主机上的Pod容器能够直接通信。

 Pod其实有两种类型:普通的Pod及静态Pod(Static Pod),后者比较特殊,它并不存放在Kubernetes的etcd存储里,而是存放在某个具体的Node上的一个具体文件中,并且只在此Node上启动运行。而普通的Pod一旦被创建,就会被放入到etcd中存储,随后会被Kubernetes Master调度到某个具体的Node上并进行绑定(Binding),随后该Pod被对应的Node上的kubelet进程实例化成一组相关的Docker容器并且启动起来。在默认情况下,当Pod里的某个容器停止时,Kubernetes会自动检测到这个问题并且重新启动这个Pod(重启Pod里的所有容器),如果Pod所在的Node宕机,则会将这个Node上的所有Pod重新调度到其他节点上。Pod、容器与Node的关系图如下图所示。

 

Pod、容器与Node的关系

 

2.4.  Label(标签)

 Label是Kubernetes系统中另外一个核心概念。一个Label是一个key=value的键值对,其中key与vaue由用户自己指定。Label可以附加到各种资源对象上,例如Node、Pod、Service、RC等,一个资源对象可以定义任意数量的Label,同一个Label也可以被添加到任意数量的资源对象上去,Label通常在资源对象定义时确定,也可以在对象创建后动态添加或者删除。

 我们可以通过指定的资源对象捆绑一个或多个不同的Label来实现多维度的资源分组管理功能,以便于灵活、方便地进行资源分配、调度、配置、部署等管理工作。例如:部署不同版本的应用到不同的环境中;或者监控和分析应用(日志记录、监控、告警)等。一些常用等label示例如下。

  • 版本标签:"release" : "stable" , "release" : "canary"...
  • 环境标签:"environment" : "dev" , "environment" : "production"
  • 架构标签:"tier" : "frontend" , "tier" : "backend" , "tier" : "middleware"
  • 分区标签:"partition" : "customerA" , "partition" : "customerB"...
  • 质量管控标签:"track" : "daily" , "track" : "weekly"

 Label相当于我们熟悉的“标签”,給某个资源对象定义一个Label,就相当于給它打了一个标签,随后可以通过Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象,Kubernetes通过这种方式实现了类似SQL的简单又通用的对象查询机制。

 mysql打个标签,mycat打个标签,放在一个服务器上跑。

2.5.  Replication Controller

 RC是Kubernetes系统中的核心概念之一,简单来说,它其实是定义了一个期望的场景,即声明某种Pod的副本数量在任意时刻都符合某个预期值,所以RC的定义包括如下几个部分。

  • Pod期待的副本数(replicas)。
  • 用于筛选目标Pod的Label Selector。
  • 当Pod的副本数量小于预期数量时,用于创建新Pod的Pod模版(template)。

 

2.6.  Deployment

 Deployment是Kubernetes v1.2引入的概念,引入的目的是为了更好地解决Pod的编排问题。为此,Deployment在内部使用了Replica Set来实现目的,无论从Deployment的作用与目的,它的YAML定义,还是从它的具体命令行操作来看,我们都可以把它看作RC的一次升级,两者相似度超过90%。

 Deployment相对于RC的一个最大升级是我们随时知道当前Pod“部署”的进度。实际上由于一个Pod的创建、调度、绑定节点及在目标Node上启动对应的容器这一完整过程需要一定的时间,所以我们期待系统启动N个Pod副本的目标状态,实际上是一个连续变化的“部署过程”导致的最终状态。

 Deployment的典型使用场景有以下几个。

  • 创建一个Deployment对象来生成对应的Replica Set并完成Pod副本的创建过程。
  • 检查Deployment的状态来看部署动作是否完成(Pod副本的数量是否达到预期的值)。
  • 更新Deployment以创建新的Pod(比如镜像升级)。
  • 如果当前Deployment不稳定,则回滚到一个早先的Deployment版本。
  • 暂停Deployment以便于一次性修改多个PodTemplateSpec的配置项,之后再恢复Deployment,进行新的发布。
  • 扩展Deployment以应对高负载。
  • 查看Deployment的状态,以此作为发布是否成功的指标。
  • 清理不再需要的旧版本ReplicaSets。

 

2.7.  StatefulSet

 在Kubernetes系统中,Pod的管理对象RC、Deployment、DaemonSet和Job都是面向无状态的服务。但现实中有很多服务是有状态的,特别是一些复杂的中间件集群,例如MySQL集群、MongoDB集群、Kafka集群、Zookeeper集群等,这些应用集群有以下一些共同点。

 

每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并且通信。

集群的规模是比较固定的,集群规模不能随意变动。

集群里的每个节点都是有状态的,通常会持久化数据到永久存储中。

如果磁盘损坏,则集群里的某个节点无法正常运行,集群功能受损。

 

 如果用RC/Deployment控制Pod副本数的方式来实现上述有状态的集群,则我们会发现第一点是无法满足的,因为Pod的名字是随机产生的,Pod的IP地址也是在运行期才确定且可能有变动的,我们事先无法为每个Pod确定唯一不变的ID,为了能够在其他节点上恢复某个失败的节点,这种集群中的Pod需要挂接某种共享存储,为了解决这个问题,Kubernetes从v1.4版本开始引入了PetSet这个新的资源对象,并且在v1.5版本时更名为StatefulSet,StatefulSet从本质上来说,可以看作Deployment/RC的一个特殊变种,它有如下一些特性。

 

StatefulSet里的每个Pod都有稳定、唯一的网络标识,可以用来发现集群内的其他成员。假设StatefulSet的名字叫kafka,那么第一个Pod叫kafak-0,第二个Pod叫kafak-1,以此类推。

StatefulSet控制的Pod副本的启停顺序是受控的,操作第n个Pod时,前n-1个Pod已经时运行且准备好的状态。

StatefulSet里的Pod采用稳定的持久化存储卷,通过PV/PVC来实现,删除Pod时默认不会删除与StatefulSet相关的存储卷(为了保证数据的安全)。

 

 StatefulSet除了要与PV卷捆绑使用以存储Pod的状态数据,还要与Headless Service配合使用,即在每个StatefulSet的定义中要声明它属于哪个Headless Service。Headless Service与普通Service的关键区别在于,它没有Cluster IP,如果解析Headless Service的DNS域名,则返回的是该Service对应的全部Pod的Endpoint列表。StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod实例创建了一个DNS域名,这个域名的格式为:

$(podname).$(headless service name)

 

 比如一个3节点的Kafka的StatefulSet集群,对应的Headless Service的名字为kafka,StatefulSet的名字为kafka,则StatefulSet里面的3个Pod的DNS名称分别为kafka-0.kafka、kafka-1.kafka、kafka-3.kafka,这些DNS名称可以直接在集群的配置文件中固定下来。

 

2.8.  Service(服务)

 Service也是Kubernetes里的最核心的资源对象之一,Kubernetes里的每个Service其实就是我们经常提起的微服务架构中的一个“微服务”,之前我们所说的Pod、RC等资源对象其实都是为这节所说的“服务”------Kubernetes Service作“嫁衣”的。下图显示了Pod、RC与Service的逻辑关系。

 

Pod、RC与Service的关系

 从图中我们看到,Kubernetes的Service定义了一个服务的访问入口地址,前端的应用(Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,Service与其后端Pod副本集群之间则是通过Label Selector来实现“无缝对接”的。而RC的作用实际上是保证Service的服务能力和服务质量始终处于预期的标准。

 

2.9.  Volume(存储卷)

 Volume是Pod中能够被多个容器访问的共享目录。Kubernetes的Volume概念、用途和目的与Docker的Volume比较类似,但两者不能等价。首先,Kubernetes中的Volume定义在Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下;其次,Kubernetes中的Volume中的数据也不会丢失。最后,Kubernetes支持多种类型的Volume,例如Gluster、Ceph等先进的分布式文件系统。

 

2.10.  Namespace

 Namespace(命名空间)是Kubernetes系统中的另一个非常重要的概念,Namespace在很多情况下用于实现多租户的资源隔离。Nameaspace通过将集群内部的资源对象“分配”到不同的Namespce中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。

 

2.11.  Annotation(注解)

 Annotation与Label类似,也使用key/value键值对的形式进行定义。不同的是Label具有严格的命名规则,它定义的是Kubernetes对象的元数据(Metadata),并且用于Label Selector。而Annotation则是用户任意定义的“附加”信息,以便于外部工具进行查找,很多时候,Kubernetes的模块自身会通过Annotation的方式标记资源对象的特殊信息。

 通常来说,用Annotation来记录的信息如下。 

  • build信息、release信息、Docker镜像信息等,例如时间戳、release id号、PR号、镜像hash值、docker registry地址等。
  • 日志库、监控库、分析库等资源库的地址信息。
  • 程序调试工具信息,例如工具、版本号等。
  • 团队等联系信息,例如电话号码、负责人名称、网址等。

 

2.12. 其他

 Kubelet

运行在节点上的服务,可读取容器清单(container manifest),确保指定的容器启动并运行。

 kubectl

Kubernetes 的命令行配置工具。

 

上述组件是Kubernetes系统的核心组件,它们共同构成了Kubernetes系统的框架和计算模型。通过对它们进行灵活组合,用户就可以快速、方便地对容器集群进行配置、创建和管理。除了本章所介绍的核心组件,在Kubernetes系统中还有许多辅助配置的资源对象,例如LimitRange、Resurce。另外,一些系统内部使用的对象Binding、Event等请参考Kubernetes的API文档。

 

三、术语

官方把 kubernetes 术语分为 12 个分类:

系统结构、社区、核心对象、扩展、基础、网络、操作、安全、存储、工具、用户类型、工作负载

由于 k8s 的术语实在太多了,想要全部记住作为新学者还是有点压力,所以我们课程中只讲解一些常用的术语。想要了解更多 kubernetes 术语,请参照官方术语表。

地址:https://kubernetes.io/docs/reference/glossary/?fundamental=true

Pods

在Kubernetes中,最小的管理元素不是一个个独立的容器,而是Pod,Pod是最小的,管理,创建,计划的最小单元。

Labels

标签其实就一对 key/value ,被关联到对象上,比如Pod;标签的使用我们倾向于能够标示对象的特殊特点,并且对用户而言是有意义的(就是一眼就看出了这个Pod是数据库),但是标签对内核系统是没有直接意义的。标签可以用来划分特定组的对象(比如,所有女的),标签可以在创建一个对象的时候直接给与,也可以在后期随时修改,每一个对象可以拥有多个标签,但是,key值必须是唯一的

"labels": {

 "key1" : "value1",

 "key2" : "value2"

 }

Namespace

Namespace 是对一组资源和对象的抽象集合,比如可以用来将系统内部的对象划分为不同的项目组或用户组。常见的pods, services, replication controllers和deployments等都是属于某一个namespace的(默认是default),而node, persistentVolumes等则不属于任何namespace。

 

Namespace常用来隔离不同的用户,比如Kubernetes自带的服务一般运行在kube-system namespace中。

Replication Controller

Replication Controller 保证了在所有时间内,都有特定数量的Pod副本正在运行,如果太多了,Replication Controller就杀死几个,如果太少了,Replication Controller会新建几个,和直接创建的pod不同的是,Replication Controller会替换掉那些删除的或者被终止的pod,不管删除的原因是什么(维护阿,更新啊,Replication Controller都不关心)。基于这个理由,我们建议即使是只创建一个pod,我们也要使用Replication Controller。Replication Controller 就像一个进程管理器,监管着不同node上的多个pod,而不是单单监控一个node上的pod,Replication Controller 会委派本地容器来启动一些节点上服务(Kubelet ,Docker)。

Node

Node是Pod真正运行的主机,可以物理机,也可以是虚拟机。为了管理Pod,每个Node节点上至少要运行container runtime(比如docker或者rkt)、kubelet和kube-proxy服务。

 

ReplicaSets

ReplicaSet是下一代复本控制器。ReplicaSet和 Replication Controller之间的唯一区别是现在的选择器支持。Replication Controller只支持基于等式的selector(env=dev或environment!=qa),但ReplicaSet还支持新的,基于集合的selector(version in (v1.0, v2.0)或env notin (dev, qa))。在试用时官方推荐ReplicaSet。

Services

Kubernetes Pod是平凡的,它门会被创建,也会死掉(生老病死),并且他们是不可复活的。 ReplicationControllers动态的创建和销毁Pods(比如规模扩大或者缩小,或者执行动态更新)。每个pod都由自己的ip,这些IP也随着时间的变化也不能持续依赖。这样就引发了一个问题:如果一些Pods(让我们叫它作后台,后端)提供了一些功能供其它的Pod使用(让我们叫作前台),在kubernete集群中是如何实现让这些前台能够持续的追踪到这些后台的?

答案是:Service

Kubernete Service 是一个定义了一组Pod的策略的抽象,我们也有时候叫做宏观服务。这些被服务标记的Pod都是(一般)通过label Selector决定的(下面我们会讲到我们为什么需要一个没有label selector的服务)

举个例子,我们假设后台是一个图形处理的后台,并且由3个副本。这些副本是可以相互替代的,并且前台并需要关心使用的哪一个后台Pod,当这个承载前台请求的pod发生变化时,前台并不需要直到这些变化,或者追踪后台的这些副本,服务是这些去耦

对于Kubernete原生的应用,Kubernete提供了一个简单的Endpoints API,这个Endpoints api的作用就是当一个服务中的pod发生变化时,Endpoints API随之变化,对于哪些不是原生的程序,Kubernetes提供了一个基于虚拟IP的网桥的服务,这个服务会将请求转发到对应的后台pod。

Volumes

容器中的磁盘的生命周期是短暂的,这就带来了一系列的问题,第一,当一个容器损坏之后,kubelet 会重启这个容器,但是文件会丢失-这个容器会是一个全新的状态,第二,当很多容器在同一Pod中运行的时候,很多时候需要数据文件的共享。Kubernete Volume解决了这个问题。

PV/PVC/StorageClass

PersistentVolume(PV)是集群中已由管理员配置的一段网络存储。 集群中的资源就像一个节点是一个集群资源。 PV是诸如卷之类的卷插件,但是具有独立于使用PV的任何单个pod的生命周期。 该API对象捕获存储的实现细节,即NFS,iSCSI或云提供商特定的存储系统。

PersistentVolumeClaim(PVC)是用户存储的请求。 它类似于pod。 Pod消耗节点资源,PVC消耗光伏资源。 荚可以请求特定级别的资源(CPU和内存)。 权利要求可以请求特定的大小和访问模式(例如,可以一旦读/写或只读许多次安装)。

虽然PersistentVolumeClaims允许用户使用抽象存储资源,但是常见的是,用户需要具有不同属性(如性能)的PersistentVolumes,用于不同的问题。 群集管理员需要能够提供多种不同于PersistentVolumes的PersistentVolumes,而不仅仅是大小和访问模式,而不会使用户了解这些卷的实现细节。 对于这些需求,存在StorageClass资源。

StorageClass为管理员提供了一种描述他们提供的存储的“类”的方法。 不同的类可能映射到服务质量级别,或备份策略,或者由群集管理员确定的任意策略。 Kubernetes本身对于什么类别代表是不言而喻的。 这个概念有时在其他存储系统中称为“配置文件”

 

例子

https://kubernetes.io/docs/user-guide/persistent-volumes/walkthrough/

Deployment

Deployment为Pod和ReplicaSet提供了一个声明式定义(declarative)方法,用来替代以前的ReplicationController来方便的管理应用。典型的应用场景包括:

l  定义Deployment来创建Pod和ReplicaSet

l  滚动升级和回滚应用

l  扩容和缩容

l  暂停和继续Deployment

比如一个简单的nginx应用可以定义为:

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: nginx-deployment

spec:

  replicas: 3

  template:

    metadata:

      labels:

        app: nginx

    spec:

      containers:

      - name: nginx

        image: nginx:1.7.9

        ports:

        - containerPort: 80

 

扩容:

kubectl scale deployment nginx-deployment --replicas 10

如果集群支持 horizontal pod autoscaling 的话,还可以为Deployment设置自动扩展:

kubectl autoscale deployment nginx-deployment --min=10 --max=15 --cpu-percent=80

更新镜像也比较简单:

kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1

回滚:

kubectl rollout undo deployment/nginx-deployment

Secret

Secret解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者Pod Spec中。Secret可以以Volume或者环境变量的方式使用。

Secret有三种类型:

l  Service Account:用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中;

l  Opaque:base64编码格式的Secret,用来存储密码、密钥等;

l  kubernetes.io/dockerconfigjson:用来存储私有docker registry的认证信息。

StatefulSet

StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括:

l  稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现

l  稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现

l  有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现

l  有序收缩,有序删除(即从N-1到0)

 

从上面的应用场景可以发现,StatefulSet由以下几个部分组成:

l  用于定义网络标志(DNS domain)的Headless Service

l  用于创建PersistentVolumes的volumeClaimTemplates

l  定义具体应用的StatefulSet

DaemonSet

DaemonSet保证在每个Node上都运行一个容器副本,常用来部署一些集群的日志、监控或者其他系统管理应用。典型的应用包括:

l  日志收集,比如fluentd,logstash等

l  系统监控,比如Prometheus Node Exporter,collectd,New Relic agent,Ganglia gmond等

l  系统程序,比如kube-proxy, kube-dns, glusterd, ceph等

Service Account

Service account是为了方便Pod里面的进程调用Kubernetes API或其他外部服务而设计的。

CronJob

CronJob即定时任务,就类似于Linux系统的crontab,在指定的时间周期运行指定的任务。在Kubernetes 1.5,使用CronJob需要开启batch/v2alpha1 API,即–runtime-config=batch/v2alpha1。

Job

Job负责批量处理短暂的一次性任务 (short lived one-off tasks),即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束。

 

Kubernetes支持以下几种Job:

l  非并行Job:通常创建一个Pod直至其成功结束

l  固定结束次数的Job:设置.spec.completions,创建多个Pod,直到.spec.completions个Pod成功结束

l  带有工作队列的并行Job:设置.spec.Parallelism但不设置.spec.completions,当所有Pod结束并且至少一个成功时,Job就认为是成功

Security Context 和 PSP

Security Context的目的是限制不可信容器的行为,保护系统和其他容器不受其影响。

Kubernetes提供了三种配置Security Context的方法:

l  Container-level Security Context:仅应用到指定的容器

l  Pod-level Security Context:应用到Pod内所有容器以及Volume

l  Pod Security Policies(PSP):应用到集群内部所有Pod以及Volume

 

Pod Security Policies(PSP)是集群级的Pod安全策略,自动为集群内的Pod和Volume设置Security Context。

使用PSP需要API Server开启extensions/v1beta1/podsecuritypolicy,并且配置PodSecurityPolicyadmission控制器。

Resource Quotas

资源配额(Resource Quotas)是用来限制用户资源用量的一种机制。

它的工作原理为:

l  资源配额应用在Namespace上,并且每个Namespace最多只能有一个ResourceQuota对象

l  开启计算资源配额后,创建容器时必须配置计算资源请求或限制(也可以用LimitRange设置默认值)

l  用户超额后禁止创建新的资源

Network Policy

Network Policy提供了基于策略的网络控制,用于隔离应用并减少攻击面。它使用标签选择器模拟传统的分段网络,并通过策略控制它们之间的流量以及来自外部的流量。

在使用Network Policy之前,需要注意:

l  apiserver开启extensions/v1beta1/networkpolicies

l  网络插件要支持Network Policy,如Calico、Romana、Weave Net和trireme等

Ingress

通常情况下,service和pod的IP仅可在集群内部访问。集群外部的请求需要通过负载均衡转发到service在Node上暴露的NodePort上,然后再由kube-proxy将其转发给相关的Pod。

而Ingress就是为进入集群的请求提供路由规则的集合,如下图所示

 

Ingress可以给service提供集群外部访问的URL、负载均衡、SSL终止、HTTP路由等。为了配置这些Ingress规则,集群管理员需要部署一个Ingress controller,它监听Ingress和service的变化,并根据规则配置负载均衡并提供访问入口。

ThirdPartyResources

ThirdPartyResources是一种无需改变代码就可以扩展Kubernetes API的机制,可以用来管理自定义对象。

ThirdPartyResources将在v1.7弃用

ThirdPartyResources将在v1.7弃用,并在未来版本中删除。建议从v1.7开始,迁移到CustomResourceDefinition。

ConfigMap

ConfigMap用于保存配置数据的键值对,可以用来保存单个属性,也可以用来保存配置文件。ConfigMap跟secret很类似,但它可以更方便地处理不包含敏感信息的字符串。

PodPreset

PodPreset用来给指定标签的Pod注入额外的信息,如环境变量、存储卷等。这样,Pod模板就不需要为每个Pod都显式设置重复的信息。

 

四、安装

4.1 K8S 集群架构方案

 

 

Kubernetes 集群组件:

l  etcd 一个高可用的K/V键值对存储和服务发现系统

l  flannel 实现夸主机的容器网络的通信

l  kube-apiserver 提供kubernetes集群的API调用

l  kube-controller-manager 确保集群服务

l  kube-scheduler 调度容器,分配到Node

l  kubelet 在Node节点上按照配置文件中定义的容器规格启动容器

l  kube-proxy 提供网络代理服务

Kubernetes 集群部署方案

如下是集群部署策略,1个master + 2个node。存储集群etcd是单点集群(真实环境不推荐此做法,需要集群)。网络使用的是flannel虚拟二次网络。

Kubernetes具有完备的集群管理能力:

  1. 包括多层次的安全防护和准入机制
  2. 多租户应用支撑能力
  3. 透明的服务注册和服务发现机制
  4. 内建智能负载均衡器
  5. 强大的故障发现和自我修复能力
  6. 服务滚动升级和在线扩容能力
  7. 可扩展的资源自动调度机制
  8. 以及多粒度的资源管理能力

同时,kubernetes提供了完善的管理工具,这些工具涵盖了包括开发、部署测试、运维监控在内的各个环节。

在kubernetes中,service(服务)是分布式集群架构的核心,一个service对象拥有如下关键特征:

  • 拥有一个唯一指定的名字(比如mysql-service)。
  • 拥有一个虚拟IP(Cluster IP、service IP或VIP)和端口号。
  • 能够提供某种远程服务能力。
  • 被映射到了提供这种服务能力的一组容器应用上。

Kubernetes.io开发了一个交互式教程,通过WEB浏览器就能使用预先部署好的一个Kubernetes集群,快速体验kubernetes的功能和应用场景。

链接:https://kubernetes.io/docs/tutorials/kubernetes-basics/

K8s官方下载地址:https://github.com/kubernetes

 

 

环境准备

节点

ip 地址

操作系统

master

192.168.100.246

CentOS 7.3-x86_64

node1

192.168.100.247

CentOS 7.3-x86_64

node2

192.168.100.248

CentOS 7.3-x86_64

harbor

192.168.100.241

CentOS 7.3-x86_64

集群详情

l  OS:CentOS Linux release 7.3.1611 (Core) 3.10.0-514.el7.x86_64

l  Kubernetes 1.6.0+(最低的版本要求是1.6)

l  Docker:建议使用 Docker CE

l  Etcd 3.3.10

l  Flannel 0.7.1 vxlan或者host-gw 网络

l  TLS 认证通信 (所有组件,如 etcd、kubernetes master 和 node)

l  RBAC 授权

l  kubelet TLS BootStrapping

l  kubedns、dashboard、heapster(influxdb、grafana)、EFK(elasticsearch、fluentd、kibana) 集群插件

l  私有docker镜像仓库harbor(请自行部署,harbor提供离线安装包,直接使用docker-compose启动即可),不会的请参官文档:

安装文档:https://github.com/goharbor/harbor/blob/master/docs/installation_guide.md

配置 https访问:https://github.com/goharbor/harbor/blob/master/docs/configure_https.md

 

环境说明

在下面的步骤中,我们将在三台CentOS系统的物理机上部署具有三个节点的kubernetes1.12.3集群。

角色分配如下:

镜像仓库:192.168.100.241,为私有镜像仓库,请替换为公共仓库或你自己的镜像仓库地址。

Master:192.168.100.246

Node:192.168.100.247192.168.100.248

注意:192.168.100.246 这台主机 master 和 node 复用。所有生成证书、执行 kubectl 命令的操作都在这台节点上执行。一旦 node 加入到 kubernetes 集群之后就不需要再登陆node节点了。

4.2 提醒

本文档介绍使用二进制部署最新 kubernetes v1.12.3 集群的所有步骤,而不是使用 kubeadm 等自动化方式来部署集群。

在部署的过程中,将详细列出各组件的启动参数,它们的含义和可能遇到的问题。

部署完成后,你将理解系统各组件的交互原理,进而能快速解决实际问题。

所以本文档主要适合于那些有一定 kubernetes 基础,想通过一步步部署的方式来学习和了解系统配置、运行原理的人。

提醒

  1. 本文档适用于 CentOS 7.x、Ubuntu 18.x 及以上版本系统
  2. 由于启用了 TLS 双向认证、RBAC 授权等严格的安全机制,建议从头开始部署,而不要从中间开始,否则可能会认证、授权等失败!
  3. 部署过程中需要有很多证书的操作,请大家耐心操作,不明白的操作可以参考本书中的其他章节的解释。
  4. 该部署操作仅是搭建成了一个可用 kubernetes 集群,而很多地方还需要进行优化,heapster 插件、EFK 插件不一定会用于真实的生产环境中,但是通过部署这些插件,可以让大家了解到如何部署应用到集群上。

特别提醒

有同学没有按照本文档的k8s版本(1.12.3)安装,自己升级了版本;有些地方k8s升级后官方默认功能做了调整,比如kube-apiserver中的1.14版本不支持Initializers插件等......;由于k8s升级和功能迭代快,所以同学们如果必要请不要自行升级版本,自行升级将可能导致参照文档安装提示报错。

文档需要根据个人环境修改的地方都用中划线 +【粗体 or 标红】表示,大家看到这种信息的时候记得修改,务必不能使用文档相同的信息。。。。。。

 

4.3  组件版本和配置策略

组件版本

l  Kubernetes 1.12.3

l  Docker 18.09.0-ce

l  Etcd 3.3.10

l  Flanneld 0.10.0

l  插件:

m  Coredns

m  Dashboard

m  Heapster (influxdb、grafana)

m  Metrics-Server

m  EFK (elasticsearch、fluentd、kibana)

l  镜像仓库:

m  docker registry

m  harbor

 

主要配置策略

kube-apiserver:

l  使用节点本地 nginx 4 层透明代理实现高可用;

l  关闭非安全端口 8080 和匿名访问;

l  在安全端口 6443 接收 https 请求;

l  严格的认证和授权策略 (x509、token、RBAC);

l  开启 bootstrap token 认证,支持 kubelet TLS bootstrapping;

l  使用 https 访问 kubelet、etcd,加密通信;

 

kube-controller-manager:

l  3 节点高可用;

l  关闭非安全端口,在安全端口 10252 接收 https 请求;

l  使用 kubeconfig 访问 apiserver 的安全端口;

l  自动 approve kubelet 证书签名请求 (CSR),证书过期后自动轮转;

l  各 controller 使用自己的 ServiceAccount 访问 apiserver;

 

kube-scheduler:

l  3 节点高可用;

l  使用 kubeconfig 访问 apiserver 的安全端口;

 

kubelet:

l  使用 kubeadm 动态创建 bootstrap token,而不是在 apiserver 中静态配置;

l  使用 TLS bootstrap 机制自动生成 client 和 server 证书,过期后自动轮转;

l  在 KubeletConfiguration 类型的 JSON 文件配置主要参数;

l  关闭只读端口,在安全端口 10250 接收 https 请求,对请求进行认证和授权,拒绝匿名访问和非授权访问;

l  使用 kubeconfig 访问 apiserver 的安全端口;

 

kube-proxy:

l  使用 kubeconfig 访问 apiserver 的安全端口;

l  在 KubeProxyConfiguration 类型的 JSON 文件配置主要参数;

l  使用 ipvs 代理模式;

 

集群插件:

l  DNS:使用功能、性能更好的 coredns;

l  Dashboard:支持登录认证;

l  Metric:heapster、metrics-server,使用 https 访问 kubelet 安全端口;

l  Log:Elasticsearch、Fluend、Kibana;

l  Registry 镜像库:docker-registry、harbor;

 

harbor私有镜像仓库:参考:https://github.com/goharbor/harbor

 

4.4 系统初始化和全局变量

 1. 集群机器

master:192.168.100.246

node1:192.168.100.247

node2:192.168.100.248

请提前在 VirtualBox 中安装三台基于 centos 7 镜像的虚拟机,本文档中的 etcd 集群、master 节点、worker 节点均使用这三台机器。

 

注意:

  1. 需要在所有机器上执行本文档的初始化命令;
  2. 需要使用具有 root 权限的账号执行这些命令。

 

 2. 主机名

设置永久主机名称,然后重新登录:

hostnamectl set-hostname master # 将 master 替换为当前主机名

l  设置的主机名保存在 /etc/hostname 文件中;

 

如果 DNS 不支持解析主机名称,则需要修改每台机器的 /etc/hosts 文件,添加主机名和 IP 的对应关系:

cat >> /etc/hosts <<EOF

192.168.100.246 master

192.168.100.247 node1

192.168.100.248 node2

EOF

3. 添加 docker 账户

在每台机器上添加 docker 账户:

useradd -m docker

4. 无密码 ssh 登录其它节点

如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令,所以需要添加该节点到其它节点的 ssh 信任关系。

设置 master 可以无密码登录所有节点的 root 账户:

ssh-keygen -t rsa

ssh-copy-id root@master

ssh-copy-id root@node1

ssh-copy-id root@node2

5. 将可执行文件路径 /opt/k8s/bin 添加到 PATH 变量中

在每台机器上添加环境变量:

echo \'PATH=/opt/k8s/bin:$PATH\' >>/root/.bashrc

source /root/.bashrc

 

6. 安装依赖包

在每台机器上安装依赖包:

 

CentOS:

yum install -y epel-release

yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wget

/usr/sbin/modprobe ip_vs

Ubuntu:

apt-get install -y conntrack ipvsadm ntp ipset jq iptables curl sysstat libseccomp

/usr/sbin/modprobe ip_vs

l  ipvs 依赖 ipset;

l  ntp 保证各机器系统时间同步;

 

7. 关闭防火墙

在每台机器上关闭防火墙,清理防火墙规则,设置默认转发策略:

systemctl stop firewalld

systemctl disable firewalld

iptables -F && iptables -X && iptables -F -t nat && iptables -X -t nat

iptables -P FORWARD ACCEPT

 

8. 关闭 swap 分区

如果开启了 swap 分区,kubelet 会启动失败(可以通过将参数 --fail-swap-on 设置为 false 来忽略 swap on),故需要在每台机器上关闭 swap 分区。同时注释 /etc/fstab 中相应的条目,防止开机自动挂载 swap 分区:

swapoff -a

sed -i \'/ swap / s/^\(.*\)$/#\1/g\' /etc/fstab

 

9. 关闭 SELinux

关闭 SELinux,否则后续 K8S 挂载目录时可能报错 Permission denied:

setenforce 0

sed -i \'s/^SELINUX=.*/SELINUX=disabled/\' /etc/selinux/config

 

10. 关闭 dnsmasq(可选)

linux 系统开启了 dnsmasq 后(如 GUI 环境),将系统 DNS Server 设置为 127.0.0.1,这会导致 docker 容器无法解析域名,需要关闭它:

systemctl stop dnsmasq

systemctl disable dnsmasq

 

11. 加载内核模块

modprobe ip_vs_rr

modprobe br_netfilter

 

12. 优化内核参数

这一步如果执行有一些报错不用理会,继续往下执行。

cat > kubernetes.conf <<EOF

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 # 禁止使用 swap 空间,只有当系统 OOM 时才允许使用它

vm.overcommit_memory=1 # 不检查物理内存是否够用

vm.panic_on_oom=0 # 开启 OOM

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

cp kubernetes.conf  /etc/sysctl.d/kubernetes.conf

sysctl -p /etc/sysctl.d/kubernetes.conf

l  必须关闭 tcp_tw_recycle,否则和 NAT 冲突,会导致服务不通;

l  关闭 IPV6,防止触发 docker BUG;

 

13. 设置系统时区

# 调整系统 TimeZone

timedatectl set-timezone Asia/Shanghai

 

# 将当前的 UTC 时间写入硬件时钟

timedatectl set-local-rtc 0

 

# 重启依赖于系统时间的服务

systemctl restart rsyslog

systemctl restart crond

 

14.  更新系统时间(可选)

ntpdate cn.pool.ntp.org

 

15.  关闭无关的服务

systemctl stop postfix && systemctl disable postfix

 

16.  设置 rsyslogd 和 systemd journal

systemd 的 journald 是 Centos 7 缺省的日志记录工具,它记录了所有系统、内核、Service Unit 的日志。

相比 systemd,journald 记录的日志有如下优势:

  1. 可以记录到内存或文件系统;(默认记录到内存,对应的位置为 /run/log/jounal)
  2. 可以限制占用的磁盘空间、保证磁盘剩余空间;
  3. 可以限制日志文件大小、保存的时间;

journald 默认将日志转发给 rsyslog,这会导致日志写了多份,/var/log/messages 中包含了太多无关日志,不方便后续查看,同时也影响系统性能。

mkdir /var/log/journal # 持久化保存日志的目录

mkdir /etc/systemd/journald.conf.d

cat > /etc/systemd/journald.conf.d/99-prophet.conf <<EOF

[Journal]

# 持久化保存到磁盘

Storage=persistent

 

# 压缩历史日志

Compress=yes

 

SyncIntervalSec=5m

RateLimitInterval=30s

RateLimitBurst=1000

 

# 最大占用空间 10G

SystemMaxUse=10G

 

# 单日志文件最大 200M

SystemMaxFileSize=200M

 

# 日志保存时间 2 周

MaxRetentionSec=2week

 

# 不将日志转发到 syslog

ForwardToSyslog=no

EOF

systemctl restart systemd-journald

 

17.  创建相关目录

创建目录:

mkdir -p  /opt/k8s/{bin,work} /etc/{kubernetes,etcd}/cert

 

18.  升级内核(版本低执行该项)

rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm

# 安装完成后检查 /boot/grub2/grub.cfg 中对应内核 menuentry 中是否包含 initrd16 配置,如果没有,再安装一次!

yum --enablerepo=elrepo-kernel install -y kernel-lt

# 设置开机从新内核启动

grub2-set-default 0

 

安装内核源文件(可选,在升级完内核并重启机器后执行):

# yum erase kernel-headers

yum --enablerepo=elrepo-kernel install kernel-lt-devel-$(uname -r) kernel-lt-headers-$(uname -r)

 

19.  关闭 NUMA(可选)

cp /etc/default/grub{,.bak}

vim /etc/default/grub # 在 GRUB_CMDLINE_LINUX 一行添加 `numa=off` 参数,如下所示:

diff /etc/default/grub.bak /etc/default/grub

6c6

< GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=centos/root rhgb quiet"

---

> GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=centos/root rhgb quiet numa=off"

重新生成 grub2 配置文件:

cp /boot/grub2/grub.cfg{,.bak}

grub2-mkconfig -o /boot/grub2/grub.cfg

 

20.  检查系统内核和模块是否适合运行 docker (仅适用于 linux 系统)

curl https://raw.githubusercontent.com/docker/docker/master/contrib/check-config.sh > check-config.sh

bash ./check-config.sh

 

21.  分发集群环境变量定义脚本(扩容时不需要执行该步骤)

后续的部署步骤将使用 environment.sh文件中定义的全局环境变量,请根据自己的机器、网络情况修改:

#!/usr/bin/bash

# 生成 EncryptionConfig 所需的加密 key
export ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)

# 集群各机器 IP 数组
export NODE_IPS=(172.26.106.83 172.26.106.81 172.26.106.82)

# 集群各 IP 对应的 主机名数组
export NODE_NAMES=(node01 node02 node03 )

# etcd 集群服务地址列表
export ETCD_ENDPOINTS="https://172.26.106.83:2379,https://172.26.106.81:2379,https://172.26.106.82:2379"

# etcd 集群间通信的 IP 和端口
export ETCD_NODES="node01=https://172.26.106.83:2380,node02=https://172.26.106.81:2380,node03=https://172.26.106.82:2380"

# kube-apiserver 的反向代理(kube-nginx)地址端口
# export KUBE_APISERVER="https://127.0.0.1:8443"
export KUBE_APISERVER="https://172.26.106.83:8443"

# 节点间互联网络接口名称
export IFACE="eth0"

# etcd 数据目录
export ETCD_DATA_DIR="/data/k8s/etcd/data"

# etcd WAL 目录,建议是 SSD 磁盘分区,或者和 ETCD_DATA_DIR 不同的磁盘分区
export ETCD_WAL_DIR="/data/k8s/etcd/wal"

# k8s 各组件数据目录
export K8S_DIR="/data/k8s/k8s"

# docker 数据目录
export DOCKER_DIR="/data/k8s/docker"

## 以下参数一般不需要修改

# TLS Bootstrapping 使用的 Token,可以使用命令 head -c 16 /dev/urandom | od -An -t x | tr -d \' \' 生成
BOOTSTRAP_TOKEN="41f7e4ba8b7be874fcff18bf5cf41a7c"

# 最好使用 当前未用的网段 来定义服务网段和 Pod 网段

# 服务网段,部署前路由不可达,部署后集群内路由可达(kube-proxy 保证)
SERVICE_CIDR="10.254.0.0/16"

# Pod 网段,建议 /16 段地址,部署前路由不可达,部署后集群内路由可达(flanneld 保证)
CLUSTER_CIDR="172.30.0.0/16"

# 服务端口范围 (NodePort Range)
export NODE_PORT_RANGE="30000-32767"

# flanneld 网络配置前缀
export FLANNEL_ETCD_PREFIX="/kubernetes/network"

# kubernetes 服务 IP (一般是 SERVICE_CIDR 中第一个IP)
export CLUSTER_KUBERNETES_SVC_IP="10.254.0.1"

# 集群 DNS 服务 IP (从 SERVICE_CIDR 中预分配)
export CLUSTER_DNS_SVC_IP="10.254.0.2"

# 集群 DNS 域名(末尾不带点号)
export CLUSTER_DNS_DOMAIN="cluster.local"

# 将二进制目录 /opt/k8s/bin 加到 PATH 中
export PATH=/opt/k8s/bin:$PATH

其中IFACE要去查看网卡信息

[root@node03 ~]# ll /etc/sysconfig/network-scripts/
total 228
-rw-r--r--  1 root root    38 Nov 29  2018 ifcfg-eth0
-rw-r--r--  1 root root   254 Jan  3  2018 ifcfg-lo
lrwxrwxrwx  1 root root    24 Nov 29  2018 ifdown -> ../../../usr/sbin/ifdown
-rwxr-xr-x  1 root root   654 Jan  3  2018 ifdown-bnep
-rwxr-xr-x  1 root root  6569 Jan  3  2018 ifdown-eth
-rwxr-xr-x  1 root root   781 Jan  3  2018 ifdown-ippp
-rwxr-xr-x  1 root root  4540 Jan  3  2018 ifdown-ipv6
lrwxrwxrwx  1 root root    11 Nov 29  2018 ifdown-isdn -> ifdown-ippp
-rwxr-xr-x  1 root root  2130 Sep 27  2018 ifdown-post
-rwxr-xr-x  1 root root  1068 Jan  3  2018 ifdown-ppp
-rwxr-xr-x  1 root root   870 Jan  3  2018 ifdown-routes
-rwxr-xr-x  1 root root  1456 Jan  3  2018 ifdown-sit
-rwxr-xr-x. 1 root root  1621 Mar 18  2017 ifdown-Team
-rwxr-xr-x. 1 root root  1556 Mar 18  2017 ifdown-TeamPort
-rwxr-xr-x  1 root root  1462 Jan  3  2018 ifdown-tunnel
lrwxrwxrwx  1 root root    22 Nov 29  2018 ifup -> ../../../usr/sbin/ifup
-rwxr-xr-x  1 root root 12415 Jan  3  2018 ifup-aliases
-rwxr-xr-x  1 root root   910 Jan  3  2018 ifup-bnep
-rwxr-xr-x  1 root root 13475 Sep 27  2018 ifup-eth
-rwxr-xr-x  1 root root 12075 Jan  3  2018 ifup-ippp
-rwxr-xr-x  1 root root 11893 Jan  3  2018 ifup-ipv6
lrwxrwxrwx  1 root root     9 Nov 29  2018 ifup-isdn -> ifup-ippp
-rwxr-xr-x  1 root root   650 Jan  3  2018 ifup-plip
-rwxr-xr-x  1 root root  1064 Jan  3  2018 ifup-plusb
-rwxr-xr-x  1 root root  4997 Sep 27  2018 ifup-post
-rwxr-xr-x  1 root root  4154 Jan  3  2018 ifup-ppp
-rwxr-xr-x  1 root root  2001 Jan  3  2018 ifup-routes
-rwxr-xr-x  1 root root  3303 Jan  3  2018 ifup-sit
-rwxr-xr-x. 1 root root  1755 Mar 18  2017 ifup-Team
-rwxr-xr-x. 1 root root  1876 Mar 18  2017 ifup-TeamPort
-rwxr-xr-x  1 root root  2711 Jan  3  2018 ifup-tunnel
-rwxr-xr-x  1 root root  1836 Jan  3  2018 ifup-wireless
-rwxr-xr-x  1 root root  5419 Jan  3  2018 init.ipv6-global
-rw-r--r--  1 root root 19948 Jan  3  2018 network-functions
-rw-r--r--  1 root root 31027 Jan  3  2018 network-functions-ipv6
[root@node03 ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
BOOTPROTO=dhcp
ONBOOT=yes

然后,把全局变量定义脚本拷贝到所有节点的 /opt/k8s/bin 目录:

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    scp /opt/k8s/bin/environment.sh root@${node_ip}:/opt/k8s/bin/

    ssh root@${node_ip} "chmod +x /opt/k8s/bin/*"

  done

 

22.  参考

系统内核相关参数参考:https://docs.openshift.com/enterprise/3.2/admin_guide/overcommit.html

 

4.5  创建 CA 证书和秘钥

为确保安全,kubernetes 系统各组件需要使用 x509 证书对通信进行加密和认证。

CA (Certificate Authority) 是自签名的根证书,用来签名后续创建的其它证书。

本文档使用 CloudFlare 的 PKI 工具集 cfssl 创建所有证书。

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

1.  安装 cfssl 工具

sudo mkdir -p /opt/k8s/work/cert && cd /opt/k8s/work

wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64

mv cfssl_linux-amd64 /opt/k8s/bin/cfssl

 

wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64

mv cfssljson_linux-amd64 /opt/k8s/bin/cfssljson

 

wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64

mv cfssl-certinfo_linux-amd64 /opt/k8s/bin/cfssl-certinfo

 

chmod +x /opt/k8s/bin/*

export PATH=/opt/k8s/bin:$PATH

 

2.  创建根证书 (CA)

CA 证书是集群所有节点共享的,只需要创建一个 CA 证书,后续创建的所有证书都由它签名。

 

3.  创建配置文件

CA 配置文件用于配置根证书的使用场景 (profile) 和具体参数 (usage,过期时间、服务端认证、客户端认证、加密等),后续在签名其它证书时需要指定特定场景。

cd /opt/k8s/work/cert

cat > ca-config.json <<EOF

{

  "signing": {

    "default": {

      "expiry": "87600h"

    },

    "profiles": {

      "kubernetes": {

        "usages": [

            "signing",

            "key encipherment",

            "server auth",

            "client auth"

        ],

        "expiry": "87600h"

      }

    }

  }

}

EOF

l  signing:表示该证书可用于签名其它证书,生成的 ca.pem 证书中 CA=TRUE;

l  server auth:表示 client 可以用该该证书对 server 提供的证书进行验证;

l  client auth:表示 server 可以用该该证书对 client 提供的证书进行验证;

 

4. 创建证书签名请求文件

cd /opt/k8s/work/cert

cat > ca-csr.json <<EOF

{

  "CN": "kubernetes",

  "key": {

    "algo": "rsa",

    "size": 2048

  },

  "names": [

    {

      "C": "CN",

      "ST": "BeiJing",

      "L": "BeiJing",

      "O": "k8s",

      "OU": "study163"

    }

  ]

}

EOF

l  CN:Common Name,kube-apiserver 从证书中提取该字段作为请求的用户名 (User Name),浏览器使用该字段验证网站是否合法;

l  O:Organization,kube-apiserver 从证书中提取该字段作为请求用户所属的组 (Group);

l  kube-apiserver 将提取的 User、Group 作为 RBAC 授权的用户标识;

 

5. 生成 CA 证书和私钥

cd /opt/k8s/work/cert

cfssl gencert -initca ca-csr.json | cfssljson -bare ca

ls ca*

 

6.  分发证书文件

将生成的 CA 证书、秘钥文件、配置文件拷贝到所有节点的 /etc/kubernetes/cert 目录下:

cd /opt/k8s/work/cert

source /opt/k8s/bin/environment.sh # 导入 NODE_IPS 环境变量

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    ssh root@${node_ip} "mkdir -p /etc/kubernetes/cert"

    scp ca*.pem ca-config.json root@${node_ip}:/etc/kubernetes/cert

  done 

7.  参考

各种 CA 证书类型:

https://github.com/kubernetes-incubator/apiserver-builder/blob/master/docs/concepts/auth.md

 

4.6  部署 kubectl 命令行工具

kubectl 是 kubernetes 集群的命令行管理工具,本小节介绍安装和配置它的步骤。

kubectl 默认从 ~/.kube/config 文件读取 kube-apiserver 地址、证书、用户名等信息,如果没有配置,执行 kubectl 命令时可能会出错:

kubectl get pods

The connection to the server localhost:8080 was refused - did you specify the right host or port?

注意:

  1. 如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。
  2. 本文档只需要部署一次,生成的 kubeconfig 文件是通用的,可以拷贝到需要执行 kubeclt 命令的机器上。

1.  下载和分发 kubectl 命令行工具

下载和解压:

cd /opt/k8s/work

wget https://dl.k8s.io/v1.12.3/kubernetes-client-linux-amd64.tar.gz

tar -xzvf kubernetes-client-linux-amd64.tar.gz

 

分发到所有使用 kubectl 的节点:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    scp kubernetes/client/bin/kubectl root@${node_ip}:/opt/k8s/bin/

    ssh root@${node_ip} "chmod +x /opt/k8s/bin/*"

  done

 

2.  创建 admin 证书和私钥

kubectl 与 apiserver https 安全端口通信,apiserver 对提供的证书进行认证和授权。

kubectl 作为集群的管理工具,需要被授予最高权限。这里创建具有最高权限的 admin 证书。

创建证书签名请求:

cd /opt/k8s/work/cert

cat > admin-csr.json <<EOF

{

  "CN": "admin",

  "hosts": [],

  "key": {

    "algo": "rsa",

    "size": 2048

  },

  "names": [

    {

      "C": "CN",

      "ST": "BeiJing",

      "L": "BeiJing",

      "O": "system:masters",

      "OU": "study163"

    }

  ]

}

EOF

l  为 system:masters,kube-apiserver 收到该证书后将请求的 Group 设置为 system:masters;

l  预定义的 ClusterRoleBinding cluster-admin 将 Group system:masters 与 Role cluster-admin 绑定,该 Role 授予所有 API的权限;

l  该证书只会被 kubectl 当做 client 证书使用,所以 hosts 字段为空;

 

生成证书和私钥:

cd /opt/k8s/work/cert


cfssl gencert -ca=/opt/k8s/work/cert/ca.pem  -ca-key=/opt/k8s/work/cert/ca-key.pem  -config=/opt/k8s/work/cert/ca-config.json -profile=kubernetes admin-csr.json | cfssljson -bare admin
ls admin*

 

3.  创建 kubeconfig 文件

kubeconfig 为 kubectl 的配置文件,包含访问 apiserver 的所有信息,如 apiserver 地址、CA 证书和自身使用的证书;

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

 

# 设置集群参数

kubectl config set-cluster kubernetes --certificate-authority=/opt/k8s/work/cert/ca.pem --embed-certs=true --server=${KUBE_APISERVER} --kubeconfig=kubectl.kubeconfig

 

# 设置客户端认证参数

kubectl config set-credentials admin --client-certificate=/opt/k8s/work/cert/admin.pem --client-key=/opt/k8s/work/cert/admin-key.pem --embed-certs=true --kubeconfig=kubectl.kubeconfig

 

# 设置上下文参数

kubectl config set-context kubernetes --cluster=kubernetes --user=admin --kubeconfig=kubectl.kubeconfig

 

# 设置默认上下文

kubectl config use-context kubernetes --kubeconfig=kubectl.kubeconfig

l  --certificate-authority:验证 kube-apiserver 证书的根证书;

l  --client-certificate、--client-key:刚生成的 admin 证书和私钥,连接 kube-apiserver 时使用;

l  --embed-certs=true:将 ca.pem 和 admin.pem 证书内容嵌入到生成的 kubectl.kubeconfig 文件中(不加时,写入的是证书文件路径);

 

4.  分发 kubeconfig 文件

分发到所有使用 kubectl 命令的节点:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    ssh root@${node_ip} "mkdir -p ~/.kube"

    scp kubectl.kubeconfig root@${node_ip}:~/.kube/config

  done

l  保存到用户的 ~/.kube/config 文件;

 

4.7  部署 etcd 集群

etcd 是基于 Raft 的分布式 key-value 存储系统,由 CoreOS 开发,常用于服务发现、共享配置以及并发控制(如 leader 选举、分布式锁等)。kubernetes 使用 etcd 存储所有运行数据。

本文档介绍部署一个三节点高可用 etcd 集群的步骤:

l  下载和分发 etcd 二进制文件;

l  创建 etcd 集群各节点的 x509 证书,用于加密客户端(如 etcdctl) 与 etcd 集群、etcd 集群之间的数据流;

l  创建 etcd 的 systemd unit 文件,配置服务参数;

l  检查集群工作状态;

 

etcd 集群各节点的名称和 IP 如下:

l  master:192.168.100.246

l  node1:192.168.100.247

l  node2:192.168.100.248

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

1. 下载和分发 etcd 二进制文件

https://github.com/coreos/etcd/releases 页面下载最新版本的发布包:

cd /opt/k8s/work

wget https://github.com/coreos/etcd/releases/download/v3.3.10/etcd-v3.3.10-linux-amd64.tar.gz

tar -xvf etcd-v3.3.10-linux-amd64.tar.gz

 

分发二进制文件到集群所有节点:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    scp etcd-v3.3.10-linux-amd64/etcd* root@${node_ip}:/opt/k8s/bin

    ssh root@${node_ip} "chmod +x /opt/k8s/bin/*"

  done

 

2. 创建 etcd 证书和私钥

创建证书签名请求:

cd /opt/k8s/work/cert

cat > etcd-csr.json <<EOF

{

  "CN": "etcd",

  "hosts": [

    "127.0.0.1",

    "192.168.100.246",

    "192.168.100.247",

    "192.168.100.248"

  ],

  "key": {

    "algo": "rsa",

    "size": 2048

  },

  "names": [

    {

      "C": "CN",

      "ST": "BeiJing",

      "L": "BeiJing",

      "O": "k8s",

      "OU": "study163"

    }

  ]

}

EOF

l  hosts 字段指定授权使用该证书的 etcd 节点 IP 或域名列表,这里将 etcd 集群的三个节点 IP 都列在其中;

 

生成证书和私钥:

cd /opt/k8s/work/cert

cfssl gencert -ca=/opt/k8s/work/cert/ca.pem -ca-key=/opt/k8s/work/cert/ca-key.pem -config=/opt/k8s/work/cert/ca-config.json -profile=kubernetes etcd-csr.json | cfssljson -bare etcd

ls etcd*pem

分发生成的证书和私钥到各 etcd 节点:

cd /opt/k8s/work/cert

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    ssh root@${node_ip} "mkdir -p /etc/etcd/cert"

    scp etcd*.pem root@${node_ip}:/etc/etcd/cert/

  done

 

3. 创建 etcd 的 systemd unit 模板文件

是启动脚本文件

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
cat > etcd.service.template <<EOF
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target
Documentation=https://github.com/coreos

[Service]
Type=notify
WorkingDirectory=${ETCD_DATA_DIR}
ExecStart=/opt/k8s/bin/etcd \\
  --data-dir=${ETCD_DATA_DIR} \\
  --wal-dir=${ETCD_WAL_DIR} \\
  --name=##NODE_NAME## \\
  --cert-file=/etc/etcd/cert/etcd.pem \\
  --key-file=/etc/etcd/cert/etcd-key.pem \\
  --trusted-ca-file=/etc/kubernetes/cert/ca.pem \\
  --peer-cert-file=/etc/etcd/cert/etcd.pem \\
  --peer-key-file=/etc/etcd/cert/etcd-key.pem \\
  --peer-trusted-ca-file=/etc/kubernetes/cert/ca.pem \\
  --peer-client-cert-auth \\
  --client-cert-auth \\
  --listen-peer-urls=https://##NODE_IP##:2380 \\
  --initial-advertise-peer-urls=https://##NODE_IP##:2380 \\
  --listen-client-urls=https://##NODE_IP##:2379,http://127.0.0.1:2379 \\
  --advertise-client-urls=https://##NODE_IP##:2379 \\
  --initial-cluster-token=etcd-cluster-0 \\
  --initial-cluster=${ETCD_NODES} \\
  --initial-cluster-state=new \\
  --auto-compaction-mode=periodic \\
  --auto-compaction-retention=1 \\
  --max-request-bytes=33554432 \\
  --quota-backend-bytes=6442450944 \\
  --heartbeat-interval=250 \\
  --election-timeout=2000
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

l  WorkingDirectory、--data-dir:指定工作目录和数据目录为 ${ETCD_DATA_DIR},需在启动服务前创建这个目录;

l  --wal-dir:指定 wal 目录,为了提高性能,一般使用 SSD 或者和 --data-dir 不同的磁盘;

l  --name:指定节点名称,当 --initial-cluster-state 值为 new 时,--name 的参数值必须位于 --initial-cluster 列表中;

l  --cert-file、--key-file:etcd server 与 client 通信时使用的证书和私钥;

l  --trusted-ca-file:签名 client 证书的 CA 证书,用于验证 client 证书;

l  --peer-cert-file、--peer-key-file:etcd 与 peer 通信使用的证书和私钥;

l  --peer-trusted-ca-file:签名 peer 证书的 CA 证书,用于验证 peer 证书;

 

好多服务的.service文件在下面目录

[root@node01 work]# cd /usr/lib/systemd/system
[root@node01 system]# ll

 

4. 为各节点创建和分发 etcd systemd unit 文件

替换模板文件中的变量,为各节点创建 systemd unit 文件:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for (( i=0; i < 3; i++ ))

  do

    sed -e "s/##NODE_NAME##/${NODE_NAMES[i]}/" -e "s/##NODE_IP##/${NODE_IPS[i]}/" etcd.service.template > etcd-${NODE_IPS[i]}.service

  done

ls *.service

l  NODE_NAMES 和 NODE_IPS 为相同长度的 bash 数组,分别为节点名称和对应的 IP;

 

分发生成的 systemd unit 文件:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    scp etcd-${node_ip}.service root@${node_ip}:/usr/lib/systemd/system/etcd.service

  done

l  文件重命名为 etcd.service;

 

5. 启动 etcd 服务

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    ssh root@${node_ip} "mkdir -p ${ETCD_DATA_DIR} ${ETCD_WAL_DIR}"

    ssh root@${node_ip} "systemctl daemon-reload && systemctl enable etcd && systemctl restart etcd " &

  done

l  必须创建 etcd 数据目录和工作目录;

l  etcd 进程首次启动时会等待其它节点的 etcd 加入集群,命令 systemctl start etcd 会卡住一段时间,为正常现象。

 

6.  检查启动结果

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    ssh root@${node_ip} "systemctl status etcd|grep Active"

  done

确保状态为 active (running),否则查看日志,确认原因:

journalctl -u etcd

 

7. 验证服务状态

部署完 etcd 集群后,在任一 etc 节点上执行如下命令:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ETCDCTL_API=3 /opt/k8s/bin/etcdctl --endpoints=https://${node_ip}:2379 --cacert=/opt/k8s/work/cert/ca.pem --cert=/etc/etcd/cert/etcd.pem --key=/etc/etcd/cert/etcd-key.pem endpoint health
  done

预期输出:

>>> 192.168.100.246

https://192.168.100.246:2379 is healthy: successfully committed proposal: took = 3.5037ms

>>> 192.168.100.247

https://192.168.100.247:2379 is healthy: successfully committed proposal: took = 17.918242ms

>>> 192.168.100.248

https://192.168.100.248:2379 is healthy: successfully committed proposal: took = 10.445815ms

 

输出均为 healthy 时表示集群服务正常。

 

8. 查看当前的 leader

 

source /opt/k8s/bin/environment.sh

ETCDCTL_API=3 /opt/k8s/bin/etcdctl -w table --cacert=/opt/k8s/work/cert/ca.pem \
  --cert=/etc/etcd/cert/etcd.pem \
  --key=/etc/etcd/cert/etcd-key.pem \
  --endpoints=${ETCD_ENDPOINTS} endpoint status

输出:

 

 

可见,当前的 leader 为 172.26.106.83

 

4.8  部署 flannel 网络

kubernetes 要求集群内各节点(包括 master 节点)能通过 Pod 网段互联互通。flannel 使用 vxlan 技术为各节点创建一个可以互通的 Pod 网络,使用的端口为 UDP 8472,需要开放该端口(如公有云 AWS 等)。

flanneld 第一次启动时,从 etcd 获取配置的 Pod 网段信息,为本节点分配一个未使用的地址段,然后创建 flannedl.1 网络接口(也可能是其它名称,如 flannel1 等)。

flannel 将分配给自己的 Pod 网段信息写入 /run/flannel/docker 文件,docker 后续使用这个文件中的环境变量设置 docker0 网桥,从而从这个地址段为本节点的所有 Pod 容器分配 IP。

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

1. 下载和分发 flanneld 二进制文件

到 https://github.com/coreos/flannel/releases 页面下载最新版本的发布包:

cd /opt/k8s/work

mkdir flannel

wget https://github.com/coreos/flannel/releases/download/v0.10.0/flannel-v0.10.0-linux-amd64.tar.gz

tar -xzvf flannel-v0.10.0-linux-amd64.tar.gz -C flannel 

分发 flanneld 二进制文件到集群所有节点:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    scp flannel/{flanneld,mk-docker-opts.sh} root@${node_ip}:/opt/k8s/bin/

    ssh root@${node_ip} "chmod +x /opt/k8s/bin/*"

  done

2. 创建 flannel 证书和私钥

flannel 从 etcd 集群存取网段分配信息,而 etcd 集群启用了双向 x509 证书认证,所以需要为 flanneld 生成证书和私钥。

 

创建证书签名请求:

cd /opt/k8s/work/cert

cat > flanneld-csr.json <<EOF

{

  "CN": "flanneld",

  "hosts": [],

  "key": {

    "algo": "rsa",

    "size": 2048

  },

  "names": [

    {

      "C": "CN",

      "ST": "BeiJing",

      "L": "BeiJing",

      "O": "k8s",

      "OU": "study163"

    }

  ]

}

EOF

l  该证书只会被 kubectl 当做 client 证书使用,所以 hosts 字段为空;

 

生成证书和私钥:

 

cfssl gencert -ca=/opt/k8s/work/cert/ca.pem \
  -ca-key=/opt/k8s/work/cert/ca-key.pem \
  -config=/opt/k8s/work/cert/ca-config.json \
  -profile=kubernetes flanneld-csr.json | cfssljson -bare flanneld

ls flannel*pem

将生成的证书和私钥分发到所有节点(master 和 worker):

cd /opt/k8s/work/cert

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    ssh root@${node_ip} "mkdir -p /etc/flanneld/cert"

    scp flanneld*.pem root@${node_ip}:/etc/flanneld/cert

  done

 

3.  向 etcd 写入集群 Pod 网段信息

注意:本步骤只需执行一次。

 

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

etcdctl \
  --endpoints=${ETCD_ENDPOINTS} \
  --ca-file=/opt/k8s/work/cert/ca.pem \
  --cert-file=/opt/k8s/work/cert/flanneld.pem \
  --key-file=/opt/k8s/work/cert/flanneld-key.pem \
  set ${FLANNEL_ETCD_PREFIX}/config \'{"Network":"\'${CLUSTER_CIDR}\'", "SubnetLen": 21, "Backend": {"Type": "vxlan"}}\'

 

l  flanneld 当前版本 (v0.10.0) 不支持 etcd v3,故使用 etcd v2 API 写入配置 key 和网段数据;

l  写入的 Pod 网段 ${CLUSTER_CIDR} 地址段如 /16 必须小于 SubnetLen,必须与 kube-controller-manager 的 --cluster-cidr 参数值一致;

 

4. 创建 flanneld 的 systemd unit 文件

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
cat > flanneld.service << EOF
[Unit]
Description=Flanneld overlay address etcd agent
After=network.target
After=network-online.target
Wants=network-online.target
After=etcd.service
Before=docker.service

[Service]
Type=notify
ExecStart=/opt/k8s/bin/flanneld \\
  -etcd-cafile=/etc/kubernetes/cert/ca.pem \\
  -etcd-certfile=/etc/flanneld/cert/flanneld.pem \\
  -etcd-keyfile=/etc/flanneld/cert/flanneld-key.pem \\
  -etcd-endpoints=${ETCD_ENDPOINTS} \\
  -etcd-prefix=${FLANNEL_ETCD_PREFIX} \\
  -iface=${IFACE} \\
  -ip-masq
ExecStartPost=/opt/k8s/bin/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker
Restart=always
RestartSec=5
StartLimitInterval=0

[Install]
WantedBy=multi-user.target
RequiredBy=docker.service
EOF

l  mk-docker-opts.sh 脚本将分配给 flanneld 的 Pod 子网网段信息写入 /run/flannel/docker 文件,后续 docker 启动时使用这个文件中的环境变量配置 docker0 网桥;

l  flanneld 使用系统缺省路由所在的接口与其它节点通信,对于有多个网络接口(如内网和公网)的节点,可以用 -iface 参数指定通信接口;

l  flanneld 运行时需要 root 权限;

l  -ip-masq: flanneld 为访问 Pod 网络外的流量设置 SNAT 规则,同时将传递给 Docker 的变量 --ip-masq(/run/flannel/docker 文件中)设置为 false,这样 Docker 将不再创建 SNAT 规则; Docker 的 --ip-masq 为 true 时,创建的 SNAT 规则比较“暴力”:将所有本节点 Pod 发起的、访问非 docker0 接口的请求做 SNAT,这样访问其他节点 Pod 的请求来源 IP 会被设置为 flannel.1 接口的 IP,导致目的 Pod 看不到真实的来源 Pod IP。 flanneld 创建的 SNAT 规则比较温和,只对访问非 Pod 网段的请求做 SNAT。

 

5. 分发 flanneld systemd unit 文件到所有节点

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp flanneld.service root@${node_ip}:/usr/lib/systemd/system/
  done

 

6. 启动 flanneld 服务

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "systemctl daemon-reload && systemctl enable flanneld && systemctl restart flanneld"
  done

 

7. 检查启动结果

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "systemctl status flanneld|grep Active"
  done

 

确保状态为 active (running),否则查看日志,确认原因:

journalctl -u flanneld

 

8. 检查分配给各 flanneld 的 Pod 网段信息

查看集群 Pod 网段(/16):

source /opt/k8s/bin/environment.sh

etcdctl \
  --endpoints=${ETCD_ENDPOINTS} \
  --ca-file=/etc/kubernetes/cert/ca.pem \
  --cert-file=/etc/flanneld/cert/flanneld.pem \
  --key-file=/etc/flanneld/cert/flanneld-key.pem \
  get ${FLANNEL_ETCD_PREFIX}/config

输出:

{"Network":"172.30.0.0/16", "SubnetLen": 21, "Backend": {"Type": "vxlan"}}

 

查看已分配的 Pod 子网段列表(/24):

source /opt/k8s/bin/environment.sh

etcdctl \
  --endpoints=${ETCD_ENDPOINTS} \
  --ca-file=/etc/kubernetes/cert/ca.pem \
  --cert-file=/etc/flanneld/cert/flanneld.pem \
  --key-file=/etc/flanneld/cert/flanneld-key.pem \
  ls ${FLANNEL_ETCD_PREFIX}/subnets

输出(结果是部署情况而定,网段可能与下面不一样):

/kubernetes/network/subnets/172.30.240.0-21

/kubernetes/network/subnets/172.30.120.0-21

/kubernetes/network/subnets/172.30.192.0-21

 

查看某一 Pod 网段对应的节点 IP 和 flannel 接口地址:

source /opt/k8s/bin/environment.sh

etcdctl \
  --endpoints=${ETCD_ENDPOINTS} \
  --ca-file=/etc/kubernetes/cert/ca.pem \
  --cert-file=/etc/flanneld/cert/flanneld.pem \
  --key-file=/etc/flanneld/cert/flanneld-key.pem \
  get ${FLANNEL_ETCD_PREFIX}/subnets/172.30.240.0-21

输出(结果是部署情况而定,网段可能与下面不一样):

{"PublicIP":"192.168.100.246","BackendType":"vxlan","BackendData":{"VtepMAC":"fa:32:14:34:86:74"}}

l  172.30.240.0/21 被分配给节点 node2(192.168.100.252);

l  VtepMAC 为 node2 节点的 flannel.1 网卡 MAC 地址;

 

9. 检查节点 flannel 网络信息

ip addr show

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000

    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

    inet 127.0.0.1/8 scope host lo

       valid_lft forever preferred_lft forever

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000

    link/ether 00:22:0d:33:89:75 brd ff:ff:ff:ff:ff:ff

    inet 172.27.137.240/20 brd 172.27.143.255 scope global dynamic eth0

       valid_lft 100647283sec preferred_lft 100647283sec

3: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default

    link/ether ce:9c:a9:08:50:03 brd ff:ff:ff:ff:ff:ff

    inet 172.30.80.0/32 scope global flannel.1

       valid_lft forever preferred_lft forever

l  flannel.1 网卡的地址为分配的 Pod 子网段的第一个 IP(.0),且是 /32 的地址;

 

ip route show |grep flannel.1

172.30.32.0/21 via 172.30.32.0 dev flannel.1 onlink

172.30.184.0/21 via 172.30.184.0 dev flannel.1 onlink

l  到其它节点 Pod 网段请求都被转发到 flannel.1 网卡;

l  flanneld 根据 etcd 中子网段的信息,如 ${FLANNEL_ETCD_PREFIX}/subnets/172.30.80.0-21 ,来决定进请求发送给哪个节点的互联 IP;

 

10.  验证各节点能通过 Pod 网段互通

在各节点上部署 flannel 后,检查是否创建了 flannel 接口(名称可能为 flannel0、flannel.0、flannel.1 等):

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh ${node_ip} "/usr/sbin/ip addr show flannel.1|grep -w inet"
  done

输出:

>>> 192.168.100.246

    inet 172.30.240.0/32 scope global flannel.1

>>> 192.168.100.247

    inet 172.30.120.0/32 scope global flannel.1

>>> 192.168.100.248

    inet 172.30.192.0/32 scope global flannel.1

 

在各节点上 ping 所有 flannel 接口 IP,确保能通:

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh ${node_ip} "ping -c 1 172.30.240.0"
    ssh ${node_ip} "ping -c 1 172.30.120.0"
    ssh ${node_ip} "ping -c 1 172.30.192.0"
  done

4.9  kube-apiserver 高可用之 nginx 代理

kuebectl 的配置文件中静态指定了某个 kube-apiserver IP,如果该 apiserver 实例挂掉,可能引起服务异常。

本章节讲解使用 nginx 4 层透明代理功能实现 K8S 节点( master 节点和 worker 节点)高可用访问 kube-apiserver 的方案。

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

1.  基于 nginx 代理的 kube-apiserver 高可用方案

l  控制节点的 kube-controller-manager、kube-scheduler 是多实例部署,所以只要有一个实例正常,就可以保证高可用;

l  集群内的 Pod 使用域名 kubernetes 访问 kube-apiserver, kube-dns 会自动解析出多个 kube-apiserver 节点的 IP,所以也是高可用的;

l  kubelet、kube-proxy、controller-manager、scheduler 通过本地的 kube-nginx(监听 127.0.0.1)访问 kube-apiserver,从而实现 kube-apiserver 的高可用。

l  kube-nginx 会对所有 kube-apiserver 实例做健康检查和负载均衡;

 

2.  下载和编译 nginx

下载源码:

cd /opt/k8s/work

wget http://nginx.org/download/nginx-1.15.3.tar.gz

tar -xzvf nginx-1.15.3.tar.gz

 

安装nginx必须的依赖包

yum -y install gcc openssl-devel pcre-devel zlib-devel

 

配置编译参数:

cd /opt/k8s/work/nginx-1.15.3

mkdir /opt/k8s/kube-nginx

./configure --prefix=/opt/k8s/kube-nginx --with-stream --without-http --without-http_uwsgi_module --without-http_scgi_module --without-http_fastcgi_module

l  --with-stream:开启 4 层透明转发(TCP Proxy)功能;

l  --without-xxx:关闭所有其他功能,这样生成的动态链接二进制程序依赖最小;

输出:

Configuration summary

  + PCRE library is not used

  + OpenSSL library is not used

  + zlib library is not used

  nginx path prefix: "/opt/k8s/kube-nginx"

  nginx binary file: "/opt/k8s/kube-nginx/sbin/nginx"

  nginx modules path: "/opt/k8s/kube-nginx/modules"

  nginx configuration prefix: "/opt/k8s/kube-nginx/conf"

  nginx configuration file: "/opt/k8s/kube-nginx/conf/nginx.conf"

  nginx pid file: "/opt/k8s/kube-nginx/logs/nginx.pid"

  nginx error log file: "/opt/k8s/kube-nginx/logs/error.log"

  nginx http access log file: "/opt/k8s/kube-nginx/logs/access.log"

  nginx http client request body temporary files: "client_body_temp"

  nginx http proxy temporary files: "proxy_temp"

 

编译和安装:

cd /opt/k8s/work/nginx-1.15.3

make && make install

 

3. 验证编译的 nginx

cd /opt/k8s/kube-nginx

./sbin/nginx -v

输出:

nginx version: nginx/1.15.3

 

查看 nginx 动态链接的库:

ldd ./sbin/nginx
linux-vdso.so.1 =>  (0x00007ffe11794000)

libdl.so.2 => /lib64/libdl.so.2 (0x00007f29eb5d5000)

libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f29eb3b9000)

libc.so.6 => /lib64/libc.so.6 (0x00007f29eafeb000)

/lib64/ld-linux-x86-64.so.2 (0x00007f29eb7e0000)

由于只开启了 4 层透明转发功能,所以除了依赖 libc 等操作系统核心 lib 库外,没有对其它 lib 的依赖(如 libz、libssl 等),这样可以方便部署到各版本操作系统中;

 

4.  安装和部署 nginx

配置 nginx,开启 4 层透明转发功能:

cat > /opt/k8s/kube-nginx/conf/nginx.conf <<EOF

worker_processes 1;
 
events {
    worker_connections  1024;
}
 
stream {

    upstream backend {
        hash $remote_addr consistent;
        server 192.168.100.246:6443   max_fails=3 fail_timeout=30s;
        server 192.168.100.247:6443   max_fails=3 fail_timeout=30s;
        server 192.168.100.248:6443   max_fails=3 fail_timeout=30s;
    }

    server {
        listen 8443;
        proxy_connect_timeout 1s;
        proxy_pass backend;
    }
}

EOF

l  需要根据集群 kube-apiserver 的实际情况,替换 backend 中 server 列表;

 

5. 配置 systemd unit 文件,启动服务

配置 kube-nginx systemd unit 文件:

cd /opt/k8s/work

cat > kube-nginx.service <<EOF
[Unit]
Description=kube-apiserver nginx proxy
After=network.target
After=network-online.target
Wants=network-online.target 

[Service]
Type=forking
ExecStartPre=/opt/k8s/kube-nginx/sbin/nginx -c /opt/k8s/kube-nginx/conf/nginx.conf -p /opt/k8s/kube-nginx -t
ExecStart=/opt/k8s/kube-nginx/sbin/nginx -c /opt/k8s/kube-nginx/conf/nginx.conf -p /opt/k8s/kube-nginx
ExecReload=/opt/k8s/kube-nginx/sbin/nginx -c /opt/k8s/kube-nginx/conf/nginx.conf -p /opt/k8s/kube-nginx -s reload
PrivateTmp=true
Restart=always
RestartSec=5
StartLimitInterval=0
LimitNOFILE=65536

 

[Install]
WantedBy=multi-user.target

EOF

启动 kube-nginx 服务:

cp kube-nginx.service /usr/lib/systemd/system/

systemctl daemon-reload && systemctl enable kube-nginx && systemctl restart kube-nginx

 

6.  检查 kube-nginx 服务运行状态

systemctl status kube-nginx |grep \'Active:\'

确保状态为 active (running),否则查看日志,确认原因:

journalctl -u kube-nginx

 

4.10 部署 master 节点

kubernetes master 节点运行如下组件:

l  kube-apiserver

l  kube-scheduler

l  kube-controller-manager

l  kube-nginx

  1. kube-scheduler 和 kube-controller-manager 会自动选举产生一个 leader 实例,其它实例处于阻塞模式,当 leader 挂了后,重新选举产生新的 leader,从而保证服务可用性;
  2. kube-apiserver 是无状态的,需要通过 kube-nginx 进行代理访问,从而保证服务可用性;

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

1. 安装和配置 kube-nginx

参考 apiserver高可用之nginx代理

 

2. 下载最新版本二进制文件

CHANGELOG页面 下载 server tarball 文件。

cd /opt/k8s/work

wget https://dl.k8s.io/v1.12.3/kubernetes-server-linux-amd64.tar.gz

tar -xzvf kubernetes-server-linux-amd64.tar.gz

cd kubernetes

tar -xzvf  kubernetes-src.tar.gz

 

将二进制文件拷贝到所有 master 节点:

cd /opt/k8s/work/kubernetes

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do

    echo ">>> ${node_ip}"

    scp server/bin/* root@${node_ip}:/opt/k8s/bin/

    ssh root@${node_ip} "chmod +x /opt/k8s/bin/*"

  done

 

3. apiserver 集群

本章节讲解部署一个三实例 kube-apiserver 集群的步骤,它们通过 kube-nginx 进行代理访问,从而保证服务可用性。

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

3.1 准备工作

注意:下载最新版本的二进制文件、安装和配置 flanneld 参考:部署master节点

3.2. 创建 kubernetes 证书和私钥

创建证书签名请求:

cd /opt/k8s/work/cert

source /opt/k8s/bin/environment.sh

cat > kubernetes-csr.json <<EOF

{
  "CN": "kubernetes",

  "hosts": [
    "127.0.0.1",
    "172.26.106.83",
    "172.26.106.81",
    "172.26.106.82",
    "${CLUSTER_KUBERNETES_SVC_IP}",
    "kubernetes",
    "kubernetes.default",
    "kubernetes.default.svc",
    "kubernetes.default.svc.cluster",
    "kubernetes.default.svc.cluster.local"
  ],

  "key": {
    "algo": "rsa",
    "size": 2048

  },

  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "k8s",
      "OU": "study163"
    }
  ]
}

EOF

l  hosts 字段指定授权使用该证书的 IP 或域名列表,这里列出了 VIP 、apiserver 节点 IP、kubernetes 服务 IP 和域名;

l  域名最后字符不能是 .(如不能为 kubernetes.default.svc.cluster.local.),否则解析时失败,提示: x509: cannot parse dnsName "kubernetes.default.svc.cluster.local.";

l  如果使用非 cluster.local 域名,如 opsnull.com,则需要修改域名列表中的最后两个域名为:kubernetes.default.svc.opsnull、kubernetes.default.svc.opsnull.com

l  kubernetes 服务 IP 是 apiserver 自动创建的,一般是 --service-cluster-ip-range 参数指定的网段的第一个IP,后续可以通过如下命令获取:

kubectl get svc kubernetes

NAME         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE

kubernetes   10.254.0.1   <none>        443/TCP   1d

 

生成证书和私钥:

cfssl gencert -ca=/opt/k8s/work/cert/ca.pem \
  -ca-key=/opt/k8s/work/cert/ca-key.pem \
  -config=/opt/k8s/work/cert/ca-config.json \
  -profile=kubernetes kubernetes-csr.json | cfssljson -bare kubernetes
ls kubernetes*pem

将生成的证书和私钥文件拷贝到 master 节点:

cd /opt/k8s/work/cert

source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "mkdir -p /etc/kubernetes/cert"
    scp kubernetes*.pem root@${node_ip}:/etc/kubernetes/cert/
  done
3.3 创建加密配置文件
cat > encryption-config.yaml <<EOF
kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: ${ENCRYPTION_KEY}
      - identity: {}
EOF

将加密配置文件拷贝到 master 节点的 /etc/kubernetes 目录下:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp encryption-config.yaml root@${node_ip}:/etc/kubernetes/
  done

 

3.4 创建 kube-apiserver systemd unit 模板文件

特别提醒:有同学没有按照老师的k8s版本安装,自己升级了版本,有些地方k8s升级后官方默认功能做了调整,比如kube-apiserver中的1.14版本不支持Initializers插件。

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
cat > kube-apiserver.service.template <<EOF
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
WorkingDirectory=${K8S_DIR}/kube-apiserver
ExecStart=/opt/k8s/bin/kube-apiserver \\
  --enable-admission-plugins=Initializers,NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
  --anonymous-auth=false \\
  --experimental-encryption-provider-config=/etc/kubernetes/encryption-config.yaml \\
  --advertise-address=##NODE_IP## \\
  --bind-address=##NODE_IP## \\
  --insecure-port=0 \\
  --authorization-mode=Node,RBAC \\
  --runtime-config=api/all \\
  --enable-bootstrap-token-auth \\
  --service-cluster-ip-range=${SERVICE_CIDR} \\
  --service-node-port-range=${NODE_PORT_RANGE} \\
  --tls-cert-file=/etc/kubernetes/cert/kubernetes.pem \\
  --tls-private-key-file=/etc/kubernetes/cert/kubernetes-key.pem \\
  --client-ca-file=/etc/kubernetes/cert/ca.pem \\
  --kubelet-certificate-authority=/etc/kubernetes/cert/ca.pem \\
  --kubelet-client-certificate=/etc/kubernetes/cert/kubernetes.pem \\
  --kubelet-client-key=/etc/kubernetes/cert/kubernetes-key.pem \\
  --kubelet-https=true \\
  --service-account-key-file=/etc/kubernetes/cert/ca.pem \\
  --etcd-cafile=/etc/kubernetes/cert/ca.pem \\
  --etcd-certfile=/etc/kubernetes/cert/kubernetes.pem \\
  --etcd-keyfile=/etc/kubernetes/cert/kubernetes-key.pem \\
  --etcd-servers=${ETCD_ENDPOINTS} \\
  --enable-swagger-ui=true \\
  --allow-privileged=true \\
  --max-mutating-requests-inflight=2000 \\
  --max-requests-inflight=4000 \\
  --apiserver-count=3 \\
  --audit-log-maxage=30 \\
  --audit-log-maxbackup=3 \\
  --audit-log-maxsize=100 \\
  --audit-log-path=${K8S_DIR}/kube-apiserver/audit.log \\
  --event-ttl=168h \\
  --logtostderr=true \\
  --v=2
Restart=on-failure
RestartSec=5
Type=notify
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

l  --experimental-encryption-provider-config:启用加密特性;

l  --authorization-mode=Node,RBAC: 开启 Node 和 RBAC 授权模式,拒绝未授权的请求;

l  --enable-admission-plugins:启用 ServiceAccount 和 NodeRestriction;

l  --service-account-key-file:签名 ServiceAccount Token 的公钥文件,kube-controller-manager 的 --service-account-private-key-file 指定私钥文件,两者配对使用;

l  --tls-*-file:指定 apiserver 使用的证书、私钥和 CA 文件。--client-ca-file 用于验证 client (kue-controller-manager、kube-scheduler、kubelet、kube-proxy 等)请求所带的证书;

l  --kubelet-client-certificate、--kubelet-client-key:如果指定,则使用 https 访问 kubelet APIs;需要为证书对应的用户(上面 kubernetes*.pem 证书的用户为 kubernetes) 用户定义 RBAC 规则,否则访问 kubelet API 时提示未授权;

l  --bind-address: 不能为 127.0.0.1,否则外界不能访问它的安全端口 6443;

l  --insecure-port=0:关闭监听非安全端口(8080);

l  --service-cluster-ip-range: 指定 Service Cluster IP 地址段;

l  --service-node-port-range: 指定 NodePort 的端口范围;

l  --runtime-config=api/all=true: 启用所有版本的 APIs,如 autoscaling/v2alpha1;

l  --enable-bootstrap-token-auth:启用 kubelet bootstrap 的 token 认证;

l  --apiserver-count=3:指定 apiserver 的实例数量;

 

3.5 为各节点创建和分发 kube-apiserver systemd unit 文件

替换模板文件中的变量,为各节点创建 systemd unit 文件:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for (( i=0; i < 3; i++ ))
  do
    sed -e "s/##NODE_NAME##/${NODE_NAMES[i]}/" -e "s/##NODE_IP##/${NODE_IPS[i]}/" kube-apiserver.service.template > kube-apiserver-${NODE_IPS[i]}.service
  done
ls kube-apiserver*.service

l  NODE_NAMES 和 NODE_IPS 为相同长度的 bash 数组,分别为节点名称和对应的 IP;

 

分发生成的 systemd unit 文件:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp kube-apiserver-${node_ip}.service root@${node_ip}:/usr/lib/systemd/system/kube-apiserver.service
  done

l  文件重命名为 kube-apiserver.service;

 

3.6 启动 kube-apiserver 服务
source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}

  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "mkdir -p ${K8S_DIR}/kube-apiserver"
    ssh root@${node_ip} "systemctl daemon-reload && systemctl enable kube-apiserver && systemctl restart kube-apiserver"
  done

l  必须创建工作目录;

 

3.7 检查 kube-apiserver 运行状态
source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "systemctl status kube-apiserver |grep \'Active:\'"
  done

确保状态为 active (running),否则到 master 节点查看日志,确认原因:

journalctl -u kube-apiserver

 

3.8 打印 kube-apiserver 写入 etcd 的数据
source /opt/k8s/bin/environment.sh
ETCDCTL_API=3 etcdctl \
    --endpoints=${ETCD_ENDPOINTS} \
    --cacert=/opt/k8s/work/cert/ca.pem \
    --cert=/opt/k8s/work/cert/etcd.pem \
    --key=/opt/k8s/work/cert/etcd-key.pem \
    get /registry/ --prefix --keys-only

 

3.9 检查集群信息
kubectl cluster-info

Kubernetes master is running at https://127.0.0.1:8443

 

To further debug and diagnose cluster problems, use \'kubectl cluster-info dump\'.

 

kubectl get all --all-namespaces

NAMESPACE   NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE

default     service/kubernetes   ClusterIP   10.254.0.1   <none>        443/TCP   4m27s

 

kubectl get componentstatuses

NAME                 STATUS      MESSAGE                                                                                     ERROR

controller-manager   Unhealthy   Get http://127.0.0.1:10252/healthz: dial tcp 127.0.0.1:10252: connect: connection refused

scheduler            Unhealthy   Get http://127.0.0.1:10251/healthz: dial tcp 127.0.0.1:10251: connect: connection refused

etcd-2               Healthy     {"health":"true"}

etcd-0               Healthy     {"health":"true"}

etcd-1               Healthy     {"health":"true"}

 

注意:

  1. 如果执行 kubectl 命令式时输出如下错误信息,则说明使用的 ~/.kube/config 文件不对,请切换到正确的账户后再执行该命令:

The connection to the server localhost:8080 was refused - did you specify the right host or port?

2.执行 kubectl get componentstatuses 命令时,apiserver 默认向 127.0.0.1 发送请求。当 controller-manager、scheduler 以集群模式运行时,有可能和 kube-apiserver 不在一台机器上,这时 controller-manager 或 scheduler 的状态为 Unhealthy,但实际上它们工作正常

 

3.10  检查 kube-apiserver 监听的端口
sudo netstat -lnpt|grep kube-api

tcp        0      0 192.168.100.246:6443     0.0.0.0:*               LISTEN      51903/kube-apiserve

l  6443: 接收 https 请求的安全端口,对所有请求做认证和授权;

l  由于关闭了非安全端口,故没有监听 8080;

 

3.11 授予 kubernetes 证书访问 kubelet API 的权限

在执行 kubectl exec、run、logs 等命令时,apiserver 会转发到 kubelet。这里定义 RBAC 规则,授权 apiserver 调用 kubelet API。

kubectl create clusterrolebinding kube-apiserver:kubelet-apis --clusterrole=system:kubelet-api-admin --user kubernetes

 

3.12 参考

关于证书域名最后字符不能是 . 的问题,实际和 Go 的版本有关,1.9 不支持这种类型的证书:https://github.com/kubernetes/ingress-nginx/issues/2188

 

 

4. controller-manager 集群

本文档介绍部署高可用 kube-controller-manager 集群的步骤。

 

该集群包含 3 个节点,启动后将通过竞争选举机制产生一个 leader 节点,其它节点为阻塞状态。当 leader 节点不可用后,剩余节点将再次进行选举产生新的 leader 节点,从而保证服务的可用性。

 

为保证通信安全,本文档先生成 x509 证书和私钥,kube-controller-manager 在如下两种情况下使用该证书:

  1. 与 kube-apiserver 的安全端口通信时;
  2. 在安全端口(https,10252) 输出 prometheus 格式的 metrics;

 

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

4.1 准备工作

下载最新版本的二进制文件、安装和配置 flanneld 参考:部署master节点

 

4.2  创建 kube-controller-manager 证书和私钥
cd /opt/k8s/work/cert
cat > kube-controller-manager-csr.json <<EOF
{
    "CN": "system:kube-controller-manager",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "hosts": [
      "127.0.0.1",
      "172.26.106.83",
      "172.26.106.81",
      "172.26.106.82"
    ],
    "names": [
      {
        "C": "CN",
        "ST": "BeiJing",
        "L": "BeiJing",
        "O": "system:kube-controller-manager",
        "OU": "study163"
      }
    ]
}
EOF

l  hosts 列表包含所有 kube-controller-manager 节点 IP;

l  CN 为 system:kube-controller-manager、O 为 system:kube-controller-manager,kubernetes 内置的 ClusterRoleBindings system:kube-controller-manager 赋予 kube-controller-manager 工作所需的权限。

 

生成证书和私钥:

cd /opt/k8s/work/cert

cfssl gencert -ca=/opt/k8s/work/cert/ca.pem \
  -ca-key=/opt/k8s/work/cert/ca-key.pem \
  -config=/opt/k8s/work/cert/ca-config.json \
  -profile=kubernetes kube-controller-manager-csr.json | cfssljson -bare kube-controller-manager

ls kube-controller-manager*pem

 

 

将生成的证书和私钥分发到所有 master 节点:

cd /opt/k8s/work/cert

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp kube-controller-manager*.pem root@${node_ip}:/etc/kubernetes/cert/
  done

 

4.3 创建和分发 kubeconfig 文件

kubeconfig 文件包含访问 apiserver 的所有信息,如 apiserver 地址、CA 证书和自身使用的证书;

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
kubectl config set-cluster kubernetes \
  --certificate-authority=/opt/k8s/work/cert/ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=kube-controller-manager.kubeconfig

kubectl config set-credentials system:kube-controller-manager \
  --client-certificate=cert/kube-controller-manager.pem \
  --client-key=cert/kube-controller-manager-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-controller-manager.kubeconfig

kubectl config set-context system:kube-controller-manager \
  --cluster=kubernetes \
  --user=system:kube-controller-manager \
  --kubeconfig=kube-controller-manager.kubeconfig

kubectl config use-context system:kube-controller-manager --kubeconfig=kube-controller-manager.kubeconfig

分发 kubeconfig 到所有 master 节点:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp kube-controller-manager.kubeconfig root@${node_ip}:/etc/kubernetes/
  done

 

 

4.4  创建和分发 kube-controller-manager systemd unit 文件
cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
cat > kube-controller-manager.service <<EOF
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/GoogleCloudPlatform/kubernetes

[Service]
WorkingDirectory=${K8S_DIR}/kube-controller-manager
ExecStart=/opt/k8s/bin/kube-controller-manager \\
  --port=0 \\
  --secure-port=10252 \\
  --bind-address=127.0.0.1 \\
  --kubeconfig=/etc/kubernetes/kube-controller-manager.kubeconfig \\
  --authentication-kubeconfig=/etc/kubernetes/kube-controller-manager.kubeconfig \\
  --authorization-kubeconfig=/etc/kubernetes/kube-controller-manager.kubeconfig \\
  --service-cluster-ip-range=${SERVICE_CIDR} \\
  --cluster-name=kubernetes \\
  --cluster-signing-cert-file=/etc/kubernetes/cert/ca.pem \\
  --cluster-signing-key-file=/etc/kubernetes/cert/ca-key.pem \\
  --experimental-cluster-signing-duration=8760h \\
  --root-ca-file=/etc/kubernetes/cert/ca.pem \\
  --service-account-private-key-file=/etc/kubernetes/cert/ca-key.pem \\
  --leader-elect=true \\
  --controllers=*,bootstrapsigner,tokencleaner \\
  --horizontal-pod-autoscaler-use-rest-clients=true \\
  --horizontal-pod-autoscaler-sync-period=10s \\
  --tls-cert-file=/etc/kubernetes/cert/kube-controller-manager.pem \\
  --tls-private-key-file=/etc/kubernetes/cert/kube-controller-manager-key.pem \\
  --use-service-account-credentials=true \\
  --kube-api-qps=1000 \\
  --kube-api-burst=2000 \\
  --logtostderr=true \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

l  --port=0:关闭监听 http /metrics 的请求,同时 --address 参数无效,--bind-address 参数有效;

l  --secure-port=10252、--bind-address=0.0.0.0: 在所有网络接口监听 10252 端口的 https /metrics 请求;

l  --kubeconfig:指定 kubeconfig 文件路径,kube-controller-manager 使用它连接和验证 kube-apiserver;

l  --authentication-kubeconfig 和 --authorization-kubeconfig:kube-controller-manager 使用它连接 apiserver,对 client 的请求进行认证和授权。kube-controller-manager 不再使用 --tls-ca-file 对请求 https metrics 的 Client 证书进行校验。如果没有配置这两个 kubeconfig 参数,则 client 连接 kube-controller-manager https 端口的请求会被拒绝(提示权限不足)。

l  --cluster-signing-*-file:签名 TLS Bootstrap 创建的证书;

l  --experimental-cluster-signing-duration:指定 TLS Bootstrap 证书的有效期;

l  --root-ca-file:放置到容器 ServiceAccount 中的 CA 证书,用来对 kube-apiserver 的证书进行校验;

l  --service-account-private-key-file:签名 ServiceAccount 中 Token 的私钥文件,必须和 kube-apiserver 的 --service-account-key-file 指定的公钥文件配对使用;

l  --service-cluster-ip-range :指定 Service Cluster IP 网段,必须和 kube-apiserver 中的同名参数一致;

l  --leader-elect=true:集群运行模式,启用选举功能;被选为 leader 的节点负责处理工作,其它节点为阻塞状态;

l  --controllers=*,bootstrapsigner,tokencleaner:启用的控制器列表,tokencleaner 用于自动清理过期的 Bootstrap token;

l  --horizontal-pod-autoscaler-*:custom metrics 相关参数,支持 autoscaling/v2alpha1;

l  --tls-cert-file、--tls-private-key-file:使用 https 输出 metrics 时使用的 Server 证书和秘钥;

l  --use-service-account-credentials=true: kube-controller-manager 中各 controller 使用 serviceaccount 访问 kube-apiserver;

 

分发 systemd unit 文件到所有 master 节点:

cd /opt/k8s/work

source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp kube-controller-manager.service root@${node_ip}:/usr/lib/systemd/system/
  done
4.5 kube-controller-manager 的权限

ClusteRole: system:kube-controller-manager 的权限很小,只能创建 secret、serviceaccount 等资源对象,各 controller 的权限分散到 ClusterRole system:controller:XXX 中。

 

需要在 kube-controller-manager 的启动参数中添加 --use-service-account-credentials=true 参数,这样 main controller 会为各 controller 创建对应的 ServiceAccount XXX-controller。

 

内置的 ClusterRoleBinding system:controller:XXX 将赋予各 XXX-controller ServiceAccount 对应的 ClusterRole system:controller:XXX 权限。

 

另外,--authentication-kubeconfig 和 --authorization-kubeconfig 参数指定的证书需要有创建 "subjectaccessreviews" 的权限,否则提示:

curl --cacert /opt/k8s/work/cert/ca.pem --cert /opt/k8s/work/cert/admin.pem --key /opt/k8s/work/cert/admin-key.pem https://127.0.0.1:10252/metrics

Internal Server Error: "/metrics": subjectaccessreviews.authorization.k8s.io is forbidden: User "system:kube-controller-manager" cannot create resource "subjectaccessreviews" in API group "authorization.k8s.io" at the cluster scope

 

解决办法是创建一个 ClusterRoleBinding,赋予相应的权限:

kubectl create clusterrolebinding controller-manager:system:auth-delegator --user system:kube-controller-manager --clusterrole system:auth-delegator

 

clusterrolebinding.rbac.authorization.k8s.io/controller-manager:system:auth-delegator created

 

参考:https://github.com/kubernetes/kubeadm/issues/1285

 

4.6 启动 kube-controller-manager 服务
source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "mkdir -p ${K8S_DIR}/kube-controller-manager"
    ssh root@${node_ip} "systemctl daemon-reload && systemctl enable kube-controller-manager && systemctl restart kube-controller-manager"
  done

l  必须创建工作目录;

 

4.7  检查服务运行状态
source /opt/k8s/bin/environment.sh

for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "systemctl status kube-controller-manager|grep Active"
  done

确保状态为 active (running),否则查看日志,确认原因:

journalctl -u kube-controller-manager

 

4.8  查看输出的 metric

注意:以下命令在 kube-controller-manager 节点上执行。

kube-controller-manager 监听 10252 端口,接收 https 请求:

sudo netstat -lnpt|grep kube-controll

tcp        0      0 127.0.0.1:10252         0.0.0.0:*               LISTEN      55755/kube-controll

 

curl --cacert /opt/k8s/work/cert/ca.pem --cert /opt/k8s/work/cert/admin.pem --key /opt/k8s/work/cert/admin-key.pem https://127.0.0.1:10252/metrics

{

  "kind": "Status",

  "apiVersion": "v1",

  "metadata": {

  },

  "status": "Failure",

  "message": "forbidden: User \"system:anonymous\" cannot get path \"/metrics\"",

  "reason": "Forbidden",

  "details": {

  },

  "code": 403

}

 

将 kube-controller-manager 的日志级别设置为 4 后,可以看到原因是:

journalctl -u kube-controller-manager -f |grep /metrics

2月 22 19:07:28 m7-inf-prod01 kube-controller-manager[1416748]: I0222 19:07:28.003325 1416748 authorization.go:73] Forbidden: "/metrics", Reason: "no RBAC policy matched"

2月 22 19:07:28 m7-inf-prod01 kube-controller-manager[1416748]: I0222 19:07:28.003472 1416748 wrap.go:42] GET /metrics: (2.600519ms) 403 [curl/7.29.0 127.0.0.1:36324]

 

这是由于没有部署 metrics-server 的缘故。后续在 metrics-server插件 一文中将介绍部署 metrics-server 的步骤。

 

参考:https://github.com/kubernetes-incubator/metrics-server/issues/85

 

4.9  测试 kube-controller-manager 集群的高可用

停掉一个或两个节点的 kube-controller-manager 服务,观察其它节点的日志,看是否获取了 leader 权限。

 

4.10 查看当前的 leader
kubectl get endpoints kube-controller-manager --namespace=kube-system  -o yaml

apiVersion: v1

kind: Endpoints

metadata:

  annotations:

    control-plane.alpha.kubernetes.io/leader: \'{"holderIdentity":"node1_084534e2-6cc4-11e8-a418-5254001f5b65","leaseDurationSeconds":15,"acquireTime":"2018-06-10T15:40:33Z","renewTime":"2018-06-10T16:19:08Z","leaderTransitions":12}\'

  creationTimestamp: 2018-06-10T13:59:42Z

  name: kube-controller-manager

  namespace: kube-system

  resourceVersion: "4540"

  selfLink: /api/v1/namespaces/kube-system/endpoints/kube-controller-manager

  uid: 862cc048-6cb6-11e8-96fa-525400ba84c6

可见,当前的 leader 为 master 节点。

4.11   参考

关于 controller 权限和 use-service-account-credentials 参数:

https://github.com/kubernetes/kubernetes/issues/48208

kublet 认证和授权:

https://kubernetes.io/docs/admin/kubelet-authentication-authorization/#kubelet-authorization

 

5.  scheduler 集群

本章节介绍部署高可用 kube-scheduler 集群的步骤。

 

该集群包含 3 个节点,启动后将通过竞争选举机制产生一个 leader 节点,其它节点为阻塞状态。当 leader 节点不可用后,剩余节点将再次进行选举产生新的 leader 节点,从而保证服务的可用性。

 

为保证通信安全,本文档先生成 x509 证书和私钥,kube-scheduler 在如下两种情况下使用该证书:

  1. 与 kube-apiserver 的安全端口通信;
  2. 安全端口(https,10251) 输出 prometheus 格式的 metrics;

 

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

5.1 准备工作

下载最新版本的二进制文件、安装和配置 flanneld 参考:部署master节点

 

5.2 创建 kube-scheduler 证书和私钥

创建证书签名请求:

cd /opt/k8s/work/cert
cat > kube-scheduler-csr.json <<EOF
{
    "CN": "system:kube-scheduler",
    "hosts": [
      "127.0.0.1",
      "192.168.100.246",
      "192.168.100.247",
      "192.168.100.248"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
      {
        "C": "CN",
        "ST": "BeiJing",
        "L": "BeiJing",
        "O": "system:kube-scheduler",
        "OU": "study163"
      }
    ]
}
EOF

l  hosts 列表包含所有 kube-scheduler 节点 IP;

l  CN 为 system:kube-scheduler、O 为 system:kube-scheduler,kubernetes 内置的 ClusterRoleBindings system:kube-scheduler 将赋予 kube-scheduler 工作所需的权限。

 

生成证书和私钥:

cd /opt/k8s/work/cert
cfssl gencert -ca=/opt/k8s/work/cert/ca.pem \
  -ca-key=/opt/k8s/work/cert/ca-key.pem \
  -config=/opt/k8s/work/cert/ca-config.json \
  -profile=kubernetes kube-scheduler-csr.json | cfssljson -bare kube-scheduler
ls kube-scheduler*pem

 

5.3 创建和分发 kubeconfig 文件

kubeconfig 文件包含访问 apiserver 的所有信息,如 apiserver 地址、CA 证书和自身使用的证书;

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
 
kubectl config set-cluster kubernetes \
  --certificate-authority=cert/ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=kube-scheduler.kubeconfig
 
kubectl config set-credentials system:kube-scheduler \
  --client-certificate=cert/kube-scheduler.pem \
  --client-key=cert/kube-scheduler-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-scheduler.kubeconfig
 
kubectl config set-context system:kube-scheduler \
  --cluster=kubernetes \
  --user=system:kube-scheduler \
  --kubeconfig=kube-scheduler.kubeconfig
 
kubectl config use-context system:kube-scheduler --kubeconfig=kube-scheduler.kubeconfig

l  上一步创建的证书、私钥以及 kube-apiserver 地址被写入到 kubeconfig 文件中;

 

分发 kubeconfig 到所有 master 节点:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp kube-scheduler.kubeconfig root@${node_ip}:/etc/kubernetes/
  done

 

5.4 创建 kube-scheduler 配置文件
cat <<EOF | sudo tee kube-scheduler.yaml
apiVersion: componentconfig/v1alpha1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/etc/kubernetes/kube-scheduler.kubeconfig"
leaderElection:
  leaderElect: true
EOF

l  --kubeconfig:指定 kubeconfig 文件路径,kube-scheduler 使用它连接和验证 kube-apiserver;

l  --leader-elect=true:集群运行模式,启用选举功能;被选为 leader 的节点负责处理工作,其它节点为阻塞状态;

 

分发 kube-scheduler 配置文件到所有 master 节点:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp kube-scheduler.yaml root@${node_ip}:/etc/kubernetes/
  done

 

5.5 创建和分发 kube-scheduler systemd unit 文件
cd /opt/k8s/work
cat > kube-scheduler.service <<EOF
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
 
[Service]
WorkingDirectory=${K8S_DIR}/kube-scheduler
ExecStart=/opt/k8s/bin/kube-scheduler \\
  --config=/etc/kubernetes/kube-scheduler.yaml \\
  --address=127.0.0.1 \\
  --kube-api-qps=100 \\
  --logtostderr=true \\
  --v=2
Restart=always
RestartSec=5
StartLimitInterval=0
 
[Install]
WantedBy=multi-user.target
EOF

l  --address:在 127.0.0.1:10251 端口接收 http /metrics 请求;kube-scheduler 目前还不支持接收 https 请求;

 

分发 systemd unit 文件到所有 master 节点:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp kube-scheduler.service root@${node_ip}:/etc/systemd/system/
  done

 

5.6 启动 kube-scheduler 服务
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "mkdir -p ${K8S_DIR}/kube-scheduler"
    ssh root@${node_ip} "systemctl daemon-reload && systemctl enable kube-scheduler && systemctl restart kube-scheduler"
  done

l  必须先创建工作目录;

 

5.7 检查服务运行状态
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "systemctl status kube-scheduler|grep Active"
  done 

确保状态为 active (running),否则查看日志,确认原因:

journalctl -u kube-scheduler

 

5.8 查看输出的 metric

注意:以下命令在 kube-scheduler 节点上执行。

 

kube-scheduler 监听 10251 端口,接收 http 请求:

sudo netstat -lnpt|grep kube-sche

tcp        0      0 127.0.0.1:10251         0.0.0.0:*               LISTEN      23783/kube-schedule

curl -s http://127.0.0.1:10251/metrics |head

# HELP apiserver_audit_event_total Counter of audit events generated and sent to the audit backend.

# TYPE apiserver_audit_event_total counter

apiserver_audit_event_total 0

# HELP apiserver_client_certificate_expiration_seconds Distribution of the remaining lifetime on the certificate used to authenticate a request.

# TYPE apiserver_client_certificate_expiration_seconds histogram

apiserver_client_certificate_expiration_seconds_bucket{le="0"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="21600"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="43200"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="86400"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="172800"} 0

 

5.9  测试 kube-scheduler 集群的高可用

随便找一个或两个 master 节点,停掉 kube-scheduler 服务,看其它节点是否获取了 leader 权限(systemd 日志)。

 

5.10 查看当前的 leader
kubectl get endpoints kube-scheduler --namespace=kube-system  -o yaml

apiVersion: v1

kind: Endpoints

metadata:

  annotations:

    control-plane.alpha.kubernetes.io/leader: \'{"holderIdentity":"k8s-1_1ea20a43-4f8e-11e9-9ac5-000c2955d204","leaseDurationSeconds":15,"acquireTime":"2019-03-26T06:12:24Z","renewTime":"2019-03-26T06:23:54Z","leaderTransitions":0}\'

  creationTimestamp: 2019-03-26T06:12:24Z

  name: kube-scheduler

  namespace: kube-system

  resourceVersion: "72504"

  selfLink: /api/v1/namespaces/kube-system/endpoints/kube-scheduler

  uid: 1f4b7394-4f8e-11e9-a869-000c2955d204

可见,当前的 leader 为 master 节点。

 

4.11  部署 worker 节点

kubernetes work 节点运行如下组件:

l  docker

l  kubelet

l  kube-proxy

l  flanneld

l  kube-nginx

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

前提条件

安装和配置 flanneld

参考 部署flannel网络

 

安装和配置 kube-nginx

参考 apiserver高可用之nginx代理

 

安装依赖包:

ssh root@${node_ip} "yum install -y epel-release"
ssh root@${node_ip} "yum install -y conntrack ipvsadm ipset jq iptables curl sysstat libseccomp && /usr/sbin/modprobe ip_vs"

1. 部署 docker 组件

docker 是容器的运行环境,管理它的生命周期。kubelet 通过 Container Runtime Interface (CRI) 与 docker 进行交互。

 

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

docker 有多种安装方式:本章节中我们使用最简单的安装方式 脚本安装,使用脚本安装不需要我们关注依赖运行脚本就能自动安装。

 

不过这种方式不安全,而且必须要依赖网络,不推荐在生产环境中使用;生成环境建议使用二进制脚本安装。

 

1.1. 安装依赖包

参考 部署worker节点

 

1.2.  下载和分发 docker 安装文件

使用脚本安装docker:

curl -fLsS https://get.docker.com/ | sh

等待安装完成,也可以指定使用 Aliyun 镜像

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

 

使用二进制文件安装docker:

https://download.docker.com/linux/static/stable/x86_64/ 页面下载最新发布包:

cd /opt/k8s/work
wget https://download.docker.com/linux/static/stable/x86_64/docker-18.09.0.tgz
tar -xvf docker-18.09.0.tgz

 

分发二进制文件到所有 worker 节点:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp docker/*  root@${node_ip}:/opt/k8s/bin/
    ssh root@${node_ip} "chmod +x /opt/k8s/bin/*"
  done

 

1.3 创建和分发 systemd unit 文件
cd /opt/k8s/work
cat > docker.service <<"EOF"
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.io
 
[Service]
WorkingDirectory=##DOCKER_DIR##
Environment="PATH=/opt/k8s/bin:/bin:/sbin:/usr/bin:/usr/sbin"
EnvironmentFile=-/run/flannel/docker
ExecStart=/opt/k8s/bin/dockerd $DOCKER_NETWORK_OPTIONS
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Delegate=yes
KillMode=process
[Install]
WantedBy=multi-user.target
EOF

l  EOF 前后有双引号,这样 bash 不会替换文档中的变量,如 $DOCKER_NETWORK_OPTIONS;

l  dockerd 运行时会调用其它 docker 命令,如 docker-proxy,所以需要将 docker 命令所在的目录加到 PATH 环境变量中;

l  flanneld 启动时将网络配置写入 /run/flannel/docker 文件中,dockerd 启动前读取该文件中的环境变量 DOCKER_NETWORK_OPTIONS ,然后设置 docker0 网桥网段;

l  如果指定了多个 EnvironmentFile 选项,则必须将 /run/flannel/docker 放在最后(确保 docker0 使用 flanneld 生成的 bip 参数);

l  docker 需要以 root 用于运行;

l  docker 从 1.13 版本开始,可能将 iptables FORWARD chain的默认策略设置为DROP,从而导致 ping 其它 Node 上的 Pod IP 失败,遇到这种情况时,需要手动设置策略为 ACCEPT:

sudo iptables -P FORWARD ACCEPT

并且把以下命令写入 /etc/rc.local 文件中,防止节点重启iptables FORWARD chain的默认策略又还原为DROP

/sbin/iptables -P FORWARD ACCEPT

 

分发 systemd unit 文件到所有 worker 机器:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
sed -i -e "s|##DOCKER_DIR##|${DOCKER_DIR}|" docker.service
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp docker.service root@${node_ip}:/usr/lib/systemd/system/
  done

 

1.4  配置和分发 docker 配置文件

使用国内的仓库镜像服务器以加快 pull image 的速度,同时增加下载的并发数 (需要重启 dockerd 生效):

cd /opt/k8s/work
cat > docker-daemon.json <<EOF
{
    "registry-mirrors": ["https://hub-mirror.c.163.com", "https://docker.mirrors.ustc.edu.cn"],
    "insecure-registries": ["docker02:35000"],
    "max-concurrent-downloads": 20,
    "live-restore": true,
    "max-concurrent-uploads": 10,
    "debug": true,
    "data-root": "/data/k8s/docker/data",
    "exec-root": "/data/k8s/docker/exec",
    "log-opts": {
      "max-size": "100m",
      "max-file": "5"
    }
}
EOF

 

分发 docker 配置文件到所有 work 节点:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "mkdir -p  /etc/docker/ ${DOCKER_DIR}/{data,exec}"
    scp docker-daemon.json root@${node_ip}:/etc/docker/daemon.json
  done

 

1.5  启动 docker 服务
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "systemctl stop firewalld && systemctl disable firewalld"
    ssh root@${node_ip} "/usr/sbin/iptables -F && /usr/sbin/iptables -X && /usr/sbin/iptables -F -t nat && /usr/sbin/iptables -X -t nat"
    ssh root@${node_ip} "/usr/sbin/iptables -P FORWARD ACCEPT"
    ssh root@${node_ip} "systemctl daemon-reload && systemctl enable docker && systemctl restart docker"
    #ssh root@${node_ip} \'for intf in /sys/devices/virtual/net/docker0/brif/*; do echo 1 > $intf/hairpin_mode; done\'
    ssh root@${node_ip} "sudo sysctl -p /etc/sysctl.d/kubernetes.conf"
  done 

l  关闭 firewalld(centos7)/ufw(ubuntu16.04),否则可能会重复创建 iptables 规则;

l  清理旧的 iptables rules 和 chains 规则;

l  开启 docker0 网桥下虚拟网卡的 hairpin 模式;

 

1.6  检查服务运行状态
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "systemctl status docker|grep Active"
  done

确保状态为 active (running),否则查看日志,确认原因:

journalctl -u docker
1.7 检查 docker0 网桥
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "/usr/sbin/ip addr show flannel.1 && /usr/sbin/ip addr show docker0"
  done

确认各 work 节点的 docker0 网桥和 flannel.1 接口的 IP 处于同一个网段中(如下 172.30.112.0/32 位于 172.30.112.1/21 中):

>>> 192.168.100.246

4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default

    link/ether fa:32:14:34:86:74 brd ff:ff:ff:ff:ff:ff

    inet 172.30.240.0/32 scope global flannel.1

       valid_lft forever preferred_lft forever

3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default

    link/ether 02:42:7f:58:af:18 brd ff:ff:ff:ff:ff:ff

    inet 172.30.240.1/21 brd 172.30.247.255 scope global docker0

       valid_lft forever preferred_lft forever

 

2.      部署kubelet组件

kublet 运行在每个 worker 节点上,接收 kube-apiserver 发送的请求,管理 Pod 容器,执行交互式命令,如 exec、run、logs 等。

kublet 启动时自动向 kube-apiserver 注册节点信息,内置的 cadvisor 统计和监控节点的资源使用情况。

为确保安全,本文档只开启接收 https 请求的安全端口,对请求进行认证和授权,拒绝未授权的访问(如 apiserver、heapster)。

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

2.1.          下载和分发 kubelet 二进制文件

参考 部署master节点

 

2.2.          安装依赖包

参考 部署worker节点

 

2.3.          创建 kubelet bootstrap kubeconfig 文件
cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_name in ${NODE_NAMES[@]}
  do
    echo ">>> ${node_name}"
 
    # 创建 token
    export BOOTSTRAP_TOKEN=$(kubeadm token create \
      --description kubelet-bootstrap-token \
      --groups system:bootstrappers:${node_name} \
      --kubeconfig ~/.kube/config)
 
    # 设置集群参数
    kubectl config set-cluster kubernetes \
      --certificate-authority=/etc/kubernetes/cert/ca.pem \
      --embed-certs=true \
      --server=${KUBE_APISERVER} \
      --kubeconfig=kubelet-bootstrap-${node_name}.kubeconfig
 
    # 设置客户端认证参数
    kubectl config set-credentials kubelet-bootstrap \
      --token=${BOOTSTRAP_TOKEN} \
      --kubeconfig=kubelet-bootstrap-${node_name}.kubeconfig
 
    # 设置上下文参数
    kubectl config set-context default \
      --cluster=kubernetes \
      --user=kubelet-bootstrap \
      --kubeconfig=kubelet-bootstrap-${node_name}.kubeconfig
 
    # 设置默认上下文
    kubectl config use-context default --kubeconfig=kubelet-bootstrap-${node_name}.kubeconfig
  done

l  证书中写入 Token 而非证书,证书后续由 kube-controller-manager 创建。

 

查看 kubeadm 为各节点创建的 token:

kubeadm token list --kubeconfig ~/.kube/config

TOKEN                     TTL       EXPIRES                     USAGES                   DESCRIPTION               EXTRA GROUPS

33xdd2.b04al5zmia3tnnf8   23h       2019-04-01T14:06:46+08:00   authentication,signing   kubelet-bootstrap-token   system:bootstrappers:node2

ch6fro.v9nps3d5z0lcmyrd   23h       2019-04-01T14:06:25+08:00   authentication,signing   kubelet-bootstrap-token   system:bootstrappers:master

napqqj.wi1qhq2gjel1dvia   23h       2019-04-01T14:06:35+08:00   authentication,signing   kubelet-bootstrap-token   system:bootstrappers:node1

l  创建的 token 有效期为 1 天,超期后将不能再被使用,且会被 kube-controller-manager 的 tokencleaner 清理(如果启用该 controller 的话);

l  kube-apiserver 接收 kubelet 的 bootstrap token 后,将请求的 user 设置为 system:bootstrap:,group 设置为 system:bootstrappers;

 

查看各 token 关联的 Secret:

kubectl get secrets -n kube-system|grep bootstrap-token

bootstrap-token-503xx3                           bootstrap.kubernetes.io/token         7      40m

bootstrap-token-cjadiv                           bootstrap.kubernetes.io/token         7      45m

bootstrap-token-iifyk9                           bootstrap.kubernetes.io/token         7      40m

 

2.4.          分发 bootstrap kubeconfig 文件到所有 worker 节点
cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_name in ${NODE_NAMES[@]}
  do
    echo ">>> ${node_name}"
    scp kubelet-bootstrap-${node_name}.kubeconfig root@${node_name}:/etc/kubernetes/kubelet-bootstrap.kubeconfig
  done

 

2.5.          创建和分发 kubelet 参数配置文件

从 v1.10 开始,kubelet 部分参数需在配置文件中配置,kubelet --help 会提示:

DEPRECATED: This parameter should be set via the config file specified by the Kubelet\'s --config flag

 

创建 kubelet 参数配置模板文件:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
cat <<EOF | tee kubelet-config.yaml.template
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
  anonymous:
    enabled: false
  webhook:
    enabled: true
  x509:
    clientCAFile: "/etc/kubernetes/cert/ca.pem"
authorization:
  mode: Webhook
clusterDomain: "${CLUSTER_DNS_DOMAIN}"
clusterDNS:
  - "${CLUSTER_DNS_SVC_IP}"
podCIDR: "${CLUSTER_CIDR}"
maxPods: 220
serializeImagePulls: false
hairpinMode: promiscuous-bridge
cgroupDriver: cgroupfs
runtimeRequestTimeout: "15m"
rotateCertificates: true
serverTLSBootstrap: true
readOnlyPort: 0
port: 10250
address: "##NODE_IP##"
EOF

l  address:API 监听地址,不能为 127.0.0.1,否则 kube-apiserver、heapster 等不能调用 kubelet 的 API;

l  readOnlyPort=0:关闭只读端口(默认 10255),等效为未指定;

l  authentication.anonymous.enabled:设置为 false,不允许匿名访问 10250 端口;

l  authentication.x509.clientCAFile:指定签名客户端证书的 CA 证书,开启 HTTP 证书认证;

l  authentication.webhook.enabled=true:开启 HTTPs bearer token 认证;

l  对于未通过 x509 证书和 webhook 认证的请求(kube-apiserver 或其他客户端),将被拒绝,提示 Unauthorized;

l  authroization.mode=Webhook:kubelet 使用 SubjectAccessReview API 查询 kube-apiserver 某 user、group 是否具有操作资源的权限(RBAC);

l  featureGates.RotateKubeletClientCertificate、featureGates.RotateKubeletServerCertificate:自动 rotate 证书,证书的有效期取决于 kube-controller-manager 的 --experimental-cluster-signing-duration 参数;

l  需要 root 账户运行;

 

为各节点创建和分发 kubelet 配置文件:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do 
    echo ">>> ${node_ip}"
    sed -e "s/##NODE_IP##/${node_ip}/" kubelet-config.yaml.template > kubelet-config-${node_ip}.yaml.template
    scp kubelet-config-${node_ip}.yaml.template root@${node_ip}:/etc/kubernetes/kubelet-config.yaml
  done

 

2.6.          创建和分发 kubelet systemd unit 文件

创建 kubelet systemd unit 文件模板:

cd /opt/k8s/work
cat > kubelet.service.template <<EOF
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=docker.service
Requires=docker.service
 
[Service]
WorkingDirectory=${K8S_DIR}/kubelet
ExecStart=/opt/k8s/bin/kubelet \\
  --root-dir=${K8S_DIR}/kubelet \\
  --bootstrap-kubeconfig=/etc/kubernetes/kubelet-bootstrap.kubeconfig \\
  --cert-dir=/etc/kubernetes/cert \\
  --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\
  --config=/etc/kubernetes/kubelet-config.yaml \\
  --hostname-override=##NODE_NAME## \\
  --pod-infra-container-image=xiaochunping/pause:3.1
  --allow-privileged=true \\
  --event-qps=0 \\
  --kube-api-qps=1000 \\
  --kube-api-burst=2000 \\
  --registry-qps=0 \\
  --image-pull-progress-deadline=30m \\
  --logtostderr=true \\
  --v=2
Restart=always
RestartSec=5
StartLimitInterval=0
 
[Install]
WantedBy=multi-user.target
EOF

l  如果设置了 --hostname-override 选项,则 kube-proxy 也需要设置该选项,否则会出现找不到 Node 的情况;

l  --bootstrap-kubeconfig:指向 bootstrap kubeconfig 文件,kubelet 使用该文件中的用户名和 token 向 kube-apiserver 发送 TLS Bootstrapping 请求;

l  K8S approve kubelet 的 csr 请求后,在 --cert-dir 目录创建证书和私钥文件,然后写入 --kubeconfig 文件;

l  --pod-infra-container-image 不使用 redhat 的 pod-infrastructure:latest 镜像,它不能回收容器的僵尸;

 

为各节点创建和分发 kubelet systemd unit 文件:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_name in ${NODE_NAMES[@]}
  do 
    echo ">>> ${node_name}"
    sed -e "s/##NODE_NAME##/${node_name}/" kubelet.service.template > kubelet-${node_name}.service
    scp kubelet-${node_name}.service root@${node_name}:/usr/lib/systemd/system/kubelet.service
  done

 

2.7.          Bootstrap Token Auth 和授予权限

kublet 启动时查找配置的 --kubeletconfig 文件是否存在,如果不存在则使用 --bootstrap-kubeconfig 向 kube-apiserver 发送证书签名请求 (CSR)。

 

kube-apiserver 收到 CSR 请求后,对其中的 Token 进行认证(事先使用 kubeadm 创建的 token),认证通过后将请求的 user 设置为 system:bootstrap:<TokenID>,group 设置为 system:bootstrappers,这一过程称为 Bootstrap Token Auth。

 

默认情况下,这个 user 和 group 没有创建 CSR 的权限,kubelet 启动失败,错误日志如下:

sudo journalctl -u kubelet -a |grep -A 2 \'certificatesigningrequests\'

May 06 06:42:36 k8s-1 kubelet[26986]: F0506 06:42:36.314378   26986 server.go:233] failed to run Kubelet: cannot create certificate signing request: certificatesigningrequests.certificates.k8s.io is forbidden: User "system:bootstrap:lemy40" cannot create certificatesigningrequests.certificates.k8s.io at the cluster scope

May 06 06:42:36 k8s-1 systemd[1]: kubelet.service: Main process exited, code=exited, status=255/n/a

May 06 06:42:36 k8s-1 systemd[1]: kubelet.service: Failed with result \'exit-code\'.

 

解决办法是:创建一个 clusterrolebinding,将 group system:bootstrappers 和 clusterrole system:node-bootstrapper 绑定:

kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers

 

2.8.          启动 kubelet 服务
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "mkdir -p ${K8S_DIR}/kubelet"
    ssh root@${node_ip} "/usr/sbin/swapoff -a"
    ssh root@${node_ip} "systemctl daemon-reload && systemctl enable kubelet && systemctl restart kubelet"
  done

l  必须创建工作目录;

l  关闭 swap 分区,否则 kubelet 会启动失败;

 

journalctl -u kubelet |tail

3月 29 15:01:53 k8s-1 systemd[1]: Job kubelet.service/start failed with result \'dependency\'.

3月 29 15:03:23 k8s-1 systemd[1]: Dependency failed for Kubernetes Kubelet.

3月 29 15:03:23 k8s-1 systemd[1]: Job kubelet.service/start failed with result \'dependency\'.

-- Reboot --

3月 30 18:48:42 k8s-1 systemd[1]: Cannot add dependency job for unit kubelet.service, ignoring: Unit not found.

3月 31 14:41:17 k8s-1 systemd[1]: [/usr/lib/systemd/system/kubelet.service:10] Unknown lvalue \'--allow-privileged\' in section \'Service\'

3月 31 14:41:17 k8s-1 systemd[1]: [/usr/lib/systemd/system/kubelet.service:10] Unknown lvalue \'--allow-privileged\' in section \'Service\'

3月 31 14:41:17 k8s-1 systemd[1]: Started Kubernetes Kubelet.

3月 31 14:41:17 k8s-1 kubelet[29768]: I0331 14:41:17.880649   29768 server.go:408] Version: v1.12.3

3月 31 14:41:17 k8s-1 kubelet[29768]: I0331 14:41:17.881049   29768 plugins.go:99] No cloud provider specified.

 

kubelet 启动后使用 --bootstrap-kubeconfig 向 kube-apiserver 发送 CSR 请求,当这个 CSR 被 approve 后,kube-controller-manager 为 kubelet 创建 TLS 客户端证书、私钥和 --kubeletconfig 文件。

 

注意:kube-controller-manager 需要配置 --cluster-signing-cert-file 和 --cluster-signing-key-file 参数,才会为 TLS Bootstrap 创建证书和私钥。

kubectl get csr

NAME                                                   AGE   REQUESTOR                 CONDITION

node-csr-7YSbJGsZGmAH5IrdOmJVcGDrUK3iGtFiDHuRMVdJk5M   22h   system:bootstrap:f9isxw   Pending

node-csr-IHHUpaUceWdFY7QPRw48ICdr_pyt1VC-KgLM9SfNWE8   23h   system:bootstrap:8w1g0a   Pending

node-csr-xqKuMm_MsfUGrMiwti1EaaJMsOpWwvlOQIT_N_3a02k   22h   system:bootstrap:2b84b8

 

kubectl get nodes

No resources found.

 

l  三个 work 节点的 csr 均处于 pending 状态;

 

2.9.          检查服务运行状态
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "systemctl status kubelet|grep Active"
  done

确保状态为 active (running),否则查看日志,确认原因:

journalctl -u kubelet

 

2.10.      approve kubelet CSR 请求

可以手动或自动 approve CSR 请求。推荐使用自动的方式,因为从 v1.8 版本开始,可以自动轮转approve csr 后生成的证书。

2.10.1.     手动 approve CSR 请求

查看 SCR 列表:

kubectl get csr

NAME                                                   AGE   REQUESTOR                 CONDITION

node-csr-7YSbJGsZGmAH5IrdOmJVcGDrUK3iGtFiDHuRMVdJk5M   22h   system:bootstrap:f9isxw   Pending

node-csr-IHHUpaUceWdFY7QPRw48ICdr_pyt1VC-KgLM9SfNWE8   23h   system:bootstrap:8w1g0a   Pending

node-csr-xqKuMm_MsfUGrMiwti1EaaJMsOpWwvlOQIT_N_3a02k   22h   system:bootstrap:2b84b8   

 

approve SCR:

kubectl certificate approve node-csr-7YSbJGsZGmAH5IrdOmJVcGDrUK3iGtFiDHuRMVdJk5M

certificatesigningrequest.certificates.k8s.io "node-csr-7YSbJGsZGmAH5IrdOmJVcGDrUK3iGtFiDHuRMVdJk5M" approved

 

查看 Approve 结果:

kubectl describe csr node-csr-7YSbJGsZGmAH5IrdOmJVcGDrUK3iGtFiDHuRMVdJk5M
2.10.2.     自动 approve CSR 请求

创建三个 ClusterRoleBinding,分别用于自动 approve client、renew client、renew server 证书:

cd /opt/k8s/work
cat > csr-crb.yaml <<EOF
 # Approve all CSRs for the group "system:bootstrappers"
 kind: ClusterRoleBinding
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
   name: auto-approve-csrs-for-group
 subjects:
 - kind: Group
   name: system:bootstrappers
   apiGroup: rbac.authorization.k8s.io
 roleRef:
   kind: ClusterRole
   name: system:certificates.k8s.io:certificatesigningrequests:nodeclient
   apiGroup: rbac.authorization.k8s.io
---
 # To let a node of the group "system:nodes" renew its own credentials
 kind: ClusterRoleBinding
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
   name: node-client-cert-renewal
 subjects:
 - kind: Group
   name: system:nodes
   apiGroup: rbac.authorization.k8s.io
 roleRef:
   kind: ClusterRole
   name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
   apiGroup: rbac.authorization.k8s.io
---
# A ClusterRole which instructs the CSR approver to approve a node requesting a
# serving cert matching its client cert.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: approve-node-server-renewal-csr
rules:
- apiGroups: ["certificates.k8s.io"]
  resources: ["certificatesigningrequests/selfnodeserver"]
  verbs: ["create"]
---
 # To let a node of the group "system:nodes" renew its own server credentials
 kind: ClusterRoleBinding
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
   name: node-server-cert-renewal
 subjects:
 - kind: Group
   name: system:nodes
   apiGroup: rbac.authorization.k8s.io
 roleRef:
   kind: ClusterRole
   name: approve-node-server-renewal-csr
   apiGroup: rbac.authorization.k8s.io
EOF

l  auto-approve-csrs-for-group:自动 approve node 的第一次 CSR; 注意第一次 CSR 时,请求的 Group 为 system:bootstrappers;

l  node-client-cert-renewal:自动 approve node 后续过期的 client 证书,自动生成的证书 Group 为 system:nodes;

l  node-server-cert-renewal:自动 approve node 后续过期的 server 证书,自动生成的证书 Group 为 system:nodes;

 

生效配置:

kubectl apply -f csr-crb.yaml

clusterrolebinding.rbac.authorization.k8s.io/auto-approve-csrs-for-group created

clusterrolebinding.rbac.authorization.k8s.io/node-client-cert-renewal created

clusterrole.rbac.authorization.k8s.io/approve-node-server-renewal-csr created

clusterrolebinding.rbac.authorization.k8s.io/node-server-cert-renewal created

 

2.11.      查看 kublet 的情况

等待一段时间(1-10 分钟),三个节点的 CSR 都被自动 approved:

kubectl get csr

NAME                                                   AGE       REQUESTOR                 CONDITION

node-csr--BjlTzxB5Y4op_6wYlDKbbQj1NtX-IOBMLmWhkupEWA   4m        system:bootstrap:8galm1   Approved,Issued

node-csr-a68FhmUgprTJkaLwnJOLQLOkDQuAviDdBy91ByVtWt0   4m        system:bootstrap:4ef7hj   Approved,Issued

node-csr-a7DI6d0QjBiPh58IBGYFPUKAZvKs6sfbqlnoc22erRs   4m        system:bootstrap:ai162m   Approved,Issued

l  Pending 的 CSR 用于创建 kubelet server 证书,需要手动 approve,下文介绍。

 

所有节点均 ready:

kubectl get nodes

NAME     STATUS   ROLES    AGE     VERSION

master   Ready    <none>   2m58s   v1.12.3

node1    Ready    <none>   2m54s   v1.12.3

node2    Ready    <none>   2m56s   v1.12.3

 

kube-controller-manager 为各 node 生成了 kubeconfig 文件和公私钥:

ls -l /etc/kubernetes/kubelet.kubeconfig

-rw------- 1 root root 2298 3月  28 17:45 /etc/kubernetes/kubelet.kubeconfig

ls -l /etc/kubernetes/cert/|grep kubelet

-rw------- 1 root root 1265 3月  28 17:45 kubelet-client-2019-03-28-17-45-48.pem

lrwxrwxrwx 1 root root   58 3月  28 17:45 kubelet-client-current.pem -> /etc/kubernetes/cert/kubelet-client-2019-03-28-17-45-48.pem

l  没有自动生成 kubelet server 证书;

 

2.12.      手动 approve server cert csr

基于安全性考虑,CSR approving controllers 默认不会自动 approve kubelet server 证书签名请求,需要手动 approve。

kubectl get csr

NAME                                                   AGE     REQUESTOR                 CONDITION

csr-42fr8                                              7m45s   system:node:master        Pending

csr-5gwjj                                              7m43s   system:node:node2         Pending

csr-klkcp                                              7m41s   system:node:node1         Pending

node-csr-7YSbJGsZGmAH5IrdOmJVcGDrUK3iGtFiDHuRMVdJk5M   23h     system:bootstrap:f9isxw   Approved,Issued

node-csr-IHHUpaUceWdFY7QPRw48ICdr_pyt1VC-KgLM9SfNWE8   24h     system:bootstrap:8w1g0a   Approved,Issued

node-csr-xqKuMm_MsfUGrMiwti1EaaJMsOpWwvlOQIT_N_3a02k   23h     system:bootstrap:2b84b8   Approved,Issued

 

kubectl certificate approve csr-42fr8
kubectl certificate approve csr-5gwjj
kubectl certificate approve csr-klkcp

certificatesigningrequest.certificates.k8s.io/csr-42fr8 approved

certificatesigningrequest.certificates.k8s.io/csr-5gwjj approved

certificatesigningrequest.certificates.k8s.io/csr-klkcp approved

ls -l /etc/kubernetes/cert/kubelet-*

-rw------- 1 root root 1265 3月  28 17:45 /etc/kubernetes/cert/kubelet-client-2019-03-28-17-45-48.pem

lrwxrwxrwx 1 root root   58 3月  28 17:45 /etc/kubernetes/cert/kubelet-client-current.pem -> /etc/kubernetes/cert/kubelet-client-2019-03-28-17-45-48.pem

-rw------- 1 root root 1301 3月  28 17:58 /etc/kubernetes/cert/kubelet-server-2019-03-28-17-58-23.pem

lrwxrwxrwx 1 root root   58 3月  28 17:58 /etc/kubernetes/cert/kubelet-server-current.pem -> /etc/kubernetes/cert/kubelet-server-2019-03-28-17-58-23.pem

 

2.13.      kubelet 提供的 API 接口

kublet 启动后监听多个端口,用于接收 kube-apiserver 或其它组件发送的请求:

sudo netstat -lnpt|grep kubelet

tcp        0      0 192.168.100.246:10250   0.0.0.0:*               LISTEN      8958/kubelet       

tcp        0      0 127.0.0.1:41848         0.0.0.0:*               LISTEN      8958/kubelet       

tcp        0      0 127.0.0.1:10248         0.0.0.0:*               LISTEN      8958/kubelet

l  10248: healthz http 服务;

l  10250: https API 服务;注意:未开启只读端口 10255;

 

例如执行 kubectl exec -it nginx-ds-5rmws -- sh 命令时,kube-apiserver 会向 kubelet 发送如下请求:

POST /exec/default/nginx-ds-5rmws/my-nginx?command=sh&input=1&output=1&tty=1

 

kubelet 接收 10250 端口的 https 请求:

l  /pods、/runningpods

l  /metrics、/metrics/cadvisor、/metrics/probes

l  /spec

l  /stats、/stats/container

l  /logs

l  /run/、"/exec/", "/attach/", "/portForward/", "/containerLogs/" 等管理;

详情参考:https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/server/server.go#L434:3

 

由于关闭了匿名认证,同时开启了 webhook 授权,所有访问 10250 端口 https API 的请求都需要被认证和授权。

 

预定义的 ClusterRole system:kubelet-api-admin 授予访问 kubelet 所有 API 的权限(kube-apiserver 使用的 kubernetes 证书 User 授予了该权限):

kubectl describe clusterrole system:kubelet-api-admin

Name:         system:kubelet-api-admin

Labels:       kubernetes.io/bootstrapping=rbac-defaults

Annotations:  rbac.authorization.kubernetes.io/autoupdate: true

PolicyRule:

  Resources      Non-Resource URLs  Resource Names  Verbs

  ---------      -----------------  --------------  -----

  nodes/log      []                 []              [*]

  nodes/metrics  []                 []              [*]

  nodes/proxy    []                 []              [*]

  nodes/spec     []                 []              [*]

  nodes/stats    []                 []              [*]

  nodes          []                 []              [get list watch proxy]

 

2.14.      kublet api 认证和授权

kublet 配置了如下认证参数:

l  authentication.anonymous.enabled:设置为 false,不允许匿名访问 10250 端口;

l  authentication.x509.clientCAFile:指定签名客户端证书的 CA 证书,开启 HTTPs 证书认证;

l  authentication.webhook.enabled=true:开启 HTTPs bearer token 认证;

 

同时配置了如下授权参数:

l  authroization.mode=Webhook:开启 RBAC 授权;

 

kubelet 收到请求后,使用 clientCAFile 对证书签名进行认证,或者查询 bearer token 是否有效。如果两者都没通过,则拒绝请求,提示 Unauthorized:

curl -s --cacert /etc/kubernetes/cert/ca.pem https://192.168.100.246:10250/metrics

Unauthorized

curl -s --cacert /etc/kubernetes/cert/ca.pem -H "Authorization: Bearer 123456" https://192.168.100.246:10250/metrics

Unauthorized

通过认证后,kubelet 使用 SubjectAccessReview API 向 kube-apiserver 发送请求,查询证书或 token 对应的 user、group 是否有操作资源的权限(RBAC);

 

2.14.1.     证书认证和授权
# 权限不足的证书
sudo curl -s --cacert /etc/kubernetes/cert/ca.pem --cert /etc/kubernetes/cert/kube-controller-manager.pem --key /etc/kubernetes/cert/kube-controller-manager-key.pem https://192.168.100.246:10250/metrics

Forbidden (user=system:kube-controller-manager, verb=get, resource=nodes, subresource=metrics)

# 使用部署 kubectl 命令行工具时创建的、具有最高权限的 admin 证书;
sudo curl -s --cacert /etc/kubernetes/cert/ca.pem --cert /opt/k8s/work/cert/admin.pem --key /opt/k8s/work/cert/admin-key.pem https://192.168.100.246:10250/metrics|head

# HELP apiserver_client_certificate_expiration_seconds Distribution of the remaining lifetime on the certificate used to authenticate a request.

# TYPE apiserver_client_certificate_expiration_seconds histogram

apiserver_client_certificate_expiration_seconds_bucket{le="0"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="21600"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="43200"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="86400"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="172800"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="345600"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="604800"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="2.592e+06"} 0

l  --cacert、--cert、--key 的参数值必须是文件路径,如上面的 ./admin.pem 不能省略 ./,否则返回 401 Unauthorized;

 

2.14.2.     bear token 认证和授权

创建一个 ServiceAccount,将它和 ClusterRole system:kubelet-api-admin 绑定,从而具有调用 kubelet API 的权限:

kubectl create sa kubelet-api-test
kubectl create clusterrolebinding kubelet-api-test --clusterrole=system:kubelet-api-admin --serviceaccount=default:kubelet-api-test
SECRET=$(kubectl get secrets | grep kubelet-api-test | awk \'{print $1}\')
TOKEN=$(kubectl describe secret ${SECRET} | grep -E \'^token\' | awk \'{print $2}\')
echo ${TOKEN}
 
curl -s --cacert /etc/kubernetes/cert/ca.pem -H "Authorization: Bearer ${TOKEN}" https://172.26.106.83:10250/metrics|head

# HELP apiserver_client_certificate_expiration_seconds Distribution of the remaining lifetime on the certificate used to authenticate a request.

# TYPE apiserver_client_certificate_expiration_seconds histogram

apiserver_client_certificate_expiration_seconds_bucket{le="0"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="21600"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="43200"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="86400"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="172800"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="345600"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="604800"} 0

apiserver_client_certificate_expiration_seconds_bucket{le="2.592e+06"} 0

 

2.14.3.     cadvisor 和 metrics

cadvisor 统计所在节点各容器的资源(CPU、内存、磁盘、网卡)使用情况,分别在自己的 http web 页面(4194 端口)和 10250 以 promehteus metrics 的形式输出。

 

浏览器访问 :

https://192.168.100.246:10250/metrics  和

https://192.168.100.246:10250/metrics/cadvisor

分别返回 kublet 和 metrics 和 cadvisor 。

 

 

 

注意:

l  kublet.config.json 设置 authentication.anonymous.enabled 为 false,不允许匿名证书访问 10250 的 https 服务;

l  参考 浏览器访问kube-apiserver安全端口,创建和导入相关证书,然后访问上面的 10250 端口;

 

2.15.      获取 kublet 的配置

从 kube-apiserver 获取各 node 的配置:

# 使用部署 kubectl 命令行工具时创建的、具有最高权限的 admin 证书;
sudo curl -sSL --cacert /etc/kubernetes/cert/ca.pem --cert /opt/k8s/work/cert/admin.pem --key /opt/k8s/work/cert/admin-key.pem ${KUBE_APISERVER}/api/v1/nodes/master/proxy/configz | jq  \'.kubeletconfig|.kind="KubeletConfiguration"|.apiVersion="kubelet.config.k8s.io/v1beta1"\'

{

  "syncFrequency": "1m0s",

  "fileCheckFrequency": "20s",

  "httpCheckFrequency": "20s",

  "address": "192.168.100.246",

  "port": 10250,

  "rotateCertificates": true,

  "serverTLSBootstrap": true,

  "authentication": {

    "x509": {

      "clientCAFile": "/etc/kubernetes/cert/ca.pem"

    },

    "webhook": {

      "enabled": true,

      "cacheTTL": "2m0s"

    },

    "anonymous": {

      "enabled": false

    }

  },

  "authorization": {

    "mode": "Webhook",

    "webhook": {

      "cacheAuthorizedTTL": "5m0s",

      "cacheUnauthorizedTTL": "30s"

    }

  },

  "registryPullQPS": 0,

  "registryBurst": 10,

  "eventRecordQPS": 0,

  "eventBurst": 10,

  "enableDebuggingHandlers": true,

  "healthzPort": 10248,

  "healthzBindAddress": "127.0.0.1",

  "oomScoreAdj": -999,

  "clusterDomain": "cluster.local",

  "clusterDNS": [

    "10.254.0.2"

  ],

  "streamingConnectionIdleTimeout": "4h0m0s",

  "nodeStatusUpdateFrequency": "10s",

  "nodeLeaseDurationSeconds": 40,

  "imageMinimumGCAge": "2m0s",

  "imageGCHighThresholdPercent": 85,

  "imageGCLowThresholdPercent": 80,

  "volumeStatsAggPeriod": "1m0s",

  "cgroupsPerQOS": true,

  "cgroupDriver": "cgroupfs",

  "cpuManagerPolicy": "none",

  "cpuManagerReconcilePeriod": "10s",

  "runtimeRequestTimeout": "15m0s",

  "hairpinMode": "promiscuous-bridge",

  "maxPods": 220,

  "podCIDR": "172.30.0.0/16",

  "podPidsLimit": -1,

  "resolvConf": "/etc/resolv.conf",

  "cpuCFSQuota": true,

  "cpuCFSQuotaPeriod": "100ms",

  "maxOpenFiles": 1000000,

  "contentType": "application/vnd.kubernetes.protobuf",

  "kubeAPIQPS": 1000,

  "kubeAPIBurst": 2000,

  "serializeImagePulls": false,

  "evictionHard": {

    "imagefs.available": "15%",

    "memory.available": "100Mi",

    "nodefs.available": "10%",

    "nodefs.inodesFree": "5%"

  },

  "evictionPressureTransitionPeriod": "5m0s",

  "enableControllerAttachDetach": true,

  "makeIPTablesUtilChains": true,

  "iptablesMasqueradeBit": 14,

  "iptablesDropBit": 15,

  "failSwapOn": true,

  "containerLogMaxSize": "10Mi",

  "containerLogMaxFiles": 5,

  "configMapAndSecretChangeDetectionStrategy": "Watch",

  "enforceNodeAllocatable": [

    "pods"

  ],

  "kind": "KubeletConfiguration",

  "apiVersion": "kubelet.config.k8s.io/v1beta1"

}

或者参考代码中的注释:

https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/apis/kubeletconfig/v1beta1/types.go

 

2.16.      参考

kubelet 认证和授权:

https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet-authentication-authorization/

 

 

3.      部署 kube-proxy 组件

kube-proxy 运行在所有 worker 节点上,,它监听 apiserver 中 service 和 Endpoint 的变化情况,创建路由规则来进行服务负载均衡。

本文档讲解部署 kube-proxy 的部署,使用 ipvs 模式。

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

3.1.          下载和分发 kube-proxy 二进制文件

参考 部署master节点

 

3.2.          安装依赖包

各节点需要安装 ipvsadm 和 ipset 命令,加载 ip_vs 内核模块。

参考 部署worker节点

 

3.3.          创建 kube-proxy 证书

创建证书签名请求:

cd /opt/k8s/work/cert
cat > kube-proxy-csr.json <<EOF
{
  "CN": "system:kube-proxy",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "k8s",
      "OU": "study163"
    }
  ]
}
EOF

l  CN:指定该证书的 User 为 system:kube-proxy;

l  预定义的 RoleBinding system:node-proxier 将User system:kube-proxy 与 Role system:node-proxier 绑定,该 Role 授予了调用 kube-apiserver Proxy 相关 API 的权限;

l  该证书只会被 kube-proxy 当做 client 证书使用,所以 hosts 字段为空;

 

生成证书和私钥:

cd /opt/k8s/work/cert
cfssl gencert -ca=/opt/k8s/work/cert/ca.pem \
  -ca-key=/opt/k8s/work/cert/ca-key.pem \
  -config=/opt/k8s/work/cert/ca-config.json \
  -profile=kubernetes  kube-proxy-csr.json | cfssljson -bare kube-proxy
ls kube-proxy*

 

3.4.          创建和分发 kubeconfig 文件
cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
kubectl config set-cluster kubernetes \
  --certificate-authority=/opt/k8s/work/cert/ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=kube-proxy.kubeconfig
 
kubectl config set-credentials kube-proxy \
  --client-certificate=/opt/k8s/work/cert/kube-proxy.pem \
  --client-key=/opt/k8s/work/cert/kube-proxy-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-proxy.kubeconfig
 
kubectl config set-context default \
  --cluster=kubernetes \
  --user=kube-proxy \
  --kubeconfig=kube-proxy.kubeconfig
 
kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig

l  --embed-certs=true:将 ca.pem 和 admin.pem 证书内容嵌入到生成的 kubectl-proxy.kubeconfig 文件中(不加时,写入的是证书文件路径);

 

分发 kubeconfig 文件:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_name in ${NODE_NAMES[@]}
  do
    echo ">>> ${node_name}"
    scp kube-proxy.kubeconfig root@${node_name}:/etc/kubernetes/
  done

 

 

3.5.          创建 kube-proxy 配置文件

从 v1.10 开始,kube-proxy 部分参数可以配置文件中配置。可以使用 --write-config-to 选项生成该配置文件,或者参考 kubeproxyconfig 的类型定义源文件

 

创建 kube-proxy config 文件模板:

cd /opt/k8s/work
cat <<EOF | tee kube-proxy-config.yaml.template
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
clientConnection:
  kubeconfig: "/etc/kubernetes/kube-proxy.kubeconfig"
bindAddress: ##NODE_IP##
clusterCIDR: ${CLUSTER_CIDR}
healthzBindAddress: ##NODE_IP##:10256
hostnameOverride: ##NODE_NAME##
metricsBindAddress: ##NODE_IP##:10249
mode: "ipvs"
EOF

l  bindAddress: 监听地址;

l  clientConnection.kubeconfig: 连接 apiserver 的 kubeconfig 文件;

l  clusterCIDR: kube-proxy 根据 --cluster-cidr 判断集群内部和外部流量,指定 --cluster-cidr 或 --masquerade-all 选项后 kube-proxy 才会对访问 Service IP 的请求做 SNAT;

l  hostnameOverride: 参数值必须与 kubelet 的值一致,否则 kube-proxy 启动后会找不到该 Node,从而不会创建任何 ipvs 规则;

l  mode: 使用 ipvs 模式;

 

为各节点创建和分发 kube-proxy 配置文件:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for (( i=0; i < 3; i++ ))
  do 
    echo ">>> ${NODE_NAMES[i]}"
    sed -e "s/##NODE_NAME##/${NODE_NAMES[i]}/" -e "s/##NODE_IP##/${NODE_IPS[i]}/" kube-proxy-config.yaml.template > kube-proxy-config-${NODE_NAMES[i]}.yaml.template
    scp kube-proxy-config-${NODE_NAMES[i]}.yaml.template root@${NODE_NAMES[i]}:/etc/kubernetes/kube-proxy-config.yaml
  done

 

3.6.          创建和分发 kube-proxy systemd unit 文件
cd /opt/k8s/work
cat > kube-proxy.service <<EOF
[Unit]
Description=Kubernetes Kube-Proxy Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target
 
[Service]
WorkingDirectory=/data/k8s/k8s/kube-proxy
ExecStart=/opt/k8s/bin/kube-proxy \\
  --config=/etc/kubernetes/kube-proxy-config.yaml \\
  --logtostderr=true \\
  --v=2
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
 
[Install]
WantedBy=multi-user.target
EOF

 

分发 kube-proxy systemd unit 文件:

cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_name in ${NODE_NAMES[@]}
  do 
    echo ">>> ${node_name}"
    scp kube-proxy.service root@${node_name}:/usr/lib/systemd/system/
  done

 

3.7.          启动 kube-proxy 服务
cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "mkdir -p ${K8S_DIR}/kube-proxy"
    ssh root@${node_ip} "systemctl daemon-reload && systemctl enable kube-proxy && systemctl restart kube-proxy"
  done

l  必须先创建工作目录;

 

3.8.          检查启动结果
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "systemctl status kube-proxy|grep Active"
  done

确保状态为 active (running),否则查看日志,确认原因:

journalctl -u kube-proxy

 

3.9.          查看监听端口和 metrics
sudo netstat -lnpt|grep kube-prox

tcp        0      0 192.168.100.246:10256   0.0.0.0:*               LISTEN      20482/kube-proxy   

tcp        0      0 192.168.100.246:10249   0.0.0.0:*               LISTEN      20482/kube-proxy

l  10249:http prometheus metrics port;

l  10256:http healthz port;

 

3.10.      查看 ipvs 路由规则
source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "/usr/sbin/ipvsadm -ln"
  done

预期输出:

>>> 192.168.100.246

IP Virtual Server version 1.2.1 (size=4096)

Prot LocalAddress:Port Scheduler Flags

  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn

TCP  10.254.0.1:443 rr

  -> 192.168.100.246:6443         Masq    1      0          0        

  -> 192.168.100.247:6443         Masq    1      0          0        

  -> 192.168.100.248:6443         Masq    1      0          0        

>>> 192.168.100.247

IP Virtual Server version 1.2.1 (size=4096)

Prot LocalAddress:Port Scheduler Flags

  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn

TCP  10.254.0.1:443 rr

  -> 192.168.100.246:6443         Masq    1      0          0        

  -> 192.168.100.247:6443         Masq    1      0          0        

  -> 192.168.100.248:6443         Masq    1      0          0        

>>> 192.168.100.248

IP Virtual Server version 1.2.1 (size=4096)

Prot LocalAddress:Port Scheduler Flags

  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn

TCP  10.254.0.1:443 rr

  -> 192.168.100.246:6443         Masq    1      0          0        

  -> 192.168.100.247:6443         Masq    1      0          0        

  -> 192.168.100.248:6443         Masq    1      0          0

可见将所有到 kubernetes cluster ip 443 端口的请求都转发到 kube-apiserver 的 6443 端口;

 

4.12 验证集群功能

本文档使用 daemonset 验证 master 和 worker 节点是否工作正常。

 

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行,然后远程分发文件和执行命令。

 

1.      检查节点状态

kubectl get nodes

NAME     STATUS   ROLES    AGE     VERSION

master   Ready    <none>   4h35m   v1.12.3

node1    Ready    <none>   4h35m   v1.12.3

node2    Ready    <none>   4h35m   v1.12.3

都为 Ready 时正常。

 

2.      创建测试文件

cd /opt/k8s/work
cat > nginx-ds.yml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: nginx-ds
  labels:
    app: nginx-ds
spec:
  type: NodePort
  selector:
    app: nginx-ds
  ports:
  - name: http
    port: 80
    targetPort: 80
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: nginx-ds
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  template:
    metadata:
      labels:
        app: nginx-ds
    spec:
      containers:
      - name: my-nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
EOF

 

3.      执行定义文件

kubectl create -f nginx-ds.yml

 

4.      检查各 Node 上的 Pod IP 连通性

kubectl get pods  -o wide|grep nginx-ds

nginx-ds-69hp5   1/1     Running   0          2m19s   172.30.8.5   node1    <none>

nginx-ds-rm744   1/1     Running   0          2m19s   172.30.48.7   master   <none>

nginx-ds-vpzk2   1/1     Running   0          2m19s   172.30.144.6   node2    <none>

 

可见,nginx-ds 的 Pod IP 分别是 172.30.8.5、172.30.48.7、172.30.144.6,在所有 Node 上分别 ping 这三个 IP,看是否连通:

source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh ${node_ip} "ping -c 1 172.30.8.5"
    ssh ${node_ip} "ping -c 1 172.30.48.7"
    ssh ${node_ip} "ping -c 1 172.30.144.6"
  done

 

5.      检查服务 IP 和端口可达性

kubectl get svc |grep nginx-ds

nginx-ds     NodePort    10.254.242.170   <none>        80:30437/TCP   5m

 

可见:

l  Service Cluster IP:10.254.242.170

l  服务端口:80

l  NodePort 端口:30437

 

在所有 Node 上 curl Service IP:

source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh ${node_ip} "curl 10.254.242.170"
  done

预期输出 nginx 欢迎页面内容。

 

6.      检查服务的 NodePort 可达性

在所有 Node 上执行:

source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh ${node_ip} "curl ${node_ip}:30437"
  done

预期输出 nginx 欢迎页面内容。

 

service ip(虚拟ip):

nodeport(虚拟ip):物理ip:nodeport虚拟端口

pod:fuent设置的网段的ip

 

五、部署集群插件

可视化自动化工具、日志收集

插件是集群的附件组件,丰富和完善了集群的功能。下面章节我们来学习一些常用的插件:

coredns

Dashboard

Heapster (influxdb、grafana)

Metrics Server

EFK (elasticsearch、fluentd、kibana)

 

5.1 dns 插件

特别重要:

source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh ${node_ip} "docker pull coredns/coredns:1.2.2 && docker tag coredns/coredns:1.2.2 k8s.gcr.io/coredns:1.2.2"
  done

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行。

 

5.1.1.    修改配置文件

将下载的 kubernetes-server-linux-amd64.tar.gz 解压后,再解压其中的 kubernetes-src.tar.gz 文件。

coredns 对应的目录是:cluster/addons/dns。

cd /opt/k8s/work/kubernetes/cluster/addons/dns/coredns
cp coredns.yaml.base coredns.yaml
source /opt/k8s/bin/environment.sh
sed -i -e "s/__PILLAR__DNS__DOMAIN__/${CLUSTER_DNS_DOMAIN}/" -e "s/__PILLAR__DNS__SERVER__/${CLUSTER_DNS_SVC_IP}/" coredns.yaml

 

5.1.2.    插件 coredns
kubectl create -f coredns.yaml

 

5.1.3.    检查 coredns 功能
kubectl get all -n kube-system

NAME                           READY   STATUS             RESTARTS   AGE

pod/coredns-779ffd89bd-v75k9   0/1     ImagePullBackOff   0          4m54s

 

NAME               TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE

service/kube-dns   ClusterIP   10.254.0.2   <none>        53/UDP,53/TCP   4m54s

 

NAME                      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

deployment.apps/coredns   1         1         1            0           4m54s

 

NAME                                 DESIRED   CURRENT   READY   AGE

replicaset.apps/coredns-779ffd89bd   1         1         0       4m54sa

 

新建一个 Deployment

cd /opt/k8s/work
cat > my-nginx.yaml <<EOF
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
EOF
kubectl create -f my-nginx.yaml

 

 

Export 该 Deployment, 生成 my-nginx 服务:

kubectl expose deploy my-nginx

service "my-nginx" exposed

 

kubectl get services --all-namespaces |grep my-nginx

default       my-nginx     ClusterIP   10.254.206.78    <none>        80/TCP          7s

 

创建另一个 Pod,查看 /etc/resolv.conf 是否包含 kubelet 配置的 --cluster-dns 和 --cluster-domain,是否能够将服务 my-nginx 解析到上面显示的 Cluster IP 10.254.206.78

cd /opt/k8s/work
cat > dnsutils-ds.yml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: dnsutils-ds
  labels:
    app: dnsutils-ds
spec:
  type: NodePort
  selector:
    app: dnsutils-ds
  ports:
  - name: http
    port: 80
    targetPort: 80
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: dnsutils-ds
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  template:
    metadata:
      labels:
        app: dnsutils-ds
    spec:
      containers:
      - name: my-dnsutils
        image: tutum/dnsutils:latest
        command:
          - sleep
          - "3600"
        ports:
        - containerPort: 80
EOF
kubectl create -f dnsutils-ds.yml

 

 

查看 pod 节点:

kubectl get pods
 
kubectl exec dnsutils-ds-9rm5v nslookup kubernetes

Server:         10.254.0.2

Address:        10.254.0.2#53

 

Name:   kubernetes.default.svc.cluster.local

Address: 10.254.0.1

kubectl exec dnsutils-ds-9rm5v nslookup www.baidu.com  # 解析外部域名时,需要以 . 结尾

Server:         10.254.0.2

Address:        10.254.0.2#53

 

Non-authoritative answer:

*** Can\'t find www.baidu.com: No answer

kubectl exec dnsutils-ds-9rm5v nslookup www.baidu.com.

Server:         10.254.0.2

Address:        10.254.0.2#53

 

Non-authoritative answer:

www.baidu.com   canonical name = www.a.shifen.com.

Name:   www.a.shifen.com

Address: 61.135.169.125

Name:   www.a.shifen.com

Address: 61.135.169.121

kubectl exec dnsutils-ds-9rm5v nslookup my-nginx

Server:         10.254.0.2

Address:        10.254.0.2#53

 

Name:   my-nginx.default.svc.cluster.local

Address: 10.254.229.163

kubectl exec dnsutils-ds-9rm5v nslookup kube-dns.kube-system.svc.cluster

Server:         10.254.0.2

Address:        10.254.0.2#53

 

Non-authoritative answer:

*** Can\'t find kube-dns.kube-system.svc.cluster: No answer

kubectl exec dnsutils-ds-9rm5v nslookup kube-dns.kube-system.svc

Server:         10.254.0.2

Address:        10.254.0.2#53

 

Name:   kube-dns.kube-system.svc.cluster.local

Address: 10.254.0.2

kubectl exec dnsutils-ds-9rm5v nslookup kube-dns.kube-system.svc.cluster.local

Server:         10.254.0.2

Address:        10.254.0.2#53

 

Non-authoritative answer:

*** Can\'t find kube-dns.kube-system.svc.cluster.local: No answer

kubectl exec dnsutils-ds-9rm5v nslookup kube-dns.kube-system.svc.cluster.local.

 

Server:         10.254.0.2

Address:        10.254.0.2#53

 

Name:   kube-dns.kube-system.svc.cluster.local

Address: 10.254.0.2

 

5.2 dashboard 插件

特别重要:

source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh ${node_ip} "docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kubernetes-dashboard-amd64:v1.8.3 && docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kubernetes-dashboard-amd64:v1.8.3 k8s.gcr.io/kubernetes-dashboard-amd64:v1.8.3"
  done

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行。

 

修改配置文件

将下载的 kubernetes-server-linux-amd64.tar.gz 解压后,再解压其中的 kubernetes-src.tar.gz 文件。

 

dashboard 对应的目录是:cluster/addons/dashboard。

cd /opt/k8s/work/kubernetes/cluster/addons/dashboard

 

5.2.1.    修改配置文件
cat dashboard-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubernetes-dashboard
  namespace: kube-system
  labels:
    k8s-app: kubernetes-dashboard
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  type: NodePort # 增加这一行
  selector:
    k8s-app: kubernetes-dashboard
  ports:
  - port: 443
    targetPort: 8443

 

l  指定端口类型为 NodePort,这样外界可以通过地址 nodeIP:nodePort 访问 dashboard;

 

5.2.2.    执行所有定义文件
$ ls *.yaml
dashboard
-configmap.yaml dashboard-controller.yaml dashboard-rbac.yaml dashboard-secret.yaml dashboard-service.yaml $ kubectl create -f .

 

 

5.2.3.    查看分配的 NodePort
$ kubectl get deployment kubernetes-dashboard -n kube-system

NAME                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

kubernetes-dashboard   1         1         1            1           2m

 
$ kubectl --namespace kube-system get pods -o wide

NAME                                    READY     STATUS    RESTARTS   AGE       IP             NODE              NOMINATED NODE

coredns-77cd44d8df-4fmfc                1/1       Running   0          1h        172.30.200.2   node2   <none>

kubernetes-dashboard-69db8c7745-jtvpj   1/1       Running   0          55s       172.30.112.3   master   <none>

 
$ kubectl get services kubernetes-dashboard -n kube-system

NAME                   TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE

kubernetes-dashboard   NodePort   10.254.9.198   <none>        443:32014/TCP   1m

l  NodePort 32014 映射到 dashboard pod 443 端口;

 

dashboard 的 --authentication-mode 支持 token、basic,默认为 token。如果使用 basic,则 kube-apiserver 必须配置 \'--authorization-mode=ABAC\' 和 \'--basic-auth-file\' 参数。

 

5.2.4.    查看 dashboard 支持的命令行参数
kubectl exec --namespace kube-system -it kubernetes-dashboard-659798bd99-x5j6d  -- /dashboard --help # kubernetes-dashboard-659798bd99-x5j6d 为 pod 名称

2019/04/01 13:12:03 Starting overwatch

Usage of /dashboard:

      --alsologtostderr                   log to standard error as well as files

      --apiserver-host string             The address of the Kubernetes Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8080. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and local discovery is attempted.

      --authentication-mode stringSlice   Enables authentication options that will be reflected on login screen. Supported values: token, basic. Default: token.Note that basic option should only be used if apiserver has \'--authorization-mode=ABAC\' and \'--basic-auth-file\' flags set. (default [token])

      --auto-generate-certificates        When set to true, Dashboard will automatically generate certificates used to serve HTTPS. Default: false.

      --bind-address ip                   The IP address on which to serve the --secure-port (set to 0.0.0.0 for all interfaces). (default 0.0.0.0)

      --default-cert-dir string           Directory path containing \'--tls-cert-file\' and \'--tls-key-file\' files. Used also when auto-generating certificates flag is set. (default "/certs")

      --disable-settings-authorizer       When enabled, Dashboard settings page will not require user to be logged in and authorized to access settings page.

      --enable-insecure-login             When enabled, Dashboard login view will also be shown when Dashboard is not served over HTTPS. Default: false.

      --heapster-host string              The address of the Heapster Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8082. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and service proxy will be used.

      --insecure-bind-address ip          The IP address on which to serve the --port (set to 0.0.0.0 for all interfaces). (default 127.0.0.1)

      --insecure-port int                 The port to listen to for incoming HTTP requests. (default 9090)

      --kubeconfig string                 Path to kubeconfig file with authorization and master location information.

      --log_backtrace_at traceLocation    when logging hits line file:N, emit a stack trace (default :0)

      --log_dir string                    If non-empty, write log files in this directory

      --logtostderr                       log to standard error instead of files

      --metric-client-check-period int    Time in seconds that defines how often configured metric client health check should be run. Default: 30 seconds. (default 30)

      --port int                          The secure port to listen to for incoming HTTPS requests. (default 8443)

      --stderrthreshold severity          logs at or above this threshold go to stderr (default 2)

      --system-banner string              When non-empty displays message to Dashboard users. Accepts simple HTML tags. Default: \'\'.

      --system-banner-severity string     Severity of system banner. Should be one of \'INFO|WARNING|ERROR\'. Default: \'INFO\'. (default "INFO")

      --tls-cert-file string              File containing the default x509 Certificate for HTTPS.

      --tls-key-file string               File containing the default x509 private key matching --tls-cert-file.

      --token-ttl int                     Expiration time (in seconds) of JWE tokens generated by dashboard. Default: 15 min. 0 - never expires (default 900)

  -v, --v Level                           log level for V logs

      --vmodule moduleSpec                comma-separated list of pattern=N settings for file-filtered logging

command terminated with exit code 2

 

5.2.5.    访问 dashboard

为了集群安全,从 1.7 开始,dashboard 只允许通过 https 访问,如果使用 kube proxy 则必须监听 localhost 或 127.0.0.1,对于 NodePort 没有这个限制,但是仅建议在开发环境中使用。

 

对于不满足这些条件的登录访问,在登录成功后浏览器不跳转,始终停在登录界面。

 

参考: https://github.com/kubernetes/dashboard/wiki/Accessing-Dashboard---1.7.X-and-above https://github.com/kubernetes/dashboard/issues/2540

 

  1. kubernetes-dashboard 服务暴露了 NodePort,可以使用 https://NodeIP:NodePort 地址访问 dashboard;
  2. 通过 kube-apiserver 访问 dashboard;
  3. 通过 kubectl proxy 访问 dashboard:

 

如果使用了 VirtualBox,需要启用 VirtualBox 的 ForworadPort 功能将虚机监听的端口和 Host 的本地端口绑定。

 

1.  通过 kubectl proxy 访问 dashboard

启动代理:

kubectl proxy --address=\'localhost\' --port=8086 --accept-hosts=\'^*$\'

Starting to serve on 127.0.0.1:8086

l  --address 必须为 localhost 或 127.0.0.1;

l  需要指定 --accept-hosts 选项,否则浏览器访问 dashboard 页面时提示 “Unauthorized”;

浏览器访问 URL:

http://47.92.173.105:8086/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy

 

2.5.2.  通过 kube-apiserver 访问 dashboard

获取集群服务地址列表:

kubectl cluster-info

Kubernetes master is running at https://192.168.100.246:8443

CoreDNS is running at https://192.168.100.246:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

kubernetes-dashboard is running at https://192.168.100.246:8443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy

 

To further debug and diagnose cluster problems, use \'kubectl cluster-info dump\'.

 

l  由于 apiserver 通过本地的 kube-nginx 做了代理,所以上面显示的 127.0.0.1:8443 为本地的 kube-nginx 的 IP 和 Port,浏览器访问时需要替换为 kube-apiserver 实际监听的 IP 和端口,如 192.168.100.246:6443;

l  必须通过 kube-apiserver 的安全端口(https)访问 dashbaord,访问时浏览器需要使用自定义证书,否则会被 kube-apiserver 拒绝访问。

l  创建和导入自定义证书的步骤,参考:浏览器访问kube-apiserver安全端口

 

浏览器访问 URL:

https://192.168.100.246:6443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy  对于 virtuabox 做了端口映射:

http://127.0.0.1:6443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/

 

 

 

5.2.6.    创建登录 dashboard 的 token 和 kubeconfig 配置文件

上面提到,Dashboard 默认只支持 token 认证,所以如果使用 KubeConfig 文件,需要在该文件中指定 token,不支持使用 client 证书认证。

 

5.2.7.    创建登录 token
kubectl create sa dashboard-admin -n kube-system
kubectl create clusterrolebinding dashboard-admin --clusterrole=cluster-admin --serviceaccount=kube-system:dashboard-admin
ADMIN_SECRET=$(kubectl get secrets -n kube-system | grep dashboard-admin | awk \'{print $1}\')
DASHBOARD_LOGIN_TOKEN=$(kubectl describe secret -n kube-system ${ADMIN_SECRET} | grep -E \'^token\' | awk \'{print $2}\')
echo ${DASHBOARD_LOGIN_TOKEN}

eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkYXNoYm9hcmQtYWRtaW4tdG9rZW4tOHE4bWoiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGFzaGJvYXJkLWFkbWluIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMWU4NmM2ZmMtNTZkNy0xMWU5LWE5Y2EtMDAwYzI5NWJhMDYzIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmRhc2hib2FyZC1hZG1pbiJ9.CKm0cnBGmKvW19TdLWprxHqSAMYClIDRxfvMagA4_J_ogi2QGXQGWVcd9n0rV_QL_s98E1I3A2399aK-FJTBAw5ZyrwiyNVYYa8aSS3ROI7zyKr1xcubGCgvdKGv1WETTPjH6xcYGXhtdR0P6MReuqft6wX0ZucjmlaRtMjpEba_N4YmLG_YOI-qjziAqgE1vv9NU6DjWM8Enyh6cN6CzeI3qtEkt25PPFfE3BsGVMbpnkGyBgcJC-HKd_VafAxSYaG1xICxgDGPWc5PiMIq7sQSFbX4wZAnBIozFG8IBtzgxrFhSdaJS_MJROm5gYZf5mRezAJf-QRovX-e2xcfkQ

 

使用输出的 token 登录 Dashboard。

 

5.2.8.    创建使用 token 的 KubeConfig 文件
cd /opt/k8s/work
source /opt/k8s/bin/environment.sh
# 设置集群参数
kubectl config set-cluster kubernetes \
  --certificate-authority=/etc/kubernetes/cert/ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=dashboard.kubeconfig
 
# 设置客户端认证参数,使用上面创建的 Token
kubectl config set-credentials dashboard_user \
  --token=${DASHBOARD_LOGIN_TOKEN} \
  --kubeconfig=dashboard.kubeconfig
 
# 设置上下文参数
kubectl config set-context default \
  --cluster=kubernetes \
  --user=dashboard_user \
  --kubeconfig=dashboard.kubeconfig
 
# 设置默认上下文
kubectl config use-context default --kubeconfig=dashboard.kubeconfig

用生成的 dashboard.kubeconfig 登录 Dashboard。

 

 

由于缺少 Heapster 插件,当前 dashboard 不能展示 Pod、Nodes 的 CPU、内存等统计数据和图表;

 

5.3  heapster 插件

特别重要:

source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh ${node_ip} "docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-grafana-amd64:v4.4.3 && docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-grafana-amd64:v4.4.3 gcr.io/google_containers/heapster-grafana-amd64:v4.4.3"
    ssh ${node_ip} "docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-amd64:v1.5.3 && docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-amd64:v1.5.3 gcr.io/google_containers/heapster-amd64:v1.5.3"
    ssh ${node_ip} "docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-influxdb-amd64:v1.3.3 && docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-influxdb-amd64:v1.3.3 gcr.io/google_containers/heapster-influxdb-amd64:v1.3.3"
  done

Heapster是一个收集者,将每个Node上的cAdvisor的数据进行汇总,然后导到第三方工具(如InfluxDB)。

 

Heapster 是通过调用 kubelet 的 http API 来获取 cAdvisor 的 metrics 数据的。

 

由于 kublet 只在 10250 端口接收 https 请求,故需要修改 heapster 的 deployment 配置。同时,需要赋予 kube-system:heapster ServiceAccount 调用 kubelet API 的权限。

 

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行。

 

5.3.1.    下载 heapster 文件

heapster release 页面 下载最新版本的 heapster

cd /opt/k8s/work
wget https://github.com/kubernetes/heapster/archive/v1.5.4.tar.gz
tar -xzvf v1.5.4.tar.gz
mv v1.5.4.tar.gz heapster-1.5.4.tar.gz

官方文件目录: heapster-1.5.4/deploy/kube-config/influxdb

 

5.3.2.    修改配置
cd heapster-1.5.4/deploy/kube-config/influxdb
cp grafana.yaml{,.orig}
 
diff grafana.yaml.orig grafana.yaml

67c67

<   # type: NodePort

---

>   type: NodePort

l  开启 NodePort;

 

cp heapster.yaml{,.orig}
diff heapster.yaml.orig heapster.yaml

<         - --source=kubernetes:https://kubernetes.default

---

>         - --source=kubernetes:https://kubernetes.default?kubeletHttps=true&kubeletPort=10250

l  由于 kubelet 只在 10250 监听 https 请求,故添加相关参数;

 

5.3.3.    执行所有定义文件
$ cd  /opt/k8s/work/heapster-1.5.4/deploy/kube-config/influxdb
$ ls *.yaml
grafana.yaml  heapster.yaml  influxdb.yaml
$ kubectl create -f  .
 
$ cd ../rbac/
$ cp heapster-rbac.yaml{,.orig}
$ diff heapster-rbac.yaml.orig heapster-rbac.yaml
12a13,26
> ---
> kind: ClusterRoleBinding
> apiVersion: rbac.authorization.k8s.io/v1beta1
> metadata:
>   name: heapster-kubelet-api
> roleRef:
>   apiGroup: rbac.authorization.k8s.io
>   kind: ClusterRole
>   name: system:kubelet-api-admin
> subjects:
> - kind: ServiceAccount
>   name: heapster
>   namespace: kube-system
 
$ kubectl create -f heapster-rbac.yaml

l  将 serviceAccount kube-system:heapster 与 ClusterRole system:kubelet-api-admin 绑定,授予它调用 kubelet API 的权限;

 

如果不修改,默认的 ClusterRole system:heapster 权限不足:

E1128 10:00:05.010716 1 manager.go:101] Error in scraping containers from kubelet:192.168.100.246:10250: failed to get all container stats from Kubelet URL "https://192.168.100.246:10250/stats/container/": request failed - "403 Forbidden", response: "Forbidden (user=system:serviceaccount:kube-system:heapster, verb=create, resource=nodes, subresource=stats)" E1128 10:00:05.018556 1 manager.go:101] Error in scraping containers from kubelet:172.27.128.149:10250: failed to get all container stats from Kubelet URL "https:// 192.168.100.247:10250/stats/container/": request failed - "403 Forbidden", response: "Forbidden (user=system:serviceaccount:kube-system:heapster, verb=create, resource=nodes, subresource=stats)" E1128 10:00:05.022664 1 manager.go:101] Error in scraping containers from kubelet:172.27.128.148:10250: failed to get all container stats from Kubelet URL "https:// 192.168.100.248:10250/stats/container/": request failed - "403 Forbidden", response: "Forbidden (user=system:serviceaccount:kube-system:heapster, verb=create, resource=nodes, subresource=stats)" W1128 10:00:25.000467 1 manager.go:152] Failed to get all responses in time (got 0/3)

 

5.3.4.    检查执行结果
$ kubectl get pods -n kube-system | grep -E \'heapster|monitoring\'
heapster-56c9dc749-j7hvz                1/1       Running   0          1m
monitoring-grafana-c797777db-lnwnc      1/1       Running   0          1m
monitoring-influxdb-cf9d95766-5wd28     1/1       Running   0          1m

 

检查 kubernets dashboard 界面,可以正确显示各 Nodes、Pods 的 CPU、内存、负载等统计数据和图表:

 

 

5.3.5.    访问 grafana
  1. 通过 kube-apiserver 访问:

获取 monitoring-grafana 服务 URL:

kubectl cluster-info

Kubernetes master is running at https://192.168.100.246:8443

Heapster is running at https://192.168.100.246:8443/api/v1/namespaces/kube-system/services/heapster/proxy

CoreDNS is running at https://192.168.100.246:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

kubernetes-dashboard is running at https://192.168.100.246:8443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy

monitoring-grafana is running at https://192.168.100.246:8443/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy

monitoring-influxdb is running at https://192.168.100.246:8443/api/v1/namespaces/kube-system/services/monitoring-influxdb/proxy

 

To further debug and diagnose cluster problems, use \'kubectl cluster-info dump\'.

 

浏览器访问 URL:

https://192.168.100.246:8443/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy

对于 virtuabox 做了端口映射:

http://127.0.0.1:8080/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy

 

  1. 通过 kubectl proxy 访问:

创建代理

kubectl proxy --address=\'192.168.100.246\' --port=8086 --accept-hosts=\'^*$\'

Starting to serve on 172.27.129.150:8086

 

浏览器访问 URL:

http://192.168.100.246:8086/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy/?orgId=1

对于 virtuabox 做了端口映射:

http://127.0.0.1:8086/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy/?orgId=1

 

  1. 通过 NodePort 访问:
kubectl get svc -n kube-system|grep -E \'monitoring|heapster\'

heapster               ClusterIP   10.254.199.65    <none>        80/TCP          3m

monitoring-grafana     NodePort    10.254.116.161   <none>        80:31470/TCP    3m

monitoring-influxdb    ClusterIP   10.254.250.185   <none>        8086/TCP        3m

l  grafana 监听 NodePort 31470;

 

浏览器访问 URL:http://192.168.100.246:31470/?orgId=1

 

 

5.3.6.    参考
  1. 配置 heapster:https://github.com/kubernetes/heapster/blob/master/docs/source-configuration.md

 

 

5.4  metrics-server 插件

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行。

 

5.4.1.    创建 metrics-server 使用的证书
cd /opt/k8s/work/cert
cat > metrics-server-csr.json <<EOF
{
  "CN": "aggregator",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "k8s",
      "OU": "study163"
    }
  ]
}
EOF

 

l  注意: CN 名称为 aggregator,需要与 kube-apiserver 的 --requestheader-allowed-names 参数配置一致;

 

生成 metrics-server 证书和私钥:

cfssl gencert -ca=/etc/kubernetes/cert/ca.pem \
  -ca-key=/etc/kubernetes/cert/ca-key.pem  \
  -config=/etc/kubernetes/cert/ca-config.json  \
  -profile=kubernetes metrics-server-csr.json | cfssljson -bare metrics-server
 

将生成的证书和私钥文件拷贝到 kube-apiserver 节点:

source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    scp metrics-server*.pem root@${node_ip}:/etc/kubernetes/cert/
  done
 
5.4.2.    修改 kubernetes 控制平面组件的配置以支持 metrics-server
5.4.2.1.  kube-apiserver

添加如下配置参数:

--requestheader-client-ca-file=/etc/kubernetes/cert/ca.pem

--requestheader-allowed-names=""

--requestheader-extra-headers-prefix="X-Remote-Extra-"

--requestheader-group-headers=X-Remote-Group

--requestheader-username-headers=X-Remote-User

--proxy-client-cert-file=/etc/kubernetes/cert/metrics-server.pem

--proxy-client-key-file=/etc/kubernetes/cert/metrics-server-key.pem

--runtime-config=api/all=true

l  --requestheader-XXX、--proxy-client-XXX 是 kube-apiserver 的 aggregator layer 相关的配置参数,metrics-server & HPA 需要使用;

l  --requestheader-client-ca-file:用于签名 --proxy-client-cert-file 和 --proxy-client-key-file 指定的证书;在启用了 metric aggregator 时使用;

l  如果 --requestheader-allowed-names 不为空,则--proxy-client-cert-file 证书的 CN 必须位于 allowed-names 中,默认为 aggregator;

 

如果 kube-apiserver 机器没有运行 kube-proxy,则还需要添加 --enable-aggregator-routing=true 参数;

 

关于 --requestheader-XXX 相关参数,参考:

https://github.com/kubernetes-incubator/apiserver-builder/blob/master/docs/concepts/auth.md

https://docs.bitnami.com/kubernetes/how-to/configure-autoscaling-custom-metrics/

 

注意:requestheader-client-ca-file 指定的 CA 证书,必须具有 client auth and server auth;

 

5.4.2.2.  kube-controllr-manager

添加如下配置参数(从 v1.12 开始,该选项默认为 true,不需要再添加):

--horizontal-pod-autoscaler-use-rest-clients=true

用于配置 HPA 控制器使用 REST 客户端获取 metrics 数据。

 

5.4.3.    整体架构

暂缺

 

5.4.4.    修改插件配置文件配置文件

metrics-server 插件位于 kubernetes 的 cluster/addons/metrics-server/ 目录下。

 

修改 metrics-server-deployment 文件:

$ cp metrics-server-deployment.yaml{,.orig}
$ diff metrics-server-deployment.yaml.orig metrics-server-deployment.yaml

51c51

<         image: mirrorgooglecontainers/metrics-server-amd64:v0.2.1

---

>         image: k8s.gcr.io/metrics-server-amd64:v0.2.1

54c54

<         - --source=kubernetes.summary_api:\'\'

---

>         - --source=kubernetes.summary_api:https://kubernetes.default?kubeletHttps=true&kubeletPort=10250

60c60

<         image: siriuszg/addon-resizer:1.8.1

---

>         image: k8s.gcr.io/addon-resizer:1.8.1

l  metrics-server 的参数格式与 heapster 类似。由于 kubelet 只在 10250 监听 https 请求,故添加相关参数;

 

授予 kube-system:metrics-server ServiceAccount 访问 kubelet API 的权限:

$ cat auth-kubelet.yaml

apiVersion: rbac.authorization.k8s.io/v1

kind: ClusterRoleBinding

metadata:

  name: metrics-server:system:kubelet-api-admin

  labels:

    kubernetes.io/cluster-service: "true"

    addonmanager.kubernetes.io/mode: Reconcile

roleRef:

  apiGroup: rbac.authorization.k8s.io

  kind: ClusterRole

  name: system:kubelet-api-admin

subjects:

- kind: ServiceAccount

  name: metrics-server

  namespace: kube-system

l  新建一个 ClusterRoleBindings 定义文件,授予相关权限;

 

5.4.5.    创建 metrics-server
$ cd /opt/k8s/kubernetes/cluster/addons/metrics-server
$ ls -l *.yaml

-rw-rw-r-- 1 k8s k8s  398 Jun  5 07:17 auth-delegator.yaml

-rw-rw-r-- 1 k8s k8s  404 Jun 16 18:02 auth-kubelet.yaml

-rw-rw-r-- 1 k8s k8s  419 Jun  5 07:17 auth-reader.yaml

-rw-rw-r-- 1 k8s k8s  393 Jun  5 07:17 metrics-apiservice.yaml

-rw-rw-r-- 1 k8s k8s 2640 Jun 16 17:54 metrics-server-deployment.yaml

-rw-rw-r-- 1 k8s k8s  336 Jun  5 07:17 metrics-server-service.yaml

-rw-rw-r-- 1 k8s k8s  801 Jun  5 07:17 resource-reader.yaml

 

$ kubectl create -f .

 

5.4.6.    查看运行情况
$ kubectl get pods -n kube-system |grep metrics-server
metrics-server-v0.2.1-7486f5bd67-v95q2   2/2       Running   0          45s
 
$ kubectl get svc -n kube-system|grep metrics-server
metrics-server         ClusterIP   10.254.115.120   <none>        443/TCP         1m

 

5.4.7.    查看 metrcs-server 输出的 metrics

metrics-server 输出的 APIs:

https://github.com/kubernetes/community/blob/master/contributors/design-proposals/instrumentation/resource-metrics-api.md

 

  1. 通过 kube-apiserver 或 kubectl proxy 访问:

https://192.168.100.246:6443/apis/metrics.k8s.io/v1beta1/nodes https://192.168.100.246:6443/apis/metrics.k8s.io/v1beta1/nodes/ https://192.168.100.246:6443/apis/metrics.k8s.io/v1beta1/pods https://192.168.100.246:6443/apis/metrics.k8s.io/v1beta1/namespace//pods/

 

  1. 直接使用 kubectl 命令访问:

 

kubectl get --raw apis/metrics.k8s.io/v1beta1/nodes kubectl get --raw apis/metrics.k8s.io/v1beta1/pods kubectl get --raw apis/metrics.k8s.io/v1beta1/nodes/ kubectl get --raw apis/metrics.k8s.io/v1beta1/namespace//pods/

 

$ kubectl get --raw "/apis/metrics.k8s.io/v1beta1" | jq .

{

  "kind": "APIResourceList",

  "apiVersion": "v1",

  "groupVersion": "metrics.k8s.io/v1beta1",

  "resources": [

    {

      "name": "nodes",

      "singularName": "",

      "namespaced": false,

      "kind": "NodeMetrics",

      "verbs": [

        "get",

        "list"

      ]

    },

    {

      "name": "pods",

      "singularName": "",

      "namespaced": true,

      "kind": "PodMetrics",

      "verbs": [

        "get",

        "list"

      ]

    }

  ]

}

 

$ kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" | jq .

{

  "kind": "NodeMetricsList",

  "apiVersion": "metrics.k8s.io/v1beta1",

  "metadata": {

    "selfLink": "/apis/metrics.k8s.io/v1beta1/nodes"

  },

  "items": [

    {

      "metadata": {

        "name": "node2",

        "selfLink": "/apis/metrics.k8s.io/v1beta1/nodes/node2",

        "creationTimestamp": "2018-06-16T10:24:03Z"

      },

      "timestamp": "2018-06-16T10:23:00Z",

      "window": "1m0s",

      "usage": {

        "cpu": "133m",

        "memory": "1115728Ki"

      }

    },

    {

      "metadata": {

        "name": "master",

        "selfLink": "/apis/metrics.k8s.io/v1beta1/nodes/master",

        "creationTimestamp": "2018-06-16T10:24:03Z"

      },

      "timestamp": "2018-06-16T10:23:00Z",

      "window": "1m0s",

      "usage": {

        "cpu": "221m",

        "memory": "6799908Ki"

      }

    },

    {

      "metadata": {

        "name": "node1",

        "selfLink": "/apis/metrics.k8s.io/v1beta1/nodes/node1",

        "creationTimestamp": "2018-06-16T10:24:03Z"

      },

      "timestamp": "2018-06-16T10:23:00Z",

      "window": "1m0s",

      "usage": {

        "cpu": "76m",

        "memory": "1130180Ki"

      }

    }

  ]

}

 

l  /apis/metrics.k8s.io/v1beta1/nodes 和 /apis/metrics.k8s.io/v1beta1/pods 返回的 usage 包含 CPU 和 Memory;

 

5. 5  EFK 插件

EFK 对应的目录:kubernetes/cluster/addons/fluentd-elasticsearch

$ cd /opt/k8s/work/kubernetes/cluster/addons/fluentd-elasticsearch
$ ls *.yaml
es-service.yaml  es-statefulset.yaml  fluentd-es-configmap.yaml  fluentd-es-ds.yaml  kibana-deployment.yaml  kibana-service.yaml
 

注意:如果没有特殊指明,本文档的所有操作均在 master 节点上执行。

 

5.5.1.    拉取镜像

注意:因为elk插件使用的是谷歌镜像,由于众所周知的原因我们无法使用,只能通过先拉去对于的其他公开镜像然后改名。或者直接修改yml文件中定义的谷歌镜像名(推荐使用第一种)。

source /opt/k8s/bin/environment.sh
for node_ip in ${NODE_IPS[@]};   
  do     
    echo ">>> ${node_ip}";     
    ssh ${node_ip} "docker pull xiaochunping/elasticsearch:v6.2.5 && docker tag xiaochunping/elasticsearch:v6.2.5 k8s.gcr.io/elasticsearch:v6.2.5"; 
    ssh ${node_ip} "docker pull xiaochunping/fluentd-elasticsearch:v2.2.0 && docker tag xiaochunping/fluentd-elasticsearch:v2.2.0 k8s.gcr.io/fluentd-elasticsearch:v2.2.0";   
  done

命令只需要在master节点上执行即可。

 

5.5.2.          给 Node 设置标签

DaemonSet fluentd-es 只会调度到设置了标签 beta.kubernetes.io/fluentd-ds-ready=true 的 Node,需要在期望运行 fluentd 的 Node 上设置该标签;

$ kubectl get nodes

NAME         STATUS    ROLES     AGE       VERSION

master   Ready     <none>    3d        v1.10.4

node1   Ready     <none>    3d        v1.10.4

node2   Ready     <none>    3d        v1.10.4

 

$ kubectl label nodes node2 beta.kubernetes.io/fluentd-ds-ready=true

node "node2" labeled

 

5.5.3.          执行定义文件
$ cd /opt/k8s/kubernetes/cluster/addons/fluentd-elasticsearch
 
$ ls *.yaml
es-service.yaml  es-statefulset.yaml  fluentd-es-configmap.yaml  fluentd-es-ds.yaml  kibana-deployment.yaml  kibana-service.yaml
 
$ kubectl create -f .

 

5.5.4.    检查执行结果
$ kubectl get pods -n kube-system -o wide|grep -E \'elasticsearch|fluentd|kibana\'

elasticsearch-logging-0                  1/1       Running   0          5m        172.30.81.7   master

elasticsearch-logging-1                  1/1       Running   0          2m        172.30.39.8   node2

fluentd-es-v2.0.4-hntfp                  1/1       Running   0          5m        172.30.39.6   node2

kibana-logging-7445dc9757-pvpcv          1/1       Running   0          5m        172.30.39.7   node2

 

$ kubectl get service  -n kube-system|grep -E \'elasticsearch|kibana\'

elasticsearch-logging   ClusterIP   10.254.50.198    <none>        9200/TCP        5m

kibana-logging         ClusterIP   10.254.255.190   <none>        5601/TCP        5m

 

kibana Pod 第一次启动时会用**较长时间(0-20分钟)**来优化和 Cache 状态页面,可以 tailf 该 Pod 的日志观察进度:

$ kubectl logs kibana-logging-7445dc9757-pvpcv -n kube-system -f

{"type":"log","@timestamp":"2018-06-16T11:36:18Z","tags":["info","optimize"],"pid":1,"message":"Optimizing and caching bundles for graph, ml, kibana, stateSessionStorageRedirect, timelion and status_page. This may take a few minutes"}

{"type":"log","@timestamp":"2018-06-16T11:40:03Z","tags":["info","optimize"],"pid":1,"message":"Optimization of bundles for graph, ml, kibana, stateSessionStorageRedirect, timelion and status_page complete in 224.57 seconds"}

 

注意:只有当的 Kibana pod 启动完成后,才能查看 kibana dashboard,否则会提示 refuse。

 

5.5.5.    访问 kibana
  1. 通过 kube-apiserver 访问:
$ kubectl cluster-info|grep -E \'Elasticsearch|Kibana\'

Elasticsearch is running at https://192.168.100.246:6443/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy

Kibana is running at https://192.168.100.246:6443/api/v1/namespaces/kube-system/services/kibana-logging/proxy

 

浏览器访问 URL:

https://192.168.100.246:6443/api/v1/namespaces/kube-system/services/kibana-logging/proxy

对于 virtuabox 做了端口映射:

http://127.0.0.1:8080/api/v1/namespaces/kube-system/services/kibana-logging/proxy

 

  1. 通过 kubectl proxy 访问:

创建代理

$ kubectl proxy --address=\'192.168.100.246\' --port=8086 --accept-hosts=\'^*$\'
Starting to serve on 172.27.129.150:8086

 

浏览器访问 URL:

http://192.168.100.246:8086/api/v1/namespaces/kube-system/services/kibana-logging/proxy 

对于 virtuabox 做了端口映射:

http://127.0.0.1:8086/api/v1/namespaces/kube-system/services/kibana-logging/proxy

 

在 Settings -> Indices 页面创建一个 index(相当于 mysql 中的一个 database),选中 Index contains time-based events,使用默认的 logstash-* pattern,点击 Create ;

 

 

创建 Index 后,稍等几分钟就可以在 Discover 菜单下看到 ElasticSearch logging 中汇聚的日志;

 

 

 

 

 

六、结合k8s的系统整体监控方案、业务部属系统

 

 

七、xx公司的探索和实践