TOC

部署要点

    正常情况下,如果我们只是在测试环境使用,那我们可以使用单Master节点,而后我们所有的数据放在etcd当中,而etcd也可以单实例,worker节点就按需而定,如果我们需要做一些持久存储的话,可以使用NFS或者glusterfs等存储系统,只要符合我们的使用就可以了;
    但是正常环境下,在生产环境中,如果我们使用单etcd实例的话,万一这个etcd出现故障,那么整个集群的所有数据,包括用户定义的期望状态和各种各样的资源的当前状态,那就丢失了,所以这是一个很非常危险的情形,因此,我们必须要对etcd进行高可用,对于etcd来讲,我们还应该对其进行周期性的数据备份,毕竟每个etcd集群节点之上,当我们误删一个资源定义时,或者是做了一些不小心的操作,那么每一个高可用的节点的所有节点的所有数据都会同步删除,因此,要想能够恢复,我们还需要周期性的对数据进行备份,才是正常维护和管理etcd实例的做法;
ApiServer高可用逻辑
    对于Master节点来讲,大体上,有这几个组件,第一个是kube-apiserver,所有对Kubernetes的管理甚至访问都应该经过ApiServer,它也是整个集群的唯一的网关,那么因此,ApiServer应该是高可用的,如果不高可用,一旦master节点出现故障,那么ApiServer将不再可用,而各个节点的kubelet,也需要周期性的访问到ApiServer来获取相关的状态信息,甚至还需要接受ApiServer的通知信息,或者说watch的类型的资源变动信息,从根本上来讲ApiServer只是一个http或者https服务,因此,我们只需要确保Master的kube-apiserver能够正常的做到多实例就可以了,因为它是一个无状态的服务,因为其数据放在了etcd存储系统之上当中,所以是的ApiServer就变成了无状态的,因此我们要对她做高可用是非常简单的,我们可以多做几个Master节点,每一个节点各自运行一个ApiServer进程;
    因为它是无状态的,因此我们有两种方案来对它进行可用性设定,第一我们这里有多个ApiServer的话,我们只需要对其做一个设定,比如在三个节点之上部署一个keepalived,keepalived本身能够流动IP地址,那么这个被流动的IP地址,在哪个节点之上,那么哪个节点就可以接收用户请求,随后对ApiServer来访问,不再使用Master节点的物理地址,而要使用被keepalived流动的这个VIP访问,这是一种访问,在这种方案当中,如果我们做了三个ApiServer的话,事实上在某一时刻只有一个在工作的,也就是说我们的VIP在哪个节点上,那么这个节点就是真正处理客户端请求的ApiServer节点,另外两个节点就处于空闲状态;
    还有一种做法,我们完全可以直接在这个三个ApiServer的前端,使用Nginx/HAProxy做一个反向代理服务器,做负载均衡,让Nginx对后端这每一个ApiServer的健康状态做检测,并且正常去负载客户端请求,到这三个ApiServer中的任何一个节点上也是可以的,只不过我们需要让每一个客户端,包括我们的controller-manager或者kubelet等等,他们所有指向的地址不再是某一个特定的ApiServer的IP地址,而应该是前端调度器的IP地址,并且我们还可以对我们的Nginx/HAProxy调度器做高可用,比如做两个调度器,然后结合keepalived都行,在这种解决逻辑下,每一个ApiServer都会发挥出作用来,而且客户端较多时,这种方式确实也能够很好的负载用户的请求;
kube-scheduler/kube-controller-manager高可用逻辑
    这两种组件都是不可以直接在Kubernetes进行多机部署的组件;
kube-scheduler高可用逻辑
    当用户通过其中某一个ApiServer提交了一个资源创建请求之后,这个资源的创建操作的内容,就是清单就会存储在etcd当中,并由ApiServer通知给kube-scheduler,因为kube-scheduler会监视这每一个Pod资源的变动,对于Pod资源当中的NodeName为空的创建请求,那么kube-scheduler就需要负责调度,而调度方式无非就是在当前集群之中的节点当中优中选优,通过预选、优选然后再选择一个节点运行Pod,另外将结果存入到etcd当中,而后etcd一旦存入以后,ApiServer就会通知给相应的通过kube-scheduler选中的节点的kubelet来负责运行此Pod;
    如果我们有多个kube-scheduler在同时运行,会出现对于创建的一个Pod,两个kube-scheduler都进行排序调度和选择,选择的结果不一样,所以对于这种逻辑来讲,很显然,我们有两个kube-scheduler是不被允许的, 但是kube-scheduler之后一个,那么Master节点一宕机,那么就没人做调度了,有两个又不能同时现在排序选择,所以kube-scheduler只能运行于主备模型,或者说活动和备用模型下;
    无论你运行了多少个kube-scheduler实例,但在某一个时刻只能够一个是活动的,剩下都是备用的、辅助的,这是它于ApiServer不一样的地方;
kube-controller-manager高可用逻辑
    对于kube-controller-manager它和我们的kube-scheduler一样,追踪每一个资源的状态,并且来检查控制器是如何实现使用控制循环的,那这个时候我们如果有两个kube-controller-manager那么他们的工作步调不一样,也会出现各种各样的问题,因此单一kube-controller-manager出现故障之后是单点,那么你多个kube-controller-manager一样不能同时运行,它也需要运行在主备模型下;
分布式协作
    那这样一来就有麻烦了,我们必须要有一种仲裁机制,来保证在某一时刻,一定之能有一个实例处于活动状态,其实像这种应用程序, 位于多个节点之上,我们都称之为分布式应用程序的协作模型,而分布式应用程序的协作,有很多种机制,比如这两个应用程序能基于某种协议,比如我们Elasticsearch,它可以位于9100端口,里面有一个进程,各Elasticsearch可以在里面发一些心跳或者选举协议,必要时能够通过领导选举,选举出一个Leader,其他的节点都是工作在Backup模式下,那么之后Leader出现故障时,那么剩下的Backup就会触发一次新的选举操作,再选取一个新的Leader出来,这是一种工作逻辑;
    还有一种工作逻辑,我们可以借助于外部的分布式锁,来做Leader选举,比如我们的zookepeer,而zookepeer在某种意义上来讲,我们可以把它理解为就是一个存储服务,如果现在有三个进程需要协作,那么我们可以让这个三个节点同事对一个zookepeer上的一个共享资源来 进行抢占,这三个节点需要选举出谁是领导来,但是他们彼此之间,并不协作,而是这三个进程要立即向这个zookepeer去请求,征用zookepeer上的某一个特定的资源,比如是一个文件描述符,对于zookepeer称之为这个Znode,一旦第一个进程占据了这个Znode,那么另外两个进程,就之能等待了,谁先抢到,谁就是占用着,而且它还是独占的;
    谁抢到了这个独占资源,谁就是Leader,说白了就看三个进程谁先联系到zookepeer,那另外两个就随时等待,随时watch这个资源,这个Znode,它的持有者还是不是存在,因为哪个进程占用了这个zookepeer,哪个进程就会周期性的,比如1秒钟一次,向这个zookepeer更新这个占用状态,另外两个节点,就随时监控着原有的占用的进程是否持续性在更新它的时间戳,它不在更新,等待一个周期、两个周期等,三个周期还不进行状态更新,那么剩下的这两个就再次看谁先抢占这个Znode,那么谁抢中了,谁就是下一个Leader,这也是一种协作机制,这种协作机制,依赖于一个外部的存储系统zookepeer,所以这个zookepeer也需要做分布式;
    其实kube-controller-manager或者kube-scheduler并没有采用这种内部使用某种协议,来进行多个组件之间做分布式协作Leader/Backup这种逻辑,它用的是上面描述的第二种方式,毕竟这已经有了一个etcd了,甚至连etcd都不是,因为etcd自己的存储系统,存储接口,被Kubernetes的ApiServer做了一次封装,所以ApiServer其实就是一个存储服务,存储的数据ApiServer自己不存,而是存储到了etcd,而etcd自己可以存任何键值数据,而ApiServer加了这个中间层以后,使得底层的etcd存储接口,被抽象成了只能够定义成特定格式的逻辑,比如你可以存为Pod资源,这种YAML格式的数据,可以存Service资源的YAML格式的数据,但是你不能随便存储一个键值进去;
    所以说ApiServer本身就是一个存储服务,那么如果ApiServer已经做了高可用,背后的etcd也做了高可用,那我们就可以让多个kube-scheduler/kube-controller-manager把ApiServer当中,我们所描述的zookepeer存储系统,因此,这个多个kube-scheduler/kube-controller-manager可以抢占ApiServer上的某一固定资源,而后这个多个kube-scheduler/kube-controller-manager谁先抢到,并定时更新,那么谁就是Leader,如果在指定周期内,它不再更新了,那么余下的kube-scheduler/kube-controller-manager再一哄而上,去抢占这个资源,谁先抢到就是谁的;
    从某种意义上来讲,kube-scheduler/kube-controller-manager无非就是在ApiServer上保存了一个专用的Endpoint资源,端点资源,它就是通过所谓的Endpoint来完成相应的功能的,那么多个kube-scheduler/kube-controller-manager就请求抢占ApiServer上一个固定的Endpoint资源,对于kube-controller-manager的这个Endpoint资源就叫做kube-controller-manager,kube-scheduler的这个资源就叫做kube-scheduler,谁抢占就是谁的;
    那么Kubernetes就是通过这种分布式锁的方式,谁抢到谁将其锁定,我们称之为分布式锁,来完成彼此间的协作;
etcd高可用逻辑
    etcd就是一个K/V存储,etcd作为一个存储系统服务来讲,如果它宕机了,那所有的数据都相应的丢失了所以etcd就内建了支持,所谓高可用的机制,而且基于raft协议来完成多个实例间的协作,而raft协议就是上面所提到的分布式协作方式,第一种方式,来完整领导选举,来完成数据一致性协作等,每一个节点都可以读写,但你写到任何一个节点的数据都会被同步到,同一集群中的另外的节点,而且还要确保,数据是强一致的,而这种强一致就是基于raft协议来实现的;
    raft协议从某种意义上来讲,它就是一个简装版的paxos协议,这是计算机分布式应用程序发展历史上非常非常重要的一个协议,到进程位置,各种分布式协作逻辑当中,出现了很多很多种协议,他们都是paxos当年的设计思想或简化,或另辟蹊径的方式,但或多收paxos的影响而设计,这个作者其实据说也用了十年之功,才设计出paxos出来;
    而raft比paxos协议要轻量化,更适用于轻量级分布式应用协作的应用场景,而且它的功能并不比paxos要弱,目前来看,如果要使用JAVA编程语言,开发分布式协作应用,一般会使用paxos协议或paxos协议的另外一个变种,就是在zookepeer之上锁使用的协议,我们称之为Zab协议,Zab叫zookepeer的原子广播协议,由Google研发的变种,它比paxos要轻量化,而且它也是zookepeer内部的协作协议;
    现在如果使用Go语言去开发应用做分布式协作,更多人们都会使用raft协议,如果我们要使用etcd的话,那么etcd内建就有高可用实现方式,我们只需要想办法,组件出多个etcd节点出来,让这些节点彼此之间能够协作激活他们内部的这个raft协议的协作逻辑,他们就可以立即成为一个集群,而且还是一个强一致性的集群,每一个节点都能读能写,它不像MySQL主从复制,它每个节点都能读写;
    而分布式系统当中,面临的一个巨大问题在于网络分区,这是非常常见的现象,分布式系统当中有一个著名的理论,叫CAP理论,任何一个分布式系统,只能满足CAP三种特性的两种就可以,C一致性、A可用性、P分区容错性;
    所以一个分布式系统,要么是CA的要么是AP的或者是CP的,一般我们不能舍弃A但是,但是分区又是一个很场景的现象,三个节点网络突然抖动,出现故障,那么 就出现网络分区了,如果不能容错,任何一个节点一离线,一抖动那整个服务都没法用了,因此P也是必须要选择的,那我们就之能舍弃C了,但舍弃C也不行,所有节点的数据不一致,那到底是哪个节点数据是真哪个节点是假;
    因此CAP这种方式,早期是一种参考理论模型,即便证明了分布系统都必须满足这些特点,但是后来BASE从另外一个角度来确保这三个维度上的特点都能被满足,也就是说一致性并不是被舍弃的,而只是从强一致,走向了弱一致;
    比如一般我们称之为,最终一致性,刚开始的时候,可能三个节点每个节点的数据是不一样的,但是过了一个时间窗口之后,它一定的一致的,这种我们称之为最终一致性;
   所以现在很多分布式系统的理论都是基于BASE理论,来设计的,其实etcd本身是一个强一致性的产品,它必须要遵循CAP这几种理论,但是从某种意义上来讲,它实际上是BASE理论的一种实践;
    于是为了确保,在网络发生分区的时候,不至于整个集群无所适从,一旦发生分区的时候,我们不能够允许,两边都能工作,他们彼此之间只是通过网络联系不到了,etcd-node1可以继续工作,但是etcd-node2和etcd-node3,但是etcd-node1和etcd-node2、etcd-node3不能通信,但是etcd-node2和etcd-node3能通信,但是某一个客户端,正在与etcd-node1通信,在里面存取数据,又有一个客户端正在etcd-node2进行通信,很有可能两百将来存储的数据是不一样的,然后过一段时间之后三个节点都能通信了,那数据不一样,此时就冲突了;
    这个时候我们就称之为脑裂,因此一个要求数据一致的分布式系统,必须要避免脑裂,那么对于etcd是这样的,我们要确保在某一时刻,一旦发生分区,只能有一部分可以代表原集群继续工作,那么在网络发生分区的情况下,可以代表集群继续工作的,就是多数方,少数方,就必须放弃自己所提供的服务,拒绝向任何客户端提供服务,以避免脑裂,那么这种判断机制,我们就称之为quorum叫做,法定票数,继续代表集群工作的我们称之为with quorum表示拥有法定票数,否则另外一方一定是 without quorum;
    假如每一个节点都代表一票,所以一旦发生分区的时候,我们的票数大于盘数,必须是大于半数才能避免脑裂的,如果一方他们发生分区之后得票总数是大于盘数的,它就可以继续代表集群工作,那etcd就是基于这种逻辑来工作,因此etcd节点的数量,必须得是奇数个就是这个原因,因此我们构建etcd集群应该是3个、5个或者7个节点,再多也没什么用,因为每一个节点的数据都是一模一样的,各自节点都是副本,所以只要有一个节点在,我们的数据就在;
    之所以做多个,是因为每一个节点都能做读写,在某种意义上来讲,每一个节点都能读写,在某一时刻,如果大家写的数据并不一样,它还能在某种意义上实现一些负载均衡的效果,如果我们有7个节点,那么用户提交一个数据,就需要写7次,所以再多的话,对写操作的影响可能会越来越严重,因此一般而言,小规模的Kubernetes集群3个节点,大规模的集群7个节点就能解决问题了;

环境规划

    通过上面的概述,分别总结测试、生产环境的部署架构;
测试环境
1、可以使用单Master节点,单etcd节点;
2、Node主机数量按需而定;
3、NFS或者Glusterfs等存储系统;
生产环境
1、高可用etcd集群,建立3、5或者7个节点;
2、高可用Master;
    kube-apiserver无状态,可多实例;
        借助于keepalived进行vip浮动实现多实例冗余;
        或在多实例前端通过HAProxy或者Nginx反代,并借助于keepalived对代理服务器进冗余;
    kube-scheduler及kube-controller-manager各自只能有一个活动实例,但可以有多个备用;
        各自自带Leadere选举功能,并且默认出于启用状态;
3、多Node主机,数量越多,冗余能力越强;
4、Ceph、Glusterfs、ISCSI、FC SAN以及各种云存储用来进行PV存储;
节点名称 IP地址 角色 组件
node01.cce.com 172.16.1.1 Master etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubelet、kubectl、nginx
node02.cce.com 172.16.1.2 Master etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubelet、kubectl
node03.cce.com 172.16.1.3 Master etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubelet、kubectl
node04.cce.com 172.16.1.4 Node kube-proxy、kubelet
node05.cce.com 172.16.1.5 Node kube-proxy、kubelet

部署

    下面开始正式部署Kubernetes集群,由于实验机器配置原因,采用三主两从的架构;
etcd
    分别在node01、node02、node03上部署etcd,组成一个名为k8s的etcd集群;
参数介绍
ETCD_DATA_DIR:数据目录;
ETCD_LISTEN_PEER_URLS:集群节点内部通信使用的URL;
ETCD_LISTEN_CLIENT_URLS:供外部客户端使用的url;
ETCD_INITIAL_ADVERTISE_PEER_URLS:广播给集群内其他成员访问的URL;
ETCD_ADVERTISE_CLIENT_URLS:广播给外部客户端使用的url,和ETCD_LISTEN_CLIENT_URLS一样;
ETCD_INITIAL_CLUSTER:集群成员初始化;
ETCD_CERT_FILE:etcd的证书;
ETCD_KEY_FILE:etcd的私钥;
ETCD_CLIENT_CERT_AUTH:是否要验证客户端证书,如果不验证任何人都可以访问;
ETCD_TRUSTED_CA_FILE:如果要检查客户端的证书,使用哪个CA来认证这个证书是否可信;
ETCD_AUTO_TLS:是否自动生成客户端证书和私钥,一般不需要;
ETCD_PEER_CERT_FILE:etcd各节点通信的证书;
ETCD_PEER_KEY_FILE:etcd各节点通信的私钥;
ETCD_PEER_CLIENT_CERT_AUTH:是否要验证节点证书;
ETCD_PEER_TRUSTED_CA_FILE:如果要检查节点的证书,使用哪个CA来认证这个证书是否可信;
ETCD_PEER_AUTO_TLS:是否自动生成节点证书和私钥,一般不需要;
非TLS模式
# 配置主机名解析
[root@node01 ~]# cat /etc/hosts|grep '.*com$'
172.16.1.1 node1 node01 node01.cce.com etcd01 etcd01.cce.com
172.16.1.2 node2 node02 node02.cce.com etcd02 etcd02.cce.com
172.16.1.3 node3 node03 node03.cce.com etcd03 etcd03.cce.com
# 配置yum源(在三个节点都执行)
[root@node01 ~]# wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
# 分别安装etcd(在三个节点都执行)
[root@node01 ~]# yum install -y etcd
# 创建数据目录(在三个节点都执行)
[root@node01 ~]# mkdir -pv /data/etcd/data
[root@node01 ~]# chown etcd:etcd /data/etcd/ -R
# 配置etcd(在三个节点都配置)
[root@node01 ~]# grep '^[^#]' /etc/etcd/etcd.conf
ETCD_DATA_DIR="/data/etcd/data" # 数据目录
ETCD_LISTEN_PEER_URLS="http://172.16.1.1:2380" # 集群节点内部通信使用的URL
ETCD_LISTEN_CLIENT_URLS="http://172.16.1.1:2379" # 供外部客户端使用的url
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://localhost:2380" # 广播给集群内其他成员访问的UR
ETCD_ADVERTISE_CLIENT_URLS="http://172.16.1.1:2379" # 广播给外部客户端使用的url,和ETCD_LISTEN_CLIENT_URLS一样
ETCD_INITIAL_CLUSTER="etcd01=http://172.16.1.1:2380,etcd02=http://172.16.1.2:2380,etcd03=http://172.16.1.3:2380" # 集群成员初始化
# 启动etcd(在三个节点都执行)
[root@node01 ~]# systemctl daemon-reload
[root@node01 ~]# systemctl start etcd
# 查看集群选举信息
[root@node01 ~]# etcdctl --endpoints="http://172.16.1.1:2379" member list
23f7d853639f3654: name=etcd03 peerURLs=http://172.16.1.3:2380 clientURLs=http://172.16.1.3:2379 isLeader=false
821e51093cc43832: name=etcd01 peerURLs=http://172.16.1.1:2380 clientURLs=http://172.16.1.1:2379 isLeader=false
9f72c56430a62979: name=etcd02 peerURLs=http://172.16.1.2:2380 clientURLs=http://172.16.1.2:2379 isLeader=true
# 查看集群状态
[root@node01 ~]# etcdctl --endpoints="http://172.16.1.1:2379" cluster-health
member 23f7d853639f3654 is healthy: got healthy result from http://172.16.1.3:2379
member 821e51093cc43832 is healthy: got healthy result from http://172.16.1.1:2379
member 9f72c56430a62979 is healthy: got healthy result from http://172.16.1.2:2379
cluster is healthy
TLS模式
# 生成etcd证书
[root@node01 ~]# git clone https://github.com/iKubernetes/k8s-certs-generator.git
[root@node01 ~]# cd k8s-certs-generator/
[root@node01 k8s-certs-generator]# bash gencerts.sh etcd
[root@node01 k8s-certs-generator]# ls etcd/pki/
apiserver-etcd-client.crt ca.crt client.crt peer.crt server.crt
apiserver-etcd-client.key ca.key client.key peer.key server.key
[root@node01 k8s-certs-generator]# cp -a etcd/pki/ /etc/etcd/pki
# 分发给各节点
[root@node01 k8s-certs-generator]# scp -r /etc/etcd/pki node2:/etc/etcd/pki
[root@node01 k8s-certs-generator]# scp -r /etc/etcd/pki node3:/etc/etcd/pki
# 分别修改三台etcd配置Security配置段,并且链接全部变成https,既然启用了ssl,那么就不能再使用IP了,得使用域名了
[root@node01 ~]# grep '^[^#]' /etc/etcd/etcd.conf
ETCD_DATA_DIR="/data/etcd/data"
ETCD_LISTEN_PEER_URLS="https://172.16.1.1:2380"
ETCD_LISTEN_CLIENT_URLS="https://172.16.1.1:2379"
ETCD_NAME="etcd01"
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://etcd01.cce.com:2380"
ETCD_ADVERTISE_CLIENT_URLS="https://etcd01.cce.com:2379"
ETCD_INITIAL_CLUSTER="etcd01=https://etcd01.cce.com:2380,etcd02=https://node02.cce.com:2380,etcd03=https://node03.cce.com:2380"  # 这里证书使用的域名cce.com,所以这里集群内部访问也需要使用域名,否则会报错error "remote error: tls: bad certificate", ServerName
ETCD_INITIAL_CLUSTER_TOKEN="k8s-etcd-cluster"
ETCD_CERT_FILE="/etc/etcd/pki/server.crt"
ETCD_KEY_FILE="/etc/etcd/pki/server.key"
ETCD_CLIENT_CERT_AUTH="true"
ETCD_TRUSTED_CA_FILE="/etc/etcd/pki/ca.crt"
ETCD_AUTO_TLS="false"
ETCD_PEER_CERT_FILE="/etc/etcd/pki/peer.crt"
ETCD_PEER_KEY_FILE="/etc/etcd/pki/peer.key"
ETCD_PEER_CLIENT_CERT_AUTH="true"
ETCD_PEER_TRUSTED_CA_FILE="/etc/etcd/pki/ca.crt"
ETCD_PEER_AUTO_TLS="false"
# 重启服务(在三个节点都执行)
[root@node01 ~]# systemctl daemon-reload
[root@node01 ~]# systemctl start etcd
# 检查集群成员信息
[root@node01 ~]# etcdctl --endpoints="https://etcd01.cce.com:2379" --key-file=/etc/etcd/pki/client.key --cert-file=/etc/etcd/pki/client.crt --ca-file=/etc/etcd/pki/ca.crt member list
6147711da86b8a61: name=etcd02 peerURLs=https://node02.cce.com:2380 clientURLs=https://etcd02.cce.com:2379 isLeader=false
88a77e250221767b: name=etcd01 peerURLs=https://node01.cce.com:2380 clientURLs=https://etcd01.cce.com:2379 isLeader=true
97ae5618cec09aaf: name=etcd03 peerURLs=https://node03.cce.com:2380 clientURLs=https://etcd03.cce.com:2379 isLeader=false
# 查看集群状态
[root@node01 ~]# etcdctl --endpoints="https://etcd01.cce.com:2379" --key-file=/etc/etcd/pki/client.key --cert-file=/etc/etcd/pki/client.crt --ca-file=/etc/etcd/pki/ca.crt cluster-health
member 6147711da86b8a61 is healthy: got healthy result from https://etcd02.cce.com:2379
member 88a77e250221767b is healthy: got healthy result from https://etcd01.cce.com:2379
member 97ae5618cec09aaf is healthy: got healthy result from https://etcd03.cce.com:2379
cluster is healthy
第一个Master节点
    接下来开始部署我们的kubernetes,首先部署Master节点;
apiserver
# 生成必要的证书和密钥
[root@node01 k8s-certs-generator]# sh gencerts.sh k8s
Enter Domain Name [ilinux.io]: cce.com
Enter Kubernetes Cluster Name [kubernetes]: kubernetes
Enter the IP Address in default namespace 
  of the Kubernetes API Server[10.96.0.1]: 
Enter Master servers name[master01 master02 master03]: node01 node02 node03
# 三台Master都创建木兰路
[root@node01 ~]#  mkdir /etc/kubernetes
# 复制证书
[root@node01 ~]# cp k8s-certs-generator/kubernetes/node01/* /etc/kubernetes/ -r
# 复制node02和node03证书
[root@node01 ~]# scp -r k8s-certs-generator/kubernetes/node02/* node2:/etc/kubernetes/
[root@node01 ~]# scp -r k8s-certs-generator/kubernetes/node03/* node3:/etc/kubernetes/
# 解压kubernetes
[root@node01 ~]# tar xf kubernetes-server-linux-amd64.tar.gz
[root@node01 ~]# mv kubernetes /usr/local/

# 准备Master所需要的二进制文件、Unit文件(直接在github上克隆)
[root@node01 ~]# git clone https://github.com/caichangen/k8s-bin-inst.git
# 复制配置文件
[root@node01 ~]# cp k8s-bin-inst/master/etc/kubernetes/* /etc/kubernetes/
# 复制Unit-file文件
[root@node01 ~]# cp k8s-bin-inst/master/unit-files/* /usr/lib/systemd/system/
# 修改apiserver配置文件
[root@node01 ~]# cat /etc/kubernetes/apiserver
###
# kubernetes system config
#
# The following values are used to configure the kube-apiserver
#

# The address on the local server to listen to.
KUBE_API_ADDRESS="--advertise-address=0.0.0.0"

# The port on the local server to listen on.
KUBE_API_PORT="--secure-port=6443 --insecure-port=0"

# Comma separated list of nodes in the etcd cluster
KUBE_ETCD_SERVERS="--etcd-servers=https://etcd01.cce.com:2379,https://etcd02.cce.com:2379,https://etcd03.cce.com:2379"

# Address range to use for services
KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.96.0.0/12"

# default admission control policies
KUBE_ADMISSION_CONTROL="--enable-admission-plugins=NodeRestriction"

# Add your own!
KUBE_API_ARGS="--authorization-mode=Node,RBAC \
    --client-ca-file=/etc/kubernetes/pki/ca.crt \
    --enable-bootstrap-token-auth=true \
    --etcd-cafile=/etc/etcd/pki/ca.crt \
    --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt \
    --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key \
    --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt \
    --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key \
    --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname \
    --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt \
    --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key \
    --requestheader-allowed-names=front-proxy-client \
    --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt \
    --requestheader-extra-headers-prefix=X-Remote-Extra- \
    --requestheader-group-headers=X-Remote-Group \
    --requestheader-username-headers=X-Remote-User\
    --service-account-key-file=/etc/kubernetes/pki/sa.pub \
    --tls-cert-file=/etc/kubernetes/pki/apiserver.crt \
    --tls-private-key-file=/etc/kubernetes/pki/apiserver.key \
    --token-auth-file=/etc/kubernetes/token.csv"
# 创建运行用户kube
[root@node01 ~]# useradd -r kube
# 创建kubernetes自己用到的临时状态保存目录
[root@node01 ~]# mkdir /var/run/kubernetes
[root@node01 ~]# chown kube:kube /var/run/kubernetes -R
# 启动apiserver服务
[root@node01 ~]# systemctl daemon-reload
[root@node01 ~]# systemctl start kube-apiserver
[root@node01 ~]# netstat -ntlp|grep 6443
tcp6       0      0 :::6443                 :::*                    LISTEN      25829/kube-apiserve 
kubectl
# 配置kubectl
[root@node01 ~]# ln -sv /usr/local/kubernetes/server/bin/kubectl /usr/bin/kubectl
# 配置自动补齐
[root@node01 ~]# kubectl completion bash > /usr/share/bash-completion/completions/kubectl
[root@node01 ~]# source /usr/share/bash-completion/completions/kubectl
# 配置认证信息
[root@node01 ~]# mkdir ~/.kube
[root@node01 ~]# cp /etc/kubernetes/auth/admin.conf ~/.kube/config
[root@node01 ~]# chown $(id -u):$(id -g) $HOME/.kube/config
# 查看配置
[root@node01 ~]# kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://node01.cce.com:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: k8s-admin
  name: k8s-admin@kubernetes
current-context: k8s-admin@kubernetes
kind: Config
preferences: {}
users:
- name: k8s-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
公共
    创建ClusterRoleBingding,这个ClusterRoleBingding是我们集群上没有的,我们要手动创建,把/etc/kubernetes/token.csv文件当中生成的用户帐号bootstrapper,把它组或者用户绑定到内部专用于允许做bootstrap的这个集群角色上面来即可,这个角色是内建的,叫做node-bootstrapper,这个步骤很关键,否则节点是无法加入集群的;
# 绑用户
[root@node01 ~]# kubectl create clusterrolebinding system:bootstrapper --group=system:bootstrappers --clusterrole=system:node-bootstrapper

# 或者(可以绑,用户,也可以绑租)

# 绑定组
[root@node01 ~]# kubectl create clusterrolebinding system:bootstrapper --user=system:bootstrapper --clusterrole=system:node-bootstrapper
[root@node01 ~]# kubectl describe clusterrolebindings.rbac.authorization.k8s.io system:bootstrapper
Name:         system:bootstrapper
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  ClusterRole
  Name:  system:node-bootstrapper
Subjects:
  Kind  Name                 Namespace
  ----  ----                 ---------
  User  system:bootstrapper 
kube-controller-manager
--bind-address:controller-manager监听的地址;
--allocate-node-cidrs:是否给节点自动分配子网网段;
--authentication-kubeconfig:controller-manager作为客户端连接apiserver时用到的kubeconfig配置文件;
--cluster-cidr:集群地址,也就是所谓的Pod网络;
--node-cidr-mask-size:每个节点的子网掩码长度;
--leader-elect:是否要启用kube-controller-manager的领导选举功能,我们有三个节点,所以必须启用,启用之后他们的争抢一个endpoint;
# 直接启动即可
[root@node01 ~]# systemctl start kube-controller-manager.service
kube-scheduler
--address:kube-scheduler监听的地址;
--kubeconfig:kube-scheduler连入apiserver用到的kubeconfig文件;
--leader-elect:是否要启用kube-scheduler的领导选举功能,我们有三个节点,所以必须启用,启用之后他们的争抢一个endpoint;
# 直接启动即可
[root@node01 ~]# systemctl start kube-scheduler.service
检测Master部署是否正常
    第一个Master节点部署完成,检测下该Master节点的各服务是否正常;
[root@node01 ~]# kubectl get cs
NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok                  
etcd-1               Healthy   {"health":"true"}   
etcd-2               Healthy   {"health":"true"}   
etcd-0               Healthy   {"health":"true"}   
controller-manager   Healthy   ok 
第一个Node节点
    有了Master节点就可以把Node加入到集群中来了,加入集群的方式要基于bootstrap的方式把它拉入到集群当中,并且第一次认证是基于token认证,我们也称之为bootstraptoken,随后认证将会基于二者的证书互相进行认证,并且通过https的方式进行通信;
    每个Node节点只需要运行两个程序,一个是kubelet一个是kube-proxy以及docker容器引擎,如果我们期望我们的service工作于ipvs模式下,我们还要确保手动去装入每一个需要用到的ipvs的模块;
docker
    安装Docker容器引擎,作为kubernetes的基础支撑;
# Docker
[root@node01 ~]# wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
[root@node01 ~]# wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker.repo
[root@node04 ~]# yum install -y bash-completion docker-ce
[root@node04 ~]# systemctl start docker
# 配置内核参数
[root@node04 ~]# cat >> /etc/sysctl.conf << EOF
# iptables透明网桥的实现
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-arptables = 1
EOF
[root@node04 ~]# sysctl -p
# 设定镜像加速器
[root@node04 ~]# cat >> /etc/docker/daemon.json << EOF
{
    "registry-mirrors": ["https://owcyje1z.mirror.aliyuncs.com"]
}
EOF
# 设定iptables
[root@node04 ~]# sed -i '/^ExecStart.*/aExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT' /usr/lib/systemd/system/docker.service
[root@node04 ~]# systemctl daemon-reload 
[root@node04 ~]# systemctl restart docker
kubelet
    kubelet支持从本地文件系统上的某文件中加载配置参数,其功能等同于使用命令行参数,但它大大简化了部署Node的配置复杂度,其配置遵循KubeletConfigration结构,存储为YAML或者json格式,kubelet程序通过--config选项加载配置文件,配置示例如下;
[root@node04 ~]# git clone https://github.com/caichangen/k8s-bin-inst.git 
[root@node04 ~]# cat k8s-bin-inst/nodes/var/lib/kubelet/config.yaml 
address: 0.0.0.0
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
  anonymous:
    enabled: false
  webhook:
    cacheTTL: 2m0s
    enabled: true
  x509:
    clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
  mode: Webhook
  webhook:
    cacheAuthorizedTTL: 5m0s
    cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
configMapAndSecretChangeDetectionStrategy: Watch
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuCFSQuotaPeriod: 100ms
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
  imagefs.available: 15%
  memory.available: 100Mi
  nodefs.available: 10%
  nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: false
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kind: KubeletConfiguration
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeLeaseDurationSeconds: 40
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
rotateCertificates: true
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
# 配置二进制程序
[root@node04 ~]# tar xf kubernetes-node-linux-amd64.tar.gz -C /usr/local/
[root@node04 ~]# ls /usr/local/kubernetes/node/bin/
kubeadm  kubectl  kubelet  kube-proxy
# 配置工作目录
[root@node04 ~]# cp -r k8s-bin-inst/nodes/var/lib/kubelet /var/lib/
[root@node04 ~]# ls /var/lib/kubelet/config.yaml
/var/lib/kubelet/config.yaml
# 提供配置文件
[root@node04 ~]# cp -r k8s-bin-inst/nodes/etc/kubernetes/ /etc/
[root@node04 ~]# ls /etc/kubernetes/
config  kubelet  proxy
# 修改kubelet配置,加入pause镜像加载地址--pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.1,pause镜像很关键,它是用来运行Pod的一个基础架构的镜像,所以必须能拉取到;
[root@node04 ~]# grep 'pause' /etc/kubernetes/kubelet
    --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.1 \
# 将先前部署Master生成的证书COPY过来
[root@node01 ~]# scp -r k8s-certs-generator/kubernetes/kubelet/* node4:/etc/kubernetes/
[root@node04 ~]# mkdir /etc/kubernetes/manifests
[root@node04 ~]# ls /etc/kubernetes/{auth,pki,manifests} -d
/etc/kubernetes/auth  /etc/kubernetes/manifests  /etc/kubernetes/pki
# 可以看到我们的/etc/kubernetes/kubelet配置文件,里面需要用到CNI插件,所以我们需要在github上去下载CNI插件到本地,下载地址<a href="https://github.com/containernetworking/plugins/releases">https://github.com/containernetworking/plugins/releases</a>
[root@node04 ~]# mkdir -p /opt/cni/bin
[root@node04 ~]# tar xf cni-plugins-linux-amd64-v0.8.5.tgz -C /opt/cni/bin/
[root@node04 ~]# ls /opt/cni/bin/
bandwidth  dhcp      flannel      host-local  loopback  portmap  sbr     tuning
bridge     firewall  host-device  ipvlan      macvlan   ptp      static  vlan
# 复制kubelet的unit-file文件
[root@node04 ~]# cp k8s-bin-inst/nodes/unit-files/kubelet.service /usr/lib/systemd/system/
启动kubelet
[root@node04 ~]# systemctl daemon-reload 
[root@node04 ~]# systemctl start kubelet 

# 查看主节点是否收到kubelet启动之后发出的证书签署请求
[root@node01 ~]# kubectl get csr
NAME        AGE   REQUESTOR             CONDITION
csr-vcrlc   26s   system:bootstrapper   Pending
# 签署证书
[root@node01 ~]# kubectl certificate approve csr-vcrlc
certificatesigningrequest.certificates.k8s.io/csr-vcrlc approved
# 查看加入进来的Node
[root@node01 ~]# kubectl get nodes -o wide
NAME             STATUS     ROLES    AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION          CONTAINER-RUNTIME
node04.cce.com   NotReady   <none>   89s   v1.17.3   172.16.1.4    <none>        CentOS Linux 7 (Core)   3.10.0-957.el7.x86_64   docker://19.3.7
kube-proxy
    kube-proxy是Kubernetes的核心组件,部署在每个Node节点上,它是实现Kubernetes Service的通信与负载均衡机制的重要组件; kube-proxy负责为Pod创建代理服务,从apiserver获取所有server信息,并根据server信息创建代理服务,实现server到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络;
# unit-file环境变量配置文件
[root@node04 ~]# ls /etc/kubernetes/proxy
/etc/kubernetes/proxy
# 复制kube-proxy配置
[root@node01 ~]# scp -r k8s-bin-inst/nodes/var/lib/kube-proxy/ node4:/var/lib/
[root@node04 ~]# ls /var/lib/kube-proxy/
config.yaml
# 启用ipvs模块
[root@node04 ~]# cat >> /etc/sysconfig/modules/ipvs.modules << EOF
#!/bin/bash
#
ipvs_mods_dir="/usr/lib/modules/\$(uname -r)/kernel/net/netfilter/ipvs"
for mod in \$(ls \$ipvs_mods_dir | grep -o "^[^.]*"); do
    /sbin/modinfo -F filename \$mod &> /dev/null
    if [ \$? -eq 0 ]; then
        /sbin/modprobe \$mod
    fi
done
EOF
# 修改文件权限,并手动为当前系统加载内核模块
[root@node1 ~]# chmod +x /etc/sysconfig/modules/ipvs.modules
[root@node1 ~]# bash /etc/sysconfig/modules/ipvs.modules
# 安装ipvsadm管理工具
[root@node04 ~]# yum install -y ipvsadm
# 复制unit-file文件
[root@node01 ~]# scp k8s-bin-inst/nodes/unit-files/kube-proxy.service node4:/usr/lib/systemd/system
# 启动kube-proxy
[root@node04 ~]# systemctl daemon-reload                  
[root@node04 ~]# systemctl start kube-proxy.service
Flannel
    在Node节点部署flannel网络组件;
# 下载配置清单
[root@node01 ~]# wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
# 修改网络模式为host-gw(测试flannel的vxlan模式对于kubernetes 1.17版本的支持貌似不是很好)
[root@node01 ~]# grep 'host-gw' kube-flannel.yml 
        "Type": "host-gw"
# 修改镜像为国内镜像,拉取快
[root@node01 ~]# sed -i 's@\<quay.io@quay.mirrors.ustc.edu.cn@g' kube-flannel.yml
# 配置flannel网络并查看nodes状态
[root@node01 ~]# kubectl apply -f kube-flannel.yml 
[root@node01 ~]# kubectl get nodes
NAME             STATUS   ROLES    AGE   VERSION
node04.cce.com   Ready    <none>   40m   v1.17.3
第二个Master节点
    部署第二个Master节点的步骤和第一个Master节点的步骤是一样的;
apiserver
    配置ApiServer,为整个kubernetes集群提供网关;
# 复制程序包
[root@node01 ~]# scp kubernetes-server-linux-amd64.tar.gz node2:/root
# 解压二进制程序
[root@node02 ~]# tar xf kubernetes-server-linux-amd64.tar.gz -C /usr/local/
# 复制配置文件
[root@node01 ~]# scp k8s-bin-inst/master/etc/kubernetes/* node2:/etc/kubernetes/
# 查看此前复制的证书和配置文件
[root@node02 ~]# ll /etc/kubernetes/
total 24
-rw-r--r-- 1 root root 1929 Mar  9 09:24 apiserver
drwxr-xr-x 2 root root   77 Mar  9 00:05 auth
-rw-r--r-- 1 root root  538 Mar  9 09:24 config
-rw-r--r-- 1 root root 1018 Mar  9 09:24 controller-manager
drwxr-xr-x 2 root root 4096 Mar  9 00:05 pki
-rw-r--r-- 1 root root  211 Mar  9 09:24 scheduler
-rw-r--r-- 1 root root   75 Mar  9 00:05 token.csv
# 复制unit-file
[root@node01 ~]# scp k8s-bin-inst/master/unit-files/* node2:/usr/lib/systemd/system
# 修改apiserver的etcd的配置
[root@node02 ~]# grep 'etcd0' /etc/kubernetes/apiserver
KUBE_ETCD_SERVERS="--etcd-servers=https://etcd01.cce.com:2379,https://etcd02.cce.com:2379,https://etcd03.cce.com:2379"
# 创建运行用户
[root@node02 ~]# useradd -r kube
# 创建工作目录
[root@node02 ~]# mkdir /var/lib/kubernetes
[root@node02 ~]# chown kube:kube /var/lib/kubernetes
# 启动服务
[root@node02 ~]# systemctl daemon-reload
[root@node02 ~]# systemctl start kube-apiserver.service 
[root@node02 ~]# netstat -ntlp|grep 6443
tcp6       0      0 :::6443                 :::*                    LISTEN      6947/kube-apiserver 
kubectl
    配置kubectl,为kubernetes用户提供命令行工具;
# 配置kubectl
[root@node02 ~]# ln -sv /usr/local/kubernetes/server/bin/kubectl /usr/bin/kubectl
# 配置自动补齐
[root@node02 ~]# kubectl completion bash > /usr/share/bash-completion/completions/kubectl
[root@node02 ~]# source /usr/share/bash-completion/completions/kubectl
# 配置认证信息
[root@node02 ~]# mkdir ~/.kube
[root@node02 ~]# cp /etc/kubernetes/auth/admin.conf ~/.kube/config
[root@node02 ~]# chown $(id -u):$(id -g) $HOME/.kube/config
[root@node02 ~]# kubectl get nodes
NAME             STATUS   ROLES    AGE   VERSION
node04.cce.com   Ready    <none>   8h    v1.17.3
kube-controller-manager/kube-scheduler
    这两个组件都是直接启动即可,复制过来的配置已经完全适用;
[root@node02 ~]# systemctl start kube-scheduler.service kube-controller-manager.service
第三个Master节点
    部署第三个Master节点的步骤和第一个Master节点的步骤是一样的;
apiserver
# 复制程序包
[root@node01 ~]# scp kubernetes-server-linux-amd64.tar.gz node3:/root
# 解压二进制程序
[root@node03 ~]# tar xf kubernetes-server-linux-amd64.tar.gz -C /usr/local/
# 复制配置文件
[root@node01 ~]# scp k8s-bin-inst/master/etc/kubernetes/* node3:/etc/kubernetes/
# 查看此前复制的证书和配置文件
[root@node03 ~]# ll /etc/kubernetes/
total 24
-rw-r--r-- 1 root root 1929 Mar  9 10:12 apiserver
drwxr-xr-x 2 root root   77 Mar  9 00:05 auth
-rw-r--r-- 1 root root  538 Mar  9 10:12 config
-rw-r--r-- 1 root root 1018 Mar  9 10:12 controller-manager
drwxr-xr-x 2 root root 4096 Mar  9 00:05 pki
-rw-r--r-- 1 root root  211 Mar  9 10:12 scheduler
-rw-r--r-- 1 root root   75 Mar  9 00:05 token.csv
# 复制unit-file
[root@node01 ~]# scp k8s-bin-inst/master/unit-files/* node3:/usr/lib/systemd/system
# 修改apiserver的etcd的配置
[root@node03 ~]# grep 'etcd0' /etc/kubernetes/apiserver
KUBE_ETCD_SERVERS="--etcd-servers=https://etcd01.cce.com:2379,https://etcd02.cce.com:2379,https://etcd03.cce.com:2379"
# 创建运行用户
[root@node03 ~]# useradd -r kube
# 创建工作目录
[root@node03 ~]# mkdir /var/lib/kubernetes
[root@node03 ~]# chown kube:kube /var/lib/kubernetes
# 启动服务
[root@node03 ~]# systemctl daemon-reload
[root@node03 ~]# systemctl start kube-apiserver.service 
[root@node03 ~]# netstat -ntlp|grep 6443
tcp6       0      0 :::6443                 :::*                    LISTEN      6947/kube-apiserver 
kubectl
    配置kubectl,为kubernetes用户提供命令行工具;
# 配置kubectl
[root@node03 ~]# ln -sv /usr/local/kubernetes/server/bin/kubectl /usr/bin/kubectl
# 配置自动补齐
[root@node03 ~]# kubectl completion bash > /usr/share/bash-completion/completions/kubectl
[root@node03 ~]# source /usr/share/bash-completion/completions/kubectl
# 配置认证信息
[root@node03 ~]# mkdir ~/.kube
[root@node03 ~]# cp /etc/kubernetes/auth/admin.conf ~/.kube/config
[root@node03 ~]# chown $(id -u):$(id -g) $HOME/.kube/config
[root@node03 ~]# kubectl get nodes
NAME             STATUS   ROLES    AGE   VERSION
node04.cce.com   Ready    <none>   8h    v1.17.3
kube-controller-manager/kube-scheduler
    这两个组件都是直接启动即可,复制过来的配置已经完全适用;
[root@node03 ~]# systemctl start kube-scheduler.service kube-controller-manager.service
部署CoreDNS
    部署CoreDNS,为我们kubernetes集群提供名称解析功能;
# 创建目录
[root@node01 ~]# mkdir /etc/kubernetes/coredns
[root@node01 ~]# cd /etc/kubernetes/coredns
# 下载文件
[root@node01 coredns]# wget https://raw.githubusercontent.com/coredns/deployment/master/kubernetes/coredns.yaml.sed
# 下载处理脚本
[root@node01 coredns]# wget https://raw.githubusercontent.com/coredns/deployment/master/kubernetes/deploy.sh
# coredns.yaml.sed是一个模版文件,我们需要执行脚本,带入自定义dns的ip,生成一个coredns的YAML格式的配置清单
[root@node01 coredns]# bash deploy.sh -i 10.96.0.10 -r "10.96.0.0/12" -s -t coredns.yaml.sed > coredns.yaml
# 创建coredns
[root@node01 coredns]# kubectl apply -f coredns.yaml
[root@node02 ~]# kubectl get pods -n kube-system                              
NAME                          READY   STATUS    RESTARTS   AGE
coredns-59845f77f8-kcfqh      1/1     Running   0          44s
kube-flannel-ds-amd64-4gf4d   1/1     Running   1          9h
# 创建一个Pod测试解析,检测coredns工作是否正常
[root@node02 ~]# cat >> busybox.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - name: busybox
    image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always
EOF
[root@node02 ~]# kubectl apply -f busybox.yaml
# 查看busybox容器内部的resolv.conf内容,测试正常
[root@node02 ~]# kubectl exec busybox cat /etc/resolv.conf 
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local lan cce.com
options ndots:5
# 在busybox 的pod里解析kubernetes.default 的IP地址,测试正常
[root@node02 ~]# kubectl exec -ti busybox -- nslookup kubernetes.default
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local
第二个Node节点
    有了Master节点就可以把Node加入到集群中来了,加入集群的方式要基于bootstrap的方式把它拉入到集群当中,并且第一次认证是基于token认证,我们也称之为bootstraptoken,随后认证将会基于二者的证书互相进行认证,并且通过https的方式进行通信;
    每个Node节点只需要运行两个程序,一个是kubelet一个是kube-proxy以及docker容器引擎,如果我们期望我们的service工作于ipvs模式下,我们还要确保手动去装入每一个需要用到的ipvs的模块;
docker
    安装Docker容器引擎,作为kubernetes的基础支撑;
# Docker
[root@node01 ~]# wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
[root@node01 ~]# wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker.repo
[root@node05 ~]# yum install -y bash-completion docker-ce
[root@node05 ~]# systemctl start docker
# 配置内核参数
[root@node05 ~]# cat >> /etc/sysctl.conf << EOF
# iptables透明网桥的实现
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-arptables = 1
EOF
[root@node05 ~]# sysctl -p
# 设定镜像加速器
[root@node05 ~]# cat >> /etc/docker/daemon.json << EOF
{
    "registry-mirrors": ["https://owcyje1z.mirror.aliyuncs.com"]
}
EOF
# 设定iptables
[root@node05 ~]# sed -i '/^ExecStart.*/aExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT' /usr/lib/systemd/system/docker.service
[root@node05 ~]# systemctl daemon-reload 
[root@node05 ~]# systemctl restart docker
kubelet
    kubelet支持从本地文件系统上的某文件中加载配置参数,其功能等同于使用命令行参数,但它大大简化了部署Node的配置复杂度,其配置遵循KubeletConfigration结构,存储为YAML或者json格式,kubelet程序通过--config选项加载配置文件;
# 配置二进制程序
[root@node05 ~]# tar xf kubernetes-node-linux-amd64.tar.gz -C /usr/local/
[root@node05 ~]# ls /usr/local/kubernetes/node/bin/
kubeadm kubectl kubelet kube-proxy
# 配置工作目录
[root@node01 ~]# scp -r k8s-bin-inst/nodes/var/lib/kubelet node5:/var/lib/ 
[root@node05 ~]# ls /var/lib/kubelet/config.yaml
/var/lib/kubelet/config.yaml
# 提供配置文件
[root@node05 ~]# scp -r k8s-bin-inst/nodes/etc/kubernetes node5:/etc/
[root@node05 ~]# ls /etc/kubernetes/
config  kubelet  proxy
# 修改kubelet配置,加入pause镜像加载地址--pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.1,pause镜像很关键,它是用来运行Pod的一个基础架构的镜像,所以必须能拉取到;
[root@node05 ~]# grep 'pause' /etc/kubernetes/kubelet
    --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.1 \
# 将先前部署Master生成的证书COPY过来
[root@node01 ~]# scp -r k8s-certs-generator/kubernetes/kubelet/* node5:/etc/kubernetes/
[root@node05 ~]# mkdir /etc/kubernetes/manifests
[root@node05 ~]# ls /etc/kubernetes/{auth,pki,manifests} -d
/etc/kubernetes/auth  /etc/kubernetes/manifests  /etc/kubernetes/pki
# 可以看到我们的/etc/kubernetes/kubelet配置文件,里面需要用到CNI插件,所以我们需要在github上去下载CNI插件到本地,下载地址https://github.com/containernetworking/plugins/releases
[root@node05 ~]# mkdir -p /opt/cni/bin
[root@node05 ~]# tar xf cni-plugins-linux-amd64-v0.8.5.tgz -C /opt/cni/bin/
[root@node05 ~]# ls /opt/cni/bin/
bandwidth dhcp flannel host-local loopback portmap sbr tuning
bridge firewall host-device ipvlan macvlan ptp static vlan
# 复制kubelet的unit-file文件
[root@node05 ~]# scp k8s-bin-inst/nodes/unit-files/kubelet.service node5:/usr/lib/systemd/system/
启动kubelet
[root@node05 ~]# systemctl daemon-reload 
[root@node05 ~]# systemctl start kubelet 

# 查看主节点是否收到kubelet启动之后发出的证书签署请求
[root@node01 ~]# kubectl get csr -n kube-system 
NAME        AGE   REQUESTOR             CONDITION
csr-q89z4   3s    system:bootstrapper   Pending
# 签署证书
[root@node01 ~]# kubectl certificate approve csr-q89z4
certificatesigningrequest.certificates.k8s.io/csr-q89z4 approved
# 查看加入进来的Node
[root@node01 ~]# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
node04.cce.com NotReady <none> 89s v1.17.3 172.16.1.4 <none> CentOS Linux 7 (Core) 3.10.0-957.el7.x86_64 docker://19.3.7
kube-proxy
    kube-proxy是Kubernetes的核心组件,部署在每个Node节点上,它是实现Kubernetes Service的通信与负载均衡机制的重要组件; kube-proxy负责为Pod创建代理服务,从apiserver获取所有server信息,并根据server信息创建代理服务,实现server到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络;
# unit-file环境变量配置文件
[root@node04 ~]# ls /etc/kubernetes/proxy
/etc/kubernetes/proxy
# 复制kube-proxy配置
[root@node01 ~]# scp -r k8s-bin-inst/nodes/var/lib/kube-proxy/ node5:/var/lib/
[root@node05 ~]# ls /var/lib/kube-proxy/
config.yaml
# 启用ipvs模块
[root@node05 ~]# cat >> /etc/sysconfig/modules/ipvs.modules << EOF
#!/bin/bash
#
ipvs_mods_dir="/usr/lib/modules/\$(uname -r)/kernel/net/netfilter/ipvs"
for mod in \$(ls \$ipvs_mods_dir | grep -o "^[^.]*"); do
    /sbin/modinfo -F filename \$mod &> /dev/null
    if [ \$? -eq 0 ]; then
        /sbin/modprobe \$mod
    fi
done
EOF
# 修改文件权限,并手动为当前系统加载内核模块
[root@node1 ~]# chmod +x /etc/sysconfig/modules/ipvs.modules
[root@node1 ~]# bash /etc/sysconfig/modules/ipvs.modules
# 安装ipvsadm管理工具
[root@node05 ~]# yum install -y ipvsadm
# 复制unit-file文件
[root@node01 ~]# scp k8s-bin-inst/nodes/unit-files/kube-proxy.service node5:/usr/lib/systemd/system
# 启动kube-proxy
[root@node05 ~]# systemctl daemon-reload                  
[root@node05 ~]# systemctl start kube-proxy.service
# 查看节点
[root@node01 ~]# kubectl get pods -n kube-system
NAME                          READY   STATUS    RESTARTS   AGE
coredns-59845f77f8-kcfqh      1/1     Running   0          122m
kube-flannel-ds-amd64-54r8z   1/1     Running   0          2m21s
kube-flannel-ds-amd64-q9t9p   1/1     Running   0          14m
校验集群
    集群已经全部部署完成,那么接下来就是直接测试集群是否可用;
校验kube-controller-manager/kube-scheduler的分布式锁
    在上面说过对于kubernetes的kube-controller-manager/kube-scheduler都是通过独占锁的方式来实现的,那么接下来我们就来通过验证kubernetes内部的独占锁,来测试kubernetes的高可用机制;
# 查看现有的endpoint(独占锁)
[root@node01 ~]# kubectl get endpoints -n kube-system
NAME                      ENDPOINTS   AGE
kube-controller-manager   <none>      10h
kube-scheduler            <none>      10h
# 查看kube-controller-manager现在是谁抢占的
[root@node01 ~]# kubectl get endpoints -n kube-system kube-controller-manager -o yaml # 可以看到此时是我们的node1抢占了kube-controller-manager这个endpoint
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"node01.cce.com_10e023e6-eae3-4b68-b562-ada190830854","leaseDurationSeconds":15,"acquireTime":"2020-03-09T01:17:58Z","renewTime":"2020-03-09T02:23:09Z","leaderTransitions":1}'
  creationTimestamp: "2020-03-08T16:20:41Z"
  name: kube-controller-manager
  namespace: kube-system
  resourceVersion: "19492"
  selfLink: /api/v1/namespaces/kube-system/endpoints/kube-controller-manager
  uid: 52ea0caa-a6b5-47a8-93ec-a0fa581f7a0a
# holderIdentity:持有人;
# leaseDurationSeconds:租赁期限;
# acquireTime:获取时间(时间差8个小时);
# renewTime:更新时间(时间差8个小时);
# leaderTransitions:leader切换次数;

# 测试将node1的kube-controller-manager停掉
[root@node01 ~]# systemctl stop kube-controller-manager.service 
# 查看其他Master节点是否会抢占kube-controller-manager的这个endpoint
[root@node01 ~]# kubectl get endpoints -n kube-system kube-controller-manager -o yaml # 可以看到已经切换到了node2
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"node02.cce.com_6d9408f8-2bb1-4a71-a82f-5a75b955e5e2","leaseDurationSeconds":15,"acquireTime":"2020-03-09T02:27:42Z","renewTime":"2020-03-09T02:29:06Z","leaderTransitions":2}'
  creationTimestamp: "2020-03-08T16:20:41Z"
  name: kube-controller-manager
  namespace: kube-system
  resourceVersion: "20464"
  selfLink: /api/v1/namespaces/kube-system/endpoints/kube-controller-manager
  uid: 52ea0caa-a6b5-47a8-93ec-a0fa581f7a0a
创建Deployment校验集群
    创建一个Deployment来测试集群是否工作正常;
[root@node01 ~]# cat test.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    author: caichangen
  name: cce
spec:
  replicas: 4
  selector:
    matchLabels:
      app: cce
  template:
    metadata:
      name: ccepod
      labels:
        app: cce
    spec:
      restartPolicy: Always
      containers:
      - image: ikubernetes/myapp:v1
        name: ccecontainers
---
apiVersion: v1
kind: Service
metadata:
  name: cce
  labels:
    app: cce
spec:
  type: "NodePort"
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30080
  selector:
    app: cce
[root@node01 ~]# kubectl apply -f test.yaml
[root@node01 ~]# kubectl apply -f test.yaml 
deployment.apps/cce created
service/cce created
[root@node01 ~]# kubectl get pod,svc -o wide -l app=cce
NAME                       READY   STATUS    RESTARTS   AGE   IP           NODE             NOMINATED NODE   READINESS GATES
pod/cce-78cd5bfc9f-566l6   1/1     Running   0          4s    10.244.0.8   node04.cce.com   <none>           <none>
pod/cce-78cd5bfc9f-h8g6v   1/1     Running   0          4s    10.244.1.4   node05.cce.com   <none>           <none>
pod/cce-78cd5bfc9f-txcpd   1/1     Running   0          4s    10.244.0.7   node04.cce.com   <none>           <none>
pod/cce-78cd5bfc9f-vrp6g   1/1     Running   0          4s    10.244.1.5   node05.cce.com   <none>           <none>

NAME          TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE   SELECTOR
service/cce   NodePort   10.98.231.67   <none>        80:30080/TCP   4s    app=cce
# 在有kube-proxy组件的节点查看ipvs规则
[root@node04 ~]# ipvsadm -L
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  localhost:30080 rr
  -> 10.244.0.7:http              Masq    1      0          0         
  -> 10.244.0.8:http              Masq    1      0          0         
  -> 10.244.1.4:http              Masq    1      0          0         
  -> 10.244.1.5:http              Masq    1      0          0   
# 在有kube-proxy组件的测试访问svc地址
[root@node04 ~]# curl 10.98.231.67/hostname.html
cce-78cd5bfc9f-566l6
[root@node04 ~]# curl 10.98.231.67/hostname.html
cce-78cd5bfc9f-vrp6g
[root@node04 ~]# curl 10.98.231.67/hostname.html
cce-78cd5bfc9f-txcpd
[root@node04 ~]# curl 10.98.231.67/hostname.html
cce-78cd5bfc9f-h8g6v
#  在有kube-proxy组件的节点地址(该deployment是NodePort的)
[root@node04 ~]# curl 172.16.1.4:30080/hostname.html
cce-78cd5bfc9f-h8g6v
[root@node04 ~]# curl 172.16.1.4:30080/hostname.html
cce-78cd5bfc9f-566l6
[root@node04 ~]# curl 172.16.1.4:30080/hostname.html
cce-78cd5bfc9f-vrp6g
[root@node04 ~]# curl 172.16.1.4:30080/hostname.html
cce-78cd5bfc9f-txcpd
配置Nginx
    配置Nginx四层代理为我们的apiserver提供负载均衡的前端窗口,此处只是提供示例,并没有结合上面的kubernetes集群;
[root@node01 ~]# wget http://nginx.org/download/nginx-1.16.1.tar.gz
[root@node01 ~]# tar xf nginx-1.16.1.tar.gz 
[root@node01 ~]# cd nginx-1.16.1/
[root@node01 ~]# useradd -r www 
[root@node01 ~]# ./configure --prefix=/usr/local/nginx \
--prefix=/usr/local/nginx \
--with-pcre --with-file-aio \
--with-http_ssl_module \
--with-http_flv_module \
--with-http_dav_module \
--with-http_sub_module \
--with-http_mp4_module \
--with-http_realip_module \
--with-http_addition_module \
--with-http_gzip_static_module \
--with-http_stub_status_module \
--lock-path=/usr/local/nginx/nginx.lock \
--pid-path=/var/run/nginx.pid \
--http-scgi-temp-path=/usr/local/nginx/scgi \
--http-fastcgi-temp-path=/usr/local/nginx/fcgi/ \
--http-uwsgi-temp-path=/usr/local/nginx/uwsgi \
--http-proxy-temp-path=/usr/local/nginx/proxy/ \
--http-client-body-temp-path=/usr/local/nginx/client/ \
--with-stream \
--user=www
[root@node01 nginx-1.16.1]# make -j 2
[root@node01 nginx-1.16.1]# make install
# 配置nginx,注意stream配置段不能放在http内部,因为它不是转发http协议的
[root@node01 ~]# cat >> /usr/local/nginx/conf/nginx.conf << EOF
stream {
      log_format ws "$remote_addr $upstream_addr $time_local $status";
      access_log /var/log/nginx/k8s.log ws;
      server {
          listen 172.16.1.1:8443;
          proxy_pass app_server;
      }
      upstream app_server{
          server 172.16.1.1:6443 max_fails=3 fail_timeout=30;  # Master1
          server 172.16.1.2:6443 max_fails=3 fail_timeout=30;  # Master2
          server 172.16.1.3:6443 max_fails=3 fail_timeout=30;  # Master3
      }
}
EOF
[root@node01 ~]# /usr/local/nginx/sbin/nginx
# 测试访问
[root@node01 ~]# curl https://172.16.1.1:8443 -k # 成功放到到apiserver
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {

  },
  "status": "Failure",
  "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
  "reason": "Forbidden",
  "details": {

  },
  "code": 403
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注