容器编排之战——kubernetes

目录

一、kubernetes简介

1、基本概念

1、传统的应用部署方式

2、新的应用部署方式(部署容器)

3、容器编排工具中的战斗机--------Kubernetes

2、Kubernetes核心概念

3、Kubernetes架构和组件

1、Master节点

2、Node节点

3、组件之间的通信

三、常用镜像仓库

四、Kubernetes集群部署方式

二进制方式部署k8s集群 

1、部署Etcd集群

2、部署Flannel网络

3、在Master节点部署组件

4、在Node节点部署组件

kubeadm方式部署k8s集群

1、获取镜像

 2、所有节点安装docker

3、禁用swap分区

4、所有节点安装kubeadm和kubelet

5、配置启动kubelet(所有节点)

6、配置master节点

 7、所有node节点操作

8、配置使用网络插件

错误整理

五、部署Harbor仓库

六、集群基本操作--查看集群信息

查看node的详细信息

创建命名空间

七、发布第一个容器化应用

八、YAML文件语法解析

九、Pod API属性详解

1、Pod调度

1、指定node节点的名称(nodeName)

2、指定node标签(nodeSelector)

2、域名解析

3、进程共享

十、容器属性

1、Pod 里最重要的字段"Containers":

2、k8s 对 Container 的定义,和 Docker 相比并没有什么太大区别。

3、ImagePullPolicy属性

4、Pod的生命周期

1、简介

2、生命周期

3、案例

 4、扩展

十一、Projected Volume

1、什么是Projected Volume?

2、Secret详解

1、命令方式创建secret

2、yaml方式创建Secret:

3、使用secret

4、映射secret key到指定的路径

5、被挂载的secret内容自动更新

6、以环境变量的形式使用Secret

7、实验:

3、ConfigMap详解

1、创建ConfigMap

2、通过命令行参数--from-literal创建

 3、通过指定文件创建

4、指定目录创建

5、通过事先写好configmap的标准yaml文件创建

6、使用ConfigMap的方式

7、通过环境变量使用

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

9、作为volume挂载使用 

4、Downward API

1、环境变量的方式

2、volume挂载方式

5、ServiceAccount

1、什么是ServiceAccount

2、ServiceAccount与UserAccount区别

3、ServiceAccount应用示例

十二、RBAC详解(基于角色的访问控制)

1、什么是RBAC

2、 Role与ClusterRole

3、实验一:role

4、实验二:clusterrole

5、设置上下文和账户切换

十三、容器监控检查及恢复机制

1、命令模式探针

2、http get方式探针

十四、POD的恢复策略

十五、Deployment详解

 1、为什么使用Deployment

2、创建Deployment

十六、Service详解

1、什么是Service

2、Service的几种类型

1、ClusterIP

2、NodePort

3、LoadBalance

4、ExternalName

5、ingress是干嘛的?

6、kube-proxy与iptables的关系

十七、RC资源(了解)

1、什么是RC

2、创建RC

3、完整TOMCAT实例

十八、K8S之暴露IP给外网

1、ClusterIP

2、NodePort(常用)

3、loadbalance

4、Ingress

十九、控制器模式解析

1、什么是控制集群模式

2、控制器种类

二十、滚动更新

二十一、版本回滚

1、查看版本历史

2、回滚到以前的旧版本

3、回滚到更早之前的版本

 二十二、部署DASHBOARD应用

二十三、k8s持久化存储PV和PVC

1、PV和PVC的引入

2、通过NFS实现持久化存储

 3、PV的回收

4、PV&PVC在应用在Mysql的持久化存储实战项目

 5、PV的动态供给

二十四、k8s控制器

1、什么是控制器

2、DaemonSet控制器

 3、StatefulSet控制器

限制:

部署和扩缩保证

部署顺序

收缩顺序

二十五、基于k8s集群的redis-cluster集群


一、kubernetes简介

1、基本概念

kubernetes,简称k8s,是一个开源的,用于管理云平台中多个主机上的容器化的应用,kubernetes的目标是让部署容器化的应用简单且高效,kubernetes提供了应用部署、规划、更新、维护的一种机制。

1、传统的应用部署方式

通过插件或脚本来安装应用。这样做的缺点是应用的运行、配 置、管理、所有生存周期将与当前操作系统绑定,这样做并不利于应用的升级更新/回滚等操作,当然也可以通过创建虚拟机的方式来实现某些功能,但是虚拟机非常重,并不利于 可移植性

2、新的应用部署方式(部署容器)

通过部署容器方式实现,每个容器之间互相隔离,每个容器有自己的文件系统 ,容器之间进程不会相互影响,能区分计算资源。相对于虚拟机,容器能快速部署, 由于容器与底层设施、机器文件系统解耦的,所以它能在不同云、不同版本操作系统间进行迁移。

容器占用资源少、部署快,每个应用可以被打包成一个容器镜像,每个应用与容器间成一对一关系也使容器有更大优势,使用容器可以在 build 或 release 的阶段,为应用创建容器镜像,因为每个应用不需要与其余的应用堆栈组合,也不依赖于生产环境基础结构, 这使得从研发到测试、生产能提供一致环境。类似地,容器比虚拟机轻量、更“透明”, 这更便于监控和管理。

3、容器编排工具中的战斗机--------Kubernetes

Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、 应用容器化管理。在生产环境中部署一个应用程序时,通常要部署该应用的多个实例以便 对应用请求进行负载均衡。
在 Kubernetes 中,我们可以创建多个容器,每个容器里面运行一个应用实例,然后通过内置的负载均衡策略,实现对这一组应用实例的管理、发现、访问,而这些细节都不需要运维人员去进行复杂的手工配置和处理。

2、Kubernetes核心概念

Master

master主要负责资源调度,控制副本,和提供统一访问集群的入口。--核心节点,也是管理节点

master nginx Pod
node1 容器
node2 容器

Node

node是kubernetes集群架构中运行pod的服务节点。node是kubernetes集群操作的单元,用来承载被分配pod的运行,是pod运行的宿主机,有master管理,并汇报容器状态个master,同时根据master要求管理容器生命周期。

Node IP

node节点的ip地址,是kubernetes集群中每个节点的物理网卡的ip地址,是真实存在的物理网络,所有属于这个网络的服务器之间都能够通过这个网络直接通信。

Pod

pod直译是豆荚,可以把容器想象成豆荚里的豆子,把一个或多个关系紧密的豆子包在一起就是豆荚(一个pod)。在k8s中我们不会直接操作容器,而是把容器包装成pod进行管理

运行在node节点上,若干相关容器的组合。pod内包含的容器运行在同一台宿主机上,使用相同的网络命名空间、ip地址和端口,能够通过localhost进行通信。pod是k8s进行创建、调度和管理的最小单位,他提供了比容器更高层次的抽象,使得部署和管理更加灵活。一个pod可以包含一个或多个容器。在公司一般一个pod只运行一个容器,加上自带的pause容器一共两个。

pod就是k8s集群里的“应用”;而一个平台应用,可以由多个容器组成。

 pause容器

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

  • 属于同一个pod的所有容器共享网络的namespace。
  • 如果pod所在的node宕机,会将这个node上的pod重新调度到其他节点上。

Pod Volume

  • docker volume对应kubernetes中的pod volume;
  • 数据卷,挂载宿主机文件、目录或者外部存储到pod中,为应用服务提供存储,也可以解决pod中容器之间共享数据。

资源限制:每个pod可以设置限额的计算机资源,有cpu和memory;

 Pod IP

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

Event

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

Namespace

命名空间将资源对象逻辑上分配到不同的namespace,可以是不同的项目、用户等区分管理,并设定控制策略,从而实现多租户。命名空间也称为虚拟集群

Replica Set

确保在任何给定时间制定的pod副本数量,并提供声明式更新等功能

Deployment

deployment是一个更高层次的API/资源对象,他管理replicasets和pod,并提供声明式更新等功能

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

RC-Replication Controller

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

部署和升级pod,声明某种pod的副本数量在任意时刻都符合某个预期值;

  • pod期待的副本数
  • 用于筛选目标pod的label selector
  • 当pod副本数量小于预期数量的时候,用于创建pod的pod模板

Service

service定义了pod的逻辑集合和访问集合的策略,是真实服务的抽象

service提供了一个统一的服务访问入口以及服务代理和发现机制,用户不需要了解后台pod是如何运行。

一个service定义了访问pod的方式,就像单个固定的ip地址和与其对应的dns名之间的关系

Service其实就是我们经常提起的微服务架构中的一个"微服务",通过分析、识别并建模系统中的所有服务为微服务——Kubernetes Service,最终我们的系统由多个提供不同业务能力而又彼此独立的微服务单元所组成,服务之间通过TCP/IP进行通信,从而形成了我们强大而又灵活的弹性网络,拥有了强大的分布式能力、弹性扩展能力、容错能力;

 如图示,每个Pod都提供了一个独立的Endpoint(Pod IP+ContainerPort)以被客户端访问,多个Pod副本组成了一个集群来提供服务,一般的做法是部署一个负载均衡器来访问它们,为这组Pod开启一个对外的服务端口如8000,并且将这些Pod的Endpoint列表加入8000端口的转发列表中,客户端可以通过负载均衡器的对外IP地址+服务端口来访问此服务。运行在Node上的kube-proxy其实就是一个智能的软件负载均衡器,它负责把对Service的请求转发到后端的某个Pod实例上,并且在内部实现服务的负载均衡与会话保持机制。Service不是共用一个负载均衡器的IP地址,而是每个Servcie分配一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP。

Cluster IP

Service的IP地址,特性: 

  • ​仅仅作用于Kubernetes Servcie这个对象,并由Kubernetes管理和分配IP地址;
  • ​无法被Ping,因为没有一个"实体网络对象"来响应;
  • ​只能结合Service Port组成一个具体的通信端口;
  • ​Node IP网、Pod IP网与Cluster IP网之间的通信,采用的是Kubernetes自己设计的一种编程方式的特殊的路由规则,与IP路由有很大的不同

Lable

Kubernetes中的任意API对象都是通过Label进行标识,Label的实质是一系列的K/V键值对。Label是Replication Controller和Service运行的基础,二者通过Label来进行关联Node上运行的Pod。

一个label是一个被附加到资源上的键/值对,譬如附加到一个Pod上,为它传递一个用户自定的并且可识别的属性.Label还可以被应用来组织和选择子网中的资源

Endpoint(IP+Port)

标识服务进程的访问点;

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

3、Kubernetes架构和组件

主从分布式架构,Master/Node

 组件:

Kubernetes Master:

集群控制节点,负责整个集群的管理和控制,基本上Kubernetes所有的控制命令都是发给它,它来负责具体的执行过程,我们后面所有执行的命令基本都是在Master节点上运行的;

包含如下组件:
1.Kubernetes API Server
作为Kubernetes系统的入口,其封装了核心对象的增删改查操作,以RESTful API接口方式提供给外部客户和内部组件调用。维护的REST对象持久化到Etcd中存储。

2.Kubernetes Scheduler
为新建立的Pod进行节点(node)选择(即分配机器),负责集群的资源调度。组件抽离,可以方便替换成其他调度器。

3.Kubernetes Controller
负责执行各种控制器,目前已经提供了很多控制器来保证Kubernetes的正常运行。
   - Replication Controller
       管理维护Replication Controller,关联Replication Controller和Pod,保证Replication Controller定义的副本数量与实际运行Pod数量一致。

​  - Deployment Controller
       管理维护Deployment,关联Deployment和Replication  Controller,保证运行指定数量的Pod。当Deployment更新时,控制实现Replication  Controller和 Pod的更新。

   - Node Controller
      管理维护Node,定期检查Node的健康状态,标识出(失效|未失效)的Node节点。

   - Namespace Controller
      管理维护Namespace,定期清理无效的Namespace,包括Namesapce下的API对象,比如Pod、Service等。

   - Service Controller
      管理维护Service,提供负载以及服务代理。

   - EndPoints Controller
      管理维护Endpoints,关联Service和Pod,创建Endpoints为Service的后端,当Pod发生变化时,实时更新Endpoints。

   - Service Account Controller
      管理维护Service Account,为每个Namespace创建默认的Service Account,同时为Service Account创建Service Account Secret。

   - Persistent Volume Controller
      管理维护Persistent Volume和Persistent Volume  Claim,为新的Persistent Volume Claim分配Persistent Volume进行绑定,为释放的Persistent  Volume执行清理回收。

   - Daemon Set Controller
      管理维护Daemon Set,负责创建Daemon Pod,保证指定的Node上正常的运行Daemon Pod。

  - Job Controller
      管理维护Job,为Jod创建一次性任务Pod,保证完成Job指定完成的任务数目

   - Pod Autoscaler Controller
      实现Pod的自动伸缩,定时获取监控数据,进行策略匹配,当满足条件时执行Pod的伸缩动作。

 

Kubernetes Node:
除了Master,Kubernetes集群中的其他机器被称为Node节点,Node节点才是Kubernetes集群中的工作负载节点,每个Node都会被Master分配一些工作负载(Docker容器),当某个Node宕机,其上的工作负载会被Master自动转移到其他节点上去;

包含如下组件:

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

  2.Kubernetes Proxy
    负责为Pod创建代理服务,Kubernetes Proxy会从Kubernetes API  Server获取所有的Service信息,并根据Service的信息创建代理服务,实现Service到Pod的请求路由和转发,从而实现Kubernetes层级的虚拟转发网络。

  3.Docker Engine(docker),Docker引擎,负责本机的容器创建和管理工作;  
  
  4.Flannel网络插件
      

数据库

etcd数据库,可以部署到master上,也可以独立部署
分布式键值存储系统。用于保存集群状态数据,比如Pod、Service等对象信息

1、Master节点

etcd:etcd存放的就是集群的状态,一般把所有的集群信息都存放到etcd当中,etcd不属于Kubernetes的某一个部分,而是单独集群部署的,API Server是它唯一的入口,可以直接访问etcd。

API Server:API Server提供了操作资源的唯一入口,例如认证、授权、访问控制、注册或者发现,都是通过API Server来完成的。

Controller Manager:负责管理集群各种资源,保证资源处于预期的状态。Controller Manager由多种controller组成,包括replication controller、endpoints controller、namespace controller、serviceaccounts controller等 。由控制器完成的主要功能主要包括生命周期功能和API业务逻辑,具体如下:

生命周期功能:包括Namespace创建和生命周期、Event垃圾回收、Pod终止相关的垃圾回收、级联垃圾回收及Node垃圾回收等。
API业务逻辑:例如,由ReplicaSet执行的Pod扩展等。
Scheduler:Scheduler调度控制器负责整个集群的资源调度,按照默认或者指定的调度策略将Pod调度到符合要求的Node节点上运行

2、Node节点

Kubelet:Kubelet维护整个容器的生命周期,API Server创建Pod,Scheduler将Pod调度到符合要求的Node节点上,该节点上的Kubelet就会去运行Pod以及Docker,Pod的存储以及网络都是Kubelet进行管理的

Docker:Docker负责镜像的管理,例如镜像的拉取、启动容器等

Kube-proxy:Kube-proxy主要是提供整个集群内部Service的负载均衡和服务发现

3、组件之间的通信

API Server是etcd访问的唯一入口,只有API Server才能访问和操作etcd集群;API Server对内和对外都提供了统一的REST API,其他组件都是通过API Server进行通信的

用户使用kubectl命令来请求API Server接口完成相应操作

Kubernetes内部组件都是通过一种watch机制去监控API Server中的资源变化,然后对其做一些相应的操作

三、常用镜像仓库

daocloud的docker镜像库:daocloud.io/library

​docker-hub的k8s镜像库:mirrorgooglecontainers

aliyun的k8s镜像库:registry.cn-hangzhou.aliyuncs.com/google-containers

aliyun的docker镜像库web页面:https://cr.console.aliyun.com/cn-hangzhou/images

google的镜像库web页面:https://console.cloud.google.com/gcr/images/google-containers?project=google-containers

四、Kubernetes集群部署方式

方式1、minikube

Minikube是一个工具,可以在本地快速运行一个单点的Kubernetes,尝试Kubernetes或日常开发的用户使用。不能用于生产环境。
​官方地址:https://kubernetes.io/docs/setup/minikube/ 

方式2、 kubeadm

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

方式3、 直接使用epel-release yum源,缺点就是版本较低 1.5

方式4、 二进制包

从官方下载发行版的二进制包,手动部署每个组件,组成Kubernetes集群。
官方也提供了一个互动测试环境供大家测试:https://kubernetes.io/cn/docs/tutorials/kubernetes-basics/cluster-interactive/

二进制方式部署k8s集群 

目标任务:


1、Kubernetes集群部署架构规划

2、部署Etcd集群

3、在Node节点安装Docker

4、部署Flannel网络插件

5、在Master节点部署组件

6、在Node节点部署组件

7、查看集群状态

8、运行一个测试示例

9、部署Dashboard(Web UI)

准备环境

三台机器,所有机器相互做解析  centos7.4
关闭防火墙和selinux
[root@k8s-master ~]# vim /etc/hosts
10.0.0.130 k8s-master
10.0.0.131 k8s-node1
10.0.0.132 k8s-node2

1、部署Etcd集群

使用cfssl来生成自签证书,任何机器都行,证书这块儿知道怎么生成、怎么用即可,暂且不用过多研究(这个证书随便在那台机器生成都可以。哪里用将证书拷贝到哪里就可以了。)

下载cfssl工具:下载的这些是可执行的二进制命令直接用就可以了
[root@k8s-master ~]# wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
[root@k8s-master ~]# wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
[root@k8s-master ~]# wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
[root@k8s-master ~]# chmod +x cfssl_linux-amd64 cfssljson_linux-amd64 cfssl-certinfo_linux-amd64
[root@k8s-master ~]# mv cfssl_linux-amd64 /usr/local/bin/cfssl
[root@k8s-master ~]# mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
[root@k8s-master ~]# mv cfssl-certinfo_linux-amd64 /usr/bin/cfssl-certinfo

生成Etcd证书:
创建以下三个文件:
[root@k8s-master ~]# mkdir cert
[root@k8s-master ~]# cd cert/
[root@k8s-master cert]# vim ca-config.json  #生成ca中心的
[root@k8s-master cert]# cat ca-config.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "www": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}

[root@k8s-master cert]# vim ca-csr.json  #生成ca中心的证书请求文件
[root@k8s-master cert]# cat ca-csr.json
{
    "CN": "etcd CA",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}

[root@k8s-master cert]# vim server-csr.json #生成服务器的证书请求文件
[root@k8s-master cert]# cat server-csr.json
{
    "CN": "etcd",
    "hosts": [
    "10.0.0.130",
    "10.0.0.131",
    "10.0.0.132"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "BeiJing",
            "ST": "BeiJing"
        }
    ]
}

生成证书:
[root@k8s-master cert]# cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
[root@k8s-master cert]# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www server-csr.json | cfssljson -bare server
[root@k8s-master cert]# ls *pem
ca-key.pem  ca.pem  server-key.pem  server.pem

安装Etcd

二进制包下载地址:https://github.com/coreos/etcd/releases/tag/v3.2.12

一下步骤三台机器都操作
[root@k8s-master ~]# wget https://github.com/etcd-io/etcd/releases/download/v3.2.12/etcd-v3.2.12-linux-amd64.tar.gz
[root@k8s-master ~]# mkdir /opt/etcd/{bin,cfg,ssl} -p
[root@k8s-master ~]# tar zxvf etcd-v3.2.12-linux-amd64.tar.gz
[root@k8s-master ~]# mv etcd-v3.2.12-linux-amd64/{etcd,etcdctl} /opt/etcd/bin/

创建etcd配置文件
[root@k8s-master ~]# cd /opt/etcd/cfg/
[root@k8s-master cfg]# vim etcd
#[Member]
ETCD_NAME="etcd01"   #节点名称,各个节点不能相同
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="https://10.0.0.130:2380"   #写当前节点的ip
ETCD_LISTEN_CLIENT_URLS="https://10.0.0.130:2379" #写当前节点的ip

#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://10.0.0.130:2380" #写当前节点的ip
ETCD_ADVERTISE_CLIENT_URLS="https://10.0.0.130:2379"  #写当前节点的ip
ETCD_INITIAL_CLUSTER="etcd01=https://10.0.0.130:2380,etcd02=https://10.0.0.131:2380,etcd03=https://10.0.0.132:2380"   #每个节点的ip

ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

参数解释:
* ETCD_NAME 节点名称,每个节点名称不一样
* ETCD_DATA_DIR 存储数据目录(他是一个数据库,不是存在内存的,存在硬盘中的,所有和k8s有关的信息都会存到etcd里面的)
* ETCD_LISTEN_PEER_URLS 集群通信监听地址
* ETCD_LISTEN_CLIENT_URLS 客户端访问监听地址
* ETCD_INITIAL_ADVERTISE_PEER_URLS 集群通告地址
* ETCD_ADVERTISE_CLIENT_URLS 客户端通告地址
* ETCD_INITIAL_CLUSTER 集群节点地址
* ETCD_INITIAL_CLUSTER_TOKEN 集群Token
* ETCD_INITIAL_CLUSTER_STATE 加入集群的当前状态,new是新集群,existing表示加入已有集群
systemd管理etcd:
[root@k8s-master cfg]# vim /usr/lib/systemd/system/etcd.service
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
EnvironmentFile=/opt/etcd/cfg/etcd
ExecStart=/opt/etcd/bin/etcd \
--name=${ETCD_NAME} \
--data-dir=${ETCD_DATA_DIR} \
--listen-peer-urls=${ETCD_LISTEN_PEER_URLS} \
--listen-client-urls=${ETCD_LISTEN_CLIENT_URLS},http://127.0.0.1:2379 \
--advertise-client-urls=${ETCD_ADVERTISE_CLIENT_URLS} \
--initial-advertise-peer-urls=${ETCD_INITIAL_ADVERTISE_PEER_URLS} \
--initial-cluster=${ETCD_INITIAL_CLUSTER} \
--initial-cluster-token=${ETCD_INITIAL_CLUSTER_TOKEN} \
--initial-cluster-state=new \
--cert-file=/opt/etcd/ssl/server.pem \
--key-file=/opt/etcd/ssl/server-key.pem \
--peer-cert-file=/opt/etcd/ssl/server.pem \
--peer-key-file=/opt/etcd/ssl/server-key.pem \
--trusted-ca-file=/opt/etcd/ssl/ca.pem \
--peer-trusted-ca-file=/opt/etcd/ssl/ca.pem
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
把刚才生成的证书拷贝到配置文件中的位置:(将master上面生成的证书scp到剩余两台机器上面)
[root@k8s-master cfg]# cd /root/cert/
[root@k8s-master cert]# cp ca*pem server*pem /opt/etcd/ssl
直接拷贝到剩余两台etcd机器:
[root@k8s-master cert]# scp ca*pem server*pem k8s-node1:/opt/etcd/ssl
[root@k8s-master cert]# scp ca*pem server*pem k8s-node2:/opt/etcd/ssl

全部启动并设置开启启动:
[root@k8s-master cert]# systemctl daemon-reload
[root@k8s-master cert]# systemctl start etcd
[root@k8s-master cert]# systemctl enable etcd

都部署完成后,三台机器都检查etcd集群状态:
[root@k8s-master cert]# /opt/etcd/bin/etcdctl --ca-file=/opt/etcd/ssl/ca.pem --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem --endpoints="https://10.0.0.130:2379,https://10.0.0.130:2379,https://10.0.0.130:2379" cluster-health
member 3db8bdf3a21a92c4 is healthy: got healthy result from https://10.0.0.132:2379
member 455016c4da9c6bbd is healthy: got healthy result from https://10.0.0.131:2379
member 8f696e193713398b is healthy: got healthy result from https://10.0.0.130:2379
cluster is healthy
如果输出上面信息,就说明集群部署成功。
如果有问题第一步先看日志:/var/log/messages 或 journalctl -u etcd

报错:
Jan 15 12:06:55 k8s-master1 etcd: request cluster ID mismatch (got 99f4702593c94f98 want cdf818194e3a8c32)
解决:因为集群搭建过程,单独启动过单一etcd,做为测试验证,集群内第一次启动其他etcd服务时候,是通过发现服务引导的,所以需要删除旧的成员信息,所有节点作以下操作
[root@k8s-master default.etcd]# pwd
/var/lib/etcd/default.etcd
[root@k8s-master1 default.etcd]# rm -rf member/
========================================================

node节点安装docker(过程略)

2、部署Flannel网络

Flannel要用etcd存储自身一个子网信息,所以要保证能成功连接Etcd,写入预定义子网段:
在node节点部署,如果没有在master部署应用,那就不要在master部署flannel,他是用来给所有的容器用来通信的。

[root@k8s-master ~]# cd cert/
[root@k8s-master cert]# /opt/etcd/bin/etcdctl \
--ca-file=ca.pem --cert-file=server.pem --key-file=server-key.pem \
--endpoints="https://10.0.0.130:2379,https://10.0.0.130:2379,https://10.0.0.130:2379" \
set /coreos.com/network/config  '{ "Network": "172.17.0.0/16", "Backend": {"Type": "vxlan"}}'
{ "Network": "172.17.0.0/16", "Backend": {"Type": "vxlan"}}

以下部署步骤在规划的每个node节点都操作。
下载二进制包:
[root@k8s-node1 ~]# wget https://github.com/coreos/flannel/releases/download/v0.10.0/flannel-v0.10.0-linux-amd64.tar.gz
[root@k8s-node1 ~]# tar zxvf flannel-v0.10.0-linux-amd64.tar.gz
[root@k8s-node1 ~]# mkdir -pv /opt/kubernetes/bin
[root@k8s-node1 ~]# mv flanneld mk-docker-opts.sh /opt/kubernetes/bin

配置Flannel:
[root@k8s-node1 ~]# mkdir -pv /opt/kubernetes/cfg/
[root@k8s-node1 ~]# vim /opt/kubernetes/cfg/flanneld
FLANNEL_OPTIONS="--etcd-endpoints=https://10.0.0.130:2379,https://10.0.0.130:2379,https://10.0.0.130:2379 -etcd-cafile=/opt/etcd/ssl/ca.pem -etcd-certfile=/opt/etcd/ssl/server.pem -etcd-keyfile=/opt/etcd/ssl/server-key.pem"

systemd管理Flannel:
[root@k8s-node1 ~]# vim /usr/lib/systemd/system/flanneld.service
[Unit]
Description=Flanneld overlay address etcd agent
After=network-online.target network.target
Before=docker.service

[Service]
Type=notify
EnvironmentFile=/opt/kubernetes/cfg/flanneld
ExecStart=/opt/kubernetes/bin/flanneld --ip-masq $FLANNEL_OPTIONS
ExecStartPost=/opt/kubernetes/bin/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/subnet.env
Restart=on-failure

[Install]
WantedBy=multi-user.target

配置Docker启动指定子网段:可以将源文件直接覆盖掉
[root@k8s-node1 ~]# vim /usr/lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target

[Service]
Type=notify
EnvironmentFile=/run/flannel/subnet.env
ExecStart=/usr/bin/dockerd $DOCKER_NETWORK_OPTIONS
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TimeoutStartSec=0
Delegate=yes
KillMode=process
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s

[Install]
WantedBy=multi-user.target

重启flannel和docker:
# systemctl daemon-reload
# systemctl start flanneld
# systemctl enable flanneld
# systemctl restart docker

注意:如果flannel启动不了请检查设置ip网段是否正确

检查是否生效:
[root@k8s-node1 ~]# ps -ef | grep docker
root       3632      1  1 22:19 ?        00:00:00 /usr/bin/dockerd --bip=172.17.77.1/24 --ip-masq=false --mtu=1450

注:
1.	确保docker0与flannel.1在同一网段。
2.	测试不同节点互通,在当前节点访问另一个Node节点docker0 IP:案例:node1机器pingnode2机器的docker0上面的ip地址
[root@k8s-node1 ~]# ping 172.17.33.1
PING 172.17.33.1 (172.17.33.1) 56(84) bytes of data.
64 bytes from 172.17.33.1: icmp_seq=1 ttl=64 time=0.520 ms
64 bytes from 172.17.33.1: icmp_seq=2 ttl=64 time=0.972 ms
64 bytes from 172.17.33.1: icmp_seq=3 ttl=64 time=0.642 ms
如果能通说明Flannel部署成功。如果不通检查下日志:journalctl -u flannel(快照吧!!!)

3、在Master节点部署组件

在部署Kubernetes之前一定要确保etcd、flannel、docker是正常工作的,否则先解决问题再继续。

生成证书

master节点操作--给api-server创建的证书。别的服务访问api-server的时候需要通过证书认证
创建CA证书:
[root@k8s-master ~]# mkdir -p /opt/crt/
[root@k8s-master ~]# cd /opt/crt/
# vim ca-config.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}

# vim ca-csr.json
{
    "CN": "kubernetes",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing",
            "O": "k8s",
            "OU": "System"
        }
    ]
}

[root@k8s-master crt]# cfssl gencert -initca ca-csr.json | cfssljson -bare ca -

生成apiserver证书:
[root@k8s-master1 crt]# vim server-csr.json
# cat server-csr.json
{
    "CN": "kubernetes",
    "hosts": [
      "10.0.0.1",         //这是后面dns要使用的虚拟网络的网关,不用改,就用这个切忌
      "127.0.0.1",
      "10.0.0.130",    // master的IP地址。
      "10.0.0.131",
      "10.0.0.132",
      "kubernetes",
      "kubernetes.default",
      "kubernetes.default.svc",
      "kubernetes.default.svc.cluster",
      "kubernetes.default.svc.cluster.local"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "BeiJing",
            "ST": "BeiJing",
            "O": "k8s",
            "OU": "System"
        }
    ]
}

[root@k8s-master crt]# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes server-csr.json | cfssljson -bare server

生成kube-proxy证书:
[root@k8s-master crt]# vim kube-proxy-csr.json
# cat kube-proxy-csr.json
{
  "CN": "system:kube-proxy",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "L": "BeiJing",
      "ST": "BeiJing",
      "O": "k8s",
      "OU": "System"
    }
  ]
}

[root@k8s-master crt]# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy

最终生成以下证书文件:
[root@k8s-master crt]# ls *pem
ca-key.pem  ca.pem  kube-proxy-key.pem  kube-proxy.pem  server-key.pem  server.pem

部署apiserver组件---在master节点进行 下载二进制包:https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.11.md
下载这个包(kubernetes-server-linux-amd64.tar.gz)就够了,包含了所需的所有组件。

[root@k8s-master ~]# wget https://dl.k8s.io/v1.11.10/kubernetes-server-linux-amd64.tar.gz
[root@k8s-master ~]# mkdir /opt/kubernetes/{bin,cfg,ssl} -pv
[root@k8s-master ~]# tar zxvf kubernetes-server-linux-amd64.tar.gz
[root@k8s-master ~]# cd kubernetes/server/bin
[root@k8s-master bin]# cp kube-apiserver kube-scheduler kube-controller-manager kubectl /opt/kubernetes/bin

[root@k8s-master bin]# cd /opt/crt/
[root@k8s-master crt]# cp server.pem server-key.pem ca.pem ca-key.pem /opt/kubernetes/ssl/

创建token文件:
[root@k8s-master1 crt]# cd /opt/kubernetes/cfg/
[root@k8s-master1 cfg]# vim token.csv
674c457d4dcf2eefe4920d7dbb6b0ddc,kubelet-bootstrap,10001,"system:kubelet-bootstrap"
第一列:随机字符串,自己可生成
第二列:用户名
第三列:UID
第四列:用户组

创建apiserver配置文件:
[root@k8s-master cfg]# pwd
/opt/kubernetes/cfg
[root@k8s-master cfg]# vim kube-apiserver
KUBE_APISERVER_OPTS="--logtostderr=true \
--v=4 \
--etcd-servers=https://10.0.0.130:2379,https://10.0.0.131:2379,https://10.0.0.132:2379 \
--bind-address=10.0.0.130 \  #master的ip地址,就是安装api-server的机器地址
--secure-port=6443 \
--advertise-address=10.0.0.130 \
--allow-privileged=true \
--service-cluster-ip-range=10.0.0.0/24 \    #这里就用这个网段切记不要修改
--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,NodeRestriction \
--authorization-mode=RBAC,Node \
--enable-bootstrap-token-auth \
--token-auth-file=/opt/kubernetes/cfg/token.csv \
--service-node-port-range=30000-50000 \
--tls-cert-file=/opt/kubernetes/ssl/server.pem  \
--tls-private-key-file=/opt/kubernetes/ssl/server-key.pem \
--client-ca-file=/opt/kubernetes/ssl/ca.pem \
--service-account-key-file=/opt/kubernetes/ssl/ca-key.pem \
--etcd-cafile=/opt/etcd/ssl/ca.pem \
--etcd-certfile=/opt/etcd/ssl/server.pem \
--etcd-keyfile=/opt/etcd/ssl/server-key.pem"

配置好前面生成的证书,确保能连接etcd。

参数说明:
* --logtostderr 启用日志
* --v 日志等级
* --etcd-servers etcd集群地址
* --bind-address 监听地址
* --secure-port https安全端口
* --advertise-address 集群通告地址
* --allow-privileged 启用授权
* --service-cluster-ip-range Service虚拟IP地址段
* --enable-admission-plugins 准入控制模块
* --authorization-mode 认证授权,启用RBAC授权和节点自管理
* --enable-bootstrap-token-auth 启用TLS bootstrap功能,后面会讲到
* --token-auth-file token文件
* --service-node-port-range Service Node类型默认分配端口范围

systemd管理apiserver:
[root@k8s-master cfg]# cd /usr/lib/systemd/system
[root@k8s-master system]# vim kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
EnvironmentFile=-/opt/kubernetes/cfg/kube-apiserver
ExecStart=/opt/kubernetes/bin/kube-apiserver $KUBE_APISERVER_OPTS
Restart=on-failure

[Install]
WantedBy=multi-user.target


启动:
# systemctl daemon-reload
# systemctl enable kube-apiserver
# systemctl start kube-apiserver
# systemctl status kube-apiserver

如果出现以下错误

 去kube-apiserver配置文件中看一下行尾有没有空格,使用set list查看

部署schduler组件---master节点

创建schduler配置文件:
[root@k8s-master cfg]# vim  /opt/kubernetes/cfg/kube-scheduler
KUBE_SCHEDULER_OPTS="--logtostderr=true \
--v=4 \
--master=127.0.0.1:8080 \
--leader-elect"
参数说明:
* --master 连接本地apiserver
* --leader-elect 当该组件启动多个时,自动选举(HA)

systemd管理schduler组件:
[root@k8s-master cfg]# cd /usr/lib/systemd/system/
[root@k8s-master system]# vim kube-scheduler.service
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes

[Service]
EnvironmentFile=-/opt/kubernetes/cfg/kube-scheduler
ExecStart=/opt/kubernetes/bin/kube-scheduler $KUBE_SCHEDULER_OPTS
Restart=on-failure

[Install]
WantedBy=multi-user.target

启动:
# systemctl daemon-reload
# systemctl enable kube-scheduler 
# systemctl start kube-scheduler 
# systemctl status kube-scheduler

部署controller-manager组件--控制管理组件

master节点操作:创建controller-manager配置文件:
[root@k8s-master ~]# cd /opt/kubernetes/cfg/
[root@k8s-master cfg]# vim kube-controller-manager
KUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=true \
--v=4 \
--master=127.0.0.1:8080 \
--leader-elect=true \
--address=127.0.0.1 \
--service-cluster-ip-range=10.0.0.0/24 \    //这是后面dns要使用的虚拟网络,不用改,就用这个  切忌
--cluster-name=kubernetes \
--cluster-signing-cert-file=/opt/kubernetes/ssl/ca.pem \
--cluster-signing-key-file=/opt/kubernetes/ssl/ca-key.pem  \
--root-ca-file=/opt/kubernetes/ssl/ca.pem \
--service-account-private-key-file=/opt/kubernetes/ssl/ca-key.pem"

systemd管理controller-manager组件:
[root@k8s-master cfg]# cd /usr/lib/systemd/system/
[root@k8s-master system]# vim kube-controller-manager.service
# cat /usr/lib/systemd/system/kube-controller-manager.service 
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes

[Service]
EnvironmentFile=-/opt/kubernetes/cfg/kube-controller-manager
ExecStart=/opt/kubernetes/bin/kube-controller-manager $KUBE_CONTROLLER_MANAGER_OPTS
Restart=on-failure

[Install]
WantedBy=multi-user.target

启动:
# systemctl daemon-reload
# systemctl enable kube-controller-manager
# systemctl start kube-controller-manager
# systemctl status kube-controller-manager.service
所有组件都已经启动成功,通过kubectl工具查看当前集群组件状态:
[root@k8s-master ~]# /opt/kubernetes/bin/kubectl get cs
NAME                 STATUS    MESSAGE              ERROR
scheduler            Healthy   ok                   
controller-manager   Healthy   ok                   
etcd-2               Healthy   {"health": "true"}   
etcd-1               Healthy   {"health": "true"}   
etcd-0               Healthy   {"health": "true"} 
如上输出说明组件都正常。

4、在Node节点部署组件

Master apiserver启用TLS认证后,Node节点kubelet组件想要加入集群,必须使用CA签发的有效证书才能与apiserver通信,当Node节点很多时,签署证书是一件很繁琐的事情,因此有了TLS Bootstrapping机制,kubelet会以一个低权限用户自动向apiserver申请证书,kubelet的证书由apiserver动态签署。

认证大致工作流程如图所示:

----------------------下面这些操作在master节点完成:---------------------------
将kubelet-bootstrap用户绑定到系统集群角色
[root@k8s-master ~]# /opt/kubernetes/bin/kubectl create clusterrolebinding kubelet-bootstrap \
  --clusterrole=system:node-bootstrapper \
  --user=kubelet-bootstrap
clusterrolebinding.rbac.authorization.k8s.io/kubelet-bootstrap created

创建kubeconfig文件:
在生成kubernetes证书的目录下执行以下命令生成kubeconfig文件:
[root@k8s-master ~]# cd /opt/crt/
指定apiserver 内网负载均衡地址
[root@k8s-master crt]# KUBE_APISERVER="https://10.0.0.130:6443"  #写你master的ip地址,集群中就写负载均衡的ip地址
[root@k8s-master1 crt]# BOOTSTRAP_TOKEN=674c457d4dcf2eefe4920d7dbb6b0ddc  这个就是前面我们在token.csv文件中写的

# 设置集群参数
[root@k8s-master crt]# /opt/kubernetes/bin/kubectl config set-cluster kubernetes \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=bootstrap.kubeconfig

# 设置客户端认证参数
[root@k8s-master crt]# /opt/kubernetes/bin/kubectl config set-credentials kubelet-bootstrap \
  --token=${BOOTSTRAP_TOKEN} \
  --kubeconfig=bootstrap.kubeconfig

# 设置上下文参数
[root@k8s-master crt]# /opt/kubernetes/bin/kubectl config set-context default \
  --cluster=kubernetes \
  --user=kubelet-bootstrap \
  --kubeconfig=bootstrap.kubeconfig

# 设置默认上下文
[root@k8s-master crt]# /opt/kubernetes/bin/kubectl config use-context default --kubeconfig=bootstrap.kubeconfig

#====================================================================================

# 创建kube-proxy kubeconfig文件

[root@k8s-master crt]# /opt/kubernetes/bin/kubectl config set-cluster kubernetes \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=kube-proxy.kubeconfig

[root@k8s-master crt]# /opt/kubernetes/bin/kubectl config set-credentials kube-proxy \
  --client-certificate=kube-proxy.pem \
  --client-key=kube-proxy-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-proxy.kubeconfig

[root@k8s-master crt]# /opt/kubernetes/bin/kubectl config set-context default \
  --cluster=kubernetes \
  --user=kube-proxy \
  --kubeconfig=kube-proxy.kubeconfig

[root@k8s-master crt]# /opt/kubernetes/bin/kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig


[root@k8s-master crt]# ls
bootstrap.kubeconfig  kube-proxy.kubeconfig

#必看:将这两个文件拷贝到Node节点/opt/kubernetes/cfg目录下。
[root@k8s-master crt]# scp *.kubeconfig k8s-node1:/opt/kubernetes/cfg/
[root@k8s-master crt]# scp *.kubeconfig k8s-node2:/opt/kubernetes/cfg/
----------------------下面这些操作在node节点完成:---------------------------
部署kubelet组件
#将前面下载的二进制包中的kubelet和kube-proxy拷贝到/opt/kubernetes/bin目录下。
将master上面的包拷贝过去
[root@k8s-master ~]# scp kubernetes-server-linux-amd64.tar.gz k8s-node1:/root/
[root@k8s-master ~]# scp kubernetes-server-linux-amd64.tar.gz k8s-node2:/root/
[root@k8s-node1 ~]# tar xzf kubernetes-server-linux-amd64.tar.gz
[root@k8s-node1 ~]# cd kubernetes/server/bin/
[root@k8s-node1 bin]# cp kubelet kube-proxy /opt/kubernetes/bin/
或者直接在master上将解压后的kubernetes/server/bin/目录中的kubelet kube-proxy这两个文件远程拷贝到node节点的/opt/kubernetes/bin目录中
#=====================================================================================
在两个node节点创建kubelet配置文件:
[root@k8s-node1 ~]# vim /opt/kubernetes/cfg/kubelet
KUBELET_OPTS="--logtostderr=true \
--v=4 \
--hostname-override=10.0.0.131 \   #每个节点自己的ip地址
--kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig \
--bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig \
--config=/opt/kubernetes/cfg/kubelet.config \
--cert-dir=/opt/kubernetes/ssl \
--pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"  #这个镜像需要提前下载

[root@k8s-node1 ~]# docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0
[root@k8s-node2 ~]# docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0
参数说明:
* --hostname-override 在集群中显示的主机名
* --kubeconfig 指定kubeconfig文件位置,会自动生成
* --bootstrap-kubeconfig 指定刚才生成的bootstrap.kubeconfig文件
* --cert-dir 颁发证书存放位置
* --pod-infra-container-image 管理Pod网络的镜像

其中/opt/kubernetes/cfg/kubelet.config配置文件如下:
[root@k8s-node1 ~]# vim /opt/kubernetes/cfg/kubelet.config
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
address: 10.0.0.131   #写你机器的ip地址
port: 10250
readOnlyPort: 10255
cgroupDriver: cgroupfs
clusterDNS: ["10.0.0.2"]      #不要改,就是这个ip地址
clusterDomain: cluster.local.
failSwapOn: false
authentication:
  anonymous:
    enabled: true
  webhook:
    enabled: false
    
systemd管理kubelet组件:
# vim /usr/lib/systemd/system/kubelet.service 
[Unit]
Description=Kubernetes Kubelet
After=docker.service
Requires=docker.service

[Service]
EnvironmentFile=/opt/kubernetes/cfg/kubelet
ExecStart=/opt/kubernetes/bin/kubelet $KUBELET_OPTS
Restart=on-failure
KillMode=process

[Install]
WantedBy=multi-user.target

启动:
# systemctl daemon-reload
# systemctl enable kubelet
# systemctl start kubelet
[root@k8s-master ~]# /opt/kubernetes/bin/kubectl get csr
NAME                                                   AGE       REQUESTOR           CONDITION
node-csr-F5AQ8SeoyloVrjPuzSbzJnFKQaUsier7EGvNFXLKTqM   17s       kubelet-bootstrap   Pending
node-csr-bjeHSWXOuUDSHganJPL_hDz_8jjYhM2FQyTkbA9pM0Q   18s       kubelet-bootstrap   Pending

在Master审批Node加入集群:
启动后还没加入到集群中,需要手动允许该节点才可以。在Master节点查看请求签名的Node:
[root@k8s-master ~]# /opt/kubernetes/bin/kubectl certificate approve XXXXID
注意:xxxid 指的是上面的NAME这一列
[root@k8s-master ~]# /opt/kubernetes/bin/kubectl get csr
NAME                                                   AGE       REQUESTOR           CONDITION
node-csr--1TVDzcozo7NoOD3WS2t9xLQqNunsVXj_i2AQ5x1mbs   1m        kubelet-bootstrap   Approved,Issued
node-csr-L0wqvr69oy8rzXwFm1u1uNx4aEMOOvd_RWPxaAERn_w   27m       kubelet-bootstrap   Approved,Issued


查看集群节点信息:
[root@k8s-master ~]# /opt/kubernetes/bin/kubectl get node
NAME              STATUS    ROLES     AGE       VERSION
10.0.0.131        Ready     <none>    1m        v1.11.10
10.0.0.132        Ready     <none>    17s       v1.11.10

部署kube-proxy组件

创建kube-proxy配置文件:还是在所有node节点  node1、node2都做
[root@k8s-node1 ~]# vim /opt/kubernetes/cfg/kube-proxy
# cat /opt/kubernetes/cfg/kube-proxy
KUBE_PROXY_OPTS="--logtostderr=true \
--v=4 \
--hostname-override=10.0.0.131 \   #写每个node节点ip
--cluster-cidr=10.0.0.0/24 \           //不要改,就是这个ip
--kubeconfig=/opt/kubernetes/cfg/kube-proxy.kubeconfig"

systemd管理kube-proxy组件:
[root@k8s-node1 ~]# cd /usr/lib/systemd/system
# cat /usr/lib/systemd/system/kube-proxy.service 
[Unit]
Description=Kubernetes Proxy
After=network.target

[Service]
EnvironmentFile=-/opt/kubernetes/cfg/kube-proxy
ExecStart=/opt/kubernetes/bin/kube-proxy $KUBE_PROXY_OPTS
Restart=on-failure

[Install]
WantedBy=multi-user.target

启动:

# systemctl daemon-reload
# systemctl enable kube-proxy
# systemctl start kube-proxy

在master查看集群状态
[root@k8s-master1 ~]# /opt/kubernetes/bin/kubectl get node
NAME              STATUS    ROLES     AGE       VERSION
10.0.0.131        Ready     <none>    19m       v1.11.10
10.0.0.132        Ready     <none>    18m       v1.11.10

查看集群状态
[root@k8s-master ~]# /opt/kubernetes/bin/kubectl get cs
NAME                 STATUS    MESSAGE              ERROR
scheduler            Healthy   ok                   
controller-manager   Healthy   ok                   
etcd-0               Healthy   {"health": "true"}   
etcd-1               Healthy   {"health": "true"}   
etcd-2               Healthy   {"health": "true"}
=====================================================================================

部署dashboard(web ui)

在msater上部署

* dashboard-deployment.yaml     	#部署Pod,提供Web服务
* dashboard-rbac.yaml               #授权访问apiserver获取信息
* dashboard-service.yaml            #发布服务,提供对外访问

创建一个目录
[root@k8s-master ~]# mkdir webui
[root@k8s-master ~]# cd webui/
[root@k8s-master webui]# cat dashboard-deployment.yaml 
apiVersion: v1
kind: Deployment
metadata:
  name: kubernetes-dashboard
  namespace: kube-system
  labels:
    k8s-app: kubernetes-dashboard
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ''
    spec:
      serviceAccountName: kubernetes-dashboard
      containers:
      - name: kubernetes-dashboard
        image: registry.cn-hangzhou.aliyuncs.com/kube_containers/kubernetes-dashboard-amd64:v1.8.1 
        resources:
          limits:
            cpu: 100m
            memory: 300Mi
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 9090
          protocol: TCP
        livenessProbe:
          httpGet:
            scheme: HTTP
            path: /
            port: 9090
          initialDelaySeconds: 30
          timeoutSeconds: 30
      tolerations:
      - key: "CriticalAddonsOnly"
        operator: "Exists"
        
[root@k8s-master webui]# cat dashboard-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
    addonmanager.kubernetes.io/mode: Reconcile
  name: kubernetes-dashboard
  namespace: kube-system
---

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
  labels:
    k8s-app: kubernetes-dashboard
    addonmanager.kubernetes.io/mode: Reconcile
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kube-system

[root@k8s-master webui]# 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: 80
    targetPort: 9090

[root@k8s-master webui]# /opt/kubernetes/bin/kubectl create -f dashboard-rbac.yaml
[root@k8s-master webui]# /opt/kubernetes/bin/kubectl create -f dashboard-deployment.yaml
[root@k8s-master webui]# /opt/kubernetes/bin/kubectl create -f dashboard-service.yaml
等待数分钟,查看资源状态:

查看名称空间:
[root@k8s-master webui]# /opt/kubernetes/bin/kubectl get all -n kube-system
NAME                                       READY     STATUS    RESTARTS   AGE
pod/kubernetes-dashboard-d9545b947-442ft   1/1       Running   0          21m

NAME                           TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
service/kubernetes-dashboard   NodePort   10.0.0.143   <none>        80:47520/TCP   21m

NAME                                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kubernetes-dashboard   1         1         1            1           21m

NAME                                             DESIRED   CURRENT   READY     AGE
replicaset.apps/kubernetes-dashboard-d9545b947   1         1         1         21m
查看访问端口:

查看指定命名空间的服务
[root@k8s-master webui]# /opt/kubernetes/bin/kubectl get svc -n kube-system
NAME                   TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
kubernetes-dashboard   NodePort   10.0.0.143   <none>        80:47520/TCP   22m

访问node节点的ip

 测试

==========================================================
运行一个测试示例--在master节点先安装docker服务
创建一个Nginx Web,判断集群是否正常工
# /opt/kubernetes/bin/kubectl run nginx --image=daocloud.io/nginx --replicas=3
# /opt/kubernetes/bin/kubectl expose deployment nginx --port=88 --target-port=80 --type=NodePort

# /opt/kub.../bin/kubectl delete -f  deployment  --all 

在master上面查看:
查看Pod,Service:
# /opt/kubernetes/bin/kubectl get pods  #需要等一会
NAME                                 READY     STATUS    RESTARTS   AGE
nginx-64f497f8fd-fjgt2       1/1      Running   3          28d
nginx-64f497f8fd-gmstq       1/1      Running   3          28d
nginx-64f497f8fd-q6wk9       1/1      Running   3          28d

查看pod详细信息:
# /opt/kubernetes/bin/kubectl describe pod nginx-64f497f8fd-fjgt2 

# /opt/kubernetes/bin/kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                        AGE
kubernetes     ClusterIP     10.0.0.1     <none>        443/TCP                        28d
nginx          NodePort     10.0.0.175   <none>        88:38696/TCP                   28d

访问nodeip加端口
打开浏览器输入:http://10.0.0.131:38696


恭喜你,集群部署成功!
============================

kubeadm方式部署k8s集群

官方文档:

Installing kubeadm | Kubernetes

切记要关闭防火墙和selinux,cpu核心数至少为2;内存4G

kubeadm部署k8s高可用集群的官方文档:

Creating Highly Available Clusters with kubeadm | Kubernetes

实验环境:

k8s-master:10.0.0.130

k8s-node1:10.0.0.131

k8s-node2:10.0.0.132

全部关闭防火墙selinux

1、获取镜像

在docker hub拉取相应的镜像并重新打标:

注意:所有节点都要进行一下操作

直接从docker官方拉取镜像可能拉取不成功,所以我们可以从 registry.cn-hangzhou.aliyuncs.com拉取镜像,然后修改镜像tag
写一个脚本,我部署的是k8s集群1.19.1版本的,其他版本套路是一样的,不过可能会出一些小问题,我也没试过,感兴趣的兄弟可以试一下
[root@k8s-master ~]# vim a.sh
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.19.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.19.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.19.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.19.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.7.0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.4.13-0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.2
docker pull quay-mirror.qiniu.com/coreos/flannel:v0.12.0-amd64
[root@k8s-master ~]# bash a.sh
....等待镜像拉取成功,注意:kube-controller-manager、kube-proxy、kube-apiserver、kube-scheduler、的版本必须一致,flannel的版本我用的是0.12.0的,这也是看网上搜的用这个版本,pause的镜像是3.2,也是网上搜的,而coredns:1.7.0、etcd:3.4.13-0是我后来初始化失败的错误回显建议我使用这个版本
[root@k8s-master ~]# docker images
REPOSITORY                                                                    TAG             IMAGE ID       CREATED         SIZE
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy                v1.19.1         33c60812eab8   21 months ago   118MB
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver            v1.19.1         ce0df89806bb   21 months ago   119MB
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager   v1.19.1         538929063f23   21 months ago   111MB
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler            v1.19.1         49eb8a235d05   21 months ago   45.7MB
registry.cn-hangzhou.aliyuncs.com/google_containers/etcd                      3.4.13-0        0369cf4303ff   21 months ago   253MB
registry.cn-hangzhou.aliyuncs.com/google_containers/coredns                   1.7.0           bfe3a36ebd25   24 months ago   45.2MB
quay.io/coreos/flannel                                                        v0.12.0-amd64   4e9f801d2217   2 years ago     52.8MB
registry.cn-hangzhou.aliyuncs.com/google_containers/pause                     3.2             80d28bedfe5d   2 years ago     683kB
然后一个一个改标签就行
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.19.1 k8s.gcr.io/kube-proxy:v1.19.1
....一个一个修改

 改成下面这样的,flannel不用改

 2、所有节点安装docker

# yum install -y yum-utils device-mapper-persistent-data lvm2 git
# yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo   #记得关闭证书验证,两个
# yum install docker-ce -y

3、禁用swap分区

所有节点都做

# swapoff -a
注释掉swap分区:
[root@k8s-master ~]# sed -i 's/.*swap.*/#&/' /etc/fstab
# free -m
              total        used        free      shared  buff/cache   available
Mem:           3935         144        3415           8         375        3518
Swap:             0           0           0

4、所有节点安装kubeadm和kubelet

# cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

# yum install -y kubelet-1.19.1-0.x86_64 kubeadm-1.19.1-0.x86_64 kubectl-1.19.1-0.x86_64 ipvsadm

加载ipvs相关内核模块
# vim c.sh
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
modprobe nf_conntrack_ipv4
modprobe br_netfilter
# bash c.sh
如果重新开机,需要重新加载(可以写在 /etc/rc.local 中开机自动加载)
# vim /etc/rc.local 
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
modprobe nf_conntrack_ipv4
modprobe br_netfilter
# chmod +x /etc/rc.local

配置转发相关参数,否则可能会出错
# cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
vm.swappiness=0
EOF
使配置生效
# sysctl --system

查看是否加载成功
# lsmod | grep ip_vs
ip_vs_sh               12688  0 
ip_vs_wrr              12697  0 
ip_vs_rr               12600  0 
ip_vs                  141092  6 ip_vs_rr,ip_vs_sh,ip_vs_wrr
nf_conntrack           133387  2 ip_vs,nf_conntrack_ipv4
libcrc32c              12644  3 xfs,ip_vs,nf_conntrack

5、配置启动kubelet(所有节点)

[root@k8s-master ~]# docker info |grep 'Cgroup Driver' | awk '{print $3}'
WARNING: IPv4 forwarding is disabled
cgroupfs    #将这个cgroupfs写到等会的配置文件中

这个是使用国内的源。-###注意我们使用谷歌的镜像--操作下面的第3标题
2.配置kubelet的cgroups
# cat >/etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--cgroup-driver=cgroupfs --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.2"
EOF

3.配置kubelet的cgroups   #我们用的是这个
# cat >/etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--cgroup-driver=cgroupfs --pod-infra-container-image=k8s.gcr.io/pause:3.2"
EOF

# cat >/etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--cgroup-driver=cgroupfs --pod-infra-container-image=k8s.gcr.io/pause:3.2"
EOF

启动

# systemctl daemon-reload
# systemctl enable kubelet && systemctl restart kubelet
在这里使用 # systemctl status kubelet,你会发现报错误信息;

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

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

6、配置master节点

运行初始化过程如下:
初始化之前,切记要关闭防火墙和selinux,cpu核心数至少为2
[root@master ~]# kubeadm init --kubernetes-version=v1.19.1 --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=10.0.0.130 --ignore-preflight-errors=Swap
注:
–kubernetes-version: 用于指定k8s版本;
–apiserver-advertise-address:用于指定kube-apiserver监听的ip地址,就是 master本机IP地址。
–pod-network-cidr:用于指定Pod的网络范围; 10.244.0.0/16
–service-cidr:用于指定SVC的网络范围;
–image-repository: 指定阿里云镜像仓库地址
注意在检查一下swap分区是否关闭

看到以下信息说明初始化成功

......

Your Kubernetes control-plane has initialized successfully!

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

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

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

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 10.0.0.130:6443 --token ymvzvj.5d3j0hbeq26wx9ff \
    --discovery-token-ca-cert-hash sha256:81086257eed9220e16c7ec407848b51adc1dba67cc4823dedde7396b830dbcc2  #需要记住


上面记录了完成的初始化输出的内容,根据输出的内容基本上可以看出手动初始化安装一个Kubernetes集群所需要的关键步骤。
其中有以下关键内容:
    [kubelet] 生成kubelet的配置文件”/var/lib/kubelet/config.yaml”
    [certificates]生成相关的各种证书
    [kubeconfig]生成相关的kubeconfig文件
    [bootstraptoken]生成token记录下来,后边使用kubeadm join往集群中添加节点时会用到

配置使用kubectl

以下操作在master节点操作

[root@k8s-master ~]# mkdir -p $HOME/.kube
[root@k8s-master ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@k8s-master ~]# chown $(id -u):$(id -g) $HOME/.kube/config

查看node节点
[root@k8s-master ~]# kubectl get nodes
NAME         STATUS     ROLES    AGE     VERSION
k8s-master   NotReady   master   2m41s   v1.19.1

 7、所有node节点操作

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

kubeadm join 10.0.0.130:6443 --token ymvzvj.5d3j0hbeq26wx9ff \
    --discovery-token-ca-cert-hash sha256:81086257eed9220e16c7ec407848b51adc1dba67cc4823dedde7396b830dbcc2   #就是刚才让记录的东西

 看到下面内容说明成功了,只要没有error,warnning不用管

8、配置使用网络插件

要让 Kubernetes Cluster 能够工作,必须安装 Pod 网络,否则 Pod 之间无法通信。

Kubernetes 支持多种网络方案,这里我们先使用 flannel,后面还会讨论 Canal。

在master节点操作
# cd ~ && mkdir flannel && cd flannel
# curl -O https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

这个是国外的网址,所以99%失败,下面是我在网上上找到的文件

vim kube-flannel.yml
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: psp.flannel.unprivileged
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
    seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
    apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
    apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
spec:
  privileged: false
  volumes:
    - configMap
    - secret
    - emptyDir
    - hostPath
  allowedHostPaths:
    - pathPrefix: "/etc/cni/net.d"
    - pathPrefix: "/etc/kube-flannel"
    - pathPrefix: "/run/flannel"
  readOnlyRootFilesystem: false
  # Users and groups
  runAsUser:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  # Privilege Escalation
  allowPrivilegeEscalation: false
  defaultAllowPrivilegeEscalation: false
  # Capabilities
  allowedCapabilities: ['NET_ADMIN']
  defaultAddCapabilities: []
  requiredDropCapabilities: []
  # Host namespaces
  hostPID: false
  hostIPC: false
  hostNetwork: true
  hostPorts:
  - min: 0
    max: 65535
  # SELinux
  seLinux:
    # SELinux is unused in CaaSP
    rule: 'RunAsAny'
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: flannel
rules:
  - apiGroups: ['extensions']
    resources: ['podsecuritypolicies']
    verbs: ['use']
    resourceNames: ['psp.flannel.unprivileged']
  - apiGroups:
      - ""
    resources:
      - pods
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes/status
    verbs:
      - patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: flannel
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: flannel
subjects:
- kind: ServiceAccount
  name: flannel
  namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: flannel
  namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-flannel-cfg
  namespace: kube-system
  labels:
    tier: node
    app: flannel
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "flannel",
          "delegate": {
            "hairpinMode": true,
            "isDefaultGateway": true
          }
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan"
      }
    }
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-amd64
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: beta.kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: beta.kubernetes.io/arch
                    operator: In
                    values:
                      - amd64
      hostNetwork: true
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-amd64
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-amd64
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        - --iface=ens33
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
            add: ["NET_ADMIN"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-arm64
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: beta.kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: beta.kubernetes.io/arch
                    operator: In
                    values:
                      - arm64
      hostNetwork: true
      tolerations:
      - operator: Exists
        effect: NoSchedule
      - key: node.kubernetes.io/not-ready
        operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-arm64
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-arm64
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-arm
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: beta.kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: beta.kubernetes.io/arch
                    operator: In
                    values:
                      - arm
      hostNetwork: true
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-arm
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-arm
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-ppc64le
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: beta.kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: beta.kubernetes.io/arch
                    operator: In
                    values:
                      - ppc64le
      hostNetwork: true
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-ppc64le
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-ppc64le
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-s390x
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: beta.kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: beta.kubernetes.io/arch
                    operator: In
                    values:
                      - s390x
      hostNetwork: true
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-s390x
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-s390x
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg

 上面的这个镜像,是解决网络问题的flannel镜像,每个节点都要拉取,刚开始的时候我们已经拉取过了

 启动flannel

启动:
# kubectl apply -f ~/flannel/kube-flannel.yml  #启动完成之后需要等待一会
NAME                                     READY   STATUS    RESTARTS   AGE
coredns-5644d7b6d9-sm8hs                 1/1     Running   0          9m18s
coredns-5644d7b6d9-vddll                 1/1     Running   0          9m18s
etcd-kub-k8s-master                      1/1     Running   0          8m14s
kube-apiserver-kub-k8s-master            1/1     Running   0          8m17s
kube-controller-manager-kub-k8s-master   1/1     Running   0          8m20s
kube-flannel-ds-amd64-9wgd8              1/1     Running   0          8m42s
kube-proxy-sgphs                         1/1     Running   0          9m18s
kube-scheduler-kub-k8s-master            1/1     Running   0          8m10s

查看:
# kubectl get pods -n kube-system
# kubectl get service
# kubectl get svc --namespace kube-system
只有网络插件也安装配置完成之后,才能会显示为ready状态

 在master操作

各种检测:
1.查看pods:
[root@k8s-master ~]# kubectl get pods -n kube-system
NAME                                     READY   STATUS    RESTARTS   AGE
coredns-5644d7b6d9-sm8hs                 1/1     Running   0          39m
coredns-5644d7b6d9-vddll                 1/1     Running   0          39m
etcd-kub-k8s-master                      1/1     Running   0          37m
kube-apiserver-kub-k8s-master            1/1     Running   0          38m
kube-controller-manager-kub-k8s-master   1/1     Running   0          38m
kube-flannel-ds-amd64-9wgd8              1/1     Running   0          38m
kube-flannel-ds-amd64-lffc8              1/1     Running   0          2m11s
kube-flannel-ds-amd64-m8kk2              1/1     Running   0          2m2s
kube-proxy-dwq9l                         1/1     Running   0          2m2s
kube-proxy-l77lz                         1/1     Running   0          2m11s
kube-proxy-sgphs                         1/1     Running   0          39m
kube-scheduler-kub-k8s-master            1/1     Running   0          37m

2.查看异常pod信息:
[root@k8s-master ~]# kubectl  describe pods kube-flannel-ds-sr6tq -n  kube-system
Name:               kube-flannel-ds-sr6tq
Namespace:          kube-system
Priority:           0
PriorityClassName:  <none>
。。。。。
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Pulling    12m                  kubelet, node2     pulling image "registry.cn-shanghai.aliyuncs.com/gcr-k8s/flannel:v0.10.0-amd64"
  Normal   Pulled     11m                  kubelet, node2     Successfully pulled image "registry.cn-shanghai.aliyuncs.com/gcr-k8s/flannel:v0.10.0-amd64"
  Normal   Created    11m                  kubelet, node2     Created container
  Normal   Started    11m                  kubelet, node2     Started container
  Normal   Created    11m (x4 over 11m)    kubelet, node2     Created container
  Normal   Started    11m (x4 over 11m)    kubelet, node2     Started container
  Normal   Pulled     10m (x5 over 11m)    kubelet, node2     Container image "registry.cn-shanghai.aliyuncs.com/gcr-k8s/flannel:v0.10.0-amd64" already present on machine
  Normal   Scheduled  7m15s                default-scheduler  Successfully assigned kube-system/kube-flannel-ds-sr6tq to node2
  Warning  BackOff    7m6s (x23 over 11m)  kubelet, node2     Back-off restarting failed container

3.遇到这种情况直接 删除异常pod:
[root@k8s-master ~]# kubectl delete pod kube-flannel-ds-sr6tq -n kube-system
pod "kube-flannel-ds-sr6tq" deleted

4.查看pods:
[root@k8s-master ~]# kubectl get pods -n kube-system
NAME                                     READY   STATUS    RESTARTS   AGE
coredns-5644d7b6d9-sm8hs                 1/1     Running   0          44m
coredns-5644d7b6d9-vddll                 1/1     Running   0          44m
etcd-kub-k8s-master                      1/1     Running   0          42m
kube-apiserver-kub-k8s-master            1/1     Running   0          43m
kube-controller-manager-kub-k8s-master   1/1     Running   0          43m
kube-flannel-ds-amd64-9wgd8              1/1     Running   0          43m
kube-flannel-ds-amd64-lffc8              1/1     Running   0          7m10s
kube-flannel-ds-amd64-m8kk2              1/1     Running   0          7m1s
kube-proxy-dwq9l                         1/1     Running   0          7m1s
kube-proxy-l77lz                         1/1     Running   0          7m10s
kube-proxy-sgphs                         1/1     Running   0          44m
kube-scheduler-kub-k8s-master            1/1     Running   0          42m

5.查看节点:
[root@k8s-master ~]# kubectl get nodes
NAME             STATUS   ROLES    AGE     VERSION
kub-k8s-master   Ready    master   43m     v1.19.1
kub-k8s-node1    Ready    <none>   6m46s   v1.19.1
kub-k8s-node2    Ready    <none>   6m37s   v1.19.1
到此集群配置完成

错误整理

1、移除node节点的方法

[root@k8s-master ~]# kubectl drain k8s-node2 --delete-local-data --force --ignore-daemonsets
[root@k8s-master ~]# kubectl delete nodes k8s-node2
node "k8s-node2" deleted
[root@k8s-master ~]# kubectl get nodes
NAME         STATUS   ROLES    AGE     VERSION
k8s-master   Ready    master   11m     v1.19.1
k8s-node1    Ready    <none>   7m39s   v1.19.1   #node2节点已经移除

2、添加已删除节点

前提:token未失效

因为之前的token还有效,我这里并没有超出token的有效期;直接执行加入集群的命令即可;
[root@k8s-node2 ~]# kubeadm join 10.0.0.132:6443 --token jvjxs2.xu92rq4fetgtpy1o     --discovery-token-ca-cert-hash sha256:56159a0de43781fd57f1df829de4fe906cf355f4fec8ff7f6f9078c77c8c292d

如果这个时候再想添加进来这个node,需要执行两步操作

第一步:停掉kubelet(需要添加进来的节点操作)

[root@k8s-node2 ~]# systemctl stop kubelet

第二步:删除相关文件

[root@k8s-node2 ~]# rm -rf /etc/kubernetes/*

第三步:添加节点

因为之前的token还有效,我这里并没有超出token的有效期;直接执行加入集群的命令即可;
[root@k8s-node2 ~]# kubeadm join 10.0.0.132:6443 --token jvjxs2.xu92rq4fetgtpy1o     --discovery-token-ca-cert-hash sha256:56159a0de43781fd57f1df829de4fe906cf355f4fec8ff7f6f9078c77c8c292d

第四步:验证查看,看一下是否有k8s-node2

kubectl get nodes

3、忘掉token再次添加进k8s集群

前提:token未失效

第一步:主节点执行命令

在主控节点,获取token:

 第二步: 获取ca证书sha256编码hash值

[root@k8s-master ~]#  openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
56159a0de43781fd57f1df829de4fe906cf355f4fec8ff7f6f9078c77c8c292d

第三步:主节点移除node2

[root@k8s-master ~]# kubectl drain k8s-node2 --delete-local-data --force --ignore-daemonsets
[root@k8s-master ~]# kubectl delete nodes k8s-node2
[root@k8s-master ~]# kubectl get node

第四步:从节点执行如下的命令

[root@k8s-node2 ~]# systemctl stop kubelet
[root@k8s-node2 ~]# rm -rf /etc/kubernetes/*

第五步:加入集群

指定主节点IP,端口是6443

在生成的证书前有sha256:

[root@k8s-node2 ~]# kubeadm join 10.0.0.132:6443 --token jvjxs2.xu92rq4fetgtpy1o     --discovery-token-ca-cert-hash sha256:56159a0de43781fd57f1df829de4fe906cf355f4fec8ff7f6f9078c77c8c292d

第六步:主节点查看验证4、加载flannel失败

[root@k8s-master flannel]# kubectl apply -f kube-flannel.yml  

4、加载flannel失败

[root@k8s-master flannel]# kubectl apply -f kube-flannel.yml  

 报错:

解决方法:

[root@k8s-master flannel]# kubectl delete -f kube-flannel.yml
[root@k8s-master flannel]# vim kube-flannel.yml #进行版本的修改

重新创建即可
[root@k8s-master flannel]# kubectl apply -f kube-flannel.yml
查看文件中,指定的api对象,是否都创建成功
[root@k8s-master flannel]# kubectl get pod -n  kube-system

 5、部署flannel网络后pod及容器无法跨主机互通问题

方法一:重启docker,
方法二:卸载flannel网络

 方法二:

在master卸载flannel

kubectl delete -f kube-flannel.yml

在node节点清理flannel网络残留文件

ifconfig cni0 down
ip link delete cni0
ifconfig flannel.1 down
ip link delete flannel.1
rm -rf /var/lib/cni/
rm -f /etc/cni/net.d/*

 重新部署flannel网络

[root@k8s-master01 flannel]# kubectl create -f kube-flannel.yml 

[root@k8s-master01 flannel]# kubectl get pod -n kube-system
NAME                                    READY   STATUS    RESTARTS   AGE
coredns-5c98db65d4-8bpdd                1/1     Running   0          17s
coredns-5c98db65d4-knfcj                1/1     Running   0          43s
etcd-k8s-master01                       1/1     Running   2          10d
kube-apiserver-k8s-master01             1/1     Running   2          10d
kube-controller-manager-k8s-master01    1/1     Running   3          10d
kube-flannel-ds-amd64-56hsf             1/1     Running   0          25m
kube-flannel-ds-amd64-56t49             1/1     Running   0          25m
kube-flannel-ds-amd64-qz42z             1/1     Running   0          25m
kube-proxy-5fn9m                        1/1     Running   1          10d
kube-proxy-6hjvp                        1/1     Running   2          10d
kube-proxy-t47n9                        1/1     Running   2          10d
kube-scheduler-k8s-master01             1/1     Running   4          10d
kubernetes-dashboard-7d75c474bb-4r7hc   1/1     Running   0          23m
[root@k8s-master01 flannel]# 

 flannel网络显示正常, 容器之间可以跨主机互通!

其他错误

错误
问题1:服务器时间不一致会报错
查看服务器时间
=====================================
问题2:kubeadm init不成功,发现如下提示,然后超时报错
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s

查看kubelet状态发现如下错误,主机master找不到和镜像下载失败,发现pause镜像是从aliyuncs下载的,其实我已经下载好了官方的pause镜像,按着提示的镜像名称重新给pause镜像打个ali的tag,最后重置kubeadm的环境重新初始化,错误解决
[root@master manifests]# systemctl  status kubelet -l
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/etc/systemd/system/kubelet.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: active (running) since 四 2019-01-31 15:20:32 CST; 5min ago
     Docs: https://kubernetes.io/docs/
 Main PID: 23908 (kubelet)
    Tasks: 19
   Memory: 30.8M
   CGroup: /system.slice/kubelet.service
           └─23908 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=cgroupfs --network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.1 --cgroup-driver=cgroupfs --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.1

1月 31 15:25:41 master kubelet[23908]: E0131 15:25:41.432357   23908 kubelet.go:2266] node "master" not found
1月 31 15:25:41 master kubelet[23908]: E0131 15:25:41.532928   23908 kubelet.go:2266] node "master" not found
1月 31 15:25:41 master kubelet[23908]: E0131 15:25:41.633192   23908 kubelet.go:2266] node "master" not found
1月 31 15:25:41 master kubelet[23908]: I0131 15:25:41.729296   23908 kubelet_node_status.go:278] Setting node annotation to enable volume controller attach/detach
1月 31 15:25:41 master kubelet[23908]: E0131 15:25:41.733396   23908 kubelet.go:2266] node "master" not found
1月 31 15:25:41 master kubelet[23908]: E0131 15:25:41.740110   23908 remote_runtime.go:96] RunPodSandbox from runtime service failed: rpc error: code = Unknown desc = failed pulling image "registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.1": Error response from daemon: Get https://registry.cn-hangzhou.aliyuncs.com/v2/: dial tcp 0.0.0.80:443: connect: invalid argument
1月 31 15:25:41 master kubelet[23908]: E0131 15:25:41.740153   23908 kuberuntime_sandbox.go:68] CreatePodSandbox for pod "kube-controller-manager-master_kube-system(e8f43404e60ae844e375d50b1e39d91e)" failed: rpc error: code = Unknown desc = failed pulling image "registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.1": Error response from daemon: Get https://registry.cn-hangzhou.aliyuncs.com/v2/: dial tcp 0.0.0.80:443: connect: invalid argument
1月 31 15:25:41 master kubelet[23908]: E0131 15:25:41.740166   23908 kuberuntime_manager.go:662] createPodSandbox for pod "kube-controller-manager-master_kube-system(e8f43404e60ae844e375d50b1e39d91e)" failed: rpc error: code = Unknown desc = failed pulling image "registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.1": Error response from daemon: Get https://registry.cn-hangzhou.aliyuncs.com/v2/: dial tcp 0.0.0.80:443: connect: invalid argument
1月 31 15:25:41 master kubelet[23908]: E0131 15:25:41.740207   23908 pod_workers.go:190] Error syncing pod e8f43404e60ae844e375d50b1e39d91e ("kube-controller-manager-master_kube-system(e8f43404e60ae844e375d50b1e39d91e)"), skipping: failed to "CreatePodSandbox" for "kube-controller-manager-master_kube-system(e8f43404e60ae844e375d50b1e39d91e)" with CreatePodSandboxError: "CreatePodSandbox for pod \"kube-controller-manager-master_kube-system(e8f43404e60ae844e375d50b1e39d91e)\" failed: rpc error: code = Unknown desc = failed pulling image \"registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.1\": Error response from daemon: Get https://registry.cn-hangzhou.aliyuncs.com/v2/: dial tcp 0.0.0.80:443: connect: invalid argument"
1月 31 15:25:41 master kubelet[23908]: E0131 15:25:41.833981   23908 kubelet.go:2266] node "master" not found

解决方法

重置kubeadm环境
整个集群所有节点(包括master)重置/移除节点
1.驱离k8s-node-1节点上的pod(master上)
[root@k8s-master ~]# kubectl drain kub-k8s-node1 --delete-local-data --force --ignore-daemonsets

2.删除节点(master上)
[root@k8s-master ~]# kubectl delete node kub-k8s-node1

3.重置节点(node上-也就是在被删除的节点上)
[root@k8s-node1 ~]# kubeadm reset

注1:需要把master也驱离、删除、重置,这里给我坑死了,第一次没有驱离和删除master,最后的结果是查看结果一切正常,但coredns死活不能用,搞了整整1天,切勿尝试

注2:master上在reset之后需要删除如下文件
# rm -rf /var/lib/cni/ $HOME/.kube/config

###注意:如果整个k8s集群都做完了,需要重置按照上面步骤操作。如果是在初始化出错只需要操作第三步

五、部署Harbor仓库

实验需要安装docker-compose,需要下载1.10.10版本的Harbor包(这个需要自己去网上找,还可能需要翻..你懂得),因为实验环境考虑到配置问题,我们就把Harbor仓库安装在我们上个实验的master节点上。

上传两个软件包
docker-compose-linux-2.2.3-x86_64
harbor-offline-installer-v1.10.10.tgz

[root@k8s-master ~]# mv docker-compose-linux-2.2.3-x86_64 /usr/local/bin/docker-compose
[root@k8s-master ~]# chmod +x /usr/local/bin/docker-compose
[root@k8s-master ~]# docker-compose -v
[root@k8s-master ~]# tar zxf harbor-offline-installer-v1.10.10.tgz
[root@k8s-master ~]# mkdir /data/cert -p   #创建目录是为了制作假证书,配置https访问Harbor仓库
[root@k8s-master ~]# cd /data/cert/
[root@k8s-master cert]# openssl genrsa -out /data/cert/server.key 2048
[root@k8s-master cert]# openssl req -x509 -new -nodes -key /data/cert/server.key -subj "/CN=10.0.0.130" -days 3650 -out /data/cert/server.crt
[root@k8s-master cert]# ls
server.crt  server.key
[root@k8s-master cert]# cd /root/harbor/
[root@k8s-master harbor]# vim harbor.yml
hostname: 10.0.0.130    #修改为本机的ip地址

# http related config
http:
  # port for http, default is 80. If https enabled, this port will redirect to https port
  port: 80

# https related config
https:
  # https port for harbor, default is 443
  port: 443
  # The path of cert and key files for nginx
  certificate: /data/cert/server.crt   #这是刚才生成的两个证书的绝对路径
  private_key: /data/cert/server.key
.......其他的我们不用动

访问https://10.0.0.130

 创建用户

创建私有仓库

 项目授权

 在两个node节点做一下操作

[root@k8s-node1 ~]# vim /etc/docker/daemon.json 
{
"insecure-registries": ["10.0.0.130"]
}
[root@k8s-node1 ~]# systemctl restart docker

拉取测试(刚才创建的是私有仓库,所以拉取的时候需要登录有权限的用户,没有权限的话会失败)

[root@k8s-node1 ~]# docker login 10.0.0.130 --username='soso' --password='***'
[root@k8s-node1 ~]# docker pull daocloud.io/library/nginx
3.查看
[root@k8s-node1 ~]# docker images
REPOSITORY                     TAG       IMAGE ID            CREATED        SIZE
daocloud.io/library/nginx     latest   98ebf73aba75        3 months ago    109MB
4.打个tag
[root@k8s-node1 ~]# docker tag daocloud.io/library/nginx:latest 192.168.246.166/jenkins/nginx
5.上传到仓库
[root@k8s-node1 ~]# docker push 192.168.246.166/jenkins/nginx
The push refers to repository [192.168.246.166/jenkins/nginx]
589561a3ffb4: Pushed 
ef7dbb0cfc81: Pushed 
d56055da3352: Pushed 
latest: digest: sha256:f83b2ffd963ac911f9e638184c8d580cc1f3139d5c8c33c87c3fb90aebdebf76 size: 948

在web界面中查看镜像是否上传

 拉取测试

来到node2

[root@k8s-node2 ~]# docker login 10.0.0.130 --username='soso' --password='***'

[root@k8s-node2 ~]# docker pull 10.0.0.130/jenkins/nginx

六、集群基本操作--查看集群信息

1、查看集群信息
[root@k8s-master ~]# kubectl get nodes
NAME         STATUS   ROLES    AGE   VERSION
k8s-master   Ready    master   29h   v1.19.1
k8s-node1    Ready    <none>   28h   v1.19.1
k8s-node2    Ready    <none>   28h   v1.19.1

2、删除节点(无效显示的也可以删除)
[root@k8s-master ~]# kubectl delete node kub-k8s-node1

3、查看某一个节点(节点名称可以用空格隔开写多个)
[root@k8s-master ~]# kubectl get node kub-k8s-node1
NAME            STATUS   ROLES    AGE   VERSION
k8s-node1       Ready    <none>   15h   v1.19.1

使用 kubectl describe 命令,查看一个 API 对象的细节:

注意:Events(事件) 值得你特别关注

在 Kubernetes 执行的过程中,对 API 对象的所有重要操作,都会被记录在这个对象的 Events 里,并且显示在 kubectl describe 指令返回的结果中。

比如,对于这个 Pod,我们可以看到它被创建之后,被调度器调度(Successfully assigned)到了 node-1,拉取了指定的镜像(pulling image),然后启动了 Pod 里定义的容器(Started container)。

这个部分正是我们将来进行 Debug 的重要依据。如果有异常发生,一定要第一时间查看这些 Events,往往可以看到非常详细的错误信息。

查看node的详细信息

[root@k8s-master ~]# kubectl describe node k8s-node1
Name:               k8s-node1
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=k8s-node1
                    kubernetes.io/os=linux
....
Events:              <none>
#注意:最后被查看的节点名称只能用get nodes里面查到的name!

查看各组件信息

查看service的信息
[root@k8s-master ~]# kubectl get service
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   29h

在不同的namespace里面查看service
[root@k8s-master ~]# kubectl get service -n kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   29h
-n:namespace名称空间  默认是dafault

查看所有名称空间内的资源:
[root@k8s-master ~]# kubectl get pods --all-namespaces

同时查看所有命名空间内的资源:
[root@k8s-master ~]# kubectl get pod,svc -n kube-system

查看主节点:
[root@k8s-master ~]# kubectl cluster-info

api查询:
[root@k8s-master ~]# kubectl api-versions

创建命名空间

编写yaml文件
[root@k8s-master ~]# mkdir prome
[root@k8s-master ~]# cd prome/
[root@k8s-master prome]# vim namespace.yaml
---
apiVersion: v1   #api版本
kind: Namespace  #类型---固定的
metadata:     #元数据
  name: zhaosi  #起个名字
  labels:
    name: zhaosi
2. 创建资源
[root@k8s-master prome]# kubectl apply -f namespace.yml
namespace/zhaosi created
3. 查看资源
[root@k8s-master prome]# kubectl get namespace
NAME              STATUS   AGE
default           Active   22h
kube-node-lease   Active   22h
kube-public       Active   22h
kube-system       Active   22h
zhaosi            Active   34s
4.查看某一个namespace
[root@k8s-master prome]# kubectl get namespace zhaosi
5.查看某个namespace的详细信息
[root@k8s-master prome]# kubectl describe namespace zhaosi
6.删除名称空间
[root@k8s-master prome]# kubectl delete -f namespace.yml
namespace "ns-monitor" deleted
或者
[root@k8s-master prome]# kubectl delete namespace zhaosi
namespace "ns-monitor" deleted

七、发布第一个容器化应用

1、有镜像

2、部署应用。--考虑做不做副本不做副本就是pod,做副本以deployment方式去创建。做了副本访问还需要做一个service,使用访问。

发布第一个容器化应用

扮演一个应用开发者的角色,使用这个 Kubernetes 集群发布第一个容器化应用。

1、 作为一个应用开发者,你首先要做的,是制作容器的镜像。
2、 有了容器镜像之后,需要按照 Kubernetes 项目的规范和要求,将你的镜像组织为它能够"认识"的方式,然后提交上去。

什么才是 Kubernetes 项目能"认识"的方式?

就是使用 Kubernetes 的必备技能:编写配置文件。
这些配置文件可以是 YAML 或者 JSON 格式的。

Kubernetes 跟 Docker 等很多项目最大的不同,就在于它不推荐你使用命令行的方式直接运行容器(虽然 Kubernetes 项目也支持这种方式,比如:kubectl run),而是希望你用 YAML 文件的方式,即:把容器的定义、参数、配置,统统记录在一个 YAML 文件中,然后用这样一句指令把它运行起来:

# kubectl create/apply -f 我的配置文件

好处:你会有一个文件能记录下 Kubernetes 到底"run"了什么,方便自身以后查看记录

使用yaml创建pod

YAML文件,对应到k8s中,就是一个API Object(API 对象)。当你为这个对象的各个字段填好值并提交给k8s之后,k8s就会负责创建出这些对象所定义的容器或者其他类型的API资源。

编写yaml文件内容如下:

[root@k8s-master prome]# vim pod.yml
---
apiVersion: v1   #api版本,支持pod的版本
kind: Pod        #Pod,定义类型注意语法开头大写
metadata:        #元数据
  name: nginx    #这是Pod的名字
  labels:
    app: nginx   #自定义,不能数字开头,不能特殊符号开头
spec:            #指定的意思
  containers:    #定义容器
    - name: nginx #容器的名字,自定义
      image: daocloud.io/library/nginx:latest #镜像,本地用的话,用本地,没有的话会拉取
      ports:
        - containerPort: 80    #容器暴露的端口

创建Pod

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

查看Pod

[root@k8s-master prome]# kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
nginx     1/1     Running   0          74s
=============================================================================
各字段含义:
NAME: Pod的名称
READY: Pod的准备状况,右边的数字表示Pod包含的容器总数目,左边的数字表示准备就绪的容器数目
STATUS: Pod的状态
RESTARTS: Pod的重启次数
AGE: Pod的运行时间

pod的准备状况指的是Pod是否准备就绪以接收请求,Pod的准备状况取决于容器,即所有容器都准备就绪了,Pod才准备就绪。这时候kubernetes的代理服务才会添加Pod作为后端,而一旦Pod的准备状况变为false(至少一个容器的准备状况为false),kubernetes会将Pod从代理服务的分发后端移除,即不会分发请求给该Pod。

一个pod刚被创建的时候是不会被调度的,因为没有任何节点被选择用来运行这个pod。调度的过程发生在创建完成之后,但是这个过程一般很快,所以你通常看不到pod是处于unscheduler状态的除非创建的过程遇到了问题。

pod被调度之后,分配到指定的节点上运行,这时候,如果该节点没有所需要的image,那么将会自动从默认的Docker Hub上pull指定的image,一切就绪之后,看到pod是处于running状态了

查看pod运行中哪台机器上

[root@k8s-master prome]# kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          3s    10.244.1.4   k8s-node1   <none>           <none>

可以测试访问:
[root@k8s-master prome]# curl  10.244.1.4   #访问pod的ip

 查看Pod定义的详细信息

查看pod的详细信息----指定pod名字
[root@k8s-master prome]# kubectl get pod nginx -o yaml
-o:output
yaml:yaml格式也可以是json格式

查看kubectl describe支持查询Pod的状态和生命周期事件

[root@k8s-master prome]# kubectl describe pod nginx
Name:         nginx
Namespace:    default
Priority:     0
Node:         k8s-node1/10.0.0.131
Start Time:   Thu, 09 Jun 2022 21:10:08 +0800
Labels:       app=nginx
...
1.各字段含义:
Name: Pod的名称
Namespace: Pod的Namespace。
Image(s): Pod使用的镜像
Node: Pod所在的Node。
Start Time: Pod的起始时间
Labels: Pod的Label。
Status: Pod的状态。
Reason: Pod处于当前状态的原因。
Message: Pod处于当前状态的信息。
IP: Pod的PodIP
Replication Controllers: Pod对应的Replication Controller。
===============================
2.Containers:Pod中容器的信息
Container ID: 容器的ID
Image: 容器的镜像
Image ID:镜像的ID
State: 容器的状态
Ready: 容器的准备状况(true表示准备就绪)。
Restart Count: 容器的重启次数统计
Environment Variables: 容器的环境变量
Conditions: Pod的条件,包含Pod准备状况(true表示准备就绪)
Volumes: Pod的数据卷
Events: 与Pod相关的事件列表
=====
生命周期:指的是status通过# kubectl get pod
生命周期包括:running、Pending、completed、

生命周期介绍

Pending:此状态表示Pod 的 YAML 文件已经提交给了 Kubernetes,API 对象已经被创建并保存在 Etcd 当中(准备状态)。但这个 Pod 里有些容器因为某种原因而不能被顺利创建。比如,调度不成功。

Running:此状态表示Pod 已经调度成功,跟一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。

Succeeded:此状态表示 Pod 里的所有容器都正常运行完毕,并且已经退出了。这种情况在运行一次性任务时最为常见。

Failed:此状态表示 Pod 里至少有一个容器以不正常的状态(非 0 的返回码)退出。这个状态的出现,意味着你得想办法 Debug 这个容器的应用,比如查看 Pod 的 Events 和日志。

Unknown:这是一个异常状态(未知状态),表示 Pod 的状态不能持续地被 kubelet 汇报给 kube-apiserver这很有可能是主从节点(Master 和 Kubelet)间的通信出现了问题

其他状态
CrashLoopBackOff: 容器退出,kubelet正在将它重启
InvalidImageName: 无法解析镜像名称
ImageInspectError: 无法校验镜像
ErrImageNeverPull: 策略禁止拉取镜像
ImagePullBackOff: 正在重试拉取
RegistryUnavailable: 连接不到镜像中心
ErrImagePull: 通用的拉取镜像出错
CreateContainerConfigError: 不能创建kubelet使用的容器配置
CreateContainerError: 创建容器失败
m.internalLifecycle.PreStartContainer  执行hook报错
RunContainerError: 启动容器失败
PostStartHookError: 执行hook报错 
ContainersNotInitialized: 容器没有初始化完毕
ContainersNotReady: 容器没有准备完毕 
ContainerCreating:容器创建中
PodInitializing:pod 初始化中 
DockerDaemonNotReady:docker还没有完全启动
NetworkPluginNotReady: 网络插件还没有完全启动

进入Pod容器内部

通过pod名称
[root@k8s-master prome]# kubectl exec -it nginx /bin/bash
root@nginx:/#

删除Pod

[root@k8s-master prome]# kubectl delete pod pod名1 pod名2   //单个或多个删除
[root@k8s-master prome]# kubectl delete pod --all   //批量删除
举例:
[root@k8s-master prome]# kubectl delete pod nginx
pod "nginx" deleted
[root@k8s-master prome]# kubectl delete -f pod.yaml 
pod "nginx" deleted

创建Pod

[root@k8s-master prome]# kubectl apply -f pod.yaml  #指定创建pod的yml文件名
[root@k8s-master prome]# kubectl apply -f pod.yaml  --validate 想看报错信息,加上--validate参数

重新启动基于yaml文件的应用(这里并不是重新启动服务)

# kubectl delete -f XXX.yaml   #删除
# kubectl apply -f XXX.yaml   #创建
create与apply的区别:
create创建的应用如果需要修改yml文件,必须先指定yml文件删除,在创建新的pod。
如果是apply创建的应用可以直接修改yml文件,继续apply创建,不用先删掉。

八、YAML文件语法解析

除了某些强制性的命令,如:kubectl run或者expose等,k8s还允许通过配置文件的方式来创建这些操作对象。

通常,使用配置文件的方式会比直接使用命令行更可取,因为这些文件可以进行版本控制,而且文件的变化和内容也可以进行审核,当使用及其复杂的配置来提供一个稳健、可靠和易维护的系统时,这些点就显得非常重要。

在声明定义配置文件的时候,所有的配置文件都存储在YAML或者JSON格式的文件中并且遵循k8s的资源配置方式。

YAML是专门用来写配置文件的语言,非常简洁和强大,使用比json更方便。它实质上是一种通用的数据串行化格式。

kubernetes中用来定义YAML文件创建Pod和创建Deployment等资源。

使用YAML用于K8s的定义的好处:
便捷性:不必添加大量的参数到命令行中执行命令
可维护性:YAML文件可以通过源头控制,跟踪每次操作
灵活性:YAML可以创建比命令行更加复杂的结构
YAML语法规则:
    1. 大小写敏感/区分大小写
    2. 使用缩进表示层级关系
    3. 缩进时不允许使用Tab键,只允许使用空格
    4. 缩进的空格数不重要,只要相同层级的元素左侧对齐即可
    5. " 表示注释,从这个字符一直到行尾,都会被解析器忽略
在 k8s 中,只需要知道两种结构类型:
1.Lists
2.Maps
字典
    a={key:value, key1:{key2:{value2}}, key3:{key4:[1,{key5:value5},3,4,5]}}
key: value
key1:
   key2: value2
key3:
   key4:
      - 1
      - key5: value5
      - 3
      - 4
      - 5

YAML  Maps
Map指的是字典,即一个Key:Value 的键值对信息。
例如:
---
apiVersion: v1
kind: Pod

注:--- 为可选的分隔符 ,当需要在一个文件中定义多个结构的时候需要使用。上述内容表示有两个键apiVersion和kind,分别对应的值为v1和Pod。

Maps的value既能够对应字符串也能够对应一个Maps。
例如:
---
apiVersion: v1
kind: Pod
metadata:
  name: kube100-site
  labels:
    app: web    
 
{apiVersion:v1,kind:Pod,Metadata:{name:kube100-site,labels:{app:web}}}  
注:上述的YAML文件中,metadata这个KEY对应的值为一个Maps,而嵌套的labels这个KEY的值又是一个Map。实际使用中可视情况进行多层嵌套。

YAML处理器根据行缩进来知道内容之间的关联。上述例子中,使用两个空格作为缩进,但空格的数据量并不重要,只是至少要求一个空格并且所有缩进保持一致的空格数 。例如,name和labels是相同缩进级别,因此YAML处理器知道他们属于同一map;它知道app是lables的值因为app的缩进更大。
注意:在YAML文件中绝对不要使用tab键
YAML   Lists
List即列表,就是数组
例如:
args:
 - beijing
 - shanghai
 - shenzhen
 - guangzhou

可以指定任何数量的项在列表中,每个项的定义以连字符(-)开头,并且与父元素之间存在缩进。

在JSON格式中,表示如下:
{
"args": ["beijing", "shanghai", "shenzhen", "guangzhou"]
}

九、Pod API属性详解

Pod API 对象

Pod是 k8s 项目中的最小编排单位。将这个设计落实到 API 对象上,容器(Container)就成了 Pod 属性里一个普通的字段。

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

解决:

Pod 扮演的是传统环境里"虚拟机"的角色。是为了使用户从传统环境(虚拟机环境)向 k8s(容器环境)的迁移,更加平滑。

把 Pod 看成传统环境里的"机器"、把容器看作是运行在这个"机器"里的"用户程序",那么很多关于 Pod 对象的设计就非常容易理解了。

凡是调度、网络、存储,以及安全相关的属性,基本上是 Pod 级别的

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

比如:

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

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

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

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

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

spec:specification of the resource content 指定该资源的内容,包括一些container,storage,volume以及其他Kubernetes需要的参数,以及诸如是否在容器失败时重新启动容器的属性。可在特定Kubernetes API找到完整的Kubernetes Pod的属性。

specification----->[spesɪfɪˈkeɪʃn]

容器可选的设置属性:

除了上述的基本属性外,还能够指定复杂的属性,包括容器启动运行的命令、使用的参数、工作目录以及每次实例化是否拉取新的副本。 还可以指定更深入的信息,例如容器的退出日志的位置。

容器可选的设置属性包括:

"name"、"image"、"command"、"args"、"workingDir"、"ports"、"env"、resource、"volumeMounts"、livenessProbe、readinessProbe、livecycle、terminationMessagePath、"imagePullPolicy"、securityContext、stdin、stdinOnce、tty

跟"机器"相关的配置

[root@k8s-master ~]# cd prome/
[root@k8s-master prome]# kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
nginx     1/1     Running   0          2d23h
[root@k8s-master prome]# kubectl get pod -o wide   #查看pod运行在哪台机器上面

1、Pod调度

nodeSelector
nodeName
这两个属性的功能是一样的都是用于人工干预调度器

1、指定node节点的名称(nodeName)

将node1上面的pod删除掉
[root@k8s-master prome]# kubectl delete -f pod.yml 
pod "website" deleted
===========================================
nodeName:是一个供用户将 Pod 与 Node 进行绑定的字段,用法:
现在指定将pod创在node2上面:
[root@k8s-master prome]# vim pod.yml
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
    - name: nginx
      image: daocloud.io/library/nginx:latest
      ports:
        - containerPort: 80
  nodeName: kub-k8s-node2     #指定node节点的名称
创建
[root@k8s-master prome]# kubectl apply -f pod.yml 
pod/nginx created
[root@k8s-master prome]# kubectl get pod -o wide  #验证是不是再node2节点上

NodeName:一旦 Pod 的这个字段被赋值,k8s就会被认为这个 Pod 已经经过了调度,调度的结果就是赋值的节点名字。这个字段一般由调度器负责设置,用户也可以设置它来"骗过"调度器,这个做法一般是在测试或者调试的时候才会用到。

2、指定node标签(nodeSelector)

1.查看node2上面的标签
[root@k8s-master prome]# kubectl describe node kub-k8s-node2

1.重新创建一个新的pod
"nodeSelector:是一个供用户将 Pod 与 Node 进行绑定的字段",,通过指定标签来指定

[root@k8s-master prome]# vim tomcat.yml
---
apiVersion: v1
kind: Pod
metadata:
  name: tomcat
  labels:
    app: tomcat
spec:
  containers:
    - name: tomcat
      image: daocloud.io/library/tomcat:8
      ports:
        - containerPort: 8080
  nodeSelector:      #指定标签
    kubernetes.io/hostname: k8s-node2
2.创建pod   
[root@k8s-master prome]# kubectl apply -f tomcat.yml 
pod/tomcat created

 注:表示这个 Pod 永远只能运行在携带了"kubernetes.io/hostname: kub-k8s-node2"标签(Label)的节点上;否则,它将调度失败。

2、域名解析

设置pod容器里面的hosts文件内容,也是做本地解析

HostAliases:定义 Pod 的 hosts 文件(比如 /etc/hosts)里的内容,用法:

1.首先先将刚创建的pod删除掉
[root@k8s-master prome]# kubectl delete -f tomcat.yml 
pod "tomcat" deleted
[root@k8s-master prome]# vim tomcat.yml
---
apiVersion: v1
kind: Pod
metadata:
  name: tomcat
  labels:
    app: tomcat
spec:
  hostAliases:
  - ip: "10.0.0.131"   #给哪个ip做解析。实验环境下这个ip自定义的
    hostnames:
    - "foo.remote"    #解析的名字。用引号引起来可以写多个
    - "bar.remote"
  containers:
    - name: tomcat
      image: daocloud.io/library/tomcat:8
      ports:
        - containerPort: 8080
2.创建pod
[root@k8s-master prome]# kubectl apply -f tomcat.yml 
pod/tomcat created
3.连接pod
[root@k8s-master prome]# kubectl exec -it tomcat /bin/bash 
root@tomcat:/usr/local/tomcat# cat /etc/hosts   #查看hosts文件

注意:

  • 在 k8s 中,如果要设置 hosts 文件里的内容,一定要通过这种方法。否则,如果直接修改了 hosts 文件,在 Pod 被删除重建之后,kubelet 会自动覆盖掉被修改的内容。

3、进程共享

凡是跟容器的 Linux Namespace 相关的属性,也一定是 Pod 级别的

原因:Pod 的设计,就是要让它里面的容器尽可能多地共享 Linux Namespace,仅保留必要的隔离和限制能力。这样,Pod 模拟出的效果,就跟虚拟机里程序间的关系非常类似了。

举例,一个 Pod 定义 yaml 文件如下:

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

在 Pod 的 YAML 文件里声明开启它们俩,等同于设置了 docker run 里的 -it(-i 即 stdin,-t 即 tty)参数。
可以直接认为 tty 就是 Linux 给用户提供的一个常驻小程序,用于接收用户的标准输入,返回操作系统的标准输出。为了能够在 tty 中输入信息,需要同时开启 stdin(标准输入流)。

此 Pod 被创建后,就可以使用 shell 容器的 tty 跟这个容器进行交互了。

我们登录node1的机器连接busybox的容器

1571583535653

 在容器里不仅可以看到它本身的 ps 指令,还可以看到 nginx 容器的进程,以及 Infra 容器的 /pause 进程。也就是说整个 Pod 里的每个容器的进程,对于所有容器来说都是可见的:它们共享了同一个 PID Namespace。

[root@k8s-master prome]# kubectl delete -f pod.yml
[root@k8s-master prome]# vim pod.yml
将shareProcessNamespace=true修改为false
[root@k8s-master prome]# kubectl apply -f pod.yml 
pod/nginx created

 凡是 Pod 中的容器要共享宿主机的 Namespace,也一定是 Pod 级别的定义。

刚才的都是pod里面容器的Namespace,并没有和本机的Namespace做共享,接下来我们可以做与本机的Namespace共享,可以在容器里面看到本机的进程。

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

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

注意:hostPID和shareProcessNamespace不能同时存在

十、容器属性

1、Pod 里最重要的字段"Containers":

"Containers"和"Init Containers"这两个字段都属于 Pod 对容器的定义,内容也完全相同,只是 Init Containers 的生命周期,会先于所有的 Containers,并且严格按照定义的顺序执行.

2、k8s 对 Container 的定义,和 Docker 相比并没有什么太大区别。

Docker中Image(镜像)、Command(启动命令)、workingDir(容器的工作目录)、Ports(容器要开发的端口),以及 volumeMounts(容器要挂载的 Volume)都是构成 k8s 中 Container 的主要字段。

3、ImagePullPolicy属性

ImagePullPolicy :定义镜像的拉取策略。之所以是一个 Container 级别的属性,是因为容器镜像本来就是 Container 定义中的一部分。

策略作用
Never只使用本地image
Always每次都下载镜像
IfNotPresent优先使用本地image,本地没有再去下载

 默认值:Always:表示每次创建pod都重新拉取一次镜像

  • 镜像存在而且已经是最新版本就不在拉取镜像
  • 如果不存在就下载镜像
  • 如果镜像存在但不是最新版本也会下载镜像

避免:不用latest,每次下载镜像直接指定版本。注:但是有bug,当镜像类似于nginx或者nginx:latest这样的名字时,ImagePullPolicy也会被认为Always。

4、Pod的生命周期

1、简介

Lifecycle:定义Container Lifecycle Hooks。作用是在容器状态发生变化时触发一系列“钩子”。

Lifecycle有两种回调函数:

  • PostStart:容器创建成功后,运行前的任务,用于资源部署,环境准备等。
  • PreStop:在容器被停止前的任务,用于优雅关闭应用程序,通知其他系统等等。

PostStart:在容器启动前,立刻执行一个指定的操作。

注意:PostStart定义的操作,虽然是在Docker容器ENTRYPOINT执行结束后,但它并不严格保证顺序,也就是说,在PostStart启动时,ENTRYPOINT有可能还没有结束。如果PostStart执行超时或者错误,k8s会在该Pod的Events中报出该容器启动失败的错误信息,导致Pod也处于失败的状态。

PreStop:是在容器被杀死之前(比如,收到了SIGKILL信号)。

注意:PreStop操作的执行,是同步的。所以它会阻塞当前的容器杀死流程,直到这个Hook定义操作完成之后,才允许容器被杀死,这跟PostStart不一样。

2、生命周期

Pod 生命周期的变化,主要体现在 Pod API 对象的Status 部分,这是除了 Metadata 和 Spec 之外的第三个重要字段。其中,pod.status.phase,就是 Pod 的当前状态,有如下几种可能的情况:

Pod 生命周期的变化,主要体现在 Pod API 对象的Status 部分,这是除了 Metadata 和 Spec 之外的第三个重要字段。其中,pod.status.phase,就是 Pod 的当前状态,有如下几种可能的情况:

Pending:此状态表示Pod 的 YAML 文件已经提交给了 Kubernetes,API 对象已经被创建并保存在 Etcd 当中(准备状态)。但这个 Pod 里有些容器因为某种原因而不能被顺利创建。比如,调度不成功。

Running:此状态表示Pod 已经调度成功,跟一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。

Succeeded:此状态表示 Pod 里的所有容器都正常运行完毕,并且已经退出了。这种情况在运行一次性任务时最为常见。

Failed:此状态表示 Pod 里至少有一个容器以不正常的状态(非 0 的返回码)退出。这个状态的出现,意味着你得想办法 Debug 这个容器的应用,比如查看 Pod 的 Events 和日志。

Unknown:这是一个异常状态(未知状态),表示 Pod 的状态不能持续地被 kubelet 汇报给 kube-apiserver
这很有可能是主从节点(Master 和 Kubelet)间的通信出现了问题

3、案例

这是k8s官方文档的一个Pod YAML文件

在这个例子中,容器成功启动之后,在/usr/share/message里写了一句“欢迎信息”(即PostStart定义的操作)。而在这个容器被删除之前,我们则先调用了nginx的退出指令(即PreStop定义的操作),从而实现了容器的“优雅退出”。

[root@k8s-master prome]# kubectl delete -f pod.yml 
pod "website" deleted
[root@k8s-master prome]# cp pod.yml pod.yml.bak
[root@k8s-master prome]# vim pod.yml
---
apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: daocloud.io/library/nginx
    lifecycle:
      postStart:  #容器启动之后
        exec: 
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
      preStop:  #容器关闭之前
        exec: 
          command: ["/usr/sbin/nginx","-s","quit"]
[root@k8s-master prome]# kubectl get pod -o wide

[root@k8s-node1 ~]# docker exec -it 3d404e658 /bin/bash
root@lifecycle-demo:~# cat /usr/share/message 
Hello from the postStart handler

 4、扩展

Pod 对象的 Status 字段,还可以再细分出一组 Conditions:

这些细分状态的值包括:

  • PodScheduled
  • Ready
  • Initialized
  • Unschedulable

它们主要用于描述造成当前 Status 的具体原因是什么。

  • 比如, Pod 当前的 Status 是 Pending,对应的 Condition 是 Unschedulable,这表示它的调度出现了问题。

  • 比如, Ready 这个细分状态表示 Pod 不仅已经正常启动(Running 状态),而且已经可以对外提供服务了。这两者之间(Running 和 Ready)是有区别的,仔细思考一下。

Pod 的这些状态信息,是判断应用运行情况的重要标准,尤其是 Pod 进入了非"Running"状态后,一定要能迅速做出反应,根据它所代表的异常情况开始跟踪和定位,而不是去手忙脚乱地查阅文档。

十一、Projected Volume

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

1、什么是Projected Volume?

在 k8s 中,有几种特殊的 Volume,它们的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换。

"而是为容器提供预先定义好的数据。"

从容器的角度来看,这些 Volume 里的信息仿佛是被 k8s "投射"(Project)进入容器当中的。

k8s 支持的 Projected Volume 一共有四种:

  • Secret
  • ConfigMap
  • Downward API
  • ServiceAccount Token

2、Secret详解

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

 Pod需要先引用才能使用某个secret

Pod有2种方式来使用secret:

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

2. 在拉取镜像的时候被kubelet引用。

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

创建自己的Secret:

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

1、命令方式创建secret

假如某个Pod要访问数据库,需要用户名密码,分别存放在2个文件中:username.txt,password.txt 

[root@k8s-master ~]# echo -n 'admin' > ./username.txt
[root@k8s-master ~]# echo -n '1f2d1e2e67df' > ./password.txt

kubectl create secret指令将用户名密码写到secret中,并在apiserver创建Secret

[root@k8s-master ~]# kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
secret/db-user-pass created

查看创建结果:

[root@k8s-master ~]# kubectl get secrets
NAME                  TYPE                                  DATA   AGE
db-user-pass          Opaque                                2      54s
default-token-6svwp   kubernetes.io/service-account-token   3      4d11h

注: opaque:英[əʊˈpeɪk] 美[oʊˈpeɪk]  模糊

查看详细信息:

[root@k8s-master ~]# kubectl describe secret db-user-pass
Name:         db-user-pass
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
password.txt:  12 bytes
username.txt:  5 bytes

get或describe指令都不会展示secret的实际内容,这是出于对数据的保护的考虑,如果想查看实际内容使用命令:

[root@k8s-master ~]# kubectl get secret db-user-pass -o json

base64解码:

[root@k8s-master ~]# echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
1f2d1e2e67df

2、yaml方式创建Secret:

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

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

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

创建

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

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

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

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

3、使用secret

一个Pod中引用Secret的例子:

[root@k8s-master prome]# vim pod_use_secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: testredis
    image: daocloud.io/library/redis
    volumeMounts:    #挂载一个卷
    - name: foo     #这个名字需要与定义的卷的名字一致
      mountPath: "/etc/foo"  #挂载到容器里哪个目录下,随便写
      readOnly: true
  volumes:     #数据卷的定义
  - name: foo   #卷的名字这个名字自定义
    secret:    #卷是直接使用的secret。
      secretName: mysecret   #调用刚才定义的secret

创建:
[root@k8s-master prome]# kubectl apply -f pod_use_secret.yaml 
pod/mypod created
[root@k8s-master prome]# kubectl exec -it mypod /bin/bash
root@mypod:/data# cd /etc/foo/
root@mypod:/etc/foo# ls
password  username
root@mypod:/etc/foo# cat password 
1f2d1e2e67df
  • 每一个被引用的Secret都要在spec.volumes中定义。
  • 如果Pod中的多个容器都要引用这个Secret那么每一个容器定义中都要指定自己的volumeMounts,但是Pod定义中声明一次spec.volumes就好了。

4、映射secret key到指定的路径

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

username被映射到了/etc/foo目录下。

5、被挂载的secret内容自动更新

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

1.设置base64加密
[root@k8s-master prome]# echo qianfeng | base64
cWlhbmZlbmcK
2.将admin替换成qianfeng
[root@k8s-master prome]# vim secret.yml
---
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: cWlhbmZlbmcK   #修改为qianfeng的base64加密后的
  password: MWYyZDFlMmU2N2Rm

1.创建
[root@k8s-master prome]# kubectl apply -f secret.yml 
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
secret/mysecret configured
2.连接pod容器
[root@k8s-master prome]# kubectl exec -it mypod /bin/bash
root@mypod:/data# cd /etc/foo/
root@mypod:/etc/foo# ls
username
root@mypod:/etc/foo# cat username 
qianfeng

6、以环境变量的形式使用Secret

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

2.创建使用secret的pod容器
[root@k8s-master prome]# kubectl apply -f pod_use_secret.yaml 
pod/mypod created
3.连接
[root@k8s-master prome]# kubectl exec -it mypod /bin/bash 
root@mypod:/data# echo $SECRET_USERNAME   #打印一下定义的变量
qianfeng

注:以环境变量的形式使用secret,secret中的内容更新后,挂载该secret的容器中不会取到更新后的值

7、实验:

1. 通过编写 YAML 文件的方式来创建这个 Secret 对象

Secret 对象要求这些数据必须是经过 Base64 转码的,以免出现明文密码的安全隐患。

转码操作:

[root@k8s-master ~]# echo -n 'admin' | base64
YWRtaW4=
[root@k8s-master ~]# echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm

 注意:像这样创建的 Secret 对象,它里面的内容仅仅是经过了转码,并没有被加密。生产环境中,需要在 Kubernetes 中开启 Secret 的加密插件,增强数据的安全性。

[root@k8s-master prome]# vim create_secret.yml
---
apiVersion: v1
kind: Secret
metadata:
  name: mysecret-01
type: Opaque
data:
  user: YWRtaW4=
  pass: MWYyZDFlMmU2N2Rm

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

[root@k8s-master prome]# kubectl get secret

 用yaml方式创建的secret调用方法如下:

[root@k8s-master prome]# vim test-projected-volume.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: test-projected-volume
spec:
  containers:
  - name: test-secret-volume
    image: daocloud.io/library/nginx
    volumeMounts:
    - name: mysql-cred
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: mysql-cred
    secret:
      secretName: mysecret-01

[root@k8s-master prome]# kubectl apply -f test-projected-volume.yaml 
pod/test-projected-volume1 created

 验证这些 Secret 对象是不是已经在容器里了:

[root@k8s-master prome]# kubectl exec -it test-projected-volume  /bin/bash
root@test-projected-volume:/# ls
bin   dev  home  lib64	mnt  proc	       root  sbin  sys	usr
boot  etc  lib	 media	opt  projected-volume  run   srv   tmp	var
root@test-projected-volume:/# ls projected-volume/
pass  user
root@test-projected-volume:/# cat projected-volume/pass 
1f2d1e2e67df
root@test-projected-volume:/# cat projected-volume/user 
admin
root@test-projected-volume:/#

 注意:

如果报错:上面这条命令会报错如下
# kubectl exec -it test-projected-volume /bin/sh
error: unable to upgrade connection: Forbidden (user=system:anonymous, verb=create, resource=nodes, subresource=proxy)

 

解决:绑定一个cluster-admin的权限
# kubectl create clusterrolebinding system:anonymous   --clusterrole=cluster-admin   --user=system:anonymous
clusterrolebinding.rbac.authorization.k8s.io/system:anonymous created

 结果中看到,保存在 Etcd 里的用户名和密码信息,已经以文件的形式出现在了容器的 Volume 目录里。而这个文件的名字,就是 kubectl create secret 指定的 Key,或者说是 Secret 对象的 data 字段指定的 Key。

3、ConfigMap详解

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

与 Secret 的区别:

  • ConfigMap 保存的是不需要加密的、应用所需的配置信息。
  • ConfigMap 的用法几乎与 Secret 完全相同:可以使用 kubectl create configmap 从文件或者目录创建 ConfigMap,也可以直接编写 ConfigMap 对象的 YAML 文件。

1、创建ConfigMap

创建ConfigMap的方式有4种:

方式1:通过直接在命令行中指定configmap参数创建,即--from-literal

方式2:通过指定文件创建,即将一个配置文件创建为一个ConfigMap,--from-file=<文件>

方式3:通过指定目录创建,即将一个目录下的所有配置文件创建为一个ConfigMap,--from-file=<目录>

方式4:事先写好标准的configmap的yaml文件,然后kubectl create -f 创建

(其实方式二和方式三等同于一种方法)

2、通过命令行参数--from-literal创建

创建命令

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

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

 3、通过指定文件创建

[root@k8s-master prome]# yum -y install nginx
[root@k8s-master prome]# nginx -v
nginx version: nginx/1.22.0
[root@k8s-master prome]# kubectl create configmap test-config2 --from-file=/etc/nginx/nginx.conf
configmap/test-config2 created

结果如下面data内容所示:

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

4、指定目录创建

configmap 目录下的config-1和config-2内容如下所示:
[root@k8s-master ~]# mkdir configmap &&cd configmap
[root@k8s-master config]# vim config
aaa
bbb
c=d
[root@k8s-master config]# vim config2
eee
fff
h=k

 创建:

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

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

5、通过事先写好configmap的标准yaml文件创建

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

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


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

 查看configmap的详细信息:

[root@k8s-master prome]# kubectl describe configmap

6、使用ConfigMap的方式

使用ConfigMap有三种方式:

  • 一种是通过环境变量的方式,直接传递pod
  • 另一种是通过在pod的命令行下运行的方式
  • 第三种是使用volume的方式挂载入到pod内

示例ConfigMap文件:

[root@k8s-master prome]# vim config-map.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: config-map
  namespace: default
data:
  home: jiaxian
  hobby: Black silk
[root@k8s-master prome]# kubectl apply -f config-map.yml 
configmap/config-map created

7、通过环境变量使用

[root@k8s-master prome]# vim testpod.yml
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - name: nginx
      image: daocloud.io/library/nginx:latest
      env:
       - name: FROM
         valueFrom:
           configMapKeyRef:
             name: config-map
             key: hobby
       - name: from
         valueFrom:
           configMapKeyRef:
             name: config-map
             key: home
  restartPolicy: Never

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

测试

[root@k8s-master prome]# kubectl exec -it nginx /bin/bash
root@nginx:/# echo $FROM
Black silk

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

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

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

[root@k8s-master prome]# kubectl apply -f testpod.yml
pod/nginx created.
[root@k8s-master prome]# kubectl exec -it nginx /bin/bash
root@nginx:/# env
HOSTNAME=dapi-test-pod
NJS_VERSION=0.3.3
NGINX_VERSION=1.17.1
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PKG_RELEASE=1~stretch
KUBERNETES_PORT=tcp://10.96.0.1:443
PWD=/
home=jiaxian
HOME=/root
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
TERM=xterm
SHLVL=1
KUBERNETES_SERVICE_PORT=443
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
hobby=Black silk
KUBERNETES_SERVICE_HOST=10.96.0.1
_=/usr/bin/env

9、作为volume挂载使用 

把test-config2中的所有key/value挂载进来(就是刚才那个--from-file=/etc/nginx/nginx.conf)

[root@k8s-master prome]# kubectl delete -f testpod.yml
pod "nginx" deleted
[root@k8s-master prome]# vim testpod.yml
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - name: nginx
      image: daocloud.io/library/nginx
      volumeMounts:
      - name: nginx
        mountPath: /etc/nginx/nginx.conf
        subPath: nginx.conf
  volumes:
    - name: nginx
      configMap:
        name: test-config2
[root@k8s-master prome]# kubectl apply -f testpod.yml 
pod/nginx created

我们修改一下test-config2中的nginx.conf的内容做验证

[root@k8s-master prome]# cat /etc/nginx/nginx.conf

[root@k8s-master prome]# kubectl delete configmap test-config2
configmap "test-config2" deleted
[root@k8s-master prome]# kubectl create configmap test-config2 --from-file=/etc/nginx/nginx.conf
configmap/test-config2 created
[root@k8s-master prome]# kubectl delete pod nginx
pod "nginx" deleted
[root@k8s-master prome]# kubectl apply -f testpod.yml
pod/nginx created
[root@k8s-master prome]# kubectl exec -it nginx /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@nginx:/# cat /etc/nginx/nginx.conf 

4、Downward API

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

Downward API提供了两种方式用于将POD的信息注入到容器内部:

  • 环境变量:用于单个变量,可以将POD信息和容器信息直接注入容器内部
  • Volume:将POD信息生成文件,直接挂载到容器内部中去。

1、环境变量的方式

通过Downward API将POD的IP、名称以及对应的namespace注入到容器的环境变量中去,然后在容器中打印全部的环境变量来进行验证

使用fieldRef获取POD的基本信息:

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

创建上面的POD:

[root@k8s-master biji]# kubectl apply -f test-env-pod.yaml 
pod/nginx1 created

POD创建成功后,查看:

[root@k8s-master biji]# kubectl exec -it nginx1 /bin/bash -n kube-system
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@nginx1:/# env |grep POD
POD_NAME=nginx1
POD_NAMESPACE=kube-system
POD_IP=10.244.2.11

2、volume挂载方式

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

[root@k8s-master biji]# vim test-volume-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx3
  namespace: kube-system
  labels:
    app: home
    web: tomcat
spec:
  containers:
  - name: nginx3
    image: daocloud.io/library/nginx:latest
    volumeMounts:
    - name: volume
      mountPath: /etc/volume
  volumes:
  - name: volume
    downwardAPI:
      items:
      - path: "labels"
        fieldRef:
          fieldPath: metadata.labels

将原数据labels和annotaions以及文件的形式挂载到了/etc/volume目录下,创建上面的POD:

[root@k8s-master biji]# kubectl apply -f test-volume-pod.yaml 
pod/nginx3 created
[root@k8s-master biji]# kubectl get pod -n kube-system
[root@k8s-master biji]# kubectl exec -it nginx3 /bin/bash -n kube-system
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@nginx3:/# cd /etc/volume/
root@nginx3:/etc/volume# ls
labels
root@nginx3:/etc/volume# cat labels 
app="home"
web="tomcat"

在实际应用中,如果你的应用有获取POD的基本信息的需求,就利用Downward API来获取基本信息,然后编写一个启动脚本或者利用initContainer将POD的信息注入到容器中去,然后在自己的应用中就可以正常的处理相关逻辑了。

目前 Downward API 支持的字段:
1. 使用 fieldRef 可以声明使用:
spec.nodeName - 宿主机名字
status.hostIP - 宿主机 IP
metadata.name - Pod 的名字
metadata.namespace - Pod 的 Namespace
status.podIP - Pod 的 IP
spec.serviceAccountName - Pod 的 Service Account 的名字
metadata.uid - Pod 的 UID
metadata.labels['<KEY>'] - 指定 <KEY> 的 Label 值
metadata.annotations['<KEY>'] - 指定 <KEY> 的 Annotation 值
metadata.labels - Pod 的所有 Label
metadata.annotations - Pod 的所有 Annotation


上面这个列表的内容,随着 Kubernetes 项目的发展肯定还会不断增加。所以这里列出来的信息仅供参考,在使用 Downward API 时,还是要记得去查阅一下官方文档。
Secret、ConfigMap,以及 Downward API 这三种 Projected Volume 定义的信息,大多还可以通过环境变量的方式出现在容器里。但是,通过环境变量获取这些信息的方式,不具备自动更新的能力。一般情况下,建议使用 Volume 文件的方式获取这些信息。

5、ServiceAccount

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

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

1、什么是ServiceAccount

当用户访问集群(例如使用kubectl命令)时,apiserver会将用户认证为一个特定的User Account(目前通常是ServiceAccount(例如defalut))

Pod 容器中的进程也可以与 apiserver 联系。 当它们在联系 apiserver 的时候,它们会被认证为一个特定的Service Account(例如default)。

使用场景:

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

2、ServiceAccount与UserAccount区别

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

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

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

4. Token controller检测service account的创建,并为它们创建secret    

5. 开启ServiceAccount Admission Controller后:
 5.1 每个Pod在创建后都会自动设置spec.serviceAccount为default(除非指定了其他ServiceAccout)
 5.2 验证Pod引用的service account已经存在,否则拒绝创建
 5.3 如果Pod没有指定ImagePullSecrets,则把service account的ImagePullSecrets加到Pod中
 5.4 每个container启动后都会挂载该service account的token和ca.crt到/run/secrets/kubernetes.io/serviceaccount/   
 
 每一个pod启动之后都会有一个和认证相关的东西存在pod里面,,存在到哪里呢?

查看系统config配置:

这里用到的token就是被授权过的SeviceAccount账户的token,集群利用token来使用ServiceAccount账户
[root@k8s-master prome]# cat /root/.kube/config

3、ServiceAccount应用示例

ServiceAccount(服务账号)测试示例

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

1、创建serviceaccount
[root@k8s-master ~]# kubectl create serviceaccount mysa
serviceaccount/mysa created
2、查看mysa
[root@k8s-master ~]# kubectl describe sa mysa
Name:                mysa
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   mysa-token-cknwf
Tokens:              mysa-token-cknwf
Events:              <none>
3、查看mysa自动创建的secret
[root@k8s-master ~]# kubectl  get secret
NAME                  TYPE                                  DATA   AGE
db-user-pass          Opaque                                2      11h
default-token-6svwp   kubernetes.io/service-account-token   3      4d23h
mysa-token-cknwf      kubernetes.io/service-account-token   3      76s
mysecret              Opaque                                2      11h
mysecret-01           Opaque                                2      6h58m
pass                  Opaque                                1      7h6m
user                  Opaque                                1      7h7m
4、使用mysa的sa资源配置pod
[root@k8s-master ~]# cd biji/
[root@k8s-master biji]# vim mysa-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
 name: nginx-pod
 labels:
   app: my-pod
spec:
 containers:
 - name: my-pod
   image: daocloud.io/library/nginx
   ports:
   - name: http
     containerPort: 80
 serviceAccountName: mysa   #指定serviceaccount的名称
 
5、导入
[root@k8s-master prome]# kubectl apply -f  mysa-pod.yaml
pod/nginx-pod created
6、查看
[root@k8s-master biji]# kubectl describe pod nginx-pod
7、查看使用的token和secret(使用的是mysa的token)
[root@k8s-master biji]# kubectl get pod nginx-pod -o jsonpath={".spec.volumes"}
[map[name:mysa-token-cknwf secret:map[defaultMode:420 secretName:mysa-token-cknwf]]]

案例1

[root@k8s-master sa]# kubectl create namespace qiangungun #创建一个名称空间
[root@k8s-master sa]# kubectl get sa -n qiangungun  #名称空间下,会自动生成serivceaccount
NAME      SECRETS   AGE
default   1         74s
[root@k8s-master sa]# kubectl get secrets -n qiangungun #同时会成成一个secrets
NAME                  TYPE                                  DATA   AGE
default-token-9bwxj   kubernetes.io/service-account-token   3      2m21s

在创建的名称空间新建一个pod

[root@k8s-master sa]# cat pod_demo.yml 
kind: Pod
apiVersion: v1
metadata:
  name: task-pv-pod
  namespace: qiangungun
spec:
  containers:
  - name: nginx
    image: ikubernetes/myapp:v1
    ports:
     - containerPort: 80
       name: www
[root@k8s-master sa]# kubectl apply -f pod_demo.yml  
[root@k8s-master sa]# kubectl get pod -n qiangungun
NAME          READY   STATUS    RESTARTS   AGE
task-pv-pod   1/1     Running   0          59s
[root@k8s-master sa]# kubectl get pod -n qiangungun -o yaml

 可以看到,pod将serviceaccount中的secrets挂载到了pod内部的/var/run/secrets/kubernetes.io/serviceaccount

 名称空间新建的pod如果不指定sa,会自动挂载当前名称空间中默认的sa(default)

进入到pod中,查看验证一下,果然是有的
[root@k8s-master sa]# kubectl exec -it task-pv-pod /bin/sh -n qiangungun
/ # cd /var/run/secrets/kubernetes.io/
/var/run/secrets/kubernetes.io # ls
serviceaccount
/var/run/secrets/kubernetes.io # cd serviceaccount/
/var/run/secrets/kubernetes.io/serviceaccount # ls
ca.crt     namespace  token

案例2:

创建ServiceAccount

[root@k8s-master sa]# cat test-sa.yml 
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-admin
  namespace: kube-system

将这个ServiceAccount跟ClusterRole进行绑定:

[root@k8s-master sa]# cat test-clusterrolebinding.yml 
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard-admin
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard-admin
  namespace: kube-system

这样ServiceAccount就拥有了ClusterRole的权限

[root@k8s-master sa]# cat test-pod.yml 
apiVersion: v1
kind: Pod
metadata:
  name: pod
  namespace: kube-system
spec:
  containers:
  - name: podtest
    image: nginx
  serviceAccountName: kubernetes-dashboard-admin
总结:serviceaccount是pod访问apiserver的权限认证,每个pod中都默认会绑定当前名称空间默认的sa中的secrets,来保证pod能正常的访问apiserver;

serviceaccount能和clusterrole进行绑定。绑定成功之后,serviceaccount的权限就和clusterrole一致了。后续pod再使用这个serviceaccount,pod的权限也就相同了;

不过这个,是pod进程的权限;如果真正关乎与用户的权限的话,还是使用RBAC

说白了,serviceaccount就是pod使用的账号而已;

十二、RBAC详解(基于角色的访问控制)

1、什么是RBAC

Service Account为服务提供了一种方便的认证机制,但它不关心授权的问题。可以配合RBAC来为Service Account鉴权

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

在RBAC API中,通过如下的步骤进行授权:

  • 定义角色:在定义角色时会指定此角色对于资源的访问控制的规则;
  • 绑定角色:将主体与角色进行绑定,对用户进行访问授权。

2、 Role与ClusterRole

  • Role:角色可以由命名空间内的Role对象定义,一个Role对象只能用于授予对某一单一命名空间中资源的访问权限。
  • ClusterRole:整个Kubernetes集群范围内有效的角色则通过ClusterRole对象实现。
简介
role:
    1、允许的操作,如get,list等

    2、允许操作的对象,如pod,service等

rolebinding:

    将哪个用户绑定到哪个role或clusterrole上

clusterrole:(集群角色)
clusterrolebinding:(绑定到s集群)
    如果使用rolebinding绑定到clusterrole上,表示绑定的用户只能用于当前namespace的权限

3、实验一:role

创建k8s账号与RBAC授权使用

创建账号/用户
1、创建私钥
[root@k8s-master ~]# (umask 077; openssl genrsa -out soso.key 2048)
Generating RSA private key, 2048 bit long modulus
...............................+++
..........................+++
e is 65537 (0x10001)
用此私钥创建一个csr(证书签名请求)文件
[root@k8s-master ~]# openssl  req -new -key soso.key -out soso.csr -subj  "/CN=soso"

拿着私钥和请求文件生成证书
[root@k8s-master ~]# openssl x509 -req -in soso.csr -CA  /etc/kubernetes/pki/ca.crt  -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out soso.crt -days 365
Signature ok
subject=/CN=soso
Getting CA Private Key
2、查看证书内容
[root@k8s-master ~]# openssl  x509 -in soso.crt -text -noout
生成账号
[root@k8s-master ~]# kubectl config set-credentials soso --client-certificate=soso.crt --client-key=soso.key --embed-certs=true
User "soso" set.
3、设置上下文环境--指的是这个账号只能在这个环境中才能用
[root@k8s-master ~]# kubectl config set-context soso@kubernetes --cluster=kubernetes --user=soso
Context "soso@kubernetes" created.
查看当前的工作上下文
[root@k8s-master ~]# kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.246.166:6443
....
4、切换用户(切换上下文)
[root@k8s-master ~]# kubectl  config use-context soso@kubernetes
Switched to context "soso@kubernetes".
验证是否已经切换到了新的上下文
[root@k8s-master ~]# kubectl config current-context
soso@kubernetes
5.测试(还未赋予权限)
[root@k8s-master ~]# kubectl  get pod
Error from server (Forbidden): pods is forbidden: User "soso" cannot list resource "pods" in API group "" in the namespace "default"
创建一个角色(role)---权限
1.切回管理帐号先
[root@k8s-master ~]# kubectl  config use-context kubernetes-admin@kubernetes
Switched to context "kubernetes-admin@kubernetes".
创建角色:
[root@k8s-master ~]# kubectl  create role  myrole  --verb=get,list,watch --resource=pod,svc
role.rbac.authorization.k8s.io/myrole created
--verb: 相当于是权限
--resource:给什么资源使用


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

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

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

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

4、实验二:clusterrole

6.删除soso账号之前绑定的rolebinding
[root@k8s-master ~]# kubectl  delete rolebinding myrole-binding 
rolebinding.rbac.authorization.k8s.io "myrole-binding" deleted
7.创建clusterrole #可以访问全部的namespace
[root@k8s-master ~]# kubectl create clusterrole myclusterrole --verb=get,list,watch --resource=pod,svc
clusterrole.rbac.authorization.k8s.io/myclusterrole created

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

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

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

注意:10.切换为管理员用户
[root@k8s-master ~]# kubectl  config use-context kubernetes-admin@kubernetes

5、设置上下文和账户切换

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

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

查看当前的工作上下文

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

切换上下文(切换用户)

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

切换为管理员用户

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

十三、容器监控检查及恢复机制

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

注:

  • k8s 中并没有 Docker 的 Stop 语义。所以如果容器被探针检测到有问题,查看状态虽然看到的是 Restart,但实际却是重新创建了容器。

1、命令模式探针

[root@k8s-master ~]# cd prome/
[root@k8s-master prome]# vim test-liveness-exec.yaml
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: test-liveness-exec
spec:
  containers:
  - name: liveness
    image: daocloud.io/library/nginx
    args:
    - /bin/sh
    - -c  
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 50
    livenessProbe:    #探针,健康检查
      exec:    #类型
        command:  #命令
        - cat 
        - /tmp/healthy
      initialDelaySeconds: 5   #健康检查,在容器启动 5 s 后开始执行
      periodSeconds: 5   #每 5 s 执行一次

文件内容解析:

  • 它在启动之后做的第一件事是在 /tmp 目录下创建了一个 healthy 文件,以此作为自己已经正常运行的标志。而 30 s 过后,它会把这个文件删除掉。

  • 与此同时,定义了一个这样的 livenessProbe(健康检查)。它的类型是 exec,它会在容器启动后,在容器里面执行一句我们指定的命令,比如:“cat /tmp/healthy”。这时,如果这个文件存在,这条命令的返回值就是 0,Pod 就会认为这个容器不仅已经启动,而且是健康的。

  • 这个健康检查,在容器启动 5 s 后开始执行(initialDelaySeconds: 5),每 5 s 执行一次(periodSeconds: 5)。

创建Pod:

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

查看 Pod 的状态:

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

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

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

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

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

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

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

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

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

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

  • RESTARTS 字段从 0 到 1 的变化,就明白原因了:这个异常的容器已经被 Kubernetes 重启了。在这个过程中,Pod 保持 Running 状态不变。

  • 注意:Kubernetes 中并没有 Docker 的 Stop 语义。所以虽然是 Restart(重启),但实际却是重新创建了容器。

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

小提示:

Pod 的恢复过程,永远都是发生在当前节点上,而不会跑到别的节点上去。事实上,一旦一个 Pod 与一个节点(Node)绑定,除非这个绑定发生了变化(pod.spec.node 字段被修改),否则它永远都不会离开这个节点。这也就意味着,如果这个宿主机宕机了,这个 Pod 也不会主动迁移到其他节点上去。

2、http get方式探针

[root@k8s-master prome]# vim liveness-httpget.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: liveness-httpget-pod
  namespace: default
spec:
  containers:
    - name: liveness-exec-container
      image: daocloud.io/library/nginx
      imagePullPolicy: IfNotPresent
      ports:
        - name: http
          containerPort: 80
      livenessProbe:  #探针,健康检查
        httpGet:
          port: http
          path: /index.html
        initialDelaySeconds: 1
        periodSeconds: 3

创建该pod

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

查看当前pod的状态

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

测试将容器内的index.html删除掉

登陆容器

[root@k8s-master prome]# kubectl exec -it liveness-httpget-pod /bin/bash
root@liveness-httpget-pod:/# mv /usr/share/nginx/html/index.html index.html
root@liveness-httpget-pod:/# command terminated with exit code 137

可以看到,当把index.html移走后,这个容器立马就退出了。

此时,查看pod的信息

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

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

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

restarts 为 1

重新登陆容器查看

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

[root@k8s-master prome]# kubectl exec -it liveness-httpget-pod /bin/bash
root@liveness-httpget-pod:/# cat /usr/share/nginx/html/index.html

十四、POD的恢复策略

Pod 的恢复策略:
可以通过设置 restartPolicy,改变 Pod 的恢复策略。一共有3种:
    1. Always:      在任何情况下,只要容器不在运行状态,就自动重启容器;
    2. OnFailure:   只在容器异常时才自动重启容器;
    3. Never:       从来不重启容器。
实际使用时,需要根据应用运行的特性,合理设置这三种恢复策略。

十五、Deployment详解

使用yaml创建Deployment
k8s deployment资源创建流程:
1. 用户通过 kubectl 创建 Deployment。
2. Deployment 创建 ReplicaSet。
3. ReplicaSet 创建 Pod。

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

对象的命名方式是:子对象的名字 = 父对象名字 + 随机字符串或数字

 1、为什么使用Deployment

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

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

这样使用一种 API 对象(Deployment)管理另一种 API 对象(Pod)的方法,在 k8s 中,叫作"控制器"模式(controller pattern)。Deployment 扮演的正是 Pod 的控制器的角色。

2、创建Deployment

创建deployment并添加volume

[root@k8s-master ~]# mkdir deploy && cd deploy
[root@k8s-master deploy]# vim a.yaml 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:     #属性,选择器
    matchLabels:
      app: nginx
  replicas: 2   #管理的副本个数
  template:     #创建的Pod模板属性
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: daocloud.io/library/nginx:latest
        ports:
        - containerPort: 80
        volumeMounts:   #定义挂载卷
        - mountPath: "/usr/share/nginx/html"
          name: nginx-vol
      volumes:    #定义共享卷
      - name: nginx-vol
        emptyDir: {}
[root@k8s-master deploy]# kubectl apply -f deploy.yaml 
deployment.apps/nginx created

检查deployment的列表
[root@k8s-master deploy]# kubectl get deployments
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
nginx       2/2     2            2           2m19s

[root@k8s-master deploy]# kubectl get pods -l app=nginx
NAME                         READY   STATUS    RESTARTS   AGE
nginx-5f857db9f9-xq6vt       1/1     Running   0          2m55s
nginx-5f857db9f9-zgvfj       1/1     Running   0          2m55s

在这里加了一个-l的参数,即获取所有匹配app:nginx标签的Pod,注意,在命令行都是用“=”,而不是用“:”

删除Deployment
[root@k8s-master deploy]# kubectl delete deployment nginx
deployment.apps "nginx" deleted
或者
[root@k8s-master deploy]# kubectl delete -f deploy.yaml
apiVersion:注意这里apiVersion对应的值是extensions/v1beta1或者apps/v1.这个版本号需要根据安装的Kubernetes版本和资源类型进行变化,记住不是写死的。此值必须在kubectl apiversion中 
[root@k8s-master prome]# kubectl api-versions
    apps/v1beta1
    authentication.k8s.io/v1beta1
    authorization.k8s.io/v1beta1
    autoscaling/v1
    batch/v1
    certificates.k8s.io/v1alpha1
    extensions/v1beta1
    policy/v1beta1
    rbac.authorization.k8s.io/v1alpha1
    storage.k8s.io/v1beta1
    v1

kind:资源类型:这里指定为Deployment。

metadata:指定一些meta信息,包括名字或标签之类的。每一个 API 对象都有一个叫作 Metadata 的字段,这个字段是 API 对象的"标识",即元数据,也是我们从 Kubernetes 里找到这个对象的主要依据。

labels:Labels是最主要的字段,是一组 key-value 格式的标签,k8s中的所有资源都支持携带label,默认情况下,pod的label会复制rc的label
    k8s使用用户自定义的key-value键值对来区分和标识资源集合(就像rc、pod等资源),这种键值对称为label。
     像 Deployment 这样的控制器对象,就可以通过这个 Labels 字段从 Kubernetes 中过滤出它所关心的被控制对象。
    
    关于Annotations:在 Metadata 中,还有一个与 Labels 格式、层级完全相同的字段叫 Annotations,它专门用来携带 key-value 格式的内部信息。所谓内部信息,指的是对这些信息感兴趣的,是 Kubernetes 组件本身,而不是用户。所以大多数 Annotations,都是在 Kubernetes 运行过程中,被自动加在这个 API 对象上。

selector:过滤规则的定义,是在 Deployment 的"spec.selector.matchLabels"字段。一般称之为:Label Selector。
    pod的label会被用来创建一个selector,用来匹配过滤携带这些label的pods。

使用labels定位pods
[root@k8s-master deploy]# kubectl get pods -l app=nginx -o wide
NAME                         READY   STATUS    RESTARTS   AGE    IP            NODE        NOMINATED NODE   READINESS GATES
nginx-5f857db9f9-lnbsh       1/1     Running   0          25s    10.244.1.25   k8s-node1   <none>           <none>
nginx-5f857db9f9-zh5z9       1/1     Running   0          25s    10.244.2.19   k8s-node2   <none>     

检查你的Pod的IPs:
[root@k8s-master deploy]# kubectl get pods -l app=nginx -o json |grep podIP
                                "f:podIP": {},
                                "f:podIPs": {
                "podIP": "10.244.1.25",
                "podIPs": [
                                "f:podIP": {},
                                "f:podIPs": {
                "podIP": "10.244.2.19",
                "podIPs": [


spec : 一个 k8s 的 API 对象的定义,大多可以分为 Metadata 和 Spec 两个部分。前者存放的是这个对象的元数据,对所有 API 对象来说,这一部分的字段和格式基本上是一样的;而后者存放的,则是属于这个对象独有的定义,用来描述它所要表达的功能。
这里定义需要两个副本,此处可以设置很多属性,主要是受此Deployment影响的Pod的选择器

replicas:定义的 Pod 副本个数 (spec.replicas) 是:2

template:定义了一个 Pod 模版(spec.template),这个模版描述了想要创建的 Pod 的细节。例子里,这个 Pod 里只有一个容器,这个容器的镜像(spec.containers.image)是 nginx:latest,这个容器监听端口(containerPort)是 80。

volumes:是属于 Pod 对象的一部分。需要修改 template.spec 字段
    案例中,在 Deployment 的 Pod 模板部分添加了一个 volumes 字段,定义了这个 Pod 声明的所有 Volume。它的名字叫作 nginx-vol,类型是 emptyDir。
    
关于emptyDir 类型:等同于 Docker 的隐式 Volume 参数,即:不显式声明宿主机目录的 Volume。所以,Kubernetes 也会在宿主机上创建一个临时目录,这个目录将来就会被绑定挂载到容器所声明的 Volume 目录上。
k8s 的 emptyDir 类型,只是把 k8s 创建的临时目录作为 Volume 的宿主机目录,交给了 Docker。这么做的原因,是 k8s 不想依赖 Docker 自己创建的那个 _data 目录。

volumeMounts:Pod 中的容器,使用的是 volumeMounts 字段来声明自己要挂载哪个 Volume,并通过 mountPath 字段来定义容器内的 Volume 目录,比如:/usr/share/nginx/html。

hostPath:k8s 也提供了显式的 Volume 定义,它叫做 hostPath。比如下面的这个 YAML 文件:
    ...   
        volumes:
          - name: nginx-vol
            hostPath: 
              path: /var/data
    这样,容器 Volume 挂载的宿主机目录,就变成了 /var/data

十六、Service详解

1、什么是Service

service是pod的一个逻辑分组,是pod服务的对外入口抽象。service同样也通过pod的标签来选择pod,与控制器一致。

 service提供pod的负载均衡的能力,但是只提供4层负载均衡的能力,而没有7层功能,只能到ip层面。

2、Service的几种类型

  • ClusterIP:默认类型,自动分配一个仅可在内部访问的虚拟IP。应用方式:内部服务访问
  • NodePort:在ClusterIP的基础之上,为集群内的每台物理机绑定一个端口,外网通过任意节点的物理机IP:端口来访问服务。应用方式:外服访问服务
  • LoadBalance:在NodePort基础之上,提供外部负载均衡器与外网统一IP,此IP可以将请求转发到对应服务上。这个是各个云厂商提供的服务。应用方式:外服访问服务
  • ExternalName:引入集群外部的服务,可以在集群内部通过别名方式访问(通过 serviceName.namespaceName.svc.cluster.local访问)

1、ClusterIP

创建deployment,nginx2个副本,然后再创建一个ClusterIP类型的service
vim a.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-dep
spec:
  selector:    #选择app:nginx2标签的Pod
    matchLabels:
      app: nginx2
  replicas: 2
  template:    #pod的模板
    metadata:
      labels:
        app: nginx2
    spec:
      containers:
      - name: nginx
        image: daocloud.io/library/nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc-clusterip
spec: 
  type: ClusterIP   #类型
  selector:   #选择app:nginx2标签的Pod
    app: nginx2
  ports:
    - protocol: TCP   #使用的协议TCP,默认也是TCP协议
      port: 80        #service对外提供的端口
      targetPort: 80  #代理的容器的端口
[root@k8s-master service]# kubectl apply -f a.yaml 
deployment.apps/nginx-dep created
service/nginx-svc-clusterip created
[root@k8s-master service]# kubectl get svc
NAME                  TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes            ClusterIP   10.96.0.1      <none>        443/TCP   6d4h
nginx-svc-clusterip   ClusterIP   10.107.84.41   <none>        80/TCP    33s

 测试ClusterIP

可以新建一个带有curl命令的Pod验证,因为我用的镜像中有curl命令,我直接进入deployment管理的2个nginx副本中修改默认页面进行验证,只需要修改一个就行,与另一个不同,方便验证
进入第1个nginx的pod中:
[root@k8s-master service]# kubectl get pod -o wide
NAME                         READY   STATUS    RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES
nginx-dep-64b7464986-k2n8r   1/1     Running   0          2m45s   10.244.2.24   k8s-node2   <none>           <none>
nginx-dep-64b7464986-rwx2b   1/1     Running   0          2m45s   10.244.1.29   k8s-node1   <none>           <none>
[root@k8s-master service]# kubectl exec -it nginx-dep-64b7464986-rwx2b /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@nginx-dep-64b7464986-rwx2b:/# echo "black silk" > /usr/share/nginx/html/index.html

直接在这个pod中,我们访问clusterIP进行验证

2、NodePort

三台服务器,master和两个node节点都安装iptables

yum install -y iptables iptables-services

 在上面clusterip的基础上添加nodeport的类型

vim a.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-dep
spec:
  selector:
    matchLabels:
      app: nginx2
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx2
    spec:
      containers:
      - name: nginx
        image: daocloud.io/library/nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc-clusterip
spec: 
  type: ClusterIP
  selector:
    app: nginx2
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: service-nodeport
spec:
  type: NodePort
  selector:
    app: nginx2
  ports:
    - protocol: TCP
      port: 80            #service对外提供的端口
      targetPort: 80      #代理的容器的端口
      nodePort: 30007     #在物理机上开辟的端口,从30000-32767

[root@k8s-master service]# kubectl apply -f a.yaml 
deployment.apps/nginx-dep unchanged
service/nginx-svc-clusterip unchanged
service/service-nodeport created
[root@k8s-master service]# kubectl get svc
NAME                  TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes            ClusterIP   10.96.0.1      <none>        443/TCP        6d4h
nginx-svc-clusterip   ClusterIP   10.107.84.41   <none>        80/TCP         15m
service-nodeport      NodePort    10.102.75.15   <none>        80:30007/TCP   110s

画一张图结合上面的实验帮助理解一下nodePort与clusterIP

 测试NodePort:

清理一下缓存,访问另一个node

端口详解

服务中的3个端口设置
这几个port的概念很容易混淆,比如创建如下service:
apiVersion: v1
kind: Service
metadata:
  name: nginx2
spec:
  type: NodePort
  ports:
    - port: 80
      nodePort: 30007
      targetPort: 80
  selector:
    app: nginx2

port 

这里的port表示:service暴露在cluster ip上的端口,cluster ip:port 是提供给集群内部客户访问service的入口。

nodePort 

首先,nodePort是kubernetes提供给集群外部客户访问service入口的一种方式(另一种方式是LoadBalancer),所以,<nodeIP>:nodePort 是提供给集群外部客户访问service的入口。

 targetPort

targetPort很好理解,targetPort是pod上的端口,从port和nodePort上到来的数据最终经过kube-proxy流入到后端pod的targetPort上进入容器。

 port、nodePort总结

总的来说,port和nodePort都是service的端口,前者暴露给集群内客户访问服务,后者暴露给集群外客户访问服务。从这两个端口到来的数据都需要经过反向代理kube-proxy流入后端pod的targetPod,从而到达pod上的容器内。

3、LoadBalance

这个LoadBalance实验需要有公网ip以及对应解析的域名,我做的话还得重新在云服务器上部署k8s集群,所以就简单说一下过程,要是有想看完整实验过程的兄弟可以私信我,我做好单发一篇

apiVersion: v1
kind: Service
metadata:
  name: loadbalance-test
spec:
  ports:
  - name: loadbalance-port
    #service对外提供的端口
    port: 80
    # 代理的容器的端口 
    targetPort: 80
    # 在物理机上开辟的端口,从30000开始
    nodePort: 32138
  selector:
    app: nginx
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - ip:  云厂商LoadbalanceIP
[root@ master ~]# kubectl get  svc -n test
NAME                TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
loadbalance-test    LoadBalancer   172.21.10.152   LoadbalanceIP 80:32138/TCP   4m

4、ExternalName

[root@k8s-master service]# cat b.yaml 
apiVersion: v1
kind: Service
metadata:
  name: service-ext
spec:
  type: ExternalName
  # 引入外部服务
  externalName: www.baidu.com
[root@k8s-master service]# kubectl apply -f b.yaml 
service/service-ext created

 任意找个pod来访问服务,通过kubectl exec -it podname sh 来对pod执行sh命令,这样可以进入容器内部,进入一个有ping命令的pod内部

[root@k8s-master service]# kubectl exec -it nginx-dep-64b7464986-k2n8r /bin/bash

 所以ExternalName也就是给另外一个网站的域名,起了一个内部使用的别名而已;

5、ingress是干嘛的?

service只能提供4层负载均衡的能力,虽然service可以通过NodePort的方式来服务,但是随着服务的增多,会在物理机上开辟太多端口,管理起来混乱。

那么我们换一种思路来暴露服务,创建一个具有N个副本的nginx服务,在nginx服务内配置各个服务的域名与集群内部的服务的IP,这些nginx服务再通过NodePort的方式来暴露。外部服务通过域名:Nginx NodePort端口来访问nginx,nginx再通过域名反向代理到真实服务。

上面的这个流程就是ingress做的事,ingress分为ingress controller与ingress配置。ingress controller是反向代理服务器,对外通过NodePort(或者其他方式)来暴露,ingress配置是抽象出来的域名代理配置。

上面我们提到有一个叫作 LoadBalancer 类型的 Service,它会为你在 Cloud Provider(比如:Google Cloud 或者 OpenStack)里创建一个与该 Service 对应的负载均衡服务。但是,相信你也应该能感受到,由于每个 Service 都要有一个负载均衡服务,所以这个做法实际上既浪费成本又高。作为用户,我其实更希望看到 Kubernetes 为我内置一个全局的负载均衡器。然后,通过我访问的 URL,把请求转发给不同的后端 Service。这种全局的、为了代理不同后端 Service 而设置的负载均衡服务,就是 Kubernetes 里的 Ingress 服务。

Ingress 的功能其实很容易理解:所谓 Ingress 就是 Service 的“Service”,这就是它们两者的关系。

internet | [ Ingress ] --|-----|-- [ Services ]

通过使用 Kubernetes 的 Ingress 来创建一个统一的负载均衡器,从而实现当用户访问不同的域名时,访问后端不同的服务

Ingress 负载均衡

可以将 Ingress 配置为服务提供外部可访问的 URL、负载均衡流量、终止 SSL/TLS,以及提供基于名称的虚拟主机等能力。 Ingress 控制器 通常负责通过负载均衡器来实现 Ingress,尽管它也可以配置边缘路由器或其他前端来帮助处理流量。

你必须具有 Ingress 控制器 才能满足 Ingress 的要求。 仅创建 Ingress 资源本身没有任何效果。你可能需要部署 Ingress 控制器,例如 ingress-nginx。 你可以从许多 Ingress 控制器 中进行选择。

假如我现在有这样一个站点:https://cafe.example.com。其中 https://cafe.example.com/coffee,对应的是“咖啡点餐系统”。而 https://cafe.example.com/tea,对应的则是“茶水点餐系统”。这两个系统,分别由名叫 coffee 和 tea 这样两个 Deployment 来提供服务,可以看到这是一种经典的扇出(fanout)行为。

6、kube-proxy与iptables的关系

当service有了port和nodePort之后,就可以对内/外提供服务。那么其具体是通过什么原理来实现的呢?原因就在kube-proxy在本地node上创建的iptables规则。

Kube-Proxy 通过配置 DNAT 规则(从容器出来的访问,从本地主机出来的访问两方面),将到这个服务地址的访问映射到本地的kube-proxy端口(随机端口)。然后 Kube-Proxy 会监听在本地的对应端口,将到这个端口的访问给代理到远端真实的 pod 地址上去。

不管是通过集群内部服务入口:port还是通过集群外部服务入口:nodePort的请求都将重定向到本地kube-proxy端口(随机端口)的映射,然后将到这个kube-proxy端口的访问给代理到远端真实的 pod 地址上去。

十七、RC资源(了解)

1、什么是RC

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

RC 的主要功能点:

  • 确保pod数量:指定某个服务在Kubernetes中有相应数量的Pod在运行;
  • 确保pod健康:当pod不健康,运行出错或者无法提供服务时,会杀死不健康pod并重新创建,保持pod数量一致;
  • 弹性伸缩:当业务高峰期的时候可以设置扩增pod数量,配合监控就可以做自动伸缩了;
  • 滚动升级:也就是蓝绿发布,当一个pod使用的镜像更新,采用滚动升级模式,RC会自动一个个pod的进行升级,关闭一个pod的同时进行升级,且在原镜像基础上创建一个新pod,当一个pod更新完成再关闭一个旧镜像pod。

2、创建RC

1.使用yaml创建并启动replicas集合

k8s通过Replication Controller来创建和管理各个不同的重复容器集合(实际上是重复的pods)。
Replication Controller会确保pod的数量在运行的时候会一直保持在一个特殊的数字,即replicas的设置。
[root@k8s-master ~]# cd prome/
[root@k8s-master prome]# vim nginx-rc.yml
---
apiVersion: v1
kind: ReplicationController
metadata:
 name: my-nginx
spec:
 replicas: 2
 template:
   metadata:
     labels:
       app: nginx
   spec:
     containers:
     - name: nginx
       image: daocloud.io/library/nginx
       ports:
       - containerPort: 80
和定义一个pod的YAML文件相比,不同的只是kind的值为ReplicationController,replicas的值需要指定,pod的相关定义在template中,pod的名字不需要显式地指定,因为它们会在rc中创建并赋予名字

创建rc:

[root@k8s-master prome]# kubectl apply -f nginx-rc.yml 
replicationcontroller/my-nginx created

和直接创建pod不一样,rc将会替换因为任何原因而被删除或者停止运行的Pod,比如说pod依赖的节点挂了。所以我们推荐使用rc来创建和管理复杂应用,即使你的应用只要使用到一个pod,在配置文件中忽略replicas字段的设置即可

2、查看Replication Controller的状态

[root@k8s-master prome]# kubectl get rc
NAME       DESIRED   CURRENT   READY   AGE
my-nginx   2         2         2       11

这个状态表示,你创建的rc将会确保你一直有两个nginx的副本。

也可以和直接创建Pod一样查看创建的Pod状态信息:

[root@k8s-master prome]# kubectl get pods
NAME                                READY   STATUS             RESTARTS   AGE
dep01-58f6d4d4cb-g6vtg              1/1     Running            0          3h8m
dep01-58f6d4d4cb-k6z47              1/1     Running            0          3h8m
my-nginx-7kbwz                      1/1     Running            0          2m49s
my-nginx-jkn8l                      1/1     Running            0          2m49s

3、删除Replication Controller

当你想停止你的应用,删除你的rc,可以使用:
[root@k8s-master prome]# kubectl delete rc my-nginx
replicationcontroller "my-nginx" deleted

默认的,这将会删除所有被这个rc管理的pod,如果pod的数量很大,将会花一些时间来完成整个删除动作,如果你想使这些pod停止运行,请指定–cascade=false。

如果你在删除rc之前尝试删除pod,rc将会立即启动新的pod来替换被删除的pod

3、完整TOMCAT实例

注意:本文中和上文中的NodePort没有完全解决外部访问Service的所有问题,比如负载均衡,假如我们又10个Node,则此时最好有一个负载均衡器,外部的请求只需访问此负载均衡器的IP地址,由负载局衡器负责转发流量到后面某个Node的NodePort上。这个负载均衡器可以是硬件,也可以是软件方式,例如HAProxy或者Nginx;

Java Web应用

注:Tomcat有可能无法正常启动,原因是虚机的内存和CPU设置过小,请酌情调大!

下载镜像

[root@k8s-node1 ~]#  docker pull daocloud.io/library/tomcat:8.5.43

构建Tomcat RC定义文件

[root@k8s-master prome]# vim myweb.rc.yml
---
apiVersion: v1
kind: ReplicationController
metadata:
 name: myweb
spec:
 replicas: 2
 selector:
   app: myweb
 template:
   metadata:
     labels:
       app: myweb
   spec:
     containers:
       - name: myweb
         image: daocloud.io/library/tomcat:8.5.43
         ports:
         - containerPort: 8080  #在8080端口上启动容器进程,PodIP与容器端口组成Endpoint,代表着一个服务进程对外通信的地址

发布到Kubernetes集群

创建RC

[root@k8s-master prome]# kubectl apply -f myweb.rc.yml 
replicationcontroller/myweb created 

查看RC

[root@-k8s-master prome]# kubectl get rc
NAME       DESIRED   CURRENT   READY   AGE
myweb      1         1         1       20s

查看Pod

[root@-k8s-master prome]# kubectl get pods
NAME                                READY   STATUS             RESTARTS   AGE
myweb-shjfn                         1/1     Running            0          52s

构建Tomcat Kubernetes Service定义文件

[root@k8s-master prome]# vim myweb-svc.yaml
apiVersion: v1
kind: Service
metadata: 
 name: myweb
spec:
 type: NodePort
 ports:
   - port: 8081
     nodePort: 30009
     targetPort: 8080
 selector:
   app: myweb

创建

[root@k8s-master prome]# kubectl apply -f myweb-svc.yaml 
service/myweb created

查看SVC

[root@k8s-master prome]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP          5d22h
mysvc        NodePort    10.110.160.108   <none>        8080:30001/TCP   3h37m
myweb        NodePort    10.96.19.61      <none>        8081:30009/TCP   33s

运行

浏览器中输入http://虚拟机IP:30009即可呈现如下内容:


注意在节点(node)中访问,不是master

[root@k8s-node1 ~]# curl 10.0.0.132:30009

如果你下载的tomcat镜像没有curl命令的话可以换成nginx镜像,创建rc:

创建rc
[root@k8s-master prome]# cat nginx-rc.yml 
---
apiVersion: v1
kind: ReplicationController
metadata:
  name: my-nginx
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: daocloud.io/library/nginx
        ports:
        - containerPort: 80
[root@k8s-master prome]# kubectl apply -f nginx-rc.yml
通过创建service暴露端口
[root@k8s-master prome]# cat nginx-svc.yml 
apiVersion: v1
kind: Service
metadata: 
  name: mynginx
spec:
  type: NodePort
  ports:
    - port: 8082
      nodePort: 30010
      targetPort: 80
  selector:
    app: nginx
[root@k8s-master prome]# kubectl apply -f nginx-svc.yml       

访问测试:

换一个Tomcat镜像测试

创建关于tomcat的rc
[root@k8s-master prome]# cat myweb.rc.yml 
---
apiVersion: v1
kind: ReplicationController
metadata:
  name: myweb
spec:
  replicas: 2
  selector:
    app: myweb
  template:
    metadata:
      labels:
        app: myweb
    spec:
      containers:
        - name: myweb
          image: hub.c.163.com/public/tomcat:7.0.28
          ports:
          - containerPort: 8080
[root@k8s-master prome]# kubectl apply -f myweb.rc.yml  

创建service通过NodePort的方式暴露tomcat容器的端口到外网
[root@k8s-master prome]# cat myweb-svc.yaml 
apiVersion: v1
kind: Service
metadata: 
  name: myweb
spec:
  type: NodePort
  ports:
    - port: 8081
      nodePort: 30009
      targetPort: 8080
  selector:
    app: myweb
[root@k8s-master prome]# kubectl apply -f myweb-svc.yml

访问测试,成功

十八、K8S之暴露IP给外网

转发K8S后端服务的四种方式

1、ClusterIP

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

2、NodePort(常用)

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

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

3、loadbalance

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

4、Ingress

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

十九、控制器模式解析

1、什么是控制集群模式

k8s 项目通过一个称作"控制器模式"(controller pattern)的设计方法,来统一地实现对各种不同的对象或者资源进行的编排操作。

k8s核心就是用一个东西去控制另一个东西,所有的内容都是被控制的,容器镜像虽然好用,但是容器这样一个"沙盒"的概念,对于描述应用来说太过简单。好比,集装箱固然好用,如果它四面都光秃秃的,吊车还怎么把这个集装箱吊起来并摆放好呢?

所以,Pod 对象,其实就是容器的升级版。它对容器进行了组合,添加了更多的属性和字段。这就好比给集装箱四面安装了吊环,使得 Kubernetes 这架"吊车",可以更轻松地操作它。

而 k8s 操作这些"集装箱"的逻辑,都由控制器(Controller)完成

回顾 Deployment 这个最基本的控制器对象。之前讲过一个 nginx-deployment 的例子:

例1:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

这个 Deployment 定义的编排动作为:

  • 确保携带了 app=nginx 标签的 Pod 的个数,永远等于 spec.replicas 指定的个数,即 2 个。
  • 如果在这个集群中,携带 app=nginx 标签的 Pod 的个数大于 2 的时候,就会有旧的 Pod 被删除;反之,就会有新的 Pod 被创建。

究竟是 Kubernetes 项目中的哪个组件,在执行这些操作呢?

kube-controller-manager 组件:这个组件,就是一系列控制器的集合

2、控制器种类

所有控制器:

deployment             job                   podautoscaler          
cloud                  disruption             namespace              
replicaset             serviceaccount         volume
cronjob                garbagecollector       nodelifecycle          replication            statefulset            daemon

上面的每一个控制器,都以独有的方式负责某种编排功能。而Deployment,正是这些控制器中的一种。

而被控制对象的定义,则来自于一个"模板"。比如,Deployment 里的 template 字段。

Deployment 这个 template 字段里的内容,跟一个标准的 Pod 对象的 API 定义,丝毫不差。而所有被这个 Deployment 管理的 Pod 实例,都是根据这个 template 字段的内容创建出来的。 

对 Deployment 以及其他类似的控制器,做一个总结:

img

如图,类似 Deployment 的一个控制器,都是由两部分组成:

  • 上半部分的控制器定义(包括期望状态)
  • 下半部分的被控制对象的模板组成的。

也正是在这个统一的编排框架下,不同的控制器可以在具体执行过程中,设计不同的业务逻辑,从而达到不同的编排效果。

这个实现思路,正是 k8s 进行容器编排的核心原理。

二十、滚动更新

概念:

将一个集群中正在运行的多个 Pod 版本,交替地逐一升级的过程,就是"滚动更新"。

实验:

[root@k8s-master prome]# kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
dep01              2/2     2            2           4h41m
nginx-deployment   2/2     2            2           5h13m

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

1571754655365
创建上节儿的:nginx-deployment

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

--record  记录下每次操作所执行的命令,以方便后面查看

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

[root@k8s-master prome]# kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
dep01              2/2     2            2           4h53m
nginx-deployment   4/4     4            4           5h25m

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

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

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

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

AVAILABLE:
当前已经可用的 Pod 的个数,即:既是 Running 状态,又是最新版本,并且已经处于 Ready(健康检查正确)状态的 Pod 的个数。只有这个字段,描述的才是用户所期望的最终状态。

修改 Deployment:

[root@k8s-master prome]# kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
dep01              2/2     2            2           4h59m
nginx-deployment   4/4     4            4           5h32m

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

# reopened with the relevant failures.
#
apiVersion: apps/v1
...
spec:
  progressDeadlineSeconds: 600
  replicas: 3   #将这里原来的2改为3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
...
保存退出,vim的方式
[root@k8s-master prome]# kubectl edit deployment/dep01
deployment.apps/dep01 edited
[root@k8s-master prome]# kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
dep01              3/3     3            3           5h16m
nginx-deployment   4/4     4            4           5h48m

进行版本的升级

创建一个新的deploy
[root@k8s-master prome]# cp nginx-depl.yml nginx-depl02.yml
[root@k8s-master prome]# vim nginx-depl02.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dep02 #注意修改
spec:
  selector:
    matchLabels:
      app: web1
  replicas: 2
  template:
      metadata:
        name: testnginx9
        labels:
          app: web1
      spec:
        containers:
          - name: testnginx9
            image: daocloud.io/library/nginx:1.14 #注意修改
            ports:
              - containerPort: 80
[root@k8s-master prome]# kubectl apply -f nginx-depl02.yml 
deployment.apps/dep02 created
[root@k8s-master prome]# kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
dep01-58f6d4d4cb-997jw              1/1     Running   0          16m
dep01-58f6d4d4cb-g6vtg              1/1     Running   0          5h32m
dep02-78dbd944fc-47czr              1/1     Running   0          44s
dep02-78dbd944fc-4snsj              1/1     Running   0          25s

[root@k8s-node1 ~]# docker exec -it 7e491bb33dcd  /bin/bash
root@dep02-8594cd6447-z5mzs:/# nginx -v
nginx version: nginx/1.14.2

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

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

[root@k8s-master prome]# kubectl describe deployment dep02
...
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  50s   deployment-controller  Scaled up replica set dep02-846bf8775b to 2
  Normal  ScalingReplicaSet  9s    deployment-controller  Scaled up replica set dep02-58f8d5678 to 1
  Normal  ScalingReplicaSet  8s    deployment-controller  Scaled down replica set dep02-846bf8775b to 1
  Normal  ScalingReplicaSet  8s    deployment-controller  Scaled up replica set dep02-58f8d5678 to 2
  Normal  ScalingReplicaSet  5s    deployment-controller  Scaled down replica set dep02-846bf8775b to 0
如此交替进行,新 ReplicaSet 管理的 Pod 副本数,从 0 个变成 1 个,再变成 2 个,最后变成 3 个。而旧的 ReplicaSet 管理的 Pod 副本数则从 3 个变成 2 个,再变成 1 个,最后变成 0 个。这样,就完成了这一组 Pod 的版本升级过程。

验证

[root@k8s-master prome]# kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
dep02-78dbd944fc-69t8x              1/1     Running   0          11h
dep02-78dbd944fc-7cn86              1/1     Running   0          11h
[root@k8s-master prome]# kubectl exec -it dep02-78dbd944fc-69t8x /bin/bash 
root@dep02-78dbd944fc-69t8x:/# nginx -v 
nginx version: nginx/1.16.1
root@dep02-78dbd944fc-69t8x:/# exit

滚动更的好处:

  • 在升级刚开始的时候,集群里只有 1 个新版本的 Pod。如果这时,新版本 Pod 有问题启动不起来,那么"滚动更新"就会停止,从而允许开发和运维人员介入。
  • 而在这个过程中,由于应用本身还有两个旧版本的 Pod 在线,所以服务并不会受到太大的影响。

二十一、版本回滚

1、查看版本历史

[root@k8s-master prome]# kubectl rollout history deployment/dep02
deployment.apps/dep02 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

2、回滚到以前的旧版本

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

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

查看回滚状态

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

验证:

[root@k8s-master prome]# kubectl get pods
NAME                                READY   STATUS             RESTARTS   AGE
dep02-8594cd6447-pqtxk              1/1     Running            0          55s
dep02-8594cd6447-tt4h4              1/1     Running            0          51s
[root@k8s-master prome]# kubectl exec -it dep02-8594cd6447-tt4h4 /bin/bash 
root@dep02-8594cd6447-tt4h4:/# nginx -v 
nginx version: nginx/1.14.2

3、回滚到更早之前的版本

(1)使用 kubectl rollout history 命令查看每次 Deployment 变更对应的版本。

[root@k8s-master prome]# kubectl rollout history deployment/dep02
deployment.apps/dep02
REVISION  CHANGE-CAUSE
2         <none>
3         <none>

由于在创建这个 Deployment 的时候,指定了–record 参数,所以创建这些版本时执行的 kubectl 命令,都会被记录下来。

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

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

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

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

验证:

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

 二十二、部署DASHBOARD应用

注意:最后部署成功之后,因为有5种方式访问dashboard:我们这里只使用Nodport方式访问
1. Nodport方式访问dashboard,service类型改为NodePort
2. loadbalacer方式,service类型改为loadbalacer
3. Ingress方式访问dashboard
4. API server方式访问 dashboard
5. kubectl proxy方式访问dashboard

1、准备yaml文件(可以自己去网上找,也可以自己编写,下面这个文件是我从网上找的,里边的内容已经修改好了,各位老铁可以直接使用)

[root@k8s-master dashboard]# cat recommended.yaml 
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


apiVersion: v1
kind: Namespace
metadata:
  name: kubernetes-dashboard


---


apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard


---


kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  type: NodePort
  ports:
    - port: 443
      targetPort: 8443
      nodePort: 31000
  selector:
    k8s-app: kubernetes-dashboard


---


apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kubernetes-dashboard
type: Opaque


---


apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-csrf
  namespace: kubernetes-dashboard
type: Opaque
data:
  csrf: ""


---


apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-key-holder
  namespace: kubernetes-dashboard
type: Opaque


---


kind: ConfigMap
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-settings
  namespace: kubernetes-dashboard


---


kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
rules:
  # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
    verbs: ["get", "update", "delete"]
    # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["kubernetes-dashboard-settings"]
    verbs: ["get", "update"]
    # Allow Dashboard to get metrics.
  - apiGroups: [""]
    resources: ["services"]
    resourceNames: ["heapster", "dashboard-metrics-scraper"]
    verbs: ["proxy"]
  - apiGroups: [""]
    resources: ["services/proxy"]
    resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
    verbs: ["get"]


---


kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
rules:
  # Allow Metrics Scraper to get metrics from the Metrics server
  - apiGroups: ["metrics.k8s.io"]
    resources: ["pods", "nodes"]
    verbs: ["get", "list", "watch"]


---


apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kubernetes-dashboard
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard


---


apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubernetes-dashboard
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard


---


kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      containers:
        - name: kubernetes-dashboard
          image: kubernetesui/dashboard:v2.4.0
          imagePullPolicy: Always
          ports:
            - containerPort: 8443
              protocol: TCP
          args:
            - --auto-generate-certificates
            - --namespace=kubernetes-dashboard
            # Uncomment the following line to manually specify Kubernetes API server Host
            # If not specified, Dashboard will attempt to auto discover the API server and connect
            # to it. Uncomment only if the default does not work.
            # - --apiserver-host=http://my-address:port
          volumeMounts:
            - name: kubernetes-dashboard-certs
              mountPath: /certs
              # Create on-disk volume to store exec logs
            - mountPath: /tmp
              name: tmp-volume
          livenessProbe:
            httpGet:
              scheme: HTTPS
              path: /
              port: 8443
            initialDelaySeconds: 30
            timeoutSeconds: 30
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      volumes:
        - name: kubernetes-dashboard-certs
          secret:
            secretName: kubernetes-dashboard-certs
        - name: tmp-volume
          emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule


---


kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 8000
      targetPort: 8000
  selector:
    k8s-app: dashboard-metrics-scraper


---


kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: dashboard-metrics-scraper
  template:
    metadata:
      labels:
        k8s-app: dashboard-metrics-scraper
    spec:
      securityContext:
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: dashboard-metrics-scraper
          image: kubernetesui/metrics-scraper:v1.0.7
          ports:
            - containerPort: 8000
              protocol: TCP
          livenessProbe:
            httpGet:
              scheme: HTTP
              path: /
              port: 8000
            initialDelaySeconds: 30
            timeoutSeconds: 30
          volumeMounts:
          - mountPath: /tmp
            name: tmp-volume
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
      volumes:
        - name: tmp-volume
          emptyDir: {}

2、下载镜像(这两个镜像需要从k8s官网上下载,所以直接下载肯定是下不下来的,需要利用一些fq软件,你懂得)

由于yaml配置文件中指定的镜像
三台机器都下载
[root@k8s-master ~]# docker pull kubernetesui/dashboard:v2.4.0
[root@k8s-master ~]# docker pull kubernetesui/metrics-scraper:v1.0.7

3、创建应用

[root@k8s-master ~]# kubectl apply -f recommended.yaml
namespace/kubernetes-dashboard unchanged
serviceaccount/kubernetes-dashboard unchanged
service/kubernetes-dashboard unchanged
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
secret/kubernetes-dashboard-certs configured
secret/kubernetes-dashboard-csrf configured
secret/kubernetes-dashboard-key-holder unchanged
configmap/kubernetes-dashboard-settings unchanged
role.rbac.authorization.k8s.io/kubernetes-dashboard unchanged
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard unchanged
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard unchanged
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard unchanged
deployment.apps/kubernetes-dashboard unchanged
service/dashboard-metrics-scraper unchanged
deployment.apps/dashboard-metrics-scraper unchanged

查看Pod 的状态为running说明dashboard已经部署成功:

[root@k8s-master dashboard]# kubectl get pod -n kubernetes-dashboard -o wide | grep dashboard
dashboard-metrics-scraper-57cbb8b86c-sjz9g   1/1     Running   0          8h     10.244.2.47   k8s-node2    <none>           <none>
kubernetes-dashboard-6b9b6c9d46-h5gwr        1/1     Running   0          8h     10.244.2.48   k8s-node2    <none> 

Dashboard 会在 kube-system namespace 中创建自己的 Deployment 和 Service:

[root@k8s-master dashboard]# kubectl get deployment kubernetes-dashboard -n kubernetes-dashboard
NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-dashboard   1/1     1            1           8h
[root@k8s-master dashboard]# kubectl get service kubernetes-dashboard -n kubernetes-dashboard
NAME                   TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)         AGE
kubernetes-dashboard   NodePort   10.109.96.224   <none>        443:31000/TCP   8h

4、访问dashboard

官方文档:https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#accessing-the-dashboard-ui

查看service,TYPE类型已经变为NodePort,端口为31000

[root@k8s-master dashboard]# kubectl get service -n kube-system | grep dashboard
dashboard-metrics-scraper   ClusterIP   10.107.226.18   <none>        8000/TCP                 8h
kubernetes-dashboard        NodePort    10.109.96.224   <none>        443:31000/TCP            8h

查看dashboard运行在那台机器上面

[root@k8s-master dashboard]# kubectl get pods -n kube-system -o wide|grep dashboard
dashboard-metrics-scraper-57cbb8b86c-sjz9g   1/1     Running   0          8h     10.244.2.47   k8s-node2    <none>           <none>
kubernetes-dashboard-6b9b6c9d46-h5gwr        1/1     Running   0          8h     10.244.2.48   k8s-node2    <none>           <none>
在node2上

浏览器访问:https://10.0.0.132:31000

如果访问失败,显示

这种情况是浏览器的安全策略导致的,可以更换双核浏览器,或者通过一下方式解决

生成证书密钥

[root@k8s-master dashboard]# openssl genrsa -out dashboard.key 2048 

使用刚才生成的密钥,创建证书申请文件

[root@k8s-master dashboard]# openssl req -new -out dashboard.csr -key dashboard.key -subj '/CN=10.0.0.130'

签发证书

[root@k8s-master dashboard]# openssl x509 -req -in dashboard.csr -signkey dashboard.key -out dashboard.cr
[root@k8s-master dashboard]# ls
dashboard.crt  dashboard.csr  dashboard.key  recommended.yaml

然后删除原有的secret,根据生成的证书创建新的secret

[root@k8s-master dashboard]# kubectl delete secret kubernetes-dashboard-certs -n kube-system

[root@k8s-master dashboard]# kubectl create secret generic kubernetes-dashboard-certs --from-file=dashboard.key --from-file=dashboard.crt -n kube-system

然后删除kubernetes-dashboard的pod,因为我们是用deployment创建的pod,删除这个pod后,deployment会立马生成一个pod,不能重新kubectl apply -f recommended.yaml,这样的话,之前创建的证书等于白做了。

[root@k8s-master dashboard]# kubectl get pod -n kube-system|grep dashboard
查看kubernetes-dashboard的名字
删除之前的kubernetes-dashboard********
[root@k8s-master dashboard]# kubectl delete pod kubernetes-dashboard-6b9b6c9d46-66lsx -n kube-system

然后在浏览去访问就可以看到正常的页面了

 选择使用token登录

 5、制作token

[root@k8s-master dashboard]# cat a.yaml 
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system

执行yaml文件

[root@k8s-master ~]# kubectl create -f dashboard-adminuser.yaml
serviceaccount/admin-user created
clusterrolebinding.rbac.authorization.k8s.io/admin-user created

说明:上面创建了一个叫admin-user的服务账号,并放在kube-system命名空间下,并将cluster-admin角色绑定到admin-user账户,这样admin-user账户就有了管理员的权限。默认情况下,kubeadm创建集群时已经创建了cluster-admin角色,直接绑定即可。

6、查看admin-user的token

[root@k8s-master dashboard]# kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
Name:         admin-user-token-8kzjr
Namespace:    kube-system
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: admin-user
              kubernetes.io/service-account.uid: d00f22a7-c6eb-4888-b591-6fbe3832b85c

Type:  kubernetes.io/service-account-token

Data
====
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IjFxd3B3SF9JRkFsaHl4S0paSlh4Yy05NWw4bnhWQ1ZCMFB3TEFOTFRVZ3MifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLThrempyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJkMDBmMjJhNy1jNmViLTQ4ODgtYjU5MS02ZmJlMzgzMmI4NWMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.FKl475PN8hMII50S8-OHDlJvNf_09s1fZZBQ-2-trfMPuktTRZrXP6HhE-8uC0eLkVXwlU8l4bwzBLz4m00U43tF1EemFzQ8NJnGfH_vbguTGjESjZBWVEaX-7X7jvS_7I9pELFheAaLotHl_BmFHhFPraGQb49e7Jcw0wHklntSErNb2Tf2INejO21RCOyASEUsF_5IzSC05gnSI-P0Sdt9ppn8MlfCSu-E9ELDTEXXiyqW7gYuOoWywNk1qvA0H0s0cz5Nw-QO9dtgbHhnUowob5ohZz0VeyJHJZSXshb65woq3M-yow9njxm0WPsCuYlkuuVVqV8tprfIyIrNPA
ca.crt:     1066 bytes
namespace:  11 bytes

把获取的token复制到登录界面的token输入框中

 7、使用dashboard

Dashboard 界面结构分为三个大的区域:

  1. 顶部操作区,在这里用户可以搜索集群中的资源、创建资源或退出。

  2. 左边导航菜单,通过导航菜单可以查看和管理集群中的各种资源。菜单项按照资源的层级分为两类:Cluster 级别的资源 ,Namespace 级别的资源 ,默认显示的是 default Namespace,可以进行切换

  3. 中间主体区,在导航菜单中点击了某类资源,中间主体区就会显示该资源所有实例,比如点击 Pods。

二十三、k8s持久化存储PV和PVC

1、PV和PVC的引入

Volume 提供了非常好的数据持久化方案,不过在可管理性上还有不足。
拿前面 AWS EBS 的例子来说,要使用 Volume,Pod 必须事先知道如下信息:
当前 Volume 来自 AWS EBS。
EBS Volume 已经提前创建,并且知道确切的 volume-id。
Pod 通常是由应用的开发人员维护,而 Volume 则通常是由存储系统的管理员维护。开发人员要获得上面的信息:
要么询问管理员。
要么自己就是管理员。
这样就带来一个管理上的问题:应用开发人员和系统管理员的职责耦合在一起了。如果系统规模较小或者对于开发环境这样的情况还可以接受。但当集群规模变大,特别是对于生成环境,考虑到效率和安全性,这就成了必须要解决的问题。

Kubernetes 给出的解决方案是 PersistentVolume 和 PersistentVolumeClaim。
PersistentVolume (PV) 是外部存储系统中的一块存储空间,由管理员创建和维护。与 Volume 一样,PV 具有持久性,生命周期独立于 Pod。
PersistentVolumeClaim (PVC) 是对 PV 的申请 (Claim)。PVC 通常由普通用户创建和维护。需要为 Pod 分配存储资源时,用户可以创建一个 PVC,指明存储资源的容量大小和访问模式(比如只读)等信息,Kubernetes 会查找并提供满足条件的 PV。
有了 PersistentVolumeClaim,用户只需要告诉 Kubernetes 需要什么样的存储资源,而不必关心真正的空间从哪里分配,如何访问等底层细节信息。这些 Storage Provider 的底层信息交给管理员来处理,只有管理员才应该关心创建 PersistentVolume 的细节信息。

2、通过NFS实现持久化存储

下面这个事项相当于PV/PVC的静态供给,后面我们还会说一下PV/PVC的动态供给

1、所有节点安装nfs

yum install -y nfs-common nfs-utils

在master节点创建共享目录,并授权

mkdir /nfsdata
chmod 777 /nfsdata -R

编辑exports文件

[root@k8s-master ~]# vim /etc/exports
/nfsdata *(rw,no_root_squash,no_all_squash,sync)
[root@k8s-master ~]# systemctl start rpcbind nfs

2、创建PV

[root@k8s-master pv]# cat a.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv1
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    path: /nfsdata
    server: 10.0.0.130    #这个是nfs服务端的ip,这里我们的服务端放在了master节点上
① capacity 指定 PV 的容量为 1G。

② accessModes 指定访问模式为 ReadWriteOnce,支持的访问模式有:
ReadWriteOnce – PV 能以 read-write 模式 mount 到单个节点。
ReadOnlyMany – PV 能以 read-only 模式 mount 到多个节点。
ReadWriteMany – PV 能以 read-write 模式 mount 到多个节点。

③ persistentVolumeReclaimPolicy 指定当 PV 的回收策略为 Recycle,支持的策略有:
Retain – 需要管理员手工回收。
Recycle – 清除 PV 中的数据,效果相当于执行 rm -rf /nfsdata/*。
Delete – 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。

④ storageClassName 指定 PV 的 class 为 nfs。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。  #如果没有指定分类的话,PVC会根据申请PV的大小来选择PV,如果有两个PV的大小是一样的,则会随机选取一个

⑤ 指定 PV 在 NFS 服务器上对应的目录。
[root@k8s-master pv]# kubectl apply -f a.yaml 
persistentvolume/mypv1 created

 STATUSAvailable,表示 mypv1 就绪,可以被 PVC 申请。

3、创建pvc,PVC 就很简单了,只需要指定 PV 的容量,访问模式和 class。

[root@k8s-master pv]# cat b.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs
[root@k8s-master pv]# kubectl apply -f b.yaml 
persistentvolumeclaim/mypvc1 created

4、创建POD

[root@k8s-master pv]# cat c.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: mypod1
  labels:
    app: nginx5
spec:
  containers:
    - name: mypod1
      image: daocloud.io/library/nginx:latest
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: mydata
  volumes:
    - name: mydata
      persistentVolumeClaim:
        claimName: mypvc1
---
apiVersion: v1
kind: Service
metadata:
  name: mysvc
spec:
  type: NodePort
  selector:
    app: nginx5
  ports:
    - protocol: TCP
      port: 8082
      targetPort: 80
      nodePort: 30008

与使用普通 Volume 的格式类似,在 volumes 中通过 persistentVolumeClaim 指定使用 mypvc1 申请的 Volume。
[root@k8s-master pv]# kubectl apply -f c.yaml 
pod/mypod1 created
service/mysvc created

验证:

[root@k8s-master pv]# kubectl exec -it mypod1 /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@mypod1:/# echo "black silk" > /usr/share/nginx/html/index.html
root@mypod1:/# touch /usr/share/nginx/html/a.txt
root@mypod1:/# exec attach failed: error on attach stdin: read escape sequence
command terminated with exit code 126
[root@k8s-master pv]# ls /nfsdata/     #也可在nfs的共享目录中查看到,说明卷共享成功
a.txt  index.html

可见,在 Pod 中创建的文件 /usr/share/nginx/html 、a.txt 确实已经保存到了 NFS 服务器目录 /nfsdata中。
如果不再需要使用 PV,可用删除 PVC 回收 PV。
在这里,可以尝试在任何一方删除文件,文件在两端都会消失;

[root@k8s-master pv]# kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE    IP            NODE        NOMINATED NODE   READINESS GATES
mypod1                              1/1     Running   0          2m6s   10.244.1.58   k8s-node1   <none>           <none>

 3、PV的回收

当 PV 不再需要时,可通过删除 PVC 回收。未删除pvc之前 pv的状态是Bound

步骤是先删除pod,在删除PVC,因为我们设置的策略是Recycle,删除完PVC后,共享目录中的数据就被回收了。

删除pod

[root@k8s-master pv]# kubectl delete -f c.yaml 
pod "mypod1" deleted
service "mysvc" deleted

 删除PVC

[root@k8s-master pv]# kubectl delete -f b.yaml 
persistentvolumeclaim "mypvc1" deleted

验证数据是否被回收(可以看到数据已经被回收了),PV的状态也变为Available状态,此时解除绑定后则可以被新的PVC申请。

[root@k8s-master pv]# vim a.yaml

 但这可能不是我们想要的结果,如果我希望数据保留,可以将策略设置为Retain

[root@k8s-master pv]# vim a.yaml

[root@k8s-master pv]# kubectl apply -f a.yaml 
persistentvolume/mypv1 configured
重新创建PVC和POD
[root@k8s-master pv]# kubectl apply -f b.yaml 
persistentvolumeclaim/mypvc1 created
[root@k8s-master pv]# kubectl apply -f c.yaml 
pod/mypod1 created
service/mysvc created

 验证Retain策略

[root@k8s-master pv]# kubectl exec -it mypod1 /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@mypod1:/# ls /usr/share/nginx/html/           
root@mypod1:/# echo "Retain" > //usr/share/nginx/html/index.html

[root@k8s-master pv]# cat /nfsdata/index.html 
Retain
删除pod和pvc
[root@k8s-master pv]# kubectl delete -f c.yaml 
pod "mypod1" deleted
service "mysvc" deleted
[root@k8s-master pv]# kubectl delete -f b.yaml 
persistentvolumeclaim "mypvc1" deleted
[root@k8s-master pv]# cat /nfsdata/index.html 
Retain       #发现数据还保留着
    

虽然 mypv1 中的数据得到了保留,但其 PV 状态会一直处于 Released,不能被其他 PVC 申请。为了重新使用存储资源,可以删除并重新创建 mypv1。删除操作只是删除了 PV 对象,存储空间中的数据并不会被删除

[root@k8s-master pv]# kubectl delete -f a.yaml 
persistentvolume "mypv1" deleted
[root@k8s-master pv]# cat  /nfsdata/index.html 
Retain   #删除PV后数据还在

重新创建一下PV,查看其状态
[root@k8s-master pv]# kubectl apply -f a.yaml 
persistentvolume/mypv1 created

 新建的 mypv1 状态为 Available,已经可以被 PVC 申请。

PV 还支持 Delete 的回收策略,会删除 PV 在 Storage Provider 上对应存储空间。NFS 的 PV 不支持 Delete,支持 Delete 的 Provider 有 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。

4、PV&PVC在应用在Mysql的持久化存储实战项目

创建PV和PVC

[root@k8s-master mysqlpv]# vim a.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    path: /nfsdata/mysql-pv
    server: 10.0.0.130
[root@k8s-master mysqlpv]# vim b.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs
[root@k8s-master mysqlpv]# vim c.yaml 
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306
  selector:
    app: mysql
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: daocloud.io/library/mysql:5.7.5-m15 
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pvc
[root@k8s-master mysqlpv]# kubectl apply -f a.yaml

[root@k8s-master mysqlpv]# kubectl apply -f b.yaml

[root@k8s-master mysqlpv]# kubectl apply -f c.yaml

[root@k8s-master mysqlpv]# kubectl get pod
NAME                                READY   STATUS    RESTARTS   AGE
mysql-6b64cbd988-gs62v              1/1     Running   1          3m55s
[root@k8s-master mysqlpv]# kubectl exec -it mysql-6b64cbd988-gs62v /bin/sh
# mysql -uroot -p'password'
mysql> create database aaa;

[root@k8s-master mysqlpv]# kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES
mysql-6b64cbd988-gs62v              1/1     Running   1          6m11s   10.244.1.80   k8s-node1   <none> 

关闭 k8s-node1,模拟节点宕机故障。

由于node1节点已经宕机,node2节点接管了这个任务,pod转移,需要等待一段时间,我这里等待了8分钟左右。

 从新进入pod,数据依然存在,持久化成功。很安全

 5、PV的动态供给

前面的例子中,我们提前创建了 PV,然后通过 PVC 申请 PV 并在 Pod 中使用,这种方式叫做静态供给(Static Provision)。

与之对应的是动态供给(Dynamical Provision),即如果没有满足 PVC 条件的 PV,会动态创建 PV。相比静态供给,动态供给有明显的优势:不需要提前创建 PV,减少了管理员的工作量,效率高。

动态供给是通过 StorageClass 实现的,StorageClass 定义了如何创建 PV

Dynamic Provisioning机制工作的核心在于StorageClass的API对象。

StorageClass声明存储插件,用于自动创建PV

当我们k8s业务上来的时候,大量的pvc,此时我们人工创建匹配的话,工作量就会非常大了,需要动态的自动挂载相应的存储

我们需要使用到StorageClass,来对接存储,靠他来自动关联pvc,并创建pv。

Kubernetes支持动态供给的存储插件:https://kubernetes.io/docs/concepts/storage/storage-classes/
因为NFS不支持动态存储,所以我们需要借用这个存储插件。
nfs动态相关部署可以参考:
https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client/deploy
部署步骤:

主节点配置nfs服务端
[root@k8s-master pvc-test]# mkdir /opt/container_data
[root@k8s-master pvc-test]# chmod 777  -R /opt/container_data
[root@k8s-master pvc-test]# cat /etc/exports
/opt/container_data *(rw,no_root_squash,no_all_squash,sync)
[root@k8s-master pvc-test]# systemctl start rpcbind
[root@k8s-master pvc-test]# systemctl start nfs

 1、定义一个storage

[root@k8s-master pvc-test]# cat storageclass-nfs.yaml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: fuseim.pri/ifs

2、部署授权

因为storage自动创建pv需要经过kube-apiserver,所以要进行授权

创建1个sa(serviceaccount)

创建1个clusterrole,并赋予应该具有的权限,比如对于一些基本api资源的增删改查;

创建1个clusterrolebinding,将sa和clusterrole绑定到一起;这样sa就有权限了;

然后pod中再使用这个sa,那么pod再创建的时候,会用到sa,sa具有创建pv的权限,便可以自动创建pv;

[root@k8s-master pvc-test]# cat rbac.yaml 
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
---

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

3、部署一个自动创建PV的服务

 这里自动创建pv的服务由nfs-client-provisioner 完成

[root@k8s-master pvc-test]# cat deployment-nfs.yaml 
kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-client-provisioner
spec:
  selector:
    matchLabels:
      app: nfs-client-provisioner
  replicas: 1
  strategy: 
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccount: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: lizhenliang/nfs-client-provisioner:v2.0.0
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              #这个值是定义storage里面的那个值
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: 172.17.0.21
            - name: NFS_PATH
              value: /opt/container_data
      volumes:
        - name: nfs-client-root
          nfs:
            server: 172.17.0.21
            path: /opt/container_data
            
参数解释:            
  strategy: 
    type: Recreate   
==========================================================================================
Recreate:设置spec.strategy.type=Recreate,该策略下将杀掉正在运行的Pod,然后创建新的。
RollingUpdate:设置spec.strategy.type=RollingUpdate,滚动更新,即逐渐减少旧Pod的同时逐渐增加新Pod。
其中默认的RollingUpdate滚动更新策略的“边删除边更新”保证了在更新期间的服务可用性,在使用这个策略时,有两个可定义参数:
spec.strategy.RollingUpdate.maxUnavailable:更新过程中Pod数量可以低于Pod期望副本的数量或百分比(默认25%)
spec.strategy.RollingUpdate.maxSurge:更新过程中Pod数量可以超过Pod期望副本的数量或百分比(默认25%)

 创建:

[root@k8s-master pvc-test]# kubectl apply -f storageclass-nfs.yaml
[root@k8s-master pvc-test]# kubectl apply -f rbac.yaml
[root@k8s-master pvc-test]# kubectl apply -f deployment-nfs.yaml

 查看创建好的storage

[root@k8s-master storage]# kubectl get sc

 nfs-client-provisioner 会以pod运行在k8s中,

[root@k8s-master pvc-test]# kubectl get pod
NAME                                     READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-c977976db-87ddb   1/1     Running   0          41s

 4、部署有状态服务,测试自动创建pv
部署yaml文件参考:https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/
我们部署一个nginx服务,让其html下面自动挂载数据卷

[root@k8s-master pvc-test]# cat nginx.yaml 
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
   matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "managed-nfs-storage"
      resources:
        requests:
          storage: 1Gi
          
[root@k8s-master pvc-test]# kubectl apply -f nginx.yaml

  进入其中一个容器,创建一个文件:

[root@k8s-master pvc-test]# 
[root@k8s-master pvc-test]# kubectl exec -it web-0 /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
# cd /usr/share/nginx/html
# touch 1.txt

 直接在web-1的目录下,创建一个文件:

[root@k8s-master pvc-test]# touch /opt/container_data/default-www-web-1-pvc-20f70001-2ca3-43c6-a12e-4d9195e54880/2.txt

 而且,删除一个pod   web-0,数据仍然存在,不会丢失。保证了数据持久化;

二十四、k8s控制器

1、什么是控制器

kubernetes中内建了很多controller(控制器),这些相当于一个状态机,用来控制pod的具体状态和行为。

上文中我们使用过Deployment、RC(ReplicationController)和StatefulSet,这些都是k8s的控制器

部分控制器类型如下:
ReplicationController 和 ReplicaSet
Deployment
DaemonSet
StatefulSet
Job/CronJob
HorizontalPodAutoscaler

2、DaemonSet控制器

DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。当有节点加入集群时,会为他们新增一个 Pod。当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。

DaemonSet 的一些典型用法:
在每个节点上运行集群存储 DaemonSet,例如 glusterd、ceph。
在每个节点上运行日志收集 DaemonSet,例如 fluentd、logstash。
在每个节点上运行监控 DaemonSet,例如 Prometheus Node Exporter、Flowmill、Sysdig 代理、collectd、Dynatrace OneAgent、AppDynamics 代理、Datadog 代理、New Relic 代理、Ganglia gmond 或者 Instana 代理。
一个简单的用法是在所有的节点上都启动一个 DaemonSet,并作为每种类型的 daemon 使用。

一个稍微复杂的用法是单独对每种 daemon 类型使用一种DaemonSet。这样有多个 DaemonSet,但具有不同的标识,并且对不同硬件类型具有不同的内存、CPU 要求。

备注:DaemonSet 中的 Pod 可以使用 hostPort,从而可以通过节点 IP 访问到 Pod;因为DaemonSet模式下Pod不会被调度到其他节点。使用示例如下:

 ports:
    - name: httpd
      containerPort: 80
      #除非绝对必要,否则不要为 Pod 指定 hostPort。 将 Pod 绑定到hostPort时,它会限制 Pod 可以调度的位置数;DaemonSet除外
      #一般情况下 containerPort与hostPort值相同
      hostPort: 8090     #可以通过宿主机+hostPort的方式访问该Pod。例如:pod在/调度到了k8s-node02【10.0.0.132】,那么该Pod可以通过10.0.0.132:8090方式进行访问。
      protocol: TCP

举个栗子:

1、创建DaemonSet

DaemonSet的描述文件和Deployment非常相似,只需要修改Kind,并去掉副本数量的配置即可,当然,我们这里的pod运行的是nginx,作为案例;

[root@k8s-master daemonset]# cat nginx-daemonset.yml 
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx-daemonset
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: daocloud.io/library/nginx:latest
        ports:
        - name: nginx
          containerPort: 80
          hostPort: 8090       #使用的hostPort   相当于docker的-p 端口映射
          protocol: TCP

2、测试效果

用宿主机的ip+8090端口,即可访问到:

 每个node节点上都有一个

 尝试删除,也会重建

 3、StatefulSet控制器

StatefulSet 是用来管理有状态应用的工作负载 API 对象。

StatefulSet 中的 Pod 拥有一个具有黏性的、独一无二的身份标识。这个标识基于 StatefulSet 控制器分配给每个 Pod 的唯一顺序索引。Pod 的名称的形式为- 。例如:web的StatefulSet 拥有两个副本,所以它创建了两个 Pod:web-0和web-1。

和 Deployment 相同的是,StatefulSet 管理了基于相同容器定义的一组 Pod。但和 Deployment 不同的是,StatefulSet 为它们的每个 Pod 维护了一个固定的 ID。这些 Pod 是基于相同的声明来创建的,但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。

【使用场景】StatefulSets 对于需要满足以下一个或多个需求的应用程序很有价值:

  1. 稳定的、唯一的网络标识符,即Pod重新调度后其PodName和HostName不变【当然IP是会变的】
  2. 稳定的、持久的存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC实现
  3. 有序的、优雅的部署和缩放
  4. 有序的、自动的滚动更新
    如上面,稳定意味着 Pod 调度或重调度的整个过程是有持久性的。

如果应用程序不需要任何稳定的标识符或有序的部署、删除或伸缩,则应该使用由一组无状态的副本控制器提供的工作负载来部署应用程序,比如使用 Deployment 或者 ReplicaSet 可能更适用于无状态应用部署需要。

限制:

给定 Pod 的存储必须由 PersistentVolume 驱动 基于所请求的 storage class 来提供,或者由管理员预先提供。

删除或者收缩 StatefulSet 并不会删除它关联的存储卷。这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。

StatefulSet 当前需要 headless 服务 来负责 Pod 的网络标识。你需要负责创建此服务。
当删除 StatefulSets 时,StatefulSet 不提供任何终止 Pod 的保证。为了实现 StatefulSet 中的 Pod 可以有序和优雅的终止,可以在删除之前将 StatefulSet 缩放为 0。

在默认 Pod 管理策略(OrderedReady) 时使用滚动更新,可能进入需要人工干预才能修复的损坏状态。

有序索引

对于具有 N 个副本的 StatefulSet,StatefulSet 中的每个 Pod 将被分配一个整数序号,从 0 到 N-1,该序号在 StatefulSet 上是唯一的。

StatefulSet 中的每个 Pod 根据 StatefulSet 中的名称和 Pod 的序号来派生出它的主机名。组合主机名的格式为(StatefulSet名称)—(序号)。

部署和扩缩保证

对于包含 N 个 副本的 StatefulSet,当部署 Pod 时,它们是依次创建的,顺序为 0~(N-1)。
当删除 Pod 时,它们是逆序终止的,顺序为 (N-1)~0。

在将缩放操作应用到 Pod 之前,它前面的所有 Pod 必须是 Running 和 Ready 状态。

在 Pod 终止之前,所有的继任者必须完全关闭。

StatefulSet 不应将 pod.Spec.TerminationGracePeriodSeconds 设置为 0。这种做法是不安全的,要强烈阻止。

部署顺序

在下面的 nginx 示例被创建后,会按照 web-0、web-1、web-2 的顺序部署三个 Pod。在 web-0 进入 Running 和 Ready 状态前不会部署 web-1。在 web-1 进入 Running 和 Ready 状态前不会部署 web-2。

如果 web-1 已经处于 Running 和 Ready 状态,而 web-2 尚未部署,在此期间发生了 web-0 运行失败,那么 web-2 将不会被部署,要等到 web-0 部署完成并进入 Running 和 Ready 状态后,才会部署 web-2。

收缩顺序

如果想将示例中的 StatefulSet 收缩为 replicas=1,首先被终止的是 web-2。在 web-2 没有被完全停止和删除前,web-1 不会被终止。当 web-2 已被终止和删除;但web-1 尚未被终止,如果在此期间发生 web-0 运行失败,那么就不会终止 web-1,必须等到 web-0 进入 Running 和 Ready 状态后才会终止 web-1。

下面这个案例跟上文中的动态PV供给是一样的,只是后来我们验证的东西不一样。

我把所有的内容写到一个文件中,各位兄弟可以不看这个文件,因为跟上文中的一样,可以直接看后边的验证思路
[root@k8s-master sts]# cat a.yaml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: fuseim.pri/ifs
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get","list","watch","create","delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
spec:
  selector:
    matchLabels:
      app: nfs-client-provisioner
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccount: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: lizhenliang/nfs-client-provisioner:v2.0.0
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: 10.0.0.130
            - name: NFS_PATH
              value: /opt/container_data
      volumes:
        - name: nfs-client-root
          nfs:
            server: 10.0.0.130
            path: /opt/container_data
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
   matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: daocloud.io/library/nginx:latest
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "managed-nfs-storage"
      resources:
        requests:
          storage: 1Gi 
[root@k8s-master sts]# kubectl apply -f a.yaml 
storageclass.storage.k8s.io/managed-nfs-storage created
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
deployment.apps/nfs-client-provisioner created
service/nginx created
statefulset.apps/web created

验证解析

每个 Pod 都拥有一个基于其顺序索引的稳定的主机名

使用 kubectl run 运行一个提供 nslookup 命令的容器,该命令来自于 dnsutils 包。通过对 Pod 的主机名执行 nslookup,你可以检查他们在集群内部的 DNS 地址

[root@k8s-master sts]# cat pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: testnginx
spec:
  containers:
  - name: testnginx
    image: daocloud.io/library/nginx:1.12.0-alpine
[root@k8s-master sts]# kubectl apply -f pod.yaml
[root@k8s-master sts]# kubectl exec -it testnginx /bin/sh

重启pod会发现,pod中的ip已经发生变化,但是pod的名称并没有发生变化;这就是为什么不要在其他应用中使用 StatefulSet 中的 Pod 的 IP 地址进行连接,这点很重要

[root@master pvc-test]# kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
[root@master pvc-test]# kubectl get pod -o wide

写入稳定的存储

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

[root@k8s-master sts]# kubectl exec -it web-0 /bin/sh
# cd /usr/share/nginx/html
# echo youngfit-1 > index.html

[root@k8s-master sts]# kubectl exec -it web-1 /bin/sh
#  cd /usr/share/nginx/html
# echo youngfit-2 > index.html                

[root@k8s-master sts]# ls /opt/container_data/default-www-web-0-pvc-ae99bd8d-a337-458d-a178-928cf4602713/
index.html
[root@k8s-master sts]# ls /opt/container_data/default-www-web-1-pvc-afac76ea-9faf-41ac-b03d-7ffc9e277029/
index.html

[root@k8s-master sts]# curl 10.244.4.5
youngfit-1
[root@k8s-master sts]# curl 10.244.1.4
youngfit-2

再次删除
[root@k8s-master sts]# kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
[root@k8s-master sts]# kubectl apply -f  nginx.yaml
[root@k8s-master sts]# kubectl get pod
NAME                                     READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-56cc44bd5-2hgxc   1/1     Running   0          27m
testnginx                                1/1     Running   0          6m20s
web-0                                    1/1     Running   0          13s
web-1                                    1/1     Running   0          6s

再次查看

扩容/缩容 StatefulSet

扩容/缩容StatefulSet 指增加或减少它的副本数。这通过更新replicas字段完成。你可以使用kubectl scale 或者kubectl patch来扩容/缩容一个 StatefulSet。

二十五、基于k8s集群的redis-cluster集群

实验思路:需要提前准备好nfs存储,然后制作动态存储storageclass与nfs关联,制作动态存储就需要权限,所以需要serviceaccount和clusterrole以及clusterrolebinding

,需要将redis的配置文件导入到pod中,所以需要configmap,然后就需要statefulset来运行redis实例了,之后我们需要一个redis-tribe工具初始换redis-cluster集群,之后就可以进行各种验证了。

1、提前准备好nfs存储

[root@k8s-master ~]# yum -y install nfs-utils nfs-common rpcbind
[root@k8s-master ~]# mkdir /data/nfs
[root@k8s-master ~]# chmod -R 777 /data/nfs
[root@k8s-master ~]# vim /etc/exports
/data/nfs *(rw,no_root_squash,sync,no_all_squash)
[root@k8s-master ~]# systemctl start rpcbind nfs
[root@k8s-master ~]# systemctl status nfs
其余节点下载nfs客户端,确保可以挂载
# yum -y install nfs-utils nfs-common
下载好之后,可以尝试用挂载命令试试,能否正常挂载

2、制作动态存储

可以使用helm工具,也可以写yaml文件创建
helm方式:
[root@k8s-master ~]# helm install stable/nfs-client-provisioner --set nfs.server=192.168.153.148 --set nfs.path=/data/nfs
[root@k8s-master ~]# helm list

配置文件方式
需要先定义一个storageclass
[root@k8s-master redis]# cat sc.yaml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  labels:
    app: redis
    appCluster: redis-cluster
  name: nfs-client
provisioner: fuseim.pri/ifs
[root@k8s-master redis]# kubectl apply -f sc.yaml

然后我们创建一个自动创建pv的服务,这个服务需要关联一个serviceaccount
[root@k8s-master redis]# cat sa.yaml 
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
[root@k8s-master redis]# kubectl apply -f sa.yaml

[root@k8s-master redis]# cat deploy.yaml 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
spec:
  selector:
    matchLabels:
      app: nfs-client-provisioner
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccount: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: lizhenliang/nfs-client-provisioner:v2.0.0
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: 10.0.0.130
            - name: NFS_PATH
              value: /data/nfs
      volumes:
        - name: nfs-client-root
          nfs:
            server: 10.0.0.130
            path: /data/nfs    #这个卷的目录要跟我们创建的共享目录对应
[root@k8s-master redis]# kubectl apply -f deploy.yaml 


 3、redis配置文件configmap

#将redis配置文件内容
[root@k8s-master redis]# cat redis.conf 
appendonly yes
cluster-enabled yes
cluster-config-file /var/lib/redis/nodes.conf
cluster-node-timeout 5000
dir /var/lib/redis
port 6379
[root@k8s-master redis]# kubectl create configmap redis-conf --from-file=redis.conf

 

 4、创建一个无头service服务

[root@k8s-master redis]# cat headless-service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: redis-service
  labels:
    app: redis
    appCluster: redis-cluster
spec:
  ports:
  - name: redis-port
    port: 6379
  clusterIP: None
  selector:
    app: redis
    appCluster: redis-cluster
[root@k8s-master redis]# kubectl apply -f headless-service.yaml

 5、statefulSet运行redis实例

创建好Headless service后,就可以利用StatefulSet创建Redis 集群节点,这也是本文的核心内容。我们先创建redis.yml文件:

[root@k8s-master redis]# cat redis.yaml 
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-app
spec:
  serviceName: "redis-service"
  replicas: 6
  selector:
    matchLabels:
      app: redis
      appCluster: redis-cluster
  template:
    metadata:
      labels:
        app: redis
        appCluster: redis-cluster
    spec:
      terminationGracePeriodSeconds: 20
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - redis
              topologyKey: kubernetes.io/hostname
      containers:
      - name: redis
        image: daocloud.io/library/redis:6-alpine3.12
        command:
          - "redis-server"   #pod运行后执行的命令
        args:
          - "/etc/redis/redis.conf"   #运行命令加的参数
          - "--protected-mode"       #加密模式为no
          - "no"
        resources:
          requests:
            cpu: "100m"     #分配的cpu使用率
            memory: "100Mi"  #分配的内存
        ports:
            - name: redis
              containerPort: 6379
              protocol: "TCP"
            - name: cluster
              containerPort: 16379
              protocol: "TCP"
        volumeMounts:
          - name: "redis-conf"
            mountPath: "/etc/redis"
          - name: "redis-data"
            mountPath: "/var/lib/redis"
      volumes:
      - name: "redis-conf"
        configMap:
          name: "redis-conf"              #引用我们前面创建的configmap
          items:
            - key: "redis.conf"           
              path: "redis.conf"
  volumeClaimTemplates:                #pvc的模板
  - metadata:
      name: redis-data
    spec:
      accessModes: [ "ReadWriteMany" ]   
      storageClassName: "nfs-client"
      resources:
        requests:
          storage: 200M   #这个值可以适当调大
这个时候直接创建会失败,因为权限不够,需要给这个statefulset管理的pod提权,我们直接创建试一下
[root@k8s-master redis]# kubectl apply -f redis.yaml 
statefulset.apps/redis-app created

我们删除这个statefulset,给它提权重新创建
[root@k8s-master redis]# kubectl delete -f redis.yaml 
statefulset.apps "redis-app" deleted
[root@k8s-master redis]# cat rbac.yaml 
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get","list","watch","create","delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
[root@k8s-master redis]# kubectl apply -f rbac.yaml 
[root@k8s-master redis]# kubectl apply -f redis.yaml 

 6.验证唯一访问标识可用性

如上,总共创建了6个Redis节点(Pod),其中3个将用于master,另外3个分别作为master的slave;Redis的配置通过volume将之前生成的redis-conf这个Configmap,挂载到了容器的/etc/redis/redis.conf;Redis的数据存储路径使用volumeClaimTemplates声明(也就是PVC),其会绑定到我们先前创建的PV上。

这里有一个关键概念——Affinity,请参考官方文档详细了解。其中,podAntiAffinity表示反亲和性,其决定了某个pod不可以和哪些Pod部署在同一拓扑域,可以用于将一个服务的POD分散在不同的主机或者拓扑域中,提高服务本身的稳定性。

而PreferredDuringSchedulingIgnoredDuringExecution 则表示,在调度期间尽量满足亲和性或者反亲和性规则,如果不能满足规则,POD也有可能被调度到对应的主机上。在之后的运行过程中,系统不会再检查这些规则是否满足。

在这里,matchExpressions规定了Redis Pod要尽量不要调度到包含app为redis的Node上,也即是说已经存在Redis的Node上尽量不要再分配Redis Pod了。但是,由于我们只有三个Node,而副本有6个,因此根据PreferredDuringSchedulingIgnoredDuringExecution,这些豌豆不得不得挤一挤,挤挤更健康~

另外,根据StatefulSet的规则,我们生成的Redis的6个Pod的hostname会被依次命名为:statefulset名称—序号:如图:

如上,可以看到这些Pods在部署时是以{0…N-1}的顺序依次创建的。注意,直到redis-app-0状态启动后达到Running状态之后,redis-app-1 才开始启动。
同时,每个Pod都会得到集群内的一个DNS域名,格式为podname.servicename.namespace名称.svc.cluster.local,比如:redis-app-0.redis-service.default.svc.cluster.local

创建一个测试pod唯一标识的pod服务(其实只要有ping命令就行),测试完删除即可

[root@k8s-master redis]# cat busybox.yaml 
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  containers:
    - name: busybox
      image: daocloud.io/library/busybox
      stdin: true
      tty: true
[root@k8s-master redis]# kubectl apply -f busybox.yaml

可以看到, redis-app-0的IP为10.244.1.33;redis-app-1的IP为10.244.2.20。当然,若Redis Pod迁移或是重启(我们可以手动删除掉一个Redis Pod来测试),IP是会改变的,但是Pod的域名、SRV records、A record都不会改变。

另外可以发现,我们之前创建的pv都被成功绑定了:

7、集群初始化

 创建好6个Redis Pod后,我们还需要利用常用的Redis-tribe工具进行集群的初始化

创建Ubuntu容器:由于Redis集群必须在所有节点启动后才能进行初始化,而如果将初始化逻辑写入Statefulset中,则是一件非常复杂而且低效的行为。这里,哥不得不赞许一下原项目作者的思路,值得学习。也就是说,我们可以在K8S上创建一个额外的容器,专门用于进行K8S集群内部某些服务的管理控制。

这里,我们专门启动一个Ubuntu的容器,可以在该容器中安装Redis-tribe,进而初始化Redis集群,执行:

[root@k8s-master redis-ha]# kubectl run -it ubuntu --image=ubuntu --restart=Never /bin/bash
如果上面镜像下载失败:
[root@k8s-master redis-ha]# kubectl run -it ubuntu --image=daocloud.io/library/ubuntu:artful-20170619 --restart=Never /bin/bash

然后下载我们等会需要用到的工具

root@ubuntu:/#  cat > /etc/apt/sources.list << EOF
> deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
> deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
> 
> deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
> deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
> 
> deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
> deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
> 
> deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
> deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
>  
> deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
> deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
> EOF
root@ubuntu:/# apt-get update

root@ubuntu:/# apt-get install -y vim wget python2.7 python-pip redis-tools dnsutils

 然后我们测试一下ubuntu这个pod能不能跟redis进行通信,结果可以

root@ubuntu:/# nslookup redis-app-1.redis-service.default.svc.cluster.local  
Server:		10.244.1.139
Address:	10.244.1.139#53

Name:	redis-app-1.redis-service.default.svc.cluster.local
Address: 10.244.1.139

然后我们安装redis-tribe工具

root@ubuntu:/# pip install redis-trib==0.5.1

 然后,创建只有Master节点的集群:

root@ubuntu:/# redis-trib.py create \
  `dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379 \
  `dig +short redis-app-1.redis-service.default.svc.cluster.local`:6379 \
  `dig +short redis-app-2.redis-service.default.svc.cluster.local`:6379

其次,为每个Master添加Slave

root@ubuntu:/# redis-trib.py replicate \
  --master-addr `dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379 \
  --slave-addr `dig +short redis-app-3.redis-service.default.svc.cluster.local`:6379

root@ubuntu:/# redis-trib.py replicate \
  --master-addr `dig +short redis-app-1.redis-service.default.svc.cluster.local`:6379 \
  --slave-addr `dig +short redis-app-4.redis-service.default.svc.cluster.local`:6379

root@ubuntu:/# redis-trib.py replicate \
  --master-addr `dig +short redis-app-2.redis-service.default.svc.cluster.local`:6379 \
  --slave-addr `dig +short redis-app-5.redis-service.default.svc.cluster.local`:6379

至此,我们的Redis集群就真正创建完毕了,连到任意一个Redis Pod中检验一下:

退出ubuntu pod

[root@k8s-master redis]# kubectl exec -it redis-app-5 /bin/bash
root@redis-app-5:/data# /usr/local/bin/redis-cli -c
127.0.0.1:6379> CLUSTER NODES

8、 创建用于访问Service(可做可不做)

前面我们创建了用于实现StatefulSet的Headless Service,但该Service没有Cluster Ip,因此不能用于外界访问。所以,我们还需要创建一个Service,专用于为Redis集群提供访问和负载均衡:

[root@k8s-master redis]# vim redis-access-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: redis-access-service
  labels:
    app: redis
spec:
  ports:
  - name: redis-port
    protocol: "TCP"
    port: 6379
    targetPort: 6379
  selector:
    app: redis
    appCluster: redis-cluster
[root@k8s-master redis]# kubectl apply -f redis-access-service.yaml

如上,该Service名称为 redis-access-service,在K8S集群中暴露6379端口,并且会对labels nameapp: redisappCluster: redis-cluster的pod进行负载均衡。

9、测试主从切换

在K8S上搭建完好Redis集群后,我们最关心的就是其原有的高可用机制是否正常。这里,我们可以任意挑选一个Master的Pod来测试集群的主从切换机制,如redis-app-0

说明:一般前3个为主节点,后三个为从节点。

 如上可以看到,redis-app-0、redis-app-1、redis-app-2为master

redis-app-3、redis-app-4、redis-app-5为slave。

验证:删除一个master节点的pod,等几秒钟看一下这个master节点对应的slave会不会变成master

 疑问扩展

六、疑问
至此,大家可能会疑惑,那为什么没有使用稳定的标志,Redis Pod也能正常进行故障转移呢?这涉及了Redis本身的机制。因为,Redis集群中每个节点都有自己的NodeId(保存在自动生成的nodes.conf中),并且该NodeId不会随着IP的变化和变化,这其实也是一种固定的网络标志。也就是说,就算某个Redis Pod重启了,该Pod依然会加载保存的NodeId来维持自己的身份。我们可以在NFS上查看redis-app-1的nodes.conf文件:

[root@k8s-node2 ~]# cat /usr/local/k8s/redis/pv1/nodes.conf 96689f2018089173e528d3a71c4ef10af68ee462 192.168.169.209:6379@16379 slave d884c4971de9748f99b10d14678d864187a9e5d3 0 1526460952651 4 connected237d46046d9b75a6822f02523ab894928e2300e6 192.168.169.200:6379@16379 slave c15f378a604ee5b200f06cc23e9371cbc04f4559 0 1526460952651 1 connected
c15f378a604ee5b200f06cc23e9371cbc04f4559 192.168.169.197:6379@16379 master - 0 1526460952651 1 connected 10923-16383d884c4971de9748f99b10d14678d864187a9e5d3 192.168.169.205:6379@16379 master - 0 1526460952651 4 connected 5462-10922c3b4ae23c80ffe31b7b34ef29dd6f8d73beaf85f 192.168.169.198:6379@16379 myself,slave c8a8f70b4c29333de6039c47b2f3453ed11fb5c2 0 1526460952565 3 connected
c8a8f70b4c29333de6039c47b2f3453ed11fb5c2 192.168.169.201:6379@16379 master - 0 1526460952651 6 connected 0-5461vars currentEpoch 6 lastVoteEpoch 4
如上,第一列为NodeId,稳定不变;第二列为IP和端口信息,可能会改变。

这里,我们介绍NodeId的两种使用场景:

当某个Slave Pod断线重连后IP改变,但是Master发现其NodeId依旧, 就认为该Slave还是之前的Slave。

当某个Master Pod下线后,集群在其Slave中选举重新的Master。待旧Master上线后,集群发现其NodeId依旧,会让旧Master变成新Master的slave。

对于这两种场景,大家有兴趣的话还可以自行测试,注意要观察Redis的日志

都看到这里了,总结不易,给个三连吧~~

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值